mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 03:07:07 +00:00
Compare commits
209 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8dcfe92f13 | ||
|
f3d5c1f226 | ||
|
8af21f6e76 | ||
|
9bf3dc4274 | ||
|
40d2f975cb | ||
|
784ba287aa | ||
|
3b9cf6cc51 | ||
|
f52abc8314 | ||
|
3199fc454a | ||
|
738f8cae3b | ||
|
9406c117a6 | ||
|
7b0e62c128 | ||
|
5d0d91b87b | ||
|
ed687d8ff6 | ||
|
cc214c320d | ||
|
0897f3b0f7 | ||
|
7986c45b3f | ||
|
8ea0241aa2 | ||
|
fabc2faa4c | ||
|
3216479530 | ||
|
6c66a54223 | ||
|
e760290beb | ||
|
3beefdff72 | ||
|
104f610ea7 | ||
|
2b337e1310 | ||
|
b78455f910 | ||
|
c3d6e20120 | ||
|
b32af0f6ba | ||
|
3469b0dbb7 | ||
|
0fd7396665 | ||
|
c6f41e1975 | ||
|
2cfc20c143 | ||
|
99197f30f6 | ||
|
aa48299d5d | ||
|
dd80191761 | ||
|
34c1c33996 | ||
|
2dcbce9cd7 | ||
|
c4fbd1cac3 | ||
|
575e3fb920 | ||
|
50fd4acccb | ||
|
f9e214de93 | ||
|
f28d354875 | ||
|
648b838a75 | ||
|
157fe31051 | ||
|
170fb94896 | ||
|
9616b4c0ca | ||
|
0c4a040394 | ||
|
8592e77e15 | ||
|
fc8850496e | ||
|
227afbfd8d | ||
|
4672af12fe | ||
|
079996d936 | ||
|
bd9b05b990 | ||
|
f2f3f7ab8e | ||
|
22222e79b6 | ||
|
55164e8ece | ||
|
336822ff5c | ||
|
82e8417b9a | ||
|
9c0ecb441f | ||
|
0c0fabcb89 | ||
|
202f437aea | ||
|
a8b06aa7c7 | ||
|
a5e634319a | ||
|
6d1262f402 | ||
|
a5fd182bd0 | ||
|
771cf8bdcf | ||
|
56304aea8d | ||
|
c6e69ddc17 | ||
|
ae55ec3e1b | ||
|
f72243304f | ||
|
5425180aec | ||
|
a496db4ddf | ||
|
26bad8eb4b | ||
|
f526080611 | ||
|
6c269825c9 | ||
|
17959b7056 | ||
|
17a8ed379a | ||
|
163e5001d3 | ||
|
5d27646ef9 | ||
|
38be147e8a | ||
|
93829aeb80 | ||
|
9098dbae9a | ||
|
9edc51c2a4 | ||
|
f1aa2b1cb2 | ||
|
bbb2cb3a2c | ||
|
7a0a32398b | ||
|
272ed8e85c | ||
|
e308d4cfac | ||
|
0162360cfe | ||
|
4ba4c0bebc | ||
|
4b0b1e69a4 | ||
|
22c0f81054 | ||
|
2494e615fd | ||
|
ab637d217b | ||
|
9d8f16f940 | ||
|
dc2c5e3c80 | ||
|
6cfdbbe597 | ||
|
487867a967 | ||
|
f3a692d294 | ||
|
72b798f7ae | ||
|
50237fb778 | ||
|
fa5b8853af | ||
|
0cd2282640 | ||
|
c2cea75bb7 | ||
|
b832ae742b | ||
|
e98d28f3b4 | ||
|
b2e26cd6bd | ||
|
cfe182f452 | ||
|
729a894a04 | ||
|
7231d493d0 | ||
|
abf1d52168 | ||
|
a5618f163f | ||
|
7d6c512f27 | ||
|
f00c0ae71c | ||
|
93b79ddcb3 | ||
|
6691f6ef70 | ||
|
6173836bdb | ||
|
a2a88c1414 | ||
|
6114867e34 | ||
|
3c5cd6046d | ||
|
7dc3702db5 | ||
|
791f75c13e | ||
|
4cfc8fcb44 | ||
|
57fc04e4aa | ||
|
1e74c4eacf | ||
|
e55052ecfd | ||
|
dc0aea9e3e | ||
|
295b55da44 | ||
|
4b9ae5fd68 | ||
|
cc8b6fa7a2 | ||
|
f28de96ea9 | ||
|
5e225b2898 | ||
|
392502dd68 | ||
|
ba329e5ef2 | ||
|
68b64a6004 | ||
|
0f694aa157 | ||
|
0d7c399094 | ||
|
2f6685ab45 | ||
|
2061887276 | ||
|
2f40024edb | ||
|
9799809ebd | ||
|
2a08bc5a14 | ||
|
ff1ace7a04 | ||
|
96f0daf535 | ||
|
1b2f560ad7 | ||
|
1c033d3a53 | ||
|
99b26fccb0 | ||
|
565aba61dc | ||
|
21eb289411 | ||
|
441e772f48 | ||
|
f360e439e9 | ||
|
98b3affe91 | ||
|
04be5cb8e8 | ||
|
7f925536b5 | ||
|
73dfcc53e1 | ||
|
3e543977c9 | ||
|
97829aa122 | ||
|
e42dd109f9 | ||
|
98a6dfc514 | ||
|
d6fd1b8614 | ||
|
dd57aabfdc | ||
|
44c8b4c29d | ||
|
1bc3a8eb02 | ||
|
7371d3e7bb | ||
|
139eeac6ce | ||
|
d33d2653cf | ||
|
ba3fc6abc4 | ||
|
1d60714054 | ||
|
020705bd4b | ||
|
1495b34e39 | ||
|
e0d11226db | ||
|
8749bc9dc5 | ||
|
716a047aba | ||
|
ed6d436a50 | ||
|
028b51facf | ||
|
8f28124237 | ||
|
f3d7a30c66 | ||
|
8f3e9f87cb | ||
|
36e4c02699 | ||
|
1817102a7c | ||
|
20820e72ad | ||
|
fb4d957025 | ||
|
fe5635db62 | ||
|
30a8230eea | ||
|
908622cf61 | ||
|
8e5ec5c4e7 | ||
|
ca071bfc48 | ||
|
4e22252c3e | ||
|
c0eee74968 | ||
|
1e054a4370 | ||
|
76ccd241fc | ||
|
e984b64fe3 | ||
|
3256cf7fce | ||
|
d9eeb690ac | ||
|
b66f4436bf | ||
|
c11bc7b78f | ||
|
3bbb48dd25 | ||
|
73b92be1e4 | ||
|
e977d79ebd | ||
|
d02896065e | ||
|
f468aa992d | ||
|
3bfbbcf111 | ||
|
e2e8b0a8cd | ||
|
c8c5f17fd1 | ||
|
7f6fc56bd8 | ||
|
40855ade01 | ||
|
d116563958 | ||
|
8f603d3112 | ||
|
998752926f |
57
.github/ISSUE_TEMPLATE/adapter_publish.yml
vendored
Normal file
57
.github/ISSUE_TEMPLATE/adapter_publish.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: 发布适配器
|
||||
title: "Adapter: {name}"
|
||||
description: 发布适配器到 NoneBot 官方商店
|
||||
labels: ["Adapter"]
|
||||
body:
|
||||
- type: input
|
||||
id: name
|
||||
attributes:
|
||||
label: 适配器名称
|
||||
description: 适配器名称
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: description
|
||||
attributes:
|
||||
label: 适配器描述
|
||||
description: 适配器描述
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: pypi
|
||||
attributes:
|
||||
label: PyPI 项目名
|
||||
description: PyPI 项目名
|
||||
placeholder: e.g. nonebot-adapter-xxx
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: module
|
||||
attributes:
|
||||
label: 适配器 import 包名
|
||||
description: 适配器 import 包名
|
||||
placeholder: e.g. nonebot_adapter_xxx
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: homepage
|
||||
attributes:
|
||||
label: 适配器项目仓库/主页链接
|
||||
description: 适配器项目仓库/主页链接
|
||||
placeholder: e.g. https://github.com/xxx/xxx
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: tags
|
||||
attributes:
|
||||
label: 标签
|
||||
description: 标签
|
||||
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
|
||||
value: "[]"
|
||||
validations:
|
||||
required: true
|
37
.github/ISSUE_TEMPLATE/bot_publish.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/bot_publish.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: 发布机器人
|
||||
title: "Bot: {name}"
|
||||
description: 发布机器人到 NoneBot 官方商店
|
||||
labels: ["Bot"]
|
||||
body:
|
||||
- type: input
|
||||
id: name
|
||||
attributes:
|
||||
label: 机器人名称
|
||||
description: 机器人名称
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: description
|
||||
attributes:
|
||||
label: 机器人描述
|
||||
description: 机器人描述
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: homepage
|
||||
attributes:
|
||||
label: 机器人项目仓库/主页链接
|
||||
description: 机器人项目仓库/主页链接
|
||||
placeholder: e.g. https://github.com/xxx/xxx
|
||||
|
||||
- type: input
|
||||
id: tags
|
||||
attributes:
|
||||
label: 标签
|
||||
description: 标签
|
||||
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
|
||||
value: "[]"
|
||||
validations:
|
||||
required: true
|
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,38 +0,0 @@
|
||||
---
|
||||
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]
|
||||
|
||||
**协议端信息:**
|
||||
|
||||
- 协议端: [e.g. go-cqhttp]
|
||||
- 协议端版本: [e.g. 1.0.0]
|
||||
|
||||
**截图或日志**
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Bug 反馈
|
||||
title: "Bug: 出现异常"
|
||||
description: 提交 Bug 反馈以帮助我们改进代码
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: dropdown
|
||||
id: env-os
|
||||
attributes:
|
||||
label: 操作系统
|
||||
description: 选择运行 NoneBot 的系统
|
||||
options:
|
||||
- Windows
|
||||
- MacOS
|
||||
- Linux
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: env-python-ver
|
||||
attributes:
|
||||
label: Python 版本
|
||||
description: 填写运行 NoneBot 的 Python 版本
|
||||
placeholder: e.g. 3.11.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: env-nb-ver
|
||||
attributes:
|
||||
label: NoneBot 版本
|
||||
description: 填写 NoneBot 版本
|
||||
placeholder: e.g. 2.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: env-adapter
|
||||
attributes:
|
||||
label: 适配器
|
||||
description: 填写使用的适配器以及版本
|
||||
placeholder: e.g. OneBot v11 2.2.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: env-protocol
|
||||
attributes:
|
||||
label: 协议端
|
||||
description: 填写连接 NoneBot 的协议端及版本
|
||||
placeholder: e.g. go-cqhttp 1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: describe
|
||||
attributes:
|
||||
label: 描述问题
|
||||
description: 清晰简洁地说明问题是什么
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
description: 提供能复现此问题的详细操作步骤
|
||||
placeholder: |
|
||||
1. 首先……
|
||||
2. 然后……
|
||||
3. 发生……
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 期望的结果
|
||||
description: 清晰简洁地描述你期望发生的事情
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 截图或日志
|
||||
description: 提供有助于诊断问题的任何日志和截图
|
13
.github/ISSUE_TEMPLATE/config.yml
vendored
13
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
- name: NoneBot 论坛
|
||||
url: https://discussions.nonebot.dev/
|
||||
about: Ask questions about nonebot
|
||||
- name: Plugin Publish
|
||||
url: https://v2.nonebot.dev/store
|
||||
about: Publish your plugin to nonebot homepage and nb-cli
|
||||
- name: Adapter Publish
|
||||
url: https://v2.nonebot.dev/store
|
||||
about: Publish your adapter to nonebot homepage and nb-cli
|
||||
- name: Bot Publish
|
||||
url: https://v2.nonebot.dev/store
|
||||
about: Publish your bot to nonebot homepage and nb-cli
|
||||
about: 前往 NoneBot 论坛提问
|
||||
|
17
.github/ISSUE_TEMPLATE/document.md
vendored
17
.github/ISSUE_TEMPLATE/document.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Document improvement
|
||||
about: Feedback on documentation, including errors and ideas
|
||||
title: 'Docs: some description'
|
||||
labels: documentation
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**描述问题或主题:**
|
||||
|
||||
|
||||
**需做出的修改:**
|
||||
|
||||
* [ ] 一些修改
|
||||
* [ ] 一些修改
|
||||
* [ ] 一些修改
|
18
.github/ISSUE_TEMPLATE/document.yml
vendored
Normal file
18
.github/ISSUE_TEMPLATE/document.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: 文档改进
|
||||
title: "Docs: 描述"
|
||||
description: 文档错误及改进意见反馈
|
||||
labels: ["documentation"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: 描述问题或主题
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: improve
|
||||
attributes:
|
||||
label: 需做出的修改
|
||||
validations:
|
||||
required: true
|
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,16 +0,0 @@
|
||||
---
|
||||
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.
|
20
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: 功能建议
|
||||
title: "Feature: 功能描述"
|
||||
description: 提出关于项目新功能的想法
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: 希望能解决的问题
|
||||
description: 在使用中遇到什么问题而需要新的功能?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: 描述所需要的功能
|
||||
description: 请说明需要的功能或解决方法
|
||||
validations:
|
||||
required: true
|
57
.github/ISSUE_TEMPLATE/plugin_publish.yml
vendored
Normal file
57
.github/ISSUE_TEMPLATE/plugin_publish.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: 发布插件
|
||||
title: "Plugin: {name}"
|
||||
description: 发布插件到 NoneBot 官方商店
|
||||
labels: ["Plugin"]
|
||||
body:
|
||||
- type: input
|
||||
id: name
|
||||
attributes:
|
||||
label: 插件名称
|
||||
description: 插件名称
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: description
|
||||
attributes:
|
||||
label: 插件描述
|
||||
description: 插件描述
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: pypi
|
||||
attributes:
|
||||
label: PyPI 项目名
|
||||
description: PyPI 项目名
|
||||
placeholder: e.g. nonebot-plugin-xxx
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: module
|
||||
attributes:
|
||||
label: 插件 import 包名
|
||||
description: 插件 import 包名
|
||||
placeholder: e.g. nonebot_plugin_xxx
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: homepage
|
||||
attributes:
|
||||
label: 插件项目仓库/主页链接
|
||||
description: 插件项目仓库/主页链接
|
||||
placeholder: e.g. https://github.com/xxx/xxx
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: tags
|
||||
attributes:
|
||||
label: 标签
|
||||
description: 标签
|
||||
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
|
||||
value: "[]"
|
||||
validations:
|
||||
required: true
|
2
.github/actions/setup-python/action.yml
vendored
2
.github/actions/setup-python/action.yml
vendored
@@ -11,7 +11,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install poetry
|
||||
run: pipx install poetry==1.3.2
|
||||
run: pipx install poetry
|
||||
shell: bash
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
|
@@ -1,4 +1,4 @@
|
||||
name: NoneBot2 Publish Bot
|
||||
name: NoneFlow
|
||||
|
||||
on:
|
||||
issues:
|
||||
@@ -7,6 +7,8 @@ on:
|
||||
types: [closed]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
|
||||
@@ -16,7 +18,23 @@ jobs:
|
||||
plugin_test:
|
||||
runs-on: ubuntu-latest
|
||||
name: nonebot2 plugin test
|
||||
if: github.event_name != 'issue_comment' || !github.event.issue.pull_request
|
||||
if: |
|
||||
!(
|
||||
(
|
||||
github.event.pull_request &&
|
||||
(
|
||||
github.event.pull_request.head.repo.fork ||
|
||||
!(
|
||||
contains(github.event.pull_request.labels.*.name, 'Plugin') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'Adapter') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'Bot')
|
||||
)
|
||||
)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'issue_comment' && github.event.issue.pull_request
|
||||
)
|
||||
)
|
||||
permissions:
|
||||
issues: read
|
||||
outputs:
|
||||
@@ -26,28 +44,36 @@ jobs:
|
||||
- name: Install Poetry
|
||||
if: ${{ !startsWith(github.event_name, 'pull_request') }}
|
||||
run: pipx install poetry
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Test Plugin
|
||||
id: plugin-test
|
||||
run: |
|
||||
curl -sSL https://github.com/nonebot/nonebot2-publish-bot/releases/latest/download/plugin_test.py -o plugin_test.py
|
||||
python plugin_test.py
|
||||
publish_bot:
|
||||
curl -sSL https://github.com/nonebot/noneflow/releases/latest/download/plugin_test.py | python -
|
||||
noneflow:
|
||||
runs-on: ubuntu-latest
|
||||
name: nonebot2 publish bot
|
||||
name: noneflow
|
||||
needs: plugin_test
|
||||
steps:
|
||||
- name: Generate token
|
||||
id: generate-token
|
||||
uses: tibdex/github-app-token@v1
|
||||
with:
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_KEY }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- name: NoneBot2 Publish Bot
|
||||
uses: docker://ghcr.io/nonebot/nonebot2-publish-bot:latest
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: NoneFlow
|
||||
uses: docker://ghcr.io/nonebot/noneflow:latest
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
config: >
|
||||
{
|
||||
"base": "master",
|
||||
@@ -58,3 +84,5 @@ jobs:
|
||||
env:
|
||||
PLUGIN_TEST_RESULT: ${{ needs.plugin_test.outputs.result }}
|
||||
PLUGIN_TEST_OUTPUT: ${{ needs.plugin_test.outputs.output }}
|
||||
APP_ID: ${{ secrets.APP_ID }}
|
||||
PRIVATE_KEY: ${{ secrets.APP_KEY }}
|
13
.github/workflows/release-drafter.yml
vendored
13
.github/workflows/release-drafter.yml
vendored
@@ -18,9 +18,16 @@ jobs:
|
||||
group: pull-request-changelog
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Generate token
|
||||
id: generate-token
|
||||
uses: tibdex/github-app-token@v1
|
||||
with:
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Setup Node Environment
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -43,8 +50,8 @@ jobs:
|
||||
- name: Commit and Push
|
||||
run: |
|
||||
yarn prettier
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email github-actions[bot]@users.noreply.github.com
|
||||
git config user.name noneflow[bot]
|
||||
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
|
||||
git add .
|
||||
git diff-index --quiet HEAD || git commit -m ":memo: Update changelog"
|
||||
git push
|
||||
|
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -6,12 +6,17 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Generate token
|
||||
id: generate-token
|
||||
uses: tibdex/github-app-token@v1
|
||||
with:
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: master
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
@@ -39,8 +44,8 @@ jobs:
|
||||
|
||||
- name: Push Tag
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email github-actions[bot]@users.noreply.github.com
|
||||
git config user.name noneflow[bot]
|
||||
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
|
||||
git add .
|
||||
git commit -m ":bookmark: Release $(poetry version -s)"
|
||||
git tag ${{ env.TAG_NAME }}
|
||||
|
@@ -19,20 +19,20 @@ repos:
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0-alpha.6
|
||||
rev: v3.0.0-alpha.9-for-vscode
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/nonebot/nonemoji
|
||||
rev: v0.1.3
|
||||
rev: v0.1.4
|
||||
hooks:
|
||||
- id: nonemoji
|
||||
stages: [prepare-commit-msg]
|
||||
|
@@ -1,3 +1,3 @@
|
||||
# Changelog
|
||||
|
||||
See [changelog.md](./website/src/pages/changelog.md) or <https://v2.nonebot.dev/changelog>
|
||||
See [changelog.md](./website/src/pages/changelog.md) or <https://nonebot.dev/changelog>
|
||||
|
@@ -84,7 +84,7 @@ NoneBot2 的代码风格遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/
|
||||
|
||||
## 为社区做贡献
|
||||
|
||||
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://v2.nonebot.dev/docs/developer/plugin-publishing) 一节。
|
||||
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://nonebot.dev/docs/developer/plugin-publishing) 一节。
|
||||
|
||||
我们仅对插件的兼容性进行简单测试,并会在下一个版本发布前对与该版本不兼容的插件作出处理。
|
||||
|
||||
|
63
README.md
63
README.md
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD033 MD041 -->
|
||||
<p align="center">
|
||||
<a href="https://v2.nonebot.dev/"><img src="https://v2.nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
|
||||
<a href="https://nonebot.dev/"><img src="https://nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
@@ -49,9 +49,9 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
||||
</a>
|
||||
<a href="https://bot.q.qq.com/wiki/">
|
||||
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
|
||||
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
|
||||
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk">
|
||||
</a>
|
||||
<!-- <a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
|
||||
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk"> -->
|
||||
</a>
|
||||
<br />
|
||||
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
|
||||
@@ -69,16 +69,16 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://v2.nonebot.dev/">文档</a>
|
||||
<a href="https://nonebot.dev/">文档</a>
|
||||
·
|
||||
<a href="https://v2.nonebot.dev/docs/quick-start">快速上手</a>
|
||||
<a href="https://nonebot.dev/docs/quick-start">快速上手</a>
|
||||
·
|
||||
<a href="#插件">文档打不开?</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://asciinema.org/a/569440">
|
||||
<img src="https://v2.nonebot.dev/img/setup.svg">
|
||||
<img src="https://nonebot.dev/img/setup.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -90,36 +90,37 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
||||
|
||||
- 异步优先:基于 Python 的异步特性,即使是~~非常~~大量的消息,也能吞吐自如
|
||||
- 易于开发:配合 NB-CLI 脚手架,代码编写上手简单,没有过多的冗余代码,可以让开发者专注于业务逻辑
|
||||
- 生而可靠:100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://v2.nonebot.dev/docs/editor-support))
|
||||
- 生而可靠:100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://nonebot.dev/docs/editor-support))
|
||||
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
|
||||
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
||||
|
||||
| 协议名称 | 状态 | 注释 |
|
||||
| :-----------------------------------------------------------------------: | :--: | :----------------------------------------------------------------: |
|
||||
| [OneBot 协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号等[平台](https://onebot.dev/ecosystem.html) |
|
||||
| [Telegram](https://core.telegram.org/bots/api) | ✅ | |
|
||||
| [飞书](https://open.feishu.cn/document/home/index) | ✅ | |
|
||||
| [GitHub](https://docs.github.com/en/developers/apps) | ✅ | GitHub APP & OAuth APP |
|
||||
| [QQ 频道](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 |
|
||||
| [钉钉](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer |
|
||||
| Console | ✅ | 控制台交互 |
|
||||
| [开黑啦](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
|
||||
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | 由社区贡献 |
|
||||
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | ↗️ | 由社区贡献 |
|
||||
| [MineCraft (Spigot)](https://github.com/17TheWord/nonebot-adapter-spigot) | ↗️ | 由社区贡献 |
|
||||
| [BiliBili Live](https://github.com/wwweww/adapter-bilibili) | ↗️ | 由社区贡献 |
|
||||
| 协议名称 | 状态 | 注释 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
|
||||
| OneBot([仓库](https://github.com/nonebot/adapter-onebot),[协议](https://onebot.dev/)) | ✅ | 支持 QQ、TG、微信公众号、KOOK 等[平台](https://onebot.dev/ecosystem.html) |
|
||||
| Telegram([仓库](https://github.com/nonebot/adapter-telegram),[协议](https://core.telegram.org/bots/api)) | ✅ | |
|
||||
| 飞书([仓库](https://github.com/nonebot/adapter-feishu),[协议](https://open.feishu.cn/document/home/index)) | ✅ | |
|
||||
| GitHub([仓库](https://github.com/nonebot/adapter-github),[协议](https://docs.github.com/en/apps)) | ✅ | GitHub APP & OAuth APP |
|
||||
| QQ 频道([仓库](https://github.com/nonebot/adapter-qqguild),[协议](https://bot.q.qq.com/wiki/)) | ✅ | 官方接口调整较多 |
|
||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||
| Console([仓库](https://github.com/nonebot/adapter-console)) | ✅ | 控制台交互 |
|
||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||
| Mirai([仓库](https://github.com/ieew/nonebot_adapter_mirai2),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
|
||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||
|
||||
| 驱动框架 | 类型 |
|
||||
| :--------------------------------------------------------: | :----: |
|
||||
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
|
||||
| [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask) | 服务端 |
|
||||
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
|
||||
| [httpx](https://www.python-httpx.org/) | 客户端 |
|
||||
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
|
||||
| 驱动框架 | 类型 |
|
||||
| :-----------------------------------------------------------------: | :----: |
|
||||
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
|
||||
| [Quart](https://quart.palletsprojects.com/en/latest/)(异步 Flask) | 服务端 |
|
||||
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
|
||||
| [httpx](https://www.python-httpx.org/) | 客户端 |
|
||||
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
|
||||
|
||||
更多:[概览](https://v2.nonebot.dev/docs/)
|
||||
更多:[概览](https://nonebot.dev/docs/)
|
||||
|
||||
## 什么不是 NoneBot2
|
||||
|
||||
@@ -131,7 +132,7 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
|
||||
|
||||
## 即刻开始
|
||||
|
||||
~~完整~~文档可以在 [这里](https://v2.nonebot.dev/) 查看。
|
||||
~~完整~~文档可以在 [这里](https://nonebot.dev/) 查看。
|
||||
|
||||
懒得看文档?下面是快速安装指南:
|
||||
|
||||
@@ -188,7 +189,7 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
|
||||
- [文档镜像(中国境内)](https://nb2.baka.icu)
|
||||
- [文档镜像(Vercel)](https://nonebot2-vercel-mirror.vercel.app)
|
||||
|
||||
- 其他插件请查看 [商店](https://v2.nonebot.dev/store)
|
||||
- 其他插件请查看 [商店](https://nonebot.dev/store)
|
||||
|
||||
## 许可证
|
||||
|
||||
|
@@ -19,6 +19,11 @@ __autodoc__ = {
|
||||
"Event": True,
|
||||
"Adapter": True,
|
||||
"Message": True,
|
||||
"Message.__getitem__": True,
|
||||
"Message.__contains__": True,
|
||||
"Message._construct": True,
|
||||
"MessageSegment": True,
|
||||
"MessageSegment.__str__": True,
|
||||
"MessageSegment.__add__": True,
|
||||
"MessageTemplate": True,
|
||||
}
|
||||
|
@@ -158,7 +158,7 @@ class Config(BaseConfig):
|
||||
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
|
||||
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
|
||||
|
||||
配置方法参考: [配置](https://v2.nonebot.dev/docs/appendices/config)
|
||||
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
|
||||
"""
|
||||
|
||||
_env_file: DotenvType = ".env", ".env.prod"
|
||||
|
@@ -42,12 +42,6 @@ SHELL_ARGV: Literal["_argv"] = "_argv"
|
||||
|
||||
REGEX_MATCHED: Literal["_matched"] = "_matched"
|
||||
"""正则匹配结果存储 key"""
|
||||
REGEX_STR: Literal["_matched_str"] = "_matched_str"
|
||||
"""正则匹配文本存储 key"""
|
||||
REGEX_GROUP: Literal["_matched_groups"] = "_matched_groups"
|
||||
"""正则匹配 group 元组存储 key"""
|
||||
REGEX_DICT: Literal["_matched_dict"] = "_matched_dict"
|
||||
"""正则匹配 group 字典存储 key"""
|
||||
STARTSWITH_KEY: Literal["_startswith"] = "_startswith"
|
||||
"""响应触发前缀 key"""
|
||||
ENDSWITH_KEY: Literal["_endswith"] = "_endswith"
|
||||
|
@@ -55,16 +55,12 @@ class Driver(BaseDriver):
|
||||
|
||||
@overrides(BaseDriver)
|
||||
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
"""
|
||||
注册一个启动时执行的函数
|
||||
"""
|
||||
"""注册一个启动时执行的函数"""
|
||||
return self._lifespan.on_startup(func)
|
||||
|
||||
@overrides(BaseDriver)
|
||||
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
"""
|
||||
注册一个停止时执行的函数
|
||||
"""
|
||||
"""注册一个停止时执行的函数"""
|
||||
return self._lifespan.on_shutdown(func)
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@@ -146,7 +142,15 @@ class Driver(BaseDriver):
|
||||
signal.signal(sig, self._handle_exit)
|
||||
|
||||
def _handle_exit(self, sig, frame):
|
||||
if self.should_exit.is_set():
|
||||
self.force_exit = True
|
||||
else:
|
||||
self.exit(force=self.should_exit.is_set())
|
||||
|
||||
def exit(self, force: bool = False):
|
||||
"""退出 none driver
|
||||
|
||||
参数:
|
||||
force: 强制退出
|
||||
"""
|
||||
if not self.should_exit.is_set():
|
||||
self.should_exit.set()
|
||||
if force:
|
||||
self.force_exit = True
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import abc
|
||||
from copy import deepcopy
|
||||
from typing_extensions import Self
|
||||
from dataclasses import field, asdict, dataclass
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -12,6 +13,7 @@ from typing import (
|
||||
TypeVar,
|
||||
Iterable,
|
||||
Optional,
|
||||
SupportsIndex,
|
||||
overload,
|
||||
)
|
||||
|
||||
@@ -19,7 +21,6 @@ from pydantic import parse_obj_as
|
||||
|
||||
from .template import MessageTemplate
|
||||
|
||||
T = TypeVar("T")
|
||||
TMS = TypeVar("TMS", bound="MessageSegment")
|
||||
TM = TypeVar("TM", bound="Message")
|
||||
|
||||
@@ -47,7 +48,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
def __len__(self) -> int:
|
||||
return len(str(self))
|
||||
|
||||
def __ne__(self: T, other: T) -> bool:
|
||||
def __ne__(self, other: Self) -> bool:
|
||||
return not self == other
|
||||
|
||||
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
@@ -61,7 +62,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
yield cls._validate
|
||||
|
||||
@classmethod
|
||||
def _validate(cls, value):
|
||||
def _validate(cls, value) -> Self:
|
||||
if isinstance(value, cls):
|
||||
return value
|
||||
if not isinstance(value, dict):
|
||||
@@ -84,7 +85,10 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
def items(self):
|
||||
return asdict(self).items()
|
||||
|
||||
def copy(self: T) -> T:
|
||||
def join(self: TMS, iterable: Iterable[Union[TMS, TM]]) -> TM:
|
||||
return self.get_message_class()(self).join(iterable)
|
||||
|
||||
def copy(self) -> Self:
|
||||
return deepcopy(self)
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -117,7 +121,7 @@ class Message(List[TMS], abc.ABC):
|
||||
self.extend(self._construct(message)) # pragma: no cover
|
||||
|
||||
@classmethod
|
||||
def template(cls: Type[TM], format_string: Union[str, TM]) -> MessageTemplate[TM]:
|
||||
def template(cls, format_string: Union[str, TM]) -> MessageTemplate[Self]:
|
||||
"""创建消息模板。
|
||||
|
||||
用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板
|
||||
@@ -146,7 +150,7 @@ class Message(List[TMS], abc.ABC):
|
||||
yield cls._validate
|
||||
|
||||
@classmethod
|
||||
def _validate(cls, value):
|
||||
def _validate(cls, value) -> Self:
|
||||
if isinstance(value, cls):
|
||||
return value
|
||||
elif isinstance(value, Message):
|
||||
@@ -169,16 +173,16 @@ class Message(List[TMS], abc.ABC):
|
||||
"""构造消息数组"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __add__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
def __add__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
|
||||
result = self.copy()
|
||||
result += other
|
||||
return result
|
||||
|
||||
def __radd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
def __radd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
|
||||
result = self.__class__(other)
|
||||
return result + self
|
||||
|
||||
def __iadd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
def __iadd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
|
||||
if isinstance(other, str):
|
||||
self.extend(self._construct(other))
|
||||
elif isinstance(other, MessageSegment):
|
||||
@@ -190,57 +194,62 @@ class Message(List[TMS], abc.ABC):
|
||||
return self
|
||||
|
||||
@overload
|
||||
def __getitem__(self: TM, __args: str) -> TM:
|
||||
"""
|
||||
def __getitem__(self, args: str) -> Self:
|
||||
"""获取仅包含指定消息段类型的消息
|
||||
|
||||
参数:
|
||||
__args: 消息段类型
|
||||
args: 消息段类型
|
||||
|
||||
返回:
|
||||
所有类型为 `__args` 的消息段
|
||||
所有类型为 `args` 的消息段
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self, __args: Tuple[str, int]) -> TMS:
|
||||
"""
|
||||
def __getitem__(self, args: Tuple[str, int]) -> TMS:
|
||||
"""索引指定类型的消息段
|
||||
|
||||
参数:
|
||||
__args: 消息段类型和索引
|
||||
args: 消息段类型和索引
|
||||
|
||||
返回:
|
||||
类型为 `__args[0]` 的消息段第 `__args[1]` 个
|
||||
类型为 `args[0]` 的消息段第 `args[1]` 个
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self: TM, __args: Tuple[str, slice]) -> TM:
|
||||
"""
|
||||
def __getitem__(self, args: Tuple[str, slice]) -> Self:
|
||||
"""切片指定类型的消息段
|
||||
|
||||
参数:
|
||||
__args: 消息段类型和切片
|
||||
args: 消息段类型和切片
|
||||
|
||||
返回:
|
||||
类型为 `__args[0]` 的消息段切片 `__args[1]`
|
||||
类型为 `args[0]` 的消息段切片 `args[1]`
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self, __args: int) -> TMS:
|
||||
"""
|
||||
def __getitem__(self, args: int) -> TMS:
|
||||
"""索引消息段
|
||||
|
||||
参数:
|
||||
__args: 索引
|
||||
args: 索引
|
||||
|
||||
返回:
|
||||
第 `__args` 个消息段
|
||||
第 `args` 个消息段
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self: TM, __args: slice) -> TM:
|
||||
"""
|
||||
def __getitem__(self, args: slice) -> Self:
|
||||
"""切片消息段
|
||||
|
||||
参数:
|
||||
__args: 切片
|
||||
args: 切片
|
||||
|
||||
返回:
|
||||
消息切片 `__args`
|
||||
消息切片 `args`
|
||||
"""
|
||||
|
||||
def __getitem__(
|
||||
self: TM,
|
||||
self,
|
||||
args: Union[
|
||||
str,
|
||||
Tuple[str, int],
|
||||
@@ -248,7 +257,7 @@ class Message(List[TMS], abc.ABC):
|
||||
int,
|
||||
slice,
|
||||
],
|
||||
) -> Union[TMS, TM]:
|
||||
) -> Union[TMS, Self]:
|
||||
arg1, arg2 = args if isinstance(args, tuple) else (args, None)
|
||||
if isinstance(arg1, int) and arg2 is None:
|
||||
return super().__getitem__(arg1)
|
||||
@@ -263,15 +272,52 @@ class Message(List[TMS], abc.ABC):
|
||||
else:
|
||||
raise ValueError("Incorrect arguments to slice") # pragma: no cover
|
||||
|
||||
def index(self, value: Union[TMS, str], *args) -> int:
|
||||
def __contains__(self, value: Union[TMS, str]) -> bool:
|
||||
"""检查消息段是否存在
|
||||
|
||||
参数:
|
||||
value: 消息段或消息段类型
|
||||
返回:
|
||||
消息内是否存在给定消息段或给定类型的消息段
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return bool(next((seg for seg in self if seg.type == value), None))
|
||||
return super().__contains__(value)
|
||||
|
||||
def has(self, value: Union[TMS, str]) -> bool:
|
||||
"""与 {ref}``__contains__` <nonebot.adapters.Message.__contains__>` 相同"""
|
||||
return value in self
|
||||
|
||||
def index(self, value: Union[TMS, str], *args: SupportsIndex) -> int:
|
||||
"""索引消息段
|
||||
|
||||
参数:
|
||||
value: 消息段或者消息段类型
|
||||
arg: start 与 end
|
||||
|
||||
返回:
|
||||
索引 index
|
||||
|
||||
异常:
|
||||
ValueError: 消息段不存在
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
first_segment = next((seg for seg in self if seg.type == value), None)
|
||||
if first_segment is None:
|
||||
raise ValueError(f"Segment with type {value} is not in message")
|
||||
raise ValueError(f"Segment with type {value!r} is not in message")
|
||||
return super().index(first_segment, *args)
|
||||
return super().index(value, *args)
|
||||
|
||||
def get(self: TM, type_: str, count: Optional[int] = None) -> TM:
|
||||
def get(self, type_: str, count: Optional[int] = None) -> Self:
|
||||
"""获取指定类型的消息段
|
||||
|
||||
参数:
|
||||
type_: 消息段类型
|
||||
count: 获取个数
|
||||
|
||||
返回:
|
||||
构建的新消息
|
||||
"""
|
||||
if count is None:
|
||||
return self[type_]
|
||||
|
||||
@@ -286,9 +332,30 @@ class Message(List[TMS], abc.ABC):
|
||||
return filtered
|
||||
|
||||
def count(self, value: Union[TMS, str]) -> int:
|
||||
"""计算指定消息段的个数
|
||||
|
||||
参数:
|
||||
value: 消息段或消息段类型
|
||||
|
||||
返回:
|
||||
个数
|
||||
"""
|
||||
return len(self[value]) if isinstance(value, str) else super().count(value)
|
||||
|
||||
def append(self: TM, obj: Union[str, TMS]) -> TM:
|
||||
def only(self, value: Union[TMS, str]) -> bool:
|
||||
"""检查消息中是否仅包含指定消息段
|
||||
|
||||
参数:
|
||||
value: 指定消息段或消息段类型
|
||||
|
||||
返回:
|
||||
是否仅包含指定消息段
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return all(seg.type == value for seg in self)
|
||||
return all(seg == value for seg in self)
|
||||
|
||||
def append(self, obj: Union[str, TMS]) -> Self:
|
||||
"""添加一个消息段到消息数组末尾。
|
||||
|
||||
参数:
|
||||
@@ -302,7 +369,7 @@ class Message(List[TMS], abc.ABC):
|
||||
raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
|
||||
return self
|
||||
|
||||
def extend(self: TM, obj: Union[TM, Iterable[TMS]]) -> TM:
|
||||
def extend(self, obj: Union[Self, Iterable[TMS]]) -> Self:
|
||||
"""拼接一个消息数组或多个消息段到消息数组末尾。
|
||||
|
||||
参数:
|
||||
@@ -312,18 +379,52 @@ class Message(List[TMS], abc.ABC):
|
||||
self.append(segment)
|
||||
return self
|
||||
|
||||
def copy(self: TM) -> TM:
|
||||
def join(self, iterable: Iterable[Union[TMS, Self]]) -> Self:
|
||||
"""将多个消息连接并将自身作为分割
|
||||
|
||||
参数:
|
||||
iterable: 要连接的消息
|
||||
|
||||
返回:
|
||||
连接后的消息
|
||||
"""
|
||||
ret = self.__class__()
|
||||
for index, msg in enumerate(iterable):
|
||||
if index != 0:
|
||||
ret.extend(self)
|
||||
if isinstance(msg, MessageSegment):
|
||||
ret.append(msg.copy())
|
||||
else:
|
||||
ret.extend(msg.copy())
|
||||
return ret
|
||||
|
||||
def copy(self) -> Self:
|
||||
"""深拷贝消息"""
|
||||
return deepcopy(self)
|
||||
|
||||
def include(self, *types: str) -> Self:
|
||||
"""过滤消息
|
||||
|
||||
参数:
|
||||
types: 包含的消息段类型
|
||||
|
||||
返回:
|
||||
新构造的消息
|
||||
"""
|
||||
return self.__class__(seg for seg in self if seg.type in types)
|
||||
|
||||
def exclude(self, *types: str) -> Self:
|
||||
"""过滤消息
|
||||
|
||||
参数:
|
||||
types: 不包含的消息段类型
|
||||
|
||||
返回:
|
||||
新构造的消息
|
||||
"""
|
||||
return self.__class__(seg for seg in self if seg.type not in types)
|
||||
|
||||
def extract_plain_text(self) -> str:
|
||||
"""提取消息内纯文本消息"""
|
||||
|
||||
return "".join(str(seg) for seg in self if seg.is_text())
|
||||
|
||||
|
||||
__autodoc__ = {
|
||||
"MessageSegment.__str__": True,
|
||||
"MessageSegment.__add__": True,
|
||||
"Message.__getitem__": True,
|
||||
"Message._construct": True,
|
||||
}
|
||||
|
@@ -71,7 +71,12 @@ def Depends(
|
||||
|
||||
|
||||
class DependParam(Param):
|
||||
"""子依赖参数"""
|
||||
"""子依赖注入参数。
|
||||
|
||||
本注入解析所有子依赖注入,然后将它们的返回值作为参数值传递给父依赖。
|
||||
|
||||
本注入应该具有最高优先级,因此应该在其他参数之前检查。
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Depends({self.extra['dependent']})"
|
||||
@@ -168,7 +173,12 @@ class DependParam(Param):
|
||||
|
||||
|
||||
class BotParam(Param):
|
||||
"""{ref}`nonebot.adapters.Bot` 参数"""
|
||||
"""{ref}`nonebot.adapters.Bot` 注入参数。
|
||||
|
||||
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Bot` 及其子类或 `None` 的参数。
|
||||
|
||||
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@@ -187,21 +197,22 @@ class BotParam(Param):
|
||||
) -> Optional["BotParam"]:
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Bot):
|
||||
checker: Optional[ModelField] = None
|
||||
if param.annotation is not Bot:
|
||||
checker = ModelField(
|
||||
name=param.name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
)
|
||||
return cls(Required, checker=checker)
|
||||
elif param.annotation == param.empty and param.name == "bot":
|
||||
return cls(Required)
|
||||
# param type is Bot(s) or subclass(es) of Bot or None
|
||||
if generic_check_issubclass(param.annotation, Bot):
|
||||
checker: Optional[ModelField] = None
|
||||
if param.annotation is not Bot:
|
||||
checker = ModelField(
|
||||
name=param.name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
)
|
||||
return cls(Required, checker=checker)
|
||||
# legacy: param is named "bot" and has no type annotation
|
||||
elif param.annotation == param.empty and param.name == "bot":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||
return bot
|
||||
@@ -212,7 +223,12 @@ class BotParam(Param):
|
||||
|
||||
|
||||
class EventParam(Param):
|
||||
"""{ref}`nonebot.adapters.Event` 参数"""
|
||||
"""{ref}`nonebot.adapters.Event` 注入参数
|
||||
|
||||
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Event` 及其子类或 `None` 的参数。
|
||||
|
||||
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@@ -231,21 +247,22 @@ class EventParam(Param):
|
||||
) -> Optional["EventParam"]:
|
||||
from nonebot.adapters import Event
|
||||
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Event):
|
||||
checker: Optional[ModelField] = None
|
||||
if param.annotation is not Event:
|
||||
checker = ModelField(
|
||||
name=param.name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
)
|
||||
return cls(Required, checker=checker)
|
||||
elif param.annotation == param.empty and param.name == "event":
|
||||
return cls(Required)
|
||||
# param type is Event(s) or subclass(es) of Event or None
|
||||
if generic_check_issubclass(param.annotation, Event):
|
||||
checker: Optional[ModelField] = None
|
||||
if param.annotation is not Event:
|
||||
checker = ModelField(
|
||||
name=param.name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
)
|
||||
return cls(Required, checker=checker)
|
||||
# legacy: param is named "event" and has no type annotation
|
||||
elif param.annotation == param.empty and param.name == "event":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||
return event
|
||||
@@ -256,7 +273,12 @@ class EventParam(Param):
|
||||
|
||||
|
||||
class StateParam(Param):
|
||||
"""事件处理状态参数"""
|
||||
"""事件处理状态注入参数
|
||||
|
||||
本注入解析所有类型为 `T_State` 的参数。
|
||||
|
||||
为保证兼容性,本注入还会解析名为 `state` 且没有类型注解的参数。
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "StateParam()"
|
||||
@@ -265,18 +287,24 @@ class StateParam(Param):
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["StateParam"]:
|
||||
if param.default == param.empty:
|
||||
if param.annotation is T_State:
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and param.name == "state":
|
||||
return cls(Required)
|
||||
# param type is T_State
|
||||
if param.annotation is T_State:
|
||||
return cls(Required)
|
||||
# legacy: param is named "state" and has no type annotation
|
||||
elif param.annotation == param.empty and param.name == "state":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||
return state
|
||||
|
||||
|
||||
class MatcherParam(Param):
|
||||
"""事件响应器实例参数"""
|
||||
"""事件响应器实例注入参数
|
||||
|
||||
本注入解析所有类型为且仅为 {ref}`nonebot.matcher.Matcher` 及其子类或 `None` 的参数。
|
||||
|
||||
为保证兼容性,本注入还会解析名为 `matcher` 且没有类型注解的参数。
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "MatcherParam()"
|
||||
@@ -287,9 +315,11 @@ class MatcherParam(Param):
|
||||
) -> Optional["MatcherParam"]:
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
if generic_check_issubclass(param.annotation, Matcher) or (
|
||||
param.annotation == param.empty and param.name == "matcher"
|
||||
):
|
||||
# param type is Matcher(s) or subclass(es) of Matcher or None
|
||||
if generic_check_issubclass(param.annotation, Matcher):
|
||||
return cls(Required)
|
||||
# legacy: param is named "matcher" and has no type annotation
|
||||
elif param.annotation == param.empty and param.name == "matcher":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
@@ -308,22 +338,28 @@ class ArgInner:
|
||||
|
||||
|
||||
def Arg(key: Optional[str] = None) -> Any:
|
||||
"""`got` 的 Arg 参数消息"""
|
||||
"""Arg 参数消息"""
|
||||
return ArgInner(key, "message")
|
||||
|
||||
|
||||
def ArgStr(key: Optional[str] = None) -> str:
|
||||
"""`got` 的 Arg 参数消息文本"""
|
||||
"""Arg 参数消息文本"""
|
||||
return ArgInner(key, "str") # type: ignore
|
||||
|
||||
|
||||
def ArgPlainText(key: Optional[str] = None) -> str:
|
||||
"""`got` 的 Arg 参数消息纯文本"""
|
||||
"""Arg 参数消息纯文本"""
|
||||
return ArgInner(key, "plaintext") # type: ignore
|
||||
|
||||
|
||||
class ArgParam(Param):
|
||||
"""`got` 的 Arg 参数"""
|
||||
"""Arg 注入参数
|
||||
|
||||
本注入解析事件响应器操作 `got` 所获取的参数。
|
||||
|
||||
可以通过 `Arg`、`ArgStr`、`ArgPlainText` 等函数参数 `key` 指定获取的参数,
|
||||
留空则会根据参数名称获取。
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
|
||||
@@ -338,7 +374,8 @@ class ArgParam(Param):
|
||||
)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
message = matcher.get_arg(self.extra["key"])
|
||||
key: str = self.extra["key"]
|
||||
message = matcher.get_arg(key)
|
||||
if message is None:
|
||||
return message
|
||||
if self.extra["type"] == "message":
|
||||
@@ -350,7 +387,12 @@ class ArgParam(Param):
|
||||
|
||||
|
||||
class ExceptionParam(Param):
|
||||
"""`run_postprocessor` 的异常参数"""
|
||||
"""{ref}`nonebot.message.run_postprocessor` 的异常注入参数
|
||||
|
||||
本注入解析所有类型为 `Exception` 或 `None` 的参数。
|
||||
|
||||
为保证兼容性,本注入还会解析名为 `exception` 且没有类型注解的参数。
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "ExceptionParam()"
|
||||
@@ -359,9 +401,11 @@ class ExceptionParam(Param):
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["ExceptionParam"]:
|
||||
if generic_check_issubclass(param.annotation, Exception) or (
|
||||
param.annotation == param.empty and param.name == "exception"
|
||||
):
|
||||
# param type is Exception(s) or subclass(es) of Exception or None
|
||||
if generic_check_issubclass(param.annotation, Exception):
|
||||
return cls(Required)
|
||||
# legacy: param is named "exception" and has no type annotation
|
||||
elif param.annotation == param.empty and param.name == "exception":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
||||
@@ -369,7 +413,12 @@ class ExceptionParam(Param):
|
||||
|
||||
|
||||
class DefaultParam(Param):
|
||||
"""默认值参数"""
|
||||
"""默认值注入参数
|
||||
|
||||
本注入解析所有剩余未能解析且具有默认值的参数。
|
||||
|
||||
本注入参数应该具有最低优先级,因此应该在所有其他注入参数之后使用。
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"DefaultParam(default={self.default!r})"
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
|
||||
|
||||
自定义 logger 请参考 [自定义日志](https://v2.nonebot.dev/docs/appendices/log)
|
||||
自定义 logger 请参考 [自定义日志](https://nonebot.dev/docs/appendices/log)
|
||||
以及 [`loguru`][loguru] 文档。
|
||||
|
||||
[loguru]: https://github.com/Delgan/loguru
|
||||
|
@@ -80,7 +80,10 @@ RUN_POSTPCS_PARAMS = (
|
||||
|
||||
|
||||
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
||||
"""事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。"""
|
||||
"""事件预处理。
|
||||
|
||||
装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。
|
||||
"""
|
||||
_event_preprocessors.add(
|
||||
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||
)
|
||||
@@ -88,7 +91,10 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
||||
|
||||
|
||||
def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
|
||||
"""事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。"""
|
||||
"""事件后处理。
|
||||
|
||||
装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。
|
||||
"""
|
||||
_event_postprocessors.add(
|
||||
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||
)
|
||||
@@ -96,7 +102,10 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
|
||||
|
||||
|
||||
def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
|
||||
"""运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。"""
|
||||
"""运行预处理。
|
||||
|
||||
装饰一个函数,使它在每次事件响应器运行前执行。
|
||||
"""
|
||||
_run_preprocessors.add(
|
||||
Dependent[Any].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
|
||||
)
|
||||
@@ -104,13 +113,222 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
|
||||
|
||||
|
||||
def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
||||
"""运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。"""
|
||||
"""运行后处理。
|
||||
|
||||
装饰一个函数,使它在每次事件响应器运行后执行。
|
||||
"""
|
||||
_run_postprocessors.add(
|
||||
Dependent[Any].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
|
||||
)
|
||||
return func
|
||||
|
||||
|
||||
async def _apply_event_preprocessors(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
show_log: bool = True,
|
||||
) -> bool:
|
||||
"""运行事件预处理。
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
show_log: 是否显示日志
|
||||
|
||||
返回:
|
||||
是否继续处理事件
|
||||
"""
|
||||
if not _event_preprocessors:
|
||||
return True
|
||||
|
||||
if show_log:
|
||||
logger.debug("Running PreProcessors...")
|
||||
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _event_preprocessors
|
||||
)
|
||||
)
|
||||
except IgnoredException as e:
|
||||
logger.opt(colors=True).info(
|
||||
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||
"Event ignored!</bg #f8bbd0></r>"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _apply_event_postprocessors(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
show_log: bool = True,
|
||||
) -> None:
|
||||
"""运行事件后处理。
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
show_log: 是否显示日志
|
||||
"""
|
||||
if not _event_postprocessors:
|
||||
return
|
||||
|
||||
if show_log:
|
||||
logger.debug("Running PostProcessors...")
|
||||
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _event_postprocessors
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
|
||||
async def _apply_run_preprocessors(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
matcher: Matcher,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""运行事件响应器运行前处理。
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
matcher: 事件响应器
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
|
||||
返回:
|
||||
是否继续处理事件
|
||||
"""
|
||||
if not _run_preprocessors:
|
||||
return True
|
||||
|
||||
# ensure matcher function can be correctly called
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _run_preprocessors
|
||||
)
|
||||
)
|
||||
except IgnoredException:
|
||||
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _apply_run_postprocessors(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
matcher: Matcher,
|
||||
exception: Optional[Exception] = None,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
"""运行事件响应器运行后处理。
|
||||
|
||||
Args:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
matcher: 事件响应器
|
||||
exception: 事件响应器运行异常
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
"""
|
||||
if not _run_postprocessors:
|
||||
return
|
||||
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
exception=exception,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=matcher.state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _run_postprocessors
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
|
||||
async def _check_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
bot: "Bot",
|
||||
@@ -118,27 +336,39 @@ async def _check_matcher(
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
) -> bool:
|
||||
"""检查事件响应器是否符合运行条件。
|
||||
|
||||
请注意,过时的事件响应器将被**销毁**。对于未过时的事件响应器,将会一次检查其响应类型、权限和规则。
|
||||
|
||||
参数:
|
||||
Matcher: 要检查的事件响应器
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
|
||||
返回:
|
||||
bool: 是否符合运行条件
|
||||
"""
|
||||
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
||||
with contextlib.suppress(Exception):
|
||||
Matcher.destroy()
|
||||
return
|
||||
return False
|
||||
|
||||
try:
|
||||
if not await Matcher.check_perm(
|
||||
bot, event, stack, dependency_cache
|
||||
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
|
||||
return
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
|
||||
)
|
||||
return
|
||||
return False
|
||||
|
||||
if Matcher.temp:
|
||||
with contextlib.suppress(Exception):
|
||||
Matcher.destroy()
|
||||
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
|
||||
return True
|
||||
|
||||
|
||||
async def _run_matcher(
|
||||
@@ -149,36 +379,38 @@ async def _run_matcher(
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
"""运行事件响应器。
|
||||
|
||||
临时事件响应器将在运行前被**销毁**。
|
||||
|
||||
参数:
|
||||
Matcher: 事件响应器
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
|
||||
异常:
|
||||
StopPropagation: 阻止事件继续传播
|
||||
"""
|
||||
logger.info(f"Event will be handled by {Matcher}")
|
||||
|
||||
matcher = Matcher()
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _run_preprocessors
|
||||
]:
|
||||
# ensure matcher function can be correctly called
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except IgnoredException:
|
||||
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
if Matcher.temp:
|
||||
with contextlib.suppress(Exception):
|
||||
Matcher.destroy()
|
||||
|
||||
return
|
||||
matcher = Matcher()
|
||||
|
||||
if not await _apply_run_preprocessors(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
matcher=matcher,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
):
|
||||
return
|
||||
|
||||
exception = None
|
||||
|
||||
@@ -191,33 +423,55 @@ async def _run_matcher(
|
||||
)
|
||||
exception = e
|
||||
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
exception=exception,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=matcher.state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _run_postprocessors
|
||||
]:
|
||||
# ensure matcher function can be correctly called
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
await _apply_run_postprocessors(
|
||||
bot=bot,
|
||||
event=event,
|
||||
matcher=matcher,
|
||||
exception=exception,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
|
||||
if matcher.block:
|
||||
raise StopPropagation
|
||||
return
|
||||
|
||||
|
||||
async def check_and_run_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
"""检查并运行事件响应器。
|
||||
|
||||
参数:
|
||||
Matcher: 事件响应器
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
"""
|
||||
if not await _check_matcher(
|
||||
Matcher=Matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
):
|
||||
return
|
||||
|
||||
await _run_matcher(
|
||||
Matcher=Matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
|
||||
|
||||
async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
@@ -245,35 +499,16 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
state: Dict[Any, Any] = {}
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
|
||||
# create event scope context
|
||||
async with AsyncExitStack() as stack:
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _event_preprocessors
|
||||
]:
|
||||
try:
|
||||
if show_log:
|
||||
logger.debug("Running PreProcessors...")
|
||||
await asyncio.gather(*coros)
|
||||
except IgnoredException as e:
|
||||
logger.opt(colors=True).info(
|
||||
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||
"Event ignored!</bg #f8bbd0></r>"
|
||||
)
|
||||
return
|
||||
if not await _apply_event_preprocessors(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
):
|
||||
return
|
||||
|
||||
# Trie Match
|
||||
try:
|
||||
@@ -284,6 +519,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
)
|
||||
|
||||
break_flag = False
|
||||
# iterate through all priority until stop propagation
|
||||
for priority in sorted(matchers.keys()):
|
||||
if break_flag:
|
||||
break
|
||||
@@ -292,14 +528,12 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
logger.debug(f"Checking for matchers in priority {priority}...")
|
||||
|
||||
pending_tasks = [
|
||||
_check_matcher(
|
||||
check_and_run_matcher(
|
||||
matcher, bot, event, state.copy(), stack, dependency_cache
|
||||
)
|
||||
for matcher in matchers[priority]
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
|
||||
|
||||
for result in results:
|
||||
if not isinstance(result, Exception):
|
||||
continue
|
||||
@@ -314,24 +548,4 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
if show_log:
|
||||
logger.debug("Checking for matchers completed")
|
||||
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _event_postprocessors
|
||||
]:
|
||||
try:
|
||||
if show_log:
|
||||
logger.debug("Running PostProcessors...")
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
await _apply_event_postprocessors(bot, event, state, stack, dependency_cache)
|
||||
|
@@ -5,8 +5,7 @@ FrontMatter:
|
||||
description: nonebot.params 模块
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
from typing import Any, Dict, List, Match, Tuple, Union, Optional
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
@@ -25,15 +24,12 @@ from nonebot.internal.params import MatcherParam as MatcherParam
|
||||
from nonebot.internal.params import ExceptionParam as ExceptionParam
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
REGEX_STR,
|
||||
PREFIX_KEY,
|
||||
REGEX_DICT,
|
||||
SHELL_ARGS,
|
||||
SHELL_ARGV,
|
||||
CMD_ARG_KEY,
|
||||
KEYWORD_KEY,
|
||||
RAW_CMD_KEY,
|
||||
REGEX_GROUP,
|
||||
ENDSWITH_KEY,
|
||||
CMD_START_KEY,
|
||||
FULLMATCH_KEY,
|
||||
@@ -142,23 +138,17 @@ def ShellCommandArgv() -> Any:
|
||||
return Depends(_shell_command_argv, use_cache=False)
|
||||
|
||||
|
||||
def _regex_matched(state: T_State) -> str:
|
||||
def _regex_matched(state: T_State) -> Match[str]:
|
||||
return state[REGEX_MATCHED]
|
||||
|
||||
|
||||
def RegexMatched() -> str:
|
||||
def RegexMatched() -> Match[str]:
|
||||
"""正则匹配结果"""
|
||||
warnings.warn(
|
||||
'"RegexMatched()" will be changed to "re.Match" object, '
|
||||
'use "RegexStr()" instead. '
|
||||
"See https://github.com/nonebot/nonebot2/pull/1453 .",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return Depends(_regex_matched, use_cache=False)
|
||||
|
||||
|
||||
def _regex_str(state: T_State) -> str:
|
||||
return state[REGEX_STR]
|
||||
return _regex_matched(state).group()
|
||||
|
||||
|
||||
def RegexStr() -> str:
|
||||
@@ -167,7 +157,7 @@ def RegexStr() -> str:
|
||||
|
||||
|
||||
def _regex_group(state: T_State) -> Tuple[Any, ...]:
|
||||
return state[REGEX_GROUP]
|
||||
return _regex_matched(state).groups()
|
||||
|
||||
|
||||
def RegexGroup() -> Tuple[Any, ...]:
|
||||
@@ -176,7 +166,7 @@ def RegexGroup() -> Tuple[Any, ...]:
|
||||
|
||||
|
||||
def _regex_dict(state: T_State) -> Dict[str, Any]:
|
||||
return state[REGEX_DICT]
|
||||
return _regex_matched(state).groupdict()
|
||||
|
||||
|
||||
def RegexDict() -> Dict[str, Any]:
|
||||
|
@@ -13,10 +13,10 @@ from nonebot.utils import path_to_module_name
|
||||
|
||||
from .plugin import Plugin
|
||||
from .manager import PluginManager
|
||||
from . import _managers, get_plugin, _module_name_to_plugin_name
|
||||
from . import _managers, get_plugin, _current_plugin_chain, _module_name_to_plugin_name
|
||||
|
||||
try: # pragma: py-gte-311
|
||||
import tomllib # pyright: reportMissingImports=false
|
||||
import tomllib # pyright: ignore[reportMissingImports]
|
||||
except ModuleNotFoundError: # pragma: py-lt-311
|
||||
import tomli as tomllib
|
||||
|
||||
@@ -161,11 +161,19 @@ def require(name: str) -> ModuleType:
|
||||
RuntimeError: 插件无法加载
|
||||
"""
|
||||
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||
# if plugin not loaded
|
||||
if not plugin:
|
||||
# plugin already declared
|
||||
if manager := _find_manager_by_name(name):
|
||||
plugin = manager.load_plugin(name)
|
||||
# plugin not declared, try to declare and load it
|
||||
else:
|
||||
plugin = load_plugin(name)
|
||||
# clear current plugin chain, ensure plugin loaded in a new context
|
||||
_t = _current_plugin_chain.set(())
|
||||
try:
|
||||
plugin = load_plugin(name)
|
||||
finally:
|
||||
_current_plugin_chain.reset(_t)
|
||||
if not plugin:
|
||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||
return plugin.module
|
||||
|
@@ -228,6 +228,7 @@ class PluginLoader(SourceFileLoader):
|
||||
# detect parent plugin before entering current plugin context
|
||||
parent_plugins = _current_plugin_chain.get()
|
||||
for pre_plugin in reversed(parent_plugins):
|
||||
# ensure parent plugin is declared before current plugin
|
||||
if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
|
||||
plugin.parent_plugin = pre_plugin
|
||||
pre_plugin.sub_plugins.add(plugin)
|
||||
|
@@ -1,9 +1,11 @@
|
||||
"""本模块定义插件对象。
|
||||
"""本模块定义插件相关信息。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 3
|
||||
description: nonebot.plugin.plugin 模块
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
from types import ModuleType
|
||||
from dataclasses import field, dataclass
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||
@@ -11,11 +13,11 @@ from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
# FIXME: backport for nonebug
|
||||
from . import _plugins as plugins # nopycln: import
|
||||
from nonebot.utils import resolve_dot_notation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.adapters import Adapter
|
||||
|
||||
from .manager import PluginManager
|
||||
|
||||
|
||||
@@ -24,14 +26,39 @@ class PluginMetadata:
|
||||
"""插件元信息,由插件编写者提供"""
|
||||
|
||||
name: str
|
||||
"""插件可阅读名称"""
|
||||
"""插件名称"""
|
||||
description: str
|
||||
"""插件功能介绍"""
|
||||
usage: str
|
||||
"""插件使用方法"""
|
||||
type: Optional[str] = None
|
||||
"""插件类型,用于商店分类"""
|
||||
homepage: Optional[str] = None
|
||||
"""插件主页"""
|
||||
config: Optional[Type[BaseModel]] = None
|
||||
"""插件配置项"""
|
||||
supported_adapters: Optional[Set[str]] = None
|
||||
"""插件支持的适配器模块路径
|
||||
|
||||
格式为 `<module>[:<Adapter>]`,`~` 为 `nonebot.adapters.` 的缩写。
|
||||
|
||||
`None` 表示支持**所有适配器**。
|
||||
"""
|
||||
extra: Dict[Any, Any] = field(default_factory=dict)
|
||||
"""插件额外信息,可由插件编写者自由扩展定义"""
|
||||
|
||||
def get_supported_adapters(self) -> Optional[Set[Type["Adapter"]]]:
|
||||
"""获取当前已安装的插件支持适配器类列表"""
|
||||
if self.supported_adapters is None:
|
||||
return None
|
||||
|
||||
adapters = set()
|
||||
for adapter in self.supported_adapters:
|
||||
with contextlib.suppress(ModuleNotFoundError, AttributeError):
|
||||
adapters.add(
|
||||
resolve_dot_notation(adapter, "Adapter", "nonebot.adapters.")
|
||||
)
|
||||
return adapters
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
|
@@ -11,6 +11,7 @@ FrontMatter:
|
||||
import re
|
||||
import shlex
|
||||
from argparse import Action
|
||||
from gettext import gettext
|
||||
from argparse import ArgumentError
|
||||
from contextvars import ContextVar
|
||||
from itertools import chain, product
|
||||
@@ -43,15 +44,12 @@ from nonebot.adapters import Bot, Event, Message, MessageSegment
|
||||
from nonebot.params import Command, EventToMe, CommandArg, CommandWhitespace
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
REGEX_STR,
|
||||
PREFIX_KEY,
|
||||
REGEX_DICT,
|
||||
SHELL_ARGS,
|
||||
SHELL_ARGV,
|
||||
CMD_ARG_KEY,
|
||||
KEYWORD_KEY,
|
||||
RAW_CMD_KEY,
|
||||
REGEX_GROUP,
|
||||
ENDSWITH_KEY,
|
||||
CMD_START_KEY,
|
||||
FULLMATCH_KEY,
|
||||
@@ -67,7 +65,7 @@ CMD_RESULT = TypedDict(
|
||||
{
|
||||
"command": Optional[Tuple[str, ...]],
|
||||
"raw_command": Optional[str],
|
||||
"command_arg": Optional[Message[MessageSegment]],
|
||||
"command_arg": Optional[Message],
|
||||
"command_start": Optional[str],
|
||||
"command_whitespace": Optional[str],
|
||||
},
|
||||
@@ -378,11 +376,12 @@ class CommandRule:
|
||||
async def __call__(
|
||||
self,
|
||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||
cmd_arg: Optional[Message] = CommandArg(),
|
||||
cmd_whitespace: Optional[str] = CommandWhitespace(),
|
||||
) -> bool:
|
||||
if cmd not in self.cmds:
|
||||
return False
|
||||
if self.force_whitespace is None:
|
||||
if self.force_whitespace is None or not cmd_arg:
|
||||
return True
|
||||
if isinstance(self.force_whitespace, str):
|
||||
return self.force_whitespace == cmd_whitespace
|
||||
@@ -450,30 +449,61 @@ class ArgumentParser(ArgParser):
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]] = ...
|
||||
) -> Namespace:
|
||||
def parse_known_args(
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: None = None,
|
||||
) -> Tuple[Namespace, List[Union[str, MessageSegment]]]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: None
|
||||
) -> Namespace:
|
||||
... # type: ignore[misc]
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
def parse_known_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||
) -> T:
|
||||
) -> Tuple[T, List[Union[str, MessageSegment]]]:
|
||||
...
|
||||
|
||||
def parse_args(
|
||||
@overload
|
||||
def parse_known_args(
|
||||
self, *, namespace: T
|
||||
) -> Tuple[T, List[Union[str, MessageSegment]]]:
|
||||
...
|
||||
|
||||
def parse_known_args(
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: Optional[T] = None,
|
||||
) -> Union[Namespace, T]:
|
||||
) -> Tuple[Union[Namespace, T], List[Union[str, MessageSegment]]]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: None = None,
|
||||
) -> Namespace:
|
||||
...
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||
) -> T:
|
||||
...
|
||||
|
||||
@overload
|
||||
def parse_args(self, *, namespace: T) -> T:
|
||||
...
|
||||
|
||||
def parse_args(
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: Optional[T] = None,
|
||||
) -> Union[Namespace, T]:
|
||||
result, argv = self.parse_known_args(args, namespace)
|
||||
if argv:
|
||||
msg = gettext("unrecognized arguments: %s")
|
||||
self.error(msg % " ".join(map(str, argv)))
|
||||
return cast(Union[Namespace, T], result)
|
||||
|
||||
def _parse_optional(
|
||||
self, arg_string: Union[str, MessageSegment]
|
||||
) -> Optional[Tuple[Optional[Action], str, Optional[str]]]:
|
||||
@@ -646,10 +676,7 @@ class RegexRule:
|
||||
except Exception:
|
||||
return False
|
||||
if matched := re.search(self.regex, str(msg), self.flags):
|
||||
state[REGEX_MATCHED] = matched.group()
|
||||
state[REGEX_STR] = matched.group()
|
||||
state[REGEX_GROUP] = matched.groups()
|
||||
state[REGEX_DICT] = matched.groupdict()
|
||||
state[REGEX_MATCHED] = matched
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@@ -12,6 +12,7 @@ import inspect
|
||||
import importlib
|
||||
import dataclasses
|
||||
from pathlib import Path
|
||||
from contextvars import copy_context
|
||||
from functools import wraps, partial
|
||||
from contextlib import asynccontextmanager
|
||||
from typing_extensions import ParamSpec, get_args, get_origin
|
||||
@@ -58,7 +59,7 @@ def generic_check_issubclass(
|
||||
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。
|
||||
|
||||
特别的,如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
|
||||
则会检查其中的类型是否是 class_or_tuple 中的一个类型子类。(None 会被忽略)
|
||||
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
|
||||
"""
|
||||
try:
|
||||
return issubclass(cls, class_or_tuple)
|
||||
@@ -111,7 +112,8 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
|
||||
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
loop = asyncio.get_running_loop()
|
||||
pfunc = partial(call, *args, **kwargs)
|
||||
result = await loop.run_in_executor(None, pfunc)
|
||||
context = copy_context()
|
||||
result = await loop.run_in_executor(None, partial(context.run, pfunc))
|
||||
return result
|
||||
|
||||
return _wrapper
|
||||
@@ -136,6 +138,7 @@ async def run_sync_ctx_manager(
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
return_on_err: None = None,
|
||||
) -> Union[T, None]:
|
||||
...
|
||||
|
||||
@@ -193,7 +196,7 @@ def resolve_dot_notation(
|
||||
|
||||
|
||||
class DataclassEncoder(json.JSONEncoder):
|
||||
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
|
||||
"""在JSON序列化 {ref}`nonebot.adapters.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
|
||||
|
||||
@overrides(json.JSONEncoder)
|
||||
def default(self, o):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<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>
|
||||
<a href="https://nonebot.dev/"><img src="https://nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
|
954
poetry.lock
generated
954
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot2"
|
||||
version = "2.0.0rc4"
|
||||
version = "2.0.0"
|
||||
description = "An asynchronous python bot framework."
|
||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
homepage = "https://v2.nonebot.dev/"
|
||||
homepage = "https://nonebot.dev/"
|
||||
repository = "https://github.com/nonebot/nonebot2"
|
||||
documentation = "https://v2.nonebot.dev/"
|
||||
documentation = "https://nonebot.dev/"
|
||||
keywords = ["bot", "qq", "qqbot", "mirai", "coolq"]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -21,16 +21,21 @@ packages = [
|
||||
]
|
||||
include = ["nonebot/py.typed"]
|
||||
|
||||
[tool.poetry.urls]
|
||||
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
||||
"Changelog" = "https://nonebot.dev/changelog"
|
||||
"Funding" = "https://afdian.net/@nonebot"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
yarl = "^1.7.2"
|
||||
loguru = "^0.6.0"
|
||||
pygtrie = "^2.4.1"
|
||||
loguru = ">=0.6.0,<1.0.0"
|
||||
typing-extensions = ">=4.0.0,<5.0.0"
|
||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
||||
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
|
||||
|
||||
websockets = { version = "^10.0", optional = true }
|
||||
websockets = { version = ">=10.0", optional = true }
|
||||
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
||||
fastapi = { version = ">=0.93.0,<1.0.0", optional = true }
|
||||
aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }
|
||||
|
@@ -25,6 +25,6 @@ def load_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def load_example(nonebug_init: None) -> Set["Plugin"]:
|
||||
# preload example plugins
|
||||
return nonebot.load_plugins(str(Path(__file__).parent / "examples"))
|
||||
def load_builtin_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
# preload builtin plugins
|
||||
return nonebot.load_builtin_plugins("echo", "single_session")
|
||||
|
@@ -1,29 +0,0 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import Arg, CommandArg, ArgPlainText
|
||||
|
||||
weather = on_command("weather", rule=to_me(), aliases={"天气", "天气预报"}, priority=5)
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def handle_first_receive(matcher: Matcher, args: Message = CommandArg()):
|
||||
plain_text = args.extract_plain_text() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海
|
||||
if plain_text:
|
||||
matcher.set_arg("city", args) # 如果用户发送了参数则直接赋值
|
||||
|
||||
|
||||
@weather.got("city", prompt="你想查询哪个城市的天气呢?")
|
||||
async def handle_city(city: Message = Arg(), city_name: str = ArgPlainText("city")):
|
||||
if city_name not in ["北京", "上海"]: # 如果参数不符合要求,则提示用户重新输入
|
||||
# 可以使用平台的 Message 类直接构造模板消息
|
||||
await weather.reject(city.template("你想查询的城市 {city} 暂不支持,请重新输入!"))
|
||||
|
||||
city_weather = await get_weather(city_name)
|
||||
await weather.finish(city_weather)
|
||||
|
||||
|
||||
# 在这里编写获取天气信息的函数
|
||||
async def get_weather(city: str) -> str:
|
||||
return f"{city}的天气是..."
|
@@ -1,5 +1,6 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from nonebot.adapters import Adapter
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
|
||||
@@ -7,10 +8,17 @@ class Config(BaseModel):
|
||||
custom: str = ""
|
||||
|
||||
|
||||
class FakeAdapter(Adapter):
|
||||
...
|
||||
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="测试插件",
|
||||
description="测试插件元信息",
|
||||
usage="无法使用",
|
||||
type="application",
|
||||
homepage="https://nonebot.dev",
|
||||
config=Config,
|
||||
supported_adapters={"~onebot.v11", "plugins.metadata:FakeAdapter"},
|
||||
extra={"author": "NoneBot"},
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple
|
||||
from typing import List, Match, Tuple
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import Message
|
||||
@@ -73,12 +73,12 @@ async def regex_group(regex_group: Tuple = RegexGroup()) -> Tuple:
|
||||
return regex_group
|
||||
|
||||
|
||||
async def regex_matched(regex_matched: str = RegexMatched()) -> str:
|
||||
async def regex_matched(regex_matched: Match[str] = RegexMatched()) -> Match[str]:
|
||||
return regex_matched
|
||||
|
||||
|
||||
async def regex_str(regex_matched: str = RegexStr()) -> str:
|
||||
return regex_matched
|
||||
async def regex_str(regex_str: str = RegexStr()) -> str:
|
||||
return regex_str
|
||||
|
||||
|
||||
async def startswith(startswith: str = Startswith()) -> str:
|
||||
|
23
tests/plugins/param/priority.py
Normal file
23
tests/plugins/param/priority.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import Optional
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import Arg, Depends
|
||||
from nonebot.adapters import Bot, Event, Message
|
||||
|
||||
|
||||
def dependency():
|
||||
return 1
|
||||
|
||||
|
||||
async def complex_priority(
|
||||
sub: int = Depends(dependency),
|
||||
bot: Optional[Bot] = None,
|
||||
event: Optional[Event] = None,
|
||||
state: T_State = {},
|
||||
matcher: Optional[Matcher] = None,
|
||||
arg: Message = Arg(),
|
||||
exception: Optional[Exception] = None,
|
||||
default: int = 1,
|
||||
):
|
||||
...
|
@@ -41,6 +41,26 @@ def test_segment_validate():
|
||||
parse_obj_as(MessageSegment, {"data": {}})
|
||||
|
||||
|
||||
def test_segment_join():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
seg = MessageSegment.text("test")
|
||||
iterable = [
|
||||
MessageSegment.text("first"),
|
||||
Message([MessageSegment.text("second"), MessageSegment.text("third")]),
|
||||
]
|
||||
|
||||
assert seg.join(iterable) == Message(
|
||||
[
|
||||
MessageSegment.text("first"),
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.text("second"),
|
||||
MessageSegment.text("third"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_segment():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
@@ -146,3 +166,124 @@ def test_message_validate():
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
parse_obj_as(Message, object())
|
||||
|
||||
|
||||
def test_message_contains():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
message = Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.image("test2"),
|
||||
MessageSegment.image("test3"),
|
||||
MessageSegment.text("test4"),
|
||||
]
|
||||
)
|
||||
|
||||
assert message.has(MessageSegment.text("test")) is True
|
||||
assert MessageSegment.text("test") in message
|
||||
assert message.has("image") is True
|
||||
assert "image" in message
|
||||
|
||||
assert message.has(MessageSegment.text("foo")) is False
|
||||
assert MessageSegment.text("foo") not in message
|
||||
assert message.has("foo") is False
|
||||
assert "foo" not in message
|
||||
|
||||
|
||||
def test_message_only():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
message = Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.text("test2"),
|
||||
]
|
||||
)
|
||||
|
||||
assert message.only("text") is True
|
||||
assert message.only(MessageSegment.text("test")) is False
|
||||
|
||||
message = Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.image("test2"),
|
||||
MessageSegment.image("test3"),
|
||||
MessageSegment.text("test4"),
|
||||
]
|
||||
)
|
||||
|
||||
assert message.only("text") is False
|
||||
|
||||
message = Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.text("test"),
|
||||
]
|
||||
)
|
||||
|
||||
assert message.only(MessageSegment.text("test")) is True
|
||||
|
||||
|
||||
def test_message_join():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
msg = Message([MessageSegment.text("test")])
|
||||
iterable = [
|
||||
MessageSegment.text("first"),
|
||||
Message([MessageSegment.text("second"), MessageSegment.text("third")]),
|
||||
]
|
||||
|
||||
assert msg.join(iterable) == Message(
|
||||
[
|
||||
MessageSegment.text("first"),
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.text("second"),
|
||||
MessageSegment.text("third"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_message_include():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
message = Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.image("test2"),
|
||||
MessageSegment.image("test3"),
|
||||
MessageSegment.text("test4"),
|
||||
]
|
||||
)
|
||||
|
||||
assert message.include("text") == Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.text("test4"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_message_exclude():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
message = Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.image("test2"),
|
||||
MessageSegment.image("test3"),
|
||||
MessageSegment.text("test4"),
|
||||
]
|
||||
)
|
||||
|
||||
assert message.exclude("image") == Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.text("test4"),
|
||||
]
|
||||
)
|
||||
|
388
tests/test_broadcast.py
Normal file
388
tests/test_broadcast.py
Normal file
@@ -0,0 +1,388 @@
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
from nonebot import on_message
|
||||
import nonebot.message as message
|
||||
from utils import make_fake_event
|
||||
from nonebot.params import Depends
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.log import logger, default_filter, default_format
|
||||
from nonebot.message import (
|
||||
run_preprocessor,
|
||||
run_postprocessor,
|
||||
event_preprocessor,
|
||||
event_postprocessor,
|
||||
)
|
||||
|
||||
|
||||
async def _dependency() -> int:
|
||||
return 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_preprocessors", set())
|
||||
|
||||
runned = False
|
||||
|
||||
@event_preprocessor
|
||||
async def test_preprocessor(
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
sub: int = Depends(_dependency),
|
||||
default: int = 1,
|
||||
):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._event_preprocessors
|
||||
}
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
|
||||
assert runned, "event_preprocessor should runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_preprocessors", set())
|
||||
|
||||
@event_preprocessor
|
||||
async def test_preprocessor():
|
||||
raise IgnoredException("pass")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._event_preprocessors
|
||||
}
|
||||
|
||||
runned = False
|
||||
|
||||
async def handler():
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message(handlers=[handler])
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
|
||||
assert not runned, "matcher should not runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor_exception(
|
||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_preprocessors", set())
|
||||
|
||||
@event_preprocessor
|
||||
async def test_preprocessor():
|
||||
raise RuntimeError("test")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._event_preprocessors
|
||||
}
|
||||
|
||||
runned = False
|
||||
|
||||
async def handler():
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
handler_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
try:
|
||||
with app.provider.context({}):
|
||||
matcher = on_message(handlers=[handler])
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
finally:
|
||||
logger.remove(handler_id)
|
||||
|
||||
assert not runned, "matcher should not runned"
|
||||
assert "RuntimeError: test" in capsys.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_postprocessors", set())
|
||||
|
||||
runned = False
|
||||
|
||||
@event_postprocessor
|
||||
async def test_postprocessor(
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
sub: int = Depends(_dependency),
|
||||
default: int = 1,
|
||||
):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
assert test_postprocessor in {
|
||||
dependent.call for dependent in message._event_postprocessors
|
||||
}
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
|
||||
assert runned, "event_postprocessor should runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_postprocessor_exception(
|
||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_postprocessors", set())
|
||||
|
||||
@event_postprocessor
|
||||
async def test_postprocessor():
|
||||
raise RuntimeError("test")
|
||||
|
||||
assert test_postprocessor in {
|
||||
dependent.call for dependent in message._event_postprocessors
|
||||
}
|
||||
|
||||
handler_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
try:
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
finally:
|
||||
logger.remove(handler_id)
|
||||
|
||||
assert "RuntimeError: test" in capsys.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_preprocessors", set())
|
||||
|
||||
runned = False
|
||||
|
||||
@run_preprocessor
|
||||
async def test_preprocessor(
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
matcher: Matcher,
|
||||
sub: int = Depends(_dependency),
|
||||
default: int = 1,
|
||||
):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
await matcher.send("test")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._run_preprocessors
|
||||
}
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "test", True, bot)
|
||||
|
||||
assert runned, "run_preprocessor should runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_preprocessors", set())
|
||||
|
||||
@run_preprocessor
|
||||
async def test_preprocessor():
|
||||
raise IgnoredException("pass")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._run_preprocessors
|
||||
}
|
||||
|
||||
runned = False
|
||||
|
||||
async def handler():
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message(handlers=[handler])
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
|
||||
assert not runned, "matcher should not runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor_exception(
|
||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_preprocessors", set())
|
||||
|
||||
@run_preprocessor
|
||||
async def test_preprocessor():
|
||||
raise RuntimeError("test")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._run_preprocessors
|
||||
}
|
||||
|
||||
runned = False
|
||||
|
||||
async def handler():
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
handler_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
try:
|
||||
with app.provider.context({}):
|
||||
matcher = on_message(handlers=[handler])
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
finally:
|
||||
logger.remove(handler_id)
|
||||
|
||||
assert not runned, "matcher should not runned"
|
||||
assert "RuntimeError: test" in capsys.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_postprocessors", set())
|
||||
|
||||
runned = False
|
||||
|
||||
@run_postprocessor
|
||||
async def test_postprocessor(
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
matcher: Matcher,
|
||||
exception: Optional[Exception],
|
||||
sub: int = Depends(_dependency),
|
||||
default: int = 1,
|
||||
):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
await matcher.send("test")
|
||||
|
||||
assert test_postprocessor in {
|
||||
dependent.call for dependent in message._run_postprocessors
|
||||
}
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "test", True, bot)
|
||||
|
||||
assert runned, "run_postprocessor should runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_postprocessor_exception(
|
||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_postprocessors", set())
|
||||
|
||||
@run_postprocessor
|
||||
async def test_postprocessor():
|
||||
raise RuntimeError("test")
|
||||
|
||||
assert test_postprocessor in {
|
||||
dependent.call for dependent in message._run_postprocessors
|
||||
}
|
||||
|
||||
handler_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
try:
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
finally:
|
||||
logger.remove(handler_id)
|
||||
|
||||
assert "RuntimeError: test" in capsys.readouterr().out
|
@@ -1,76 +0,0 @@
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
from utils import make_fake_event, make_fake_message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_weather(app: App):
|
||||
from examples.weather import weather
|
||||
|
||||
# 将此处的 make_fake_message() 替换为你要发送的平台消息 Message 类型
|
||||
# from nonebot.adapters.console import Message
|
||||
Message = make_fake_message()
|
||||
|
||||
async with app.test_matcher(weather) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
|
||||
msg = Message("/天气 上海")
|
||||
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
|
||||
# from nonebot.adapters.console import MessageEvent
|
||||
# event = MessageEvent(message=msg, to_me=True, ...)
|
||||
event = make_fake_event(_message=msg, _to_me=True)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "上海的天气是...", True)
|
||||
ctx.should_finished()
|
||||
|
||||
async with app.test_matcher(weather) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
|
||||
msg = Message("/天气 南京")
|
||||
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
|
||||
event = make_fake_event(_message=msg, _to_me=True)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("南京"),
|
||||
True,
|
||||
)
|
||||
ctx.should_rejected()
|
||||
|
||||
msg = Message("北京")
|
||||
event = make_fake_event(_message=msg)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "北京的天气是...", True)
|
||||
ctx.should_finished()
|
||||
|
||||
async with app.test_matcher(weather) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
|
||||
msg = Message("/天气")
|
||||
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
|
||||
event = make_fake_event(_message=msg, _to_me=True)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "你想查询哪个城市的天气呢?", True)
|
||||
|
||||
msg = Message("杭州")
|
||||
event = make_fake_event(_message=msg)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("杭州"),
|
||||
True,
|
||||
)
|
||||
ctx.should_rejected()
|
||||
|
||||
msg = Message("北京")
|
||||
event = make_fake_event(_message=msg)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "北京的天气是...", True)
|
||||
ctx.should_finished()
|
@@ -2,8 +2,8 @@ import pytest
|
||||
from nonebug import App
|
||||
|
||||
from nonebot.permission import User
|
||||
from nonebot.message import _check_matcher
|
||||
from nonebot.matcher import Matcher, matchers
|
||||
from nonebot.message import check_and_run_matcher
|
||||
from utils import make_fake_event, make_fake_message
|
||||
|
||||
|
||||
@@ -200,19 +200,19 @@ async def test_expire(app: App):
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_temp_matcher in matchers[test_temp_matcher.priority]
|
||||
await _check_matcher(test_temp_matcher, bot, event, {})
|
||||
await check_and_run_matcher(test_temp_matcher, bot, event, {})
|
||||
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
|
||||
|
||||
event = make_fake_event()()
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
|
||||
await _check_matcher(test_datetime_matcher, bot, event, {})
|
||||
await check_and_run_matcher(test_datetime_matcher, bot, event, {})
|
||||
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
|
||||
|
||||
event = make_fake_event()()
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
|
||||
await _check_matcher(test_timedelta_matcher, bot, event, {})
|
||||
await check_and_run_matcher(test_timedelta_matcher, bot, event, {})
|
||||
assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import TypeMisMatch
|
||||
from utils import make_fake_event, make_fake_message
|
||||
from nonebot.params import (
|
||||
@@ -16,15 +19,12 @@ from nonebot.params import (
|
||||
)
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
REGEX_STR,
|
||||
PREFIX_KEY,
|
||||
REGEX_DICT,
|
||||
SHELL_ARGS,
|
||||
SHELL_ARGV,
|
||||
CMD_ARG_KEY,
|
||||
KEYWORD_KEY,
|
||||
RAW_CMD_KEY,
|
||||
REGEX_GROUP,
|
||||
ENDSWITH_KEY,
|
||||
CMD_START_KEY,
|
||||
FULLMATCH_KEY,
|
||||
@@ -226,6 +226,7 @@ async def test_state(app: App):
|
||||
)
|
||||
|
||||
fake_message = make_fake_message()("text")
|
||||
fake_matched = re.match(r"\[cq:(?P<type>.*?),(?P<arg>.*?)\]", "[cq:test,arg=value]")
|
||||
fake_state = {
|
||||
PREFIX_KEY: {
|
||||
CMD_KEY: ("cmd",),
|
||||
@@ -236,10 +237,7 @@ async def test_state(app: App):
|
||||
},
|
||||
SHELL_ARGV: ["-h"],
|
||||
SHELL_ARGS: {"help": True},
|
||||
REGEX_MATCHED: "[cq:test,arg=value]",
|
||||
REGEX_STR: "[cq:test,arg=value]",
|
||||
REGEX_GROUP: ("test", "arg=value"),
|
||||
REGEX_DICT: {"type": "test", "arg": "value"},
|
||||
REGEX_MATCHED: fake_matched,
|
||||
STARTSWITH_KEY: "startswith",
|
||||
ENDSWITH_KEY: "endswith",
|
||||
FULLMATCH_KEY: "fullmatch",
|
||||
@@ -312,19 +310,19 @@ async def test_state(app: App):
|
||||
regex_str, allow_types=[StateParam, DependParam]
|
||||
) as ctx:
|
||||
ctx.pass_params(state=fake_state)
|
||||
ctx.should_return(fake_state[REGEX_STR])
|
||||
ctx.should_return("[cq:test,arg=value]")
|
||||
|
||||
async with app.test_dependent(
|
||||
regex_group, allow_types=[StateParam, DependParam]
|
||||
) as ctx:
|
||||
ctx.pass_params(state=fake_state)
|
||||
ctx.should_return(fake_state[REGEX_GROUP])
|
||||
ctx.should_return(("test", "arg=value"))
|
||||
|
||||
async with app.test_dependent(
|
||||
regex_dict, allow_types=[StateParam, DependParam]
|
||||
) as ctx:
|
||||
ctx.pass_params(state=fake_state)
|
||||
ctx.should_return(fake_state[REGEX_DICT])
|
||||
ctx.should_return({"type": "test", "arg": "arg=value"})
|
||||
|
||||
async with app.test_dependent(
|
||||
startswith, allow_types=[StateParam, DependParam]
|
||||
@@ -416,3 +414,41 @@ async def test_default(app: App):
|
||||
|
||||
async with app.test_dependent(default, allow_types=[DefaultParam]) as ctx:
|
||||
ctx.should_return(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_priority():
|
||||
from plugins.param.priority import complex_priority
|
||||
|
||||
dependent = Dependent.parse(
|
||||
call=complex_priority,
|
||||
allow_types=[
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
MatcherParam,
|
||||
ArgParam,
|
||||
ExceptionParam,
|
||||
DefaultParam,
|
||||
],
|
||||
)
|
||||
for param in dependent.params:
|
||||
if param.name == "sub":
|
||||
assert isinstance(param.field_info, DependParam)
|
||||
elif param.name == "bot":
|
||||
assert isinstance(param.field_info, BotParam)
|
||||
elif param.name == "event":
|
||||
assert isinstance(param.field_info, EventParam)
|
||||
elif param.name == "state":
|
||||
assert isinstance(param.field_info, StateParam)
|
||||
elif param.name == "matcher":
|
||||
assert isinstance(param.field_info, MatcherParam)
|
||||
elif param.name == "arg":
|
||||
assert isinstance(param.field_info, ArgParam)
|
||||
elif param.name == "exception":
|
||||
assert isinstance(param.field_info, ExceptionParam)
|
||||
elif param.name == "default":
|
||||
assert isinstance(param.field_info, DefaultParam)
|
||||
else:
|
||||
raise ValueError(f"unknown param {param.name}")
|
||||
|
@@ -22,11 +22,11 @@ async def test_load_plugin():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugins(load_plugin: Set[Plugin], load_example: Set[Plugin]):
|
||||
async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[Plugin]):
|
||||
loaded_plugins = {
|
||||
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
||||
}
|
||||
assert loaded_plugins >= load_plugin | load_example
|
||||
assert loaded_plugins >= load_plugin | load_builtin_plugin
|
||||
|
||||
# check simple plugin
|
||||
assert "plugins.export" in sys.modules
|
||||
@@ -128,7 +128,7 @@ async def test_require_not_found():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_plugin_metadata():
|
||||
from plugins.metadata import Config
|
||||
from plugins.metadata import Config, FakeAdapter
|
||||
|
||||
plugin = nonebot.get_plugin("metadata")
|
||||
assert plugin
|
||||
@@ -137,6 +137,11 @@ async def test_plugin_metadata():
|
||||
"name": "测试插件",
|
||||
"description": "测试插件元信息",
|
||||
"usage": "无法使用",
|
||||
"type": "application",
|
||||
"homepage": "https://nonebot.dev",
|
||||
"config": Config,
|
||||
"supported_adapters": {"~onebot.v11", "plugins.metadata:FakeAdapter"},
|
||||
"extra": {"author": "NoneBot"},
|
||||
}
|
||||
|
||||
assert plugin.metadata.get_supported_adapters() == {FakeAdapter}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import re
|
||||
import sys
|
||||
from typing import Dict, Tuple, Union, Optional
|
||||
from typing import Match, Tuple, Union, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -9,14 +10,11 @@ from utils import make_fake_event, make_fake_message
|
||||
from nonebot.exception import ParserExit, SkippedException
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
REGEX_STR,
|
||||
PREFIX_KEY,
|
||||
REGEX_DICT,
|
||||
SHELL_ARGS,
|
||||
SHELL_ARGV,
|
||||
CMD_ARG_KEY,
|
||||
KEYWORD_KEY,
|
||||
REGEX_GROUP,
|
||||
ENDSWITH_KEY,
|
||||
FULLMATCH_KEY,
|
||||
REGEX_MATCHED,
|
||||
@@ -275,22 +273,34 @@ async def test_keyword(
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"cmds, cmd, force_whitespace, whitespace, expected",
|
||||
"cmds, force_whitespace, cmd, whitespace, arg_text, expected",
|
||||
[
|
||||
[(("help",),), ("help",), None, None, True],
|
||||
[(("help",),), ("foo",), None, None, False],
|
||||
[(("help", "foo"),), ("help", "foo"), True, " ", True],
|
||||
[(("help",), ("foo",)), ("help",), " ", " ", True],
|
||||
[(("help",),), ("help",), False, " ", False],
|
||||
[(("help",),), ("help",), True, None, False],
|
||||
[(("help",),), ("help",), "\n", " ", False],
|
||||
# command tests
|
||||
[(("help",),), None, ("help",), None, None, True],
|
||||
[(("help",),), None, ("foo",), None, None, False],
|
||||
[(("help", "foo"),), None, ("help", "foo"), None, None, True],
|
||||
[(("help", "foo"),), None, ("help", "bar"), None, None, False],
|
||||
[(("help",), ("foo",)), None, ("help",), None, None, True],
|
||||
[(("help",), ("foo",)), None, ("bar",), None, None, False],
|
||||
# whitespace tests
|
||||
[(("help",),), True, ("help",), " ", "arg", True],
|
||||
[(("help",),), True, ("help",), None, "arg", False],
|
||||
[(("help",),), True, ("help",), None, None, True],
|
||||
[(("help",),), False, ("help",), " ", "arg", False],
|
||||
[(("help",),), False, ("help",), None, "arg", True],
|
||||
[(("help",),), False, ("help",), None, None, True],
|
||||
[(("help",),), " ", ("help",), " ", "arg", True],
|
||||
[(("help",),), " ", ("help",), "\n", "arg", False],
|
||||
[(("help",),), " ", ("help",), None, "arg", False],
|
||||
[(("help",),), " ", ("help",), None, None, True],
|
||||
],
|
||||
)
|
||||
async def test_command(
|
||||
cmds: Tuple[Tuple[str, ...]],
|
||||
cmd: Tuple[str, ...],
|
||||
force_whitespace: Optional[Union[str, bool]],
|
||||
cmd: Tuple[str, ...],
|
||||
whitespace: Optional[str],
|
||||
arg_text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_command = command(*cmds, force_whitespace=force_whitespace)
|
||||
@@ -300,7 +310,10 @@ async def test_command(
|
||||
assert isinstance(checker, CommandRule)
|
||||
assert checker.cmds == cmds
|
||||
|
||||
state = {PREFIX_KEY: {CMD_KEY: cmd, CMD_WHITESPACE_KEY: whitespace}}
|
||||
arg = arg_text if arg_text is None else make_fake_message()(arg_text)
|
||||
state = {
|
||||
PREFIX_KEY: {CMD_KEY: cmd, CMD_WHITESPACE_KEY: whitespace, CMD_ARG_KEY: arg}
|
||||
}
|
||||
assert await dependent(state=state) == expected
|
||||
|
||||
|
||||
@@ -371,6 +384,19 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS].status != 0
|
||||
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
|
||||
|
||||
test_parser_remain_args = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_remain_args.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = MessageSegment.text("-a 1 2") + MessageSegment.image("test")
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == ["-a", "1", "2", MessageSegment.image("test")]
|
||||
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||
assert state[SHELL_ARGS].status != 0
|
||||
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
|
||||
|
||||
test_message_parser = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_message_parser.checkers)[0]
|
||||
checker = dependent.call
|
||||
@@ -401,21 +427,18 @@ async def test_shell_command():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"pattern, type, text, expected, matched, string, group, dict",
|
||||
"pattern, type, text, expected, matched",
|
||||
[
|
||||
(
|
||||
r"(?P<key>key\d)",
|
||||
"message",
|
||||
"_key1_",
|
||||
True,
|
||||
"key1",
|
||||
"key1",
|
||||
("key1",),
|
||||
{"key": "key1"},
|
||||
re.search(r"(?P<key>key\d)", "_key1_"),
|
||||
),
|
||||
(r"foo", "message", None, False, None, None, None, None),
|
||||
(r"foo", "notice", "foo", True, "foo", "foo", tuple(), {}),
|
||||
(r"foo", "notice", "bar", False, None, None, None, None),
|
||||
(r"foo", "message", None, False, None),
|
||||
(r"foo", "notice", "foo", True, re.search(r"foo", "foo")),
|
||||
(r"foo", "notice", "bar", False, None),
|
||||
],
|
||||
)
|
||||
async def test_regex(
|
||||
@@ -423,10 +446,7 @@ async def test_regex(
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
matched: Optional[str],
|
||||
string: Optional[str],
|
||||
group: Optional[Tuple[str, ...]],
|
||||
dict: Optional[Dict[str, str]],
|
||||
matched: Optional[Match[str]],
|
||||
):
|
||||
test_regex = regex(pattern)
|
||||
dependent = list(test_regex.checkers)[0]
|
||||
@@ -439,10 +459,13 @@ async def test_regex(
|
||||
event = make_fake_event(_type=type, _message=message)()
|
||||
state = {}
|
||||
assert await dependent(event=event, state=state) == expected
|
||||
assert state.get(REGEX_MATCHED) == matched
|
||||
assert state.get(REGEX_STR) == string
|
||||
assert state.get(REGEX_GROUP) == group
|
||||
assert state.get(REGEX_DICT) == dict
|
||||
result: Optional[Match[str]] = state.get(REGEX_MATCHED)
|
||||
if matched is None:
|
||||
assert result is None
|
||||
else:
|
||||
assert isinstance(result, Match)
|
||||
assert result.group() == matched.group()
|
||||
assert result.span() == matched.span()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
@@ -13,7 +13,7 @@ def escape_text(s: str, *, escape_comma: bool = True) -> str:
|
||||
|
||||
|
||||
def make_fake_message():
|
||||
class FakeMessageSegment(MessageSegment):
|
||||
class FakeMessageSegment(MessageSegment["FakeMessage"]):
|
||||
@classmethod
|
||||
def get_message_class(cls):
|
||||
return FakeMessage
|
||||
@@ -36,7 +36,7 @@ def make_fake_message():
|
||||
def is_text(self) -> bool:
|
||||
return self.type == "text"
|
||||
|
||||
class FakeMessage(Message):
|
||||
class FakeMessage(Message[FakeMessageSegment]):
|
||||
@classmethod
|
||||
def get_segment_class(cls):
|
||||
return FakeMessageSegment
|
||||
@@ -50,7 +50,9 @@ def make_fake_message():
|
||||
yield FakeMessageSegment(**seg)
|
||||
return
|
||||
|
||||
def __add__(self, other):
|
||||
def __add__(
|
||||
self, other: Union[str, FakeMessageSegment, Iterable[FakeMessageSegment]]
|
||||
):
|
||||
other = escape_text(other) if isinstance(other, str) else other
|
||||
return super().__add__(other)
|
||||
|
||||
|
@@ -35,7 +35,7 @@ driver = nonebot.get_driver()
|
||||
driver.register_adapter(Adapter)
|
||||
```
|
||||
|
||||
我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。
|
||||
我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。如果我们需要多平台支持,可以多次调用 `register_adapter` 方法来注册多个适配器。
|
||||
|
||||
## 获取已注册的适配器
|
||||
|
||||
|
@@ -212,19 +212,18 @@ async def _(e: Union[ActionFailed, NetworkError]): ...
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters.console import MessageEvent
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
async def check(event: MessageEvent, matcher: Matcher) -> MessageEvent:
|
||||
async def check(event: Event) -> Event:
|
||||
if event.get_user_id() in BLACKLIST:
|
||||
await matcher.finish()
|
||||
await test.finish()
|
||||
return event
|
||||
|
||||
@test.handle()
|
||||
async def _(event: Annotated[MessageEvent, Depends(check)]):
|
||||
async def _(event: Annotated[Event, Depends(check)]):
|
||||
...
|
||||
```
|
||||
|
||||
@@ -233,19 +232,18 @@ async def _(event: Annotated[MessageEvent, Depends(check)]):
|
||||
|
||||
```python {2,14}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters.console import MessageEvent
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
async def check(event: MessageEvent, matcher: Matcher) -> MessageEvent:
|
||||
async def check(event: Event) -> Event:
|
||||
if event.get_user_id() in BLACKLIST:
|
||||
await matcher.finish()
|
||||
await test.finish()
|
||||
return event
|
||||
|
||||
@test.handle()
|
||||
async def _(event: MessageEvent = Depends(check)):
|
||||
async def _(event: Event = Depends(check)):
|
||||
...
|
||||
```
|
||||
|
||||
@@ -256,6 +254,24 @@ async def _(event: MessageEvent = Depends(check)):
|
||||
|
||||
通过将 `Depends` 包裹的子依赖作为参数的默认值,我们就可以在执行事件处理函数之前执行子依赖,并将其返回值作为参数传入事件处理函数。子依赖和普通的事件处理函数并没有区别,同样可以使用依赖注入,并且可以返回任何类型的值。但需要注意的是,如果事件处理函数参数的类型注解与子依赖返回值的类型**不一致**,将会触发[重载](../appendices/overload.md)而跳过当前事件处理函数。
|
||||
|
||||
特别的,我们可以为 `Dependent` 对象定义一系列前置子依赖,它们会在参数执行前被顺序执行,且返回值将会被忽略,例如:
|
||||
|
||||
```python {2,14}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
async def check(event: Event):
|
||||
if event.get_user_id() in BLACKLIST:
|
||||
await test.finish()
|
||||
|
||||
@test.handle(parameterless=[Depends(check)])
|
||||
async def _():
|
||||
...
|
||||
```
|
||||
|
||||
### 依赖缓存
|
||||
|
||||
NoneBot 在执行子依赖时,会将其返回值缓存起来。当我们在使用子依赖时,`Depends` 具有一个参数 `use_cache`,默认为 `True`。此时在事件处理流程中,多次使用同一个子依赖时,将会使用缓存中的结果而不会重复执行。这在很多情景中非常有用,例如:
|
||||
@@ -428,7 +444,7 @@ async def get_client() -> AsyncGenerator[httpx.AsyncClient, None]:
|
||||
|
||||
@test.handle()
|
||||
async def _(x: Annotated[httpx.AsyncClient, Depends(get_client)]):
|
||||
resp = await x.get("https://v2.nonebot.dev")
|
||||
resp = await x.get("https://nonebot.dev")
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -450,7 +466,7 @@ async def get_client() -> AsyncGenerator[httpx.AsyncClient, None]:
|
||||
|
||||
@test.handle()
|
||||
async def _(x: httpx.AsyncClient = Depends(get_client)):
|
||||
resp = await x.get("https://v2.nonebot.dev")
|
||||
resp = await x.get("https://nonebot.dev")
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
@@ -10,7 +10,7 @@ options:
|
||||
|
||||
# 事件响应器进阶
|
||||
|
||||
在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,以及内置的响应规则。
|
||||
在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,内置的响应规则,与第三方响应规则拓展。
|
||||
|
||||
## 事件响应器组成
|
||||
|
||||
@@ -302,3 +302,119 @@ group = MatcherGroup(rule=to_me())
|
||||
matcher1 = group.on_message()
|
||||
matcher2 = group.on_message()
|
||||
```
|
||||
|
||||
## 第三方响应规则
|
||||
|
||||
### Alconna
|
||||
|
||||
[`nonebot-plugin-alconna`](https://github.com/ArcletProject/nonebot-plugin-alconna) 是一类提供了拓展响应规则的插件。
|
||||
该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
|
||||
是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||
|
||||
特点包括:
|
||||
|
||||
- 高效
|
||||
- 直观的命令组件创建方式
|
||||
- 强大的类型解析与类型转换功能
|
||||
- 自定义的帮助信息格式
|
||||
- 多语言支持
|
||||
- 易用的快捷命令创建与使用
|
||||
- 可创建命令补全会话, 以实现多轮连续的补全提示
|
||||
- 可嵌套的多级子命令
|
||||
- 正则匹配支持
|
||||
|
||||
该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。
|
||||
|
||||
同时,基于 [Annotated 支持](https://github.com/nonebot/nonebot2/pull/1832), 添加了两类注解 `AlcMatches` 与 `AlcResult`
|
||||
|
||||
该插件还可以通过 `handle(parameterless)` 来控制一个具体的响应函数是否在不满足条件时跳过响应:
|
||||
|
||||
- `pip.handle([Check(assign("add.name", "nb"))])` 表示仅在命令为 `role-group add` 并且 name 为 `nb` 时响应
|
||||
- `pip.handle([Check(assign("list"))])` 表示仅在命令为 `role-group list` 时响应
|
||||
- `pip.handle([Check(assign("add"))])` 表示仅在命令为 `role-group add` 时响应
|
||||
|
||||
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
|
||||
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
|
||||
|
||||
#### 插件安装
|
||||
|
||||
```shell
|
||||
nb plugin install nonebot-plugin-alconna
|
||||
```
|
||||
|
||||
或
|
||||
|
||||
```shell
|
||||
pip install nonebot-plugin-alconna
|
||||
```
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.adapters import At
|
||||
from nonebot.adapters.onebot.v12 import Message
|
||||
from nonebot_plugin_alconna.adapters.onebot12 import Image
|
||||
from nonebot_plugin_alconna import AlconnaMatches, on_alconna
|
||||
from nonebot.adapters.onebot.v12 import MessageSegment as Ob12MS
|
||||
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
|
||||
|
||||
alc = Alconna(
|
||||
"role-group",
|
||||
Subcommand(
|
||||
"add",
|
||||
Args["name", str],
|
||||
Option("member", Args["target", MultiVar(At)]),
|
||||
),
|
||||
Option("list"),
|
||||
)
|
||||
rg = on_alconna(alc, auto_send_output=True)
|
||||
|
||||
|
||||
@rg.handle()
|
||||
async def _(result: Arparma = AlconnaMatches()):
|
||||
if result.find("list"):
|
||||
img = await gen_role_group_list_image()
|
||||
await rg.finish(Message([Image(img)]))
|
||||
if result.find("add"):
|
||||
group = await create_role_group(result["add.name"])
|
||||
if result.find("add.member"):
|
||||
ats: tuple[Ob12MS, ...] = result["add.member.target"]
|
||||
group.extend(member.data["user_id"] for member in ats)
|
||||
await rg.finish("添加成功")
|
||||
```
|
||||
|
||||
我们可以看到主要的两大组件:`Option` 与 `Subcommand`。
|
||||
|
||||
`Option` 可以传入一组别名,如 `Option("--foo|-F|--FOO|-f")` 或 `Option("--foo", alias=["-F"]`
|
||||
|
||||
`Subcommand` 则可以传入自己的 `Option` 与 `Subcommand`:
|
||||
|
||||
他们拥有如下共同参数:
|
||||
|
||||
- `help_text`: 传入该组件的帮助信息
|
||||
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
|
||||
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
|
||||
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
|
||||
|
||||
其次使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
|
||||
|
||||
`on_alconna` 的所有参数如下:
|
||||
|
||||
- `command: Alconna | str`: Alconna 命令
|
||||
- `skip_for_unmatch: bool = True`: 是否在命令不匹配时跳过该响应
|
||||
- `auto_send_output: bool = False`: 是否自动发送输出信息并跳过响应
|
||||
- `output_converter: TConvert | None = None`: 输出信息字符串转换为消息序列方法
|
||||
- `aliases: set[str | tuple[str, ...]] | None = None`: 命令别名, 作用类似于 `on_command` 中的 aliases
|
||||
- `comp_config: CompConfig | None = None`: 补全会话配置, 不传入则不启用补全会话
|
||||
|
||||
`AlconnaMatches` 是一个依赖注入函数,可注入 `Alconna` 命令解析结果。
|
||||
|
||||
#### 参考
|
||||
|
||||
插件文档: [📦 这里](https://github.com/ArcletProject/nonebot-plugin-alconna/blob/master/docs.md)
|
||||
|
||||
官方文档: [👉 指路](https://arclet.top/)
|
||||
|
||||
QQ 交流群: [🔗 链接](https://jq.qq.com/?_wv=1027&k=PUPOnCSH)
|
||||
|
||||
友链: [📚 文档](https://graiax.cn/guide/message_parser/alconna.html)
|
||||
|
@@ -68,3 +68,7 @@ async def handle_onebot(bot: OneBot):
|
||||
|
||||
但 Bot 和 Event 二者的参数类型注解具有最高检查优先级,如果二者类型注解不匹配,那么其他依赖注入将不会执行(如:`Depends`)。
|
||||
:::
|
||||
|
||||
:::tip 提示
|
||||
如何更好地编写一个跨平台的插件,我们将在[最佳实践](../best-practice/multi-adapter.mdx)中介绍。
|
||||
:::
|
||||
|
@@ -94,28 +94,59 @@ nb docker up
|
||||
|
||||
当看到 `Running` 字样时,说明机器人已经启动成功。我们可以通过以下命令来查看机器人的运行日志:
|
||||
|
||||
<Tabs groupId="deploy-tool">
|
||||
<TabItem value="nb-cli" label="NB CLI" default>
|
||||
|
||||
```bash
|
||||
nb docker logs
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="docker-compose" label="Docker Compose">
|
||||
|
||||
```bash
|
||||
docker compose logs
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
如果需要停止机器人,我们可以使用以下命令:
|
||||
|
||||
<Tabs groupId="deploy-tool">
|
||||
<TabItem value="nb-cli" label="NB CLI" default>
|
||||
|
||||
```bash
|
||||
nb docker down
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="docker-compose" label="Docker Compose">
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### 自定义部署
|
||||
|
||||
通常情况下,自动生成的配置文件并不能满足复杂场景,我们需要根据实际需求手动修改配置文件。使用以下命令来生成基础配置文件:
|
||||
在部分情况下,我们需要事先生成 Docker 配置文件,再到生产环境进行部署;或者自动生成的配置文件并不能满足复杂场景,需要根据实际需求手动修改配置文件。我们可以使用以下命令来生成基础配置文件:
|
||||
|
||||
```bash
|
||||
nb docker generate
|
||||
```
|
||||
|
||||
nb-cli 将会在项目目录下生成 `docker-compose.yml` 和 `Dockerfile` 等配置文件,我们可以参考 [Dockerfile 文件规范](https://docs.docker.com/engine/reference/builder/)和 [Compose 文件规范](https://docs.docker.com/compose/compose-file/)修改这两个文件。
|
||||
nb-cli 将会在项目目录下生成 `docker-compose.yml` 和 `Dockerfile` 等配置文件。在 nb-cli 完成配置文件的生成后,我们可以根据部署环境的实际情况使用 nb-cli 或者 Docker Compose 来启动机器人。
|
||||
|
||||
我们可以参考 [Dockerfile 文件规范](https://docs.docker.com/engine/reference/builder/)和 [Compose 文件规范](https://docs.docker.com/compose/compose-file/)修改这两个文件。
|
||||
|
||||
修改完成后我们可以直接启动或者手动构建镜像:
|
||||
|
||||
<Tabs groupId="deploy-tool">
|
||||
<TabItem value="nb-cli" label="NB CLI" default>
|
||||
|
||||
```bash
|
||||
# 启动机器人
|
||||
nb docker up
|
||||
@@ -123,6 +154,19 @@ nb docker up
|
||||
nb docker build
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="docker-compose" label="Docker Compose">
|
||||
|
||||
```bash
|
||||
# 启动机器人
|
||||
docker compose up -d
|
||||
# 手动构建镜像
|
||||
docker compose build
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### 持续集成
|
||||
|
||||
我们可以使用 GitHub Actions 来实现持续集成(CI),我们只需要在 GitHub 上发布 Release 即可自动构建镜像并推送至镜像仓库。
|
||||
|
183
website/docs/best-practice/multi-adapter.mdx
Normal file
183
website/docs/best-practice/multi-adapter.mdx
Normal file
@@ -0,0 +1,183 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
description: 插件跨平台支持
|
||||
---
|
||||
|
||||
# 插件跨平台支持
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
由于不同平台的事件与接口之间,存在着极大的差异性,NoneBot 通过[重载](../appendices/overload.md)的方式,使得插件可以在不同平台上正确响应。但为了减少跨平台的兼容性问题,我们应该尽可能的使用基类方法实现原生跨平台,而不是使用特定平台的方法。当基类方法无法满足需求时,我们可以使用依赖注入的方式,将特定平台的事件或机器人注入到事件处理函数中,实现针对特定平台的处理。
|
||||
|
||||
:::tip 提示
|
||||
如果需要在多平台上**使用**跨平台插件,首先应该根据[注册适配器](../advanced/adapter.md#注册适配器)一节,为机器人注册各平台对应的适配器。
|
||||
:::
|
||||
|
||||
## 基于基类的跨平台
|
||||
|
||||
在[事件通用信息](../advanced/adapter.md#获取事件通用信息)中,我们了解了事件基类能够提供的通用信息。同时,[事件响应器操作](../appendices/session-control.mdx#更多事件响应器操作)也为我们提供了基本的用户交互方式。使用这些方法,可以让我们的插件运行在任何平台上。例如,一个简单的命令处理插件:
|
||||
|
||||
```python {5,11}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
|
||||
async def is_blacklisted(event: Event) -> bool:
|
||||
return event.get_user_id() not in BLACKLIST
|
||||
|
||||
weather = on_command("天气", rule=is_blacklisted, priority=10, block=True)
|
||||
|
||||
@weather.handle()
|
||||
async def handle_function():
|
||||
await weather.finish("今天的天气是...")
|
||||
```
|
||||
|
||||
由于此插件仅使用了事件通用信息和事件响应器操作的纯文本交互方式,这些方法不使用特定平台的信息或接口,因此是原生跨平台的,并不需要额外处理。但在一些较为复杂的需求下,例如发送图片消息时,并非所有平台都具有统一的接口,因此基类便无能为力,我们需要引入特定平台的适配器了。
|
||||
|
||||
## 基于重载的跨平台
|
||||
|
||||
重载是 NoneBot 跨平台操作的核心,在[事件类型与重载](../appendices/overload.md#重载)一节中,我们初步了解了如何通过类型注解来实现针对不同平台事件的处理方式。在[依赖注入](../advanced/dependency.mdx)一节中,我们又对依赖注入的使用方法进行了详细的介绍。结合这两节内容,我们可以实现更复杂的跨平台操作。
|
||||
|
||||
### 处理近似事件
|
||||
|
||||
对于一系列**差异不大**的事件,我们往往具有相同的处理逻辑。这时,我们不希望将相同的逻辑编写两遍,而应该复用代码,以实现在同一个事件处理函数中处理多个近似事件。我们可以使用[事件重载](../advanced/dependency.mdx#Event)的特性来实现这一功能。例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.10" label="Python 3.10+" default>
|
||||
|
||||
```python
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent as OnebotV11MessageEvent
|
||||
from nonebot.adapters.onebot.v12 import MessageEvent as OnebotV12MessageEvent
|
||||
|
||||
echo = on_command("echo", priority=10, block=True)
|
||||
|
||||
@echo.handle()
|
||||
async def handle_function(event: OnebotV11MessageEvent | OnebotV12MessageEvent, args: Message = CommandArg()):
|
||||
await echo.finish(args)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent as OnebotV11MessageEvent
|
||||
from nonebot.adapters.onebot.v12 import MessageEvent as OnebotV12MessageEvent
|
||||
|
||||
echo = on_command("echo", priority=10, block=True)
|
||||
|
||||
@echo.handle()
|
||||
async def handle_function(event: Union[OnebotV11MessageEvent, OnebotV12MessageEvent], args: Message = CommandArg()):
|
||||
await echo.finish(args)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### 在依赖注入中使用重载
|
||||
|
||||
NoneBot 依赖注入系统提供了自定义子依赖的方法,子依赖的类型同样会影响到事件处理函数的重载行为。例如:
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.console import MessageEvent
|
||||
|
||||
echo = on_command("echo", priority=10, block=True)
|
||||
|
||||
def get_event_time(event: MessageEvent):
|
||||
return event.time
|
||||
|
||||
# 处理控制台消息事件
|
||||
@echo.handle()
|
||||
async def handle_function(time: datetime = Depends(get_event_time)):
|
||||
await echo.finish(time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
```
|
||||
|
||||
示例中 ,我们为 `handle_function` 事件处理函数注入了自定义的 `get_event_time` 子依赖,而此子依赖注入参数为 Console 适配器的 `MessageEvent`。因此 `handle_function` 仅会响应 Console 适配器的 `MessageEvent` ,而不能响应其他事件。
|
||||
|
||||
### 处理多平台事件
|
||||
|
||||
不同平台的事件之间,往往存在着极大的差异性。为了满足我们插件的跨平台运行,通常我们需要抽离业务逻辑,以保证代码的复用性。一个合理的做法是,在事件响应器的处理流程中,首先先针对不同平台的事件分别进行处理,提取出核心业务逻辑所需要的信息;然后再将这些信息传递给业务逻辑处理函数;最后将业务逻辑的输出以各平台合适的方式返回给用户。也就是说,与平台绑定的处理部分应该与平台无关部分尽量分离。例如:
|
||||
|
||||
```python
|
||||
import inspect
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import CommandArg, ArgPlainText
|
||||
from nonebot.adapters.console import Bot as ConsoleBot
|
||||
from nonebot.adapters.onebot.v11 import Bot as OnebotBot
|
||||
from nonebot.adapters.console import MessageSegment as ConsoleMessageSegment
|
||||
|
||||
weather = on_command("天气", priority=10, block=True)
|
||||
|
||||
@weather.handle()
|
||||
async def handle_function(matcher: Matcher, args: Message = CommandArg()):
|
||||
if args.extract_plain_text():
|
||||
matcher.set_arg("location", args)
|
||||
|
||||
|
||||
async def get_weather(state: T_State, location: str = ArgPlainText()):
|
||||
if location not in ["北京", "上海", "广州", "深圳"]:
|
||||
await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
|
||||
|
||||
state["weather"] = "⛅ 多云 20℃~24℃"
|
||||
|
||||
|
||||
# 处理控制台询问
|
||||
@weather.got(
|
||||
"location",
|
||||
prompt=ConsoleMessageSegment.emoji("question") + "请输入地名",
|
||||
parameterless=[Depends(get_weather)],
|
||||
)
|
||||
async def handle_console(bot: ConsoleBot):
|
||||
pass
|
||||
|
||||
# 处理 OneBot 询问
|
||||
@weather.got(
|
||||
"location",
|
||||
prompt="请输入地名",
|
||||
parameterless=[Depends(get_weather)],
|
||||
)
|
||||
async def handle_onebot(bot: OnebotBot):
|
||||
pass
|
||||
|
||||
# 通过依赖注入或事件处理函数来进行业务逻辑处理
|
||||
|
||||
# 处理控制台回复
|
||||
@weather.handle()
|
||||
async def handle_console_reply(bot: ConsoleBot, state: T_State, location: str = ArgPlainText()):
|
||||
await weather.send(
|
||||
ConsoleMessageSegment.markdown(
|
||||
inspect.cleandoc(
|
||||
f"""
|
||||
# {location}
|
||||
|
||||
- 今天
|
||||
|
||||
{state['weather']}
|
||||
"""
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# 处理 OneBot 回复
|
||||
@weather.handle()
|
||||
async def handle_onebot_reply(bot: OnebotBot, state: T_State, location: str = ArgPlainText()):
|
||||
await weather.send(f"今天{location}的天气是{state['weather']}")
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
NoneBot 社区中有一些插件,例如[all4one](https://github.com/nonepkg/nonebot-plugin-all4one)、[send-anything-anywhere](https://github.com/felinae98/nonebot-plugin-send-anything-anywhere),可以帮助你更好地处理跨平台功能,包括事件处理和消息发送等。
|
||||
:::
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"label": "单元测试",
|
||||
"position": 4
|
||||
"position": 5
|
||||
}
|
||||
|
191
website/docs/ospp/2021.md
Normal file
191
website/docs/ospp/2021.md
Normal file
@@ -0,0 +1,191 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: 开源软件供应链点亮计划 - 暑期 2021
|
||||
---
|
||||
|
||||
# 暑期 2021
|
||||
|
||||
**开源软件供应链点亮计划 - 暑期 2021** 是**中国科学院软件研究所**与 **openEuler 社区**共同举办的一项面向高校学生的暑期活动,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer.iscas.ac.cn/) 和 [帮助文档](https://summer.iscas.ac.cn/help/)。
|
||||
|
||||
NoneBot 社区有幸作为开源社区参与了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学在上面给出的活动官网报名,或通过 <contact@nonebot.dev> 联系我们。
|
||||
|
||||
## NoneBot v1
|
||||
|
||||
### 更新 NoneBot v1 文档中的“指南”部分
|
||||
|
||||
由于 NoneBot v1 和 aiocqhttp 最初基于的 QQ 机器人平台不再提供服务,CQHTTP 接口也转型且改名为 OneBot 标准,目前 NoneBot v1 文档的“指南”部分和 aiocqhttp 文档有部分过时内容需要更新。我们希望将其中与旧的机器人平台相关的内容改为基于 go-cqhttp 或通用的 OneBot 表述,同时对 NoneBot v1 的 awesome-bot 示例做一次全面检查,修改其中可能已经不可用的部分。
|
||||
|
||||
**难度**:低
|
||||
|
||||
**导师**:[@cleoold](https://github.com/cleoold)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 修改“指南”文档和 aiocqhttp 文档中与旧的 QQ 机器人平台相关的部分
|
||||
- 检查 awesome-bot 示例是否有已经过时/不可用的地方,并更新/修复
|
||||
- 修改“图灵机器人”案例,使用其它 AI 聊天 API 提供商(需先做简单调研)
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 Python 编程语言及 asyncio 机制
|
||||
- 了解 Git 基本用法
|
||||
- 了解聊天机器人基本开发过程
|
||||
- 了解 VuePress 更佳
|
||||
|
||||
### NoneBot v1 API 文档自动生成
|
||||
|
||||
目前 NoneBot v1 的文档中“API”部分是手动编写的,在更新代码接口的同时需要手动更新文档,可能造成文档与代码不匹配,形成额外的维护成本。我们希望将 API 文档改为直接编写在 Python docstring 中,通过工具自动生成 API 文档。
|
||||
|
||||
**难度**:中
|
||||
|
||||
**导师**:[@cleoold](https://github.com/cleoold)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 调研市面上常见的 Python API 文档生成工具
|
||||
- 在代码中补充 API 文档
|
||||
- 编写或应用开源工具自动生成 API 文档
|
||||
- 配置 GitHub Actions 或其它 CI 自动化构建和部署 API 文档
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 Python 编程语言及 asyncio 和 Type Hints
|
||||
- 了解 Git 基本用法
|
||||
- 了解 Sphinx 等文档生成工具更佳
|
||||
- 了解 GitHub Actions 等 CI 工具更佳
|
||||
|
||||
## NoneBot v2
|
||||
|
||||
### NoneBot v2 自动化测试框架“NoneBug”
|
||||
|
||||
在聊天机器人的开发过程中,一套自动化的测试机制是非常重要的,特别是对于 NoneBot 2 这类为大型机器人开发而设计的项目来说,需要手动测试每一个边际条件是非常痛苦的。我们希望能够开发一款基于 NoneBot 2 插件机制的自动化测试框架,为 NoneBot 2 用户提供一套易用便捷、高度灵活的自动化测试框架。
|
||||
|
||||
**难度**:高
|
||||
|
||||
**导师**:[@yanyongyu](https://github.com/yanyongyu)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 调研现有的 Python 和其它语言集成测试框架
|
||||
- 设计 NoneBug 的用户 API 和实现方式
|
||||
- 实现 NoneBug 自动化测试框架
|
||||
- 编写详细的使用文档
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 Python 编程语言及 asyncio 和 Type Hints
|
||||
- 了解 Git 基本用法
|
||||
- 了解 NoneBot v2 的基本原理和使用方式
|
||||
- 了解主流的 Python 自动化测试框架
|
||||
|
||||
### NoneBot v2 Telegram 适配器
|
||||
|
||||
目前 NoneBot v2 已支持 OneBot、Mirai HTTP API、钉钉协议,社区反馈有更多的平台需求,希望能在 NoneBot v2 获得更多的跨平台支持,提高机器人的便携性。同时,我们也希望随着新平台加入,提升现有 NoneBot v2 核心代码的平台通用性。Telegram 是一款较为广泛使用的安全即时聊天软件,同时其官方提供了丰富的聊天机器人 API,因此我们希望为 NoneBot v2 编写一个 Telegram 适配器来支持 Telegram 机器人的开发。
|
||||
|
||||
**难度**:中
|
||||
|
||||
**导师**:[@yanyongyu](https://github.com/yanyongyu)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 调研 Telegram Bot API 以及 WebHook 等官方接口
|
||||
- 编写 Telegram 适配器并能够使用
|
||||
- 代码遵守项目 Contributing 规范
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 Python 编程语言及 asyncio 和 Type Hints
|
||||
- 了解 Git 基本用法
|
||||
- 了解 Web 开发相关知识
|
||||
- 了解 Sphinx 等文档生成工具更佳
|
||||
|
||||
### NoneBot v2 飞书适配器
|
||||
|
||||
目前 NoneBot v2 已支持 OneBot、Mirai HTTP API、钉钉协议,社区反馈有更多的平台需求,希望能在 NoneBot v2 获得更多的跨平台支持,提高机器人的便携性。同时,我们也希望随着新平台加入,提升现有 NoneBot v2 核心代码的平台通用性。飞书是目前企业用户广泛使用的即时聊天和协作软件,其官方提供了丰富的聊天机器人 API,因此我们希望为 NoneBot v2 编写一个飞书适配器来支持飞书机器人的开发。
|
||||
|
||||
**难度**:中
|
||||
|
||||
**导师**:[@yanyongyu](https://github.com/yanyongyu)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 调研飞书机器人 API 以及 WebHook 等官方接口
|
||||
- 编写飞书适配器并能够使用
|
||||
- 代码遵守项目 Contributing 规范
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 Python 编程语言及 asyncio 和 Type Hints
|
||||
- 了解 Git 基本用法
|
||||
- 了解 Web 开发相关知识
|
||||
- 了解 Sphinx 等文档生成工具更佳
|
||||
|
||||
## OneBot
|
||||
|
||||
### 设计 OneBot v12 接口标准
|
||||
|
||||
目前的 OneBot 标准的 v11 版本仍然与 QQ 平台有较多耦合,我们希望在 v12 去掉与 QQ 耦合的历史包袱,形成一个通用的、可扩展的、易于使用的同时易于实现的聊天机器人接口标准。
|
||||
|
||||
**难度**:中
|
||||
|
||||
**导师**:[@richardchien](https://github.com/richardchien)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 调研各聊天机器人平台的官方/非官方接口特点
|
||||
- 通用化 OneBot 核心 API,分离 QQ 特定的 API,去掉无用 API
|
||||
- 优化现有的通信、消息表示机制
|
||||
- 补充 QQ 特定的缺失 API
|
||||
- 文档需符合风格指南
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉至少两个聊天平台的聊天机器人开发
|
||||
- 了解 Git 基本用法
|
||||
- 了解使用不同语言编写聊天机器人时的常用实践
|
||||
- 对文档的优雅性与美观性有追求更佳
|
||||
|
||||
### 实现 Rust 版 libonebot
|
||||
|
||||
目前最常用的 OneBot 实现包括 go-cqhttp、onebot-kotlin、node-onebot 等,这些实现都各自重复实现了 Web 通信、消息解析、配置读写等功能,当社区中的开发者想针对一个新的聊天平台实现 OneBot 时,他们往往同样需要再次实现类似逻辑。我们希望使用 Rust 编写一个 libonebot 模块,该模块实现所有 OneBot 实现所共享的功能,从而方便其他开发者们使用 Rust 快速编写具体的 OneBot 实现。同时,我们希望借此项目在聊天机器人社区中推广 Rust 编程语言。
|
||||
|
||||
> 注:这里的逻辑是 libonebot + 针对某聊天平台的对接代码 = 某聊天平台的 OneBot 实现,libonebot 要做的是让 OneBot 实现的开发者只需编写针对特定聊天平台的对接代码,而无需关心 OneBot 标准定义的通信方式、消息格式等。
|
||||
|
||||
**难度**:高
|
||||
|
||||
**导师**:[@richardchien](https://github.com/richardchien)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 实现所有 OneBot 实现所共享的功能,包括 Web 通信、消息解析、配置读写等
|
||||
- 充分考虑同时兼容 OneBot v11 和 v12 接口
|
||||
- 能够根据用户(OneBot 实现的开发者)所实现的接口自动实现类似 get_available_apis 等接口
|
||||
- 编写详细的使用文档
|
||||
- 如果可能,与 v12 设计项目联动,实现第一手 v12 支持
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉聊天机器人开发
|
||||
- 熟悉 Rust Web 开发
|
||||
|
||||
### 实现自选语言版 libonebot
|
||||
|
||||
目前最常用的 OneBot 实现包括 go-cqhttp、onebot-kotlin、node-onebot 等,这些实现都各自重复实现了 Web 通信、消息解析、配置读写等功能,当社区中的开发者想针对一个新的聊天平台实现 OneBot 时,他们往往同样需要再次实现类似逻辑。我们希望使用 Python、Go、Kotlin、Node、PHP、C#.NET 等主流语言(任选一个)编写 libonebot 模块,该模块实现所有 OneBot 实现所共享的功能,从而方便其他开发者们使用对应语言快速编写具体的 OneBot 实现。
|
||||
|
||||
> 注:这里的逻辑是 libonebot + 针对某聊天平台的对接代码 = 某聊天平台的 OneBot 实现,libonebot 要做的是让 OneBot 实现的开发者只需编写针对特定聊天平台的对接代码,而无需关心 OneBot 标准定义的通信方式、消息格式等。
|
||||
|
||||
**难度**:中
|
||||
|
||||
**导师**:[@richardchien](https://github.com/richardchien)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 实现所有 OneBot 实现所共享的功能,包括 Web 通信、消息解析、配置读写等
|
||||
- 充分考虑同时兼容 OneBot v11 和 v12 接口
|
||||
- 编写详细的使用文档
|
||||
- 如果可能,实现更多附加特性,如根据用户(OneBot 实现的开发者)所实现的接口自动实现类似 get_available_apis 等接口、实现第一手 v12 支持等
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉聊天机器人开发
|
||||
- 熟悉所选语言的 Web 开发
|
96
website/docs/ospp/2022.md
Normal file
96
website/docs/ospp/2022.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: 开源之夏 - 暑期 2022
|
||||
---
|
||||
|
||||
# 暑期 2022
|
||||
|
||||
**开源之夏 - 暑期 2022** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
||||
|
||||
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/#/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a/) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学加入 QQ 群 [737131827](https://jq.qq.com/?_wv=1027&k=PEgyGeEu) 或通过 <contact@nonebot.dev> 联系我们。
|
||||
|
||||
## NoneBot2 命令行 CLI 交互体验升级
|
||||
|
||||
NoneBot2 为用户提供了命令行脚手架 ──`nb-cli`,辅助用户更好地上手项目以及进行开发。nb-cli 主要包括:创建项目、运行项目、安装与卸载插件、部署项目等功能。随着 NoneBot2 Beta 版本的发布,脚手架功能存在一定的定位不明确、功能体验不佳。本项目旨在重新设计 nb-cli 功能框架,完善功能,优化用户体验。
|
||||
|
||||
**难度**:进阶
|
||||
|
||||
**导师**:[@yanyongyu](https://github.com/yanyongyu)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 设计 nb-cli 功能框架
|
||||
- 明确各功能模块
|
||||
- 设计用户交互模式
|
||||
- 完成 nb-cli 主要功能代码
|
||||
- 项目管理
|
||||
- 插件管理
|
||||
- 其它
|
||||
- 同步更新使用文档
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 Python 命令行交互代码编写
|
||||
- 熟悉 NoneBot2 框架功能
|
||||
- 熟悉 NoneBot2 项目组织方式
|
||||
|
||||
**成果仓库**
|
||||
|
||||
- <https://github.com/nonebot/nb-cli>
|
||||
- <https://github.com/nonebot/nonebot2>
|
||||
|
||||
## NoneBot2 命令行即时交互通信设计与实现
|
||||
|
||||
NoneBot2 在早期提供了基于网页的 nonebot-plugin-test 插件,无需平台适配接入即可对机器人进行测试,方便了开发者直观的感受机器人文本交互功能。我们希望提供一款基于命令行的适配器/驱动器,用于无平台适配接入、可以运行机器人的场景进行功能体验或测试。
|
||||
|
||||
**难度**:进阶
|
||||
|
||||
**导师**:[@mnixry](https://github.com/mnixry)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 设计命令行与 NoneBot2 通信模式
|
||||
- 直接调用/HTTP/WebSocket
|
||||
- 设计命令行交互界面
|
||||
- 实现相应适配器/驱动器
|
||||
- 同步更新使用说明文档
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 Python 命令行交互代码编写
|
||||
- 熟悉 NoneBot2 框架功能
|
||||
- 熟悉 NoneBot2 项目组织方式
|
||||
|
||||
**成果仓库**
|
||||
|
||||
- <https://github.com/nonebot/adapter-console>
|
||||
|
||||
## NoneBot2 用户上手与深入教程设计
|
||||
|
||||
NoneBot2 为用户提供了详细的文档介绍,辅助用户更好的上手项目以及进行开发。文档分为基础与进阶两个部分。基础部分帮助新用户快速上手开发,主要包括:安装 NoneBot2、使用脚手架、创建配置项目、使用适配器、加载插件、定义消息事件、处理消息事件、调用平台 API 等。进阶部分向已经熟悉开发流程的用户介绍更多高级技巧,主要包括:NoneBot2 工作原理、定时任务、权限控制、钩子函数、跨插件访问、单元测试、发布插件等。目前文档对于用户而言过于费解,导致用户难以理解 NoneBot2 开发。本项目旨在优化文档内容,使其更加通俗易懂,不让文档成为用户上手的阻碍,同时完善进阶内容,让有更复杂需求的用户,同样能从文档中受益。
|
||||
|
||||
相关 issue:
|
||||
|
||||
- <https://github.com/nonebot/nonebot2/issues/793>
|
||||
- <https://github.com/nonebot/nonebot2/issues/295>
|
||||
|
||||
**难度**:进阶
|
||||
|
||||
**导师**:[@SK-415](https://github.com/SK-415)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 文档通俗易懂
|
||||
- 附有适当的图片指引(如 asciinema)
|
||||
- 内容完整,由浅入深
|
||||
- 适当的界面美化,合理分配布局
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉文档结构组织与语言表达
|
||||
- 熟悉 NoneBot2 框架功能
|
||||
- 熟悉 NoneBot2 项目组织方式
|
||||
|
||||
**成果仓库**
|
||||
|
||||
- <https://github.com/nonebot/nonebot2>
|
89
website/docs/ospp/2023.md
Normal file
89
website/docs/ospp/2023.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: 开源之夏 - 暑期 2023
|
||||
---
|
||||
|
||||
# 暑期 2023
|
||||
|
||||
**开源之夏 - 暑期 2023** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
||||
|
||||
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 <contact@nonebot.dev> 联系我们。
|
||||
|
||||
## NoneBot 项目管理图形化面板
|
||||
|
||||
NoneBot 目前提供了开箱即用的命令行脚手架来帮助初次使用的用户更快的上手编写应用。但是,对于未有一定开发经验的用户,命令行的使用仍具有一定的困难。此外,其他项目如 koishi、vue 等,均可通过图形化界面的形式为用户提供更便捷的项目开发。因此,我们希望借助现有命令行脚手架的可扩展特性,提供一个项目管理面板服务,以网页的形式帮助用户开发 NoneBot 应用。
|
||||
|
||||
**难度**:进阶
|
||||
|
||||
**导师**:[@mnixry](https://github.com/mnixry)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 设计并实现项目管理面板相关功能
|
||||
- 创建与管理项目
|
||||
- 配置与运行项目
|
||||
- NoneBot 插件管理
|
||||
- 实现相应 nb-cli 插件提供面板服务
|
||||
- 代码符合 NoneBot Contributing 规范
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 nb-cli 相关功能
|
||||
- 熟悉 NoneBot 框架功能
|
||||
- 熟悉前后端相关实现方式
|
||||
|
||||
**成果仓库**
|
||||
|
||||
- <https://github.com/nonebot/cli-plugin-webui>
|
||||
|
||||
## NoneBot Discord 适配器
|
||||
|
||||
NoneBot 作为一个跨平台聊天机器人框架,目前已有 OneBot、飞书、Telegram、QQ 频道等诸多平台的适配支持。作为众多用户期待的平台适配之一,我们希望借此机会接入 Discord 聊天机器人。
|
||||
|
||||
**难度**:进阶
|
||||
|
||||
**导师**:[@iyume](https://github.com/iyume)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 调研 Discord Bot 相关功能与接口
|
||||
- 设计与编写 NoneBot Discord 适配器
|
||||
- 代码符合 NoneBot Contributing 规范
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 NoneBot 框架功能
|
||||
- 熟悉 NoneBot 各模块职责与适配器编写
|
||||
|
||||
**成果仓库**
|
||||
|
||||
- <https://github.com/nonebot/adapter-discord>
|
||||
|
||||
## NoneBot 数据库支持插件
|
||||
|
||||
NoneBot 的插件系统为用户实现应用提供了极高的便捷性,但因此也增加了插件统一管理的难度。目前,我们发现许多用户发布的插件中存在文件存储结构化数据、数据存放散乱等现象,同时插件间也可能产生冲突。因此,我们希望提供一个统一的数据存储与管理方式,便于用户读写应用数据。
|
||||
|
||||
**难度**:进阶
|
||||
|
||||
**导师**:[@yanyongyu](https://github.com/yanyongyu)
|
||||
|
||||
**产出要求**
|
||||
|
||||
- 设计并实现 ORM 插件
|
||||
- 提供关系模型定义功能
|
||||
- 提供模型迁移与管理功能
|
||||
- 能较好的支持 Python 类型检查与推导
|
||||
- 编写相应的用户使用文档
|
||||
- 代码符合 NoneBot Contributing 规范
|
||||
|
||||
**技术要求**
|
||||
|
||||
- 熟悉 NoneBot 框架功能与插件编写
|
||||
- 熟悉 SQLAlchemy 等 ORM 框架
|
||||
- 熟悉 SQLAlchemy ORM
|
||||
- 熟悉 alembic 等迁移工具
|
||||
- 熟悉 nb-cli 插件编写
|
||||
|
||||
**成果仓库**
|
||||
|
||||
- <https://github.com/nonebot/plugin-orm>
|
@@ -26,8 +26,9 @@ import Messenger from "@site/src/components/Messenger";
|
||||
|
||||
例如,我们可以继续改进上一章节中的 `weather` 插件,使其可以获取到 `天气` 命令的地名参数,并根据地名返回天气信息。
|
||||
|
||||
```python {8,10} title=weather/__init__.py
|
||||
```python {9,11} title=weather/__init__.py
|
||||
from nonebot import on_command
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import CommandArg
|
||||
|
||||
|
@@ -26,7 +26,8 @@ import Messenger from "@site/src/components/Messenger";
|
||||
|
||||
顾名思义,“事件处理函数装饰器”是一个[装饰器(decorator)](https://docs.python.org/zh-cn/3/glossary.html#term-decorator),那么它的使用方法也同[函数定义](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function-definitions)中所展示的包装用法相同。例如:
|
||||
|
||||
```python {5-7} title=weather/__init__.py
|
||||
```python {6-8} title=weather/__init__.py
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.plugin import on_command
|
||||
|
||||
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
|
||||
@@ -44,7 +45,8 @@ async def handle_function():
|
||||
|
||||
事件响应器操作与事件处理函数装饰器类似,通常作为事件响应器 `Matcher` 的[类方法](https://docs.python.org/zh-cn/3/library/functions.html#classmethod)存在,因此事件响应器操作的调用方法也是 `Matcher.func()` 的形式。不过不同的是,事件响应器操作并不是装饰器,因此并不需要@进行标注。
|
||||
|
||||
```python {7,8} title=weather/__init__.py
|
||||
```python {8,9} title=weather/__init__.py
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.plugin import on_command
|
||||
|
||||
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
|
||||
|
@@ -120,16 +120,37 @@ Message(
|
||||
|
||||
### 遍历
|
||||
|
||||
`Message` 继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。
|
||||
消息序列继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。
|
||||
|
||||
```python
|
||||
for segment in message:
|
||||
...
|
||||
```
|
||||
|
||||
### 索引与切片
|
||||
### 比较
|
||||
|
||||
`Message` 对列表的索引与切片进行了增强,在原有列表 int 索引与切片的基础上,支持 `type` 过滤索引与切片。
|
||||
消息和消息段都可以使用 `==` 或 `!=` 运算符比较是否相同。
|
||||
|
||||
```python
|
||||
MessageSegment.text("text") != MessageSegment.text("foo")
|
||||
|
||||
some_message == Message([MessageSegment.text("text")])
|
||||
```
|
||||
|
||||
### 检查消息段
|
||||
|
||||
我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
|
||||
|
||||
```python
|
||||
# 是否存在消息段
|
||||
MessageSegment.text("text") in message
|
||||
# 是否存在指定类型的消息段
|
||||
"text" in message
|
||||
```
|
||||
|
||||
### 过滤、索引与切片
|
||||
|
||||
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片。
|
||||
|
||||
```python
|
||||
from nonebot.adapters.console import Message, MessageSegment
|
||||
@@ -160,7 +181,14 @@ message["markdown", 0:2] == Message(
|
||||
)
|
||||
```
|
||||
|
||||
同样的,`Message` 对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。
|
||||
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤。
|
||||
|
||||
```python
|
||||
message.include("text", "markdown")
|
||||
message.exclude("text")
|
||||
```
|
||||
|
||||
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。
|
||||
|
||||
```python
|
||||
# 指定类型首个消息段索引
|
||||
@@ -169,7 +197,7 @@ message.index("markdown") == 1
|
||||
message.count("markdown") == 2
|
||||
```
|
||||
|
||||
此外,`Message` 添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。
|
||||
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。
|
||||
|
||||
```python
|
||||
# 获取指定类型指定个数的消息段
|
||||
@@ -214,6 +242,31 @@ msg.append(MessageSegment.text("text"))
|
||||
msg.extend([MessageSegment.text("text")])
|
||||
```
|
||||
|
||||
我们也可以通过消息段或消息序列的 `join` 方法来拼接一串消息:
|
||||
|
||||
```python
|
||||
seg = MessageSegment.text("text")
|
||||
msg = seg.join(
|
||||
[
|
||||
MessageSegment.text("first"),
|
||||
Message(
|
||||
[
|
||||
MessageSegment.text("second"),
|
||||
MessageSegment.text("third"),
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
msg == Message(
|
||||
[
|
||||
MessageSegment.text("first"),
|
||||
MessageSegment.text("text"),
|
||||
MessageSegment.text("second"),
|
||||
MessageSegment.text("third"),
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
### 使用消息模板
|
||||
|
||||
为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列
|
||||
|
@@ -8,7 +8,7 @@ const darkCodeTheme = require("prism-react-renderer/themes/dracula");
|
||||
const config = {
|
||||
title: "NoneBot",
|
||||
tagline: "跨平台 Python 异步机器人框架",
|
||||
url: "https://v2.nonebot.dev",
|
||||
url: "https://nonebot.dev",
|
||||
baseUrl: process.env.BASE_URL || "/",
|
||||
onBrokenLinks: "throw",
|
||||
onBrokenMarkdownLinks: "warn",
|
||||
@@ -100,8 +100,10 @@ const config = {
|
||||
docId: "developer/plugin-publishing",
|
||||
},
|
||||
{ label: "社区", type: "docLink", docId: "community/contact" },
|
||||
{ label: "开源之夏", type: "docLink", docId: "ospp/2023" },
|
||||
{ label: "商店", to: "/store" },
|
||||
{ label: "更新日志", to: "/changelog" },
|
||||
{ label: "论坛", href: "https://discussions.nonebot.dev" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -110,14 +112,14 @@ const config = {
|
||||
},
|
||||
],
|
||||
docsVersionItemAfter: [
|
||||
{
|
||||
label: "2.0.0rc3",
|
||||
href: "https://63ccf1c05efb245d36e901fa--nonebot2.netlify.app/",
|
||||
},
|
||||
{
|
||||
label: "2.0.0a16",
|
||||
href: "https://61d3d9dbcadf413fd3238e89--nonebot2.netlify.app/",
|
||||
},
|
||||
{
|
||||
label: "1.x",
|
||||
href: "https://v1.nonebot.dev/",
|
||||
},
|
||||
],
|
||||
},
|
||||
hideableSidebar: true,
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"version": "2.0.0",
|
||||
"description": "跨平台 Python 异步机器人框架",
|
||||
"private": true,
|
||||
"homepage": "https://v2.nonebot.dev/",
|
||||
"homepage": "https://nonebot.dev/",
|
||||
"repository": "https://github.com/nonebot/nonebot2/",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nonebot/nonebot2/issues"
|
||||
|
@@ -84,6 +84,17 @@ const sidebars = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "开源之夏",
|
||||
collapsible: false,
|
||||
items: [
|
||||
{
|
||||
type: "autogenerated",
|
||||
dirName: "ospp",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "社区资源",
|
||||
@@ -94,6 +105,11 @@ const sidebars = {
|
||||
label: "商店",
|
||||
href: "/store",
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
label: "论坛",
|
||||
href: "https://discussions.nonebot.dev",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@@ -41,42 +41,27 @@ export default function Adapter(): JSX.Element {
|
||||
const [label, setLabel] = useState<string>("");
|
||||
const [color, setColor] = useState<string>("#ea5252");
|
||||
|
||||
const urlEncode = (str: string) =>
|
||||
encodeURIComponent(str).replace(/%2B/gi, "+");
|
||||
|
||||
const onSubmit = () => {
|
||||
setModalOpen(false);
|
||||
const title = encodeURIComponent(`Adapter: ${form.name}`).replace(
|
||||
/%2B/gi,
|
||||
"+"
|
||||
);
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**协议名称:**
|
||||
|
||||
${form.name}
|
||||
|
||||
**协议功能:**
|
||||
|
||||
${form.desc}
|
||||
|
||||
**PyPI 项目名:**
|
||||
|
||||
${form.projectLink}
|
||||
|
||||
**协议 import 包名:**
|
||||
|
||||
${form.moduleName}
|
||||
|
||||
**协议项目仓库/主页链接:**
|
||||
|
||||
${form.homepage}
|
||||
|
||||
**标签:**
|
||||
|
||||
${JSON.stringify(tags)}
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Adapter`
|
||||
);
|
||||
const queries: { key: string; value: string }[] = [
|
||||
{ key: "template", value: "adapter_publish.yml" },
|
||||
{ key: "title", value: form.name && `Adapter: ${form.name}` },
|
||||
{ key: "labels", value: "Adapter" },
|
||||
{ key: "name", value: form.name },
|
||||
{ key: "description", value: form.desc },
|
||||
{ key: "pypi", value: form.projectLink },
|
||||
{ key: "module", value: form.moduleName },
|
||||
{ key: "homepage", value: form.homepage },
|
||||
{ key: "tags", value: JSON.stringify(tags) },
|
||||
];
|
||||
const urlQueries = queries
|
||||
.filter((query) => !!query.value)
|
||||
.map((query) => `${query.key}=${urlEncode(query.value)}`)
|
||||
.join("&");
|
||||
window.open(`https://github.com/nonebot/nonebot2/issues/new?${urlQueries}`);
|
||||
};
|
||||
const onChange = (event) => {
|
||||
const target = event.target;
|
||||
|
@@ -39,31 +39,25 @@ export default function Bot(): JSX.Element {
|
||||
const [label, setLabel] = useState<string>("");
|
||||
const [color, setColor] = useState<string>("#ea5252");
|
||||
|
||||
const urlEncode = (str: string) =>
|
||||
encodeURIComponent(str).replace(/%2B/gi, "+");
|
||||
|
||||
const onSubmit = () => {
|
||||
setModalOpen(false);
|
||||
const title = encodeURIComponent(`Bot: ${form.name}`).replace(/%2B/gi, "+");
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**机器人名称:**
|
||||
|
||||
${form.name}
|
||||
|
||||
**机器人功能:**
|
||||
|
||||
${form.desc}
|
||||
|
||||
**机器人项目仓库/主页链接:**
|
||||
|
||||
${form.homepage}
|
||||
|
||||
**标签:**
|
||||
|
||||
${JSON.stringify(tags)}
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Bot`
|
||||
);
|
||||
const queries: { key: string; value: string }[] = [
|
||||
{ key: "template", value: "bot_publish.yml" },
|
||||
{ key: "title", value: form.name && `Bot: ${form.name}` },
|
||||
{ key: "labels", value: "Bot" },
|
||||
{ key: "name", value: form.name },
|
||||
{ key: "description", value: form.desc },
|
||||
{ key: "homepage", value: form.homepage },
|
||||
{ key: "tags", value: JSON.stringify(tags) },
|
||||
];
|
||||
const urlQueries = queries
|
||||
.filter((query) => !!query.value)
|
||||
.map((query) => `${query.key}=${urlEncode(query.value)}`)
|
||||
.join("&");
|
||||
window.open(`https://github.com/nonebot/nonebot2/issues/new?${urlQueries}`);
|
||||
};
|
||||
const onChange = (event) => {
|
||||
const target = event.target;
|
||||
|
@@ -41,42 +41,27 @@ export default function Plugin(): JSX.Element {
|
||||
const [label, setLabel] = useState<string>("");
|
||||
const [color, setColor] = useState<string>("#ea5252");
|
||||
|
||||
const urlEncode = (str: string) =>
|
||||
encodeURIComponent(str).replace(/%2B/gi, "+");
|
||||
|
||||
const onSubmit = () => {
|
||||
setModalOpen(false);
|
||||
const title = encodeURIComponent(`Plugin: ${form.name}`).replace(
|
||||
/%2B/gi,
|
||||
"+"
|
||||
);
|
||||
const body = encodeURIComponent(
|
||||
`
|
||||
**插件名称:**
|
||||
|
||||
${form.name}
|
||||
|
||||
**插件功能:**
|
||||
|
||||
${form.desc}
|
||||
|
||||
**PyPI 项目名:**
|
||||
|
||||
${form.projectLink}
|
||||
|
||||
**插件 import 包名:**
|
||||
|
||||
${form.moduleName}
|
||||
|
||||
**插件项目仓库/主页链接:**
|
||||
|
||||
${form.homepage}
|
||||
|
||||
**标签:**
|
||||
|
||||
${JSON.stringify(tags)}
|
||||
`.trim()
|
||||
).replace(/%2B/gi, "+");
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Plugin`
|
||||
);
|
||||
const queries: { key: string; value: string }[] = [
|
||||
{ key: "template", value: "plugin_publish.yml" },
|
||||
{ key: "title", value: form.name && `Plugin: ${form.name}` },
|
||||
{ key: "labels", value: "Plugin" },
|
||||
{ key: "name", value: form.name },
|
||||
{ key: "description", value: form.desc },
|
||||
{ key: "pypi", value: form.projectLink },
|
||||
{ key: "module", value: form.moduleName },
|
||||
{ key: "homepage", value: form.homepage },
|
||||
{ key: "tags", value: JSON.stringify(tags) },
|
||||
];
|
||||
const urlQueries = queries
|
||||
.filter((query) => !!query.value)
|
||||
.map((query) => `${query.key}=${urlEncode(query.value)}`)
|
||||
.join("&");
|
||||
window.open(`https://github.com/nonebot/nonebot2/issues/new?${urlQueries}`);
|
||||
};
|
||||
const onChange = (event) => {
|
||||
const target = event.target;
|
||||
|
@@ -5,6 +5,136 @@ toc_max_heading_level: 2
|
||||
|
||||
# 更新日志
|
||||
|
||||
## v2.0.0
|
||||
|
||||
### 💥 破坏性变更
|
||||
|
||||
- Feature: 支持 `re.Match` 依赖注入 [@yanyongyu](https://github.com/yanyongyu) ([#1950](https://github.com/nonebot/nonebot2/pull/1950))
|
||||
|
||||
### 🚀 新功能
|
||||
|
||||
- Feature: 优化事件分发方法 [@yanyongyu](https://github.com/yanyongyu) ([#2067](https://github.com/nonebot/nonebot2/pull/2067))
|
||||
- Feature: 移除部分依赖注入参数默认值检查 [@yanyongyu](https://github.com/yanyongyu) ([#2034](https://github.com/nonebot/nonebot2/pull/2034))
|
||||
- Feature: 添加插件元数据字段 `type` `homepage` `supported_adapters` [@yanyongyu](https://github.com/yanyongyu) ([#2012](https://github.com/nonebot/nonebot2/pull/2012))
|
||||
- Feature: 支持 `re.Match` 依赖注入 [@yanyongyu](https://github.com/yanyongyu) ([#1950](https://github.com/nonebot/nonebot2/pull/1950))
|
||||
- Feature: 支持主动停止 `none` 系列驱动器 [@yanyongyu](https://github.com/yanyongyu) ([#1951](https://github.com/nonebot/nonebot2/pull/1951))
|
||||
- Feature: 为消息类添加 `has` `join` `include` `exclude` 方法 [@yanyongyu](https://github.com/yanyongyu) ([#1895](https://github.com/nonebot/nonebot2/pull/1895))
|
||||
|
||||
### 🐛 Bug 修复
|
||||
|
||||
- Fix: 修复插件 require 未声明插件会识别为子插件 [@yanyongyu](https://github.com/yanyongyu) ([#2040](https://github.com/nonebot/nonebot2/pull/2040))
|
||||
- Fix: 修复命令强制空白符影响无参数情况 [@yanyongyu](https://github.com/yanyongyu) ([#1975](https://github.com/nonebot/nonebot2/pull/1975))
|
||||
- Fix: `run_sync` 上下文 [@synodriver](https://github.com/synodriver) ([#1968](https://github.com/nonebot/nonebot2/pull/1968))
|
||||
- Fix: shell command 包含富文本时报错信息出错 [@yanyongyu](https://github.com/yanyongyu) ([#1923](https://github.com/nonebot/nonebot2/pull/1923))
|
||||
|
||||
### 📝 文档
|
||||
|
||||
- Docs: 添加 Alconna 响应器介绍 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2069](https://github.com/nonebot/nonebot2/pull/2069))
|
||||
- Docs: 更新 README 适配器链接 [@yanyongyu](https://github.com/yanyongyu) ([#2068](https://github.com/nonebot/nonebot2/pull/2068))
|
||||
- Docs: 使用 issue form 进行商店发布 [@yanyongyu](https://github.com/yanyongyu) ([#2010](https://github.com/nonebot/nonebot2/pull/2010))
|
||||
- Docs: 修复获取事件信息文档代码范例中的高亮行 [@Lptr-byte](https://github.com/Lptr-byte) ([#1983](https://github.com/nonebot/nonebot2/pull/1983))
|
||||
- Docs: 修复事件处理函数文档代码范例中缺失的 import [@Lptr-byte](https://github.com/Lptr-byte) ([#1982](https://github.com/nonebot/nonebot2/pull/1982))
|
||||
- Docs: 修复获取事件信息文档代码范例中缺失的 import [@Lptr-byte](https://github.com/Lptr-byte) ([#1980](https://github.com/nonebot/nonebot2/pull/1980))
|
||||
- Docs: 新增插件跨平台指南 [@Well2333](https://github.com/Well2333) ([#1938](https://github.com/nonebot/nonebot2/pull/1938))
|
||||
- Docs: 开启 blank issues [@yanyongyu](https://github.com/yanyongyu) ([#1945](https://github.com/nonebot/nonebot2/pull/1945))
|
||||
- Docs: 使用 issue 表单替换 issue 模板 [@A-kirami](https://github.com/A-kirami) ([#1928](https://github.com/nonebot/nonebot2/pull/1928))
|
||||
- Docs: 修正教程中部分 import 缺失的问题 [@Well2333](https://github.com/Well2333) ([#1927](https://github.com/nonebot/nonebot2/pull/1927))
|
||||
- Docs: 添加 Walle-Q 到 Readme [@yanyongyu](https://github.com/yanyongyu) ([#1891](https://github.com/nonebot/nonebot2/pull/1891))
|
||||
- Docs: 更新部署文档 [@yanyongyu](https://github.com/yanyongyu) ([#1890](https://github.com/nonebot/nonebot2/pull/1890))
|
||||
|
||||
### 💫 杂项
|
||||
|
||||
- Plugin: Hello World 添加 tag [@A-kirami](https://github.com/A-kirami) ([#2056](https://github.com/nonebot/nonebot2/pull/2056))
|
||||
- Plugin: 修改 nonebot-plugin-logpile 的名称和描述 [@A-kirami](https://github.com/A-kirami) ([#2057](https://github.com/nonebot/nonebot2/pull/2057))
|
||||
- Plugin: 移除 `nonebot_paddle_ocr` 和 `nonebot_poe_chat` [@canxin121](https://github.com/canxin121) ([#2039](https://github.com/nonebot/nonebot2/pull/2039))
|
||||
- Plugin: 移除 `nonebot-plugin-rtfm` 插件 [@MingxuanGame](https://github.com/MingxuanGame) ([#2037](https://github.com/nonebot/nonebot2/pull/2037))
|
||||
- Plugin: 移除 extrautils 工具拓展插件(暂停维护) [@NCBM](https://github.com/NCBM) ([#2033](https://github.com/nonebot/nonebot2/pull/2033))
|
||||
- Adapter: 更新 Minecraft 适配器 [@17TheWord](https://github.com/17TheWord) ([#1972](https://github.com/nonebot/nonebot2/pull/1972))
|
||||
- Docs: 更正 issue 表单部分内容 [@A-kirami](https://github.com/A-kirami) ([#1961](https://github.com/nonebot/nonebot2/pull/1961))
|
||||
- Plugin: 更新 AutoReply 插件描述 [@lgc2333](https://github.com/lgc2333) ([#1949](https://github.com/nonebot/nonebot2/pull/1949))
|
||||
- Plugin: 移除 `MC_QQ_MCRcon` [@17TheWord](https://github.com/17TheWord) ([#1948](https://github.com/nonebot/nonebot2/pull/1948))
|
||||
- Plugin: 更新 lgc2333 插件仓库地址 [@lgc2333](https://github.com/lgc2333) ([#1935](https://github.com/nonebot/nonebot2/pull/1935))
|
||||
- Plugin: 更新多功能哔哩哔哩解析工具 [@djkcyl](https://github.com/djkcyl) ([#1913](https://github.com/nonebot/nonebot2/pull/1913))
|
||||
- CI: 跳过 PR 仓库为 fork 的情况 [@he0119](https://github.com/he0119) ([#1905](https://github.com/nonebot/nonebot2/pull/1905))
|
||||
- Plugin: 移除旧版本的 GenshinUID [@KimigaiiWuyi](https://github.com/KimigaiiWuyi) ([#1904](https://github.com/nonebot/nonebot2/pull/1904))
|
||||
- CI: 使用最新的 NoneFlow [@he0119](https://github.com/he0119) ([#1899](https://github.com/nonebot/nonebot2/pull/1899))
|
||||
- CI: 使用 NoneFlow 管理工作流 [@yanyongyu](https://github.com/yanyongyu) ([#1892](https://github.com/nonebot/nonebot2/pull/1892))
|
||||
- CI: 移除 poetry 版本限制 [@yanyongyu](https://github.com/yanyongyu) ([#1872](https://github.com/nonebot/nonebot2/pull/1872))
|
||||
|
||||
### 🍻 插件发布
|
||||
|
||||
- Plugin: stablediffusion 绘画插件 [@noneflow](https://github.com/noneflow) ([#2066](https://github.com/nonebot/nonebot2/pull/2066))
|
||||
- Plugin: 随机抽取自定义内容 [@noneflow](https://github.com/noneflow) ([#2064](https://github.com/nonebot/nonebot2/pull/2064))
|
||||
- Plugin: NAGA 公交车 [@noneflow](https://github.com/noneflow) ([#2062](https://github.com/nonebot/nonebot2/pull/2062))
|
||||
- Plugin: 本子标题关键词提取 [@noneflow](https://github.com/noneflow) ([#2058](https://github.com/nonebot/nonebot2/pull/2058))
|
||||
- Plugin: puzzle [@noneflow](https://github.com/noneflow) ([#2054](https://github.com/nonebot/nonebot2/pull/2054))
|
||||
- Plugin: homo_mathematician [@noneflow](https://github.com/noneflow) ([#2052](https://github.com/nonebot/nonebot2/pull/2052))
|
||||
- Plugin: cuber [@noneflow](https://github.com/noneflow) ([#2048](https://github.com/nonebot/nonebot2/pull/2048))
|
||||
- Plugin: nonebot-plugin-lua [@noneflow](https://github.com/noneflow) ([#2049](https://github.com/nonebot/nonebot2/pull/2049))
|
||||
- Plugin: Github 仓库卡片 [@noneflow](https://github.com/noneflow) ([#2042](https://github.com/nonebot/nonebot2/pull/2042))
|
||||
- Plugin: 股票看盘助手 [@noneflow](https://github.com/noneflow) ([#2032](https://github.com/nonebot/nonebot2/pull/2032))
|
||||
- Plugin: 便携插件安装器 [@noneflow](https://github.com/noneflow) ([#2027](https://github.com/nonebot/nonebot2/pull/2027))
|
||||
- Plugin: 会话 id [@noneflow](https://github.com/noneflow) ([#2025](https://github.com/nonebot/nonebot2/pull/2025))
|
||||
- Plugin: SD 绘画插件 [@noneflow](https://github.com/noneflow) ([#2023](https://github.com/nonebot/nonebot2/pull/2023))
|
||||
- Plugin: 《女神异闻录 5》预告信生成器 [@noneflow](https://github.com/noneflow) ([#2021](https://github.com/nonebot/nonebot2/pull/2021))
|
||||
- Plugin: 小小的 WEBAPI 调用插件 [@noneflow](https://github.com/noneflow) ([#2020](https://github.com/nonebot/nonebot2/pull/2020))
|
||||
- Plugin: MultiNCM [@noneflow](https://github.com/noneflow) ([#2018](https://github.com/nonebot/nonebot2/pull/2018))
|
||||
- Plugin: 签到 [@noneflow](https://github.com/noneflow) ([#2014](https://github.com/nonebot/nonebot2/pull/2014))
|
||||
- Plugin: 链接解析 [@noneflow](https://github.com/noneflow) ([#2011](https://github.com/nonebot/nonebot2/pull/2011))
|
||||
- Plugin: 信鸽巴夫 [@noneflow](https://github.com/noneflow) ([#2008](https://github.com/nonebot/nonebot2/pull/2008))
|
||||
- Plugin: 明日方舟抽卡模拟 [@noneflow](https://github.com/noneflow) ([#2005](https://github.com/nonebot/nonebot2/pull/2005))
|
||||
- Plugin: 雷神工业 [@noneflow](https://github.com/noneflow) ([#2003](https://github.com/nonebot/nonebot2/pull/2003))
|
||||
- Plugin: nonebot-plugin-logpile [@noneflow](https://github.com/noneflow) ([#1999](https://github.com/nonebot/nonebot2/pull/1999))
|
||||
- Plugin: Spark-GPT [@noneflow](https://github.com/noneflow) ([#1997](https://github.com/nonebot/nonebot2/pull/1997))
|
||||
- Plugin: 企鹅物流统计数据查询 [@noneflow](https://github.com/noneflow) ([#1995](https://github.com/nonebot/nonebot2/pull/1995))
|
||||
- Plugin: CallAPI [@noneflow](https://github.com/noneflow) ([#1990](https://github.com/nonebot/nonebot2/pull/1990))
|
||||
- Plugin: 群聊人数锁定 [@noneflow](https://github.com/noneflow) ([#1988](https://github.com/nonebot/nonebot2/pull/1988))
|
||||
- Plugin: CSGO 开箱模拟器 [@noneflow](https://github.com/noneflow) ([#1986](https://github.com/nonebot/nonebot2/pull/1986))
|
||||
- Plugin: wordle_help [@noneflow](https://github.com/noneflow) ([#1974](https://github.com/nonebot/nonebot2/pull/1974))
|
||||
- Plugin: 星穹铁道活动日历 [@noneflow](https://github.com/noneflow) ([#1970](https://github.com/nonebot/nonebot2/pull/1970))
|
||||
- Plugin: 水印大师 [@noneflow](https://github.com/noneflow) ([#1965](https://github.com/nonebot/nonebot2/pull/1965))
|
||||
- Plugin: 图片/漫画翻译 [@noneflow](https://github.com/noneflow) ([#1955](https://github.com/nonebot/nonebot2/pull/1955))
|
||||
- Plugin: 为美好群聊献上爆炎 [@noneflow](https://github.com/noneflow) ([#1953](https://github.com/nonebot/nonebot2/pull/1953))
|
||||
- Plugin: 公共画板插件 [@noneflow](https://github.com/noneflow) ([#1957](https://github.com/nonebot/nonebot2/pull/1957))
|
||||
- Plugin: 运行代码 [@noneflow](https://github.com/noneflow) ([#1942](https://github.com/nonebot/nonebot2/pull/1942))
|
||||
- Plugin: brainfuck [@noneflow](https://github.com/noneflow) ([#1944](https://github.com/nonebot/nonebot2/pull/1944))
|
||||
- Plugin: Mixin [@noneflow](https://github.com/noneflow) ([#1947](https://github.com/nonebot/nonebot2/pull/1947))
|
||||
- Plugin: AppInsights 日志监控 [@noneflow](https://github.com/noneflow) ([#1940](https://github.com/nonebot/nonebot2/pull/1940))
|
||||
- Plugin: nonebot_poe_chat [@noneflow](https://github.com/noneflow) ([#1937](https://github.com/nonebot/nonebot2/pull/1937))
|
||||
- Plugin: 更改 BOT 群名片 [@noneflow](https://github.com/noneflow) ([#1934](https://github.com/nonebot/nonebot2/pull/1934))
|
||||
- Plugin: Akinator [@noneflow](https://github.com/noneflow) ([#1925](https://github.com/nonebot/nonebot2/pull/1925))
|
||||
- Plugin: Bilifan [@noneflow](https://github.com/noneflow) ([#1921](https://github.com/nonebot/nonebot2/pull/1921))
|
||||
- Plugin: osu!入群审批 [@noneflow](https://github.com/noneflow) ([#1919](https://github.com/nonebot/nonebot2/pull/1919))
|
||||
- Plugin: 与 ChatGpt 聊天 [@noneflow](https://github.com/noneflow) ([#1917](https://github.com/nonebot/nonebot2/pull/1917))
|
||||
- Plugin: TataruBot2 [@noneflow](https://github.com/noneflow) ([#1915](https://github.com/nonebot/nonebot2/pull/1915))
|
||||
- Plugin: 宝可梦融合 [@noneflow](https://github.com/noneflow) ([#1912](https://github.com/nonebot/nonebot2/pull/1912))
|
||||
- Plugin: FuckYou [@noneflow](https://github.com/noneflow) ([#1910](https://github.com/nonebot/nonebot2/pull/1910))
|
||||
- Plugin: SDGPT [@noneflow](https://github.com/noneflow) ([#1908](https://github.com/nonebot/nonebot2/pull/1908))
|
||||
- Plugin: nonebot clock 群闹钟 ⏰ [@noneflow](https://github.com/noneflow) ([#1906](https://github.com/nonebot/nonebot2/pull/1906))
|
||||
- Plugin: B 站直播间路灯 [@noneflow](https://github.com/noneflow) ([#1901](https://github.com/nonebot/nonebot2/pull/1901))
|
||||
- Plugin: GenshinUID [@noneflow](https://github.com/noneflow) ([#1903](https://github.com/nonebot/nonebot2/pull/1903))
|
||||
- Plugin: 多功能哔哩哔哩解析工具 [@noneflow](https://github.com/noneflow) ([#1898](https://github.com/nonebot/nonebot2/pull/1898))
|
||||
- Plugin: Steam 游戏状态播报 [@yanyongyu](https://github.com/yanyongyu) ([#1887](https://github.com/nonebot/nonebot2/pull/1887))
|
||||
- Plugin: AI 生成 PPT [@yanyongyu](https://github.com/yanyongyu) ([#1884](https://github.com/nonebot/nonebot2/pull/1884))
|
||||
- Plugin: nonebot_paddle_ocr [@yanyongyu](https://github.com/yanyongyu) ([#1882](https://github.com/nonebot/nonebot2/pull/1882))
|
||||
- Plugin: nonebot_api_paddle [@yanyongyu](https://github.com/yanyongyu) ([#1880](https://github.com/nonebot/nonebot2/pull/1880))
|
||||
- Plugin: 来份睡眠套餐 [@yanyongyu](https://github.com/yanyongyu) ([#1876](https://github.com/nonebot/nonebot2/pull/1876))
|
||||
- Plugin: 今日老婆 [@yanyongyu](https://github.com/yanyongyu) ([#1874](https://github.com/nonebot/nonebot2/pull/1874))
|
||||
- Plugin: 激战 2!!! [@yanyongyu](https://github.com/yanyongyu) ([#1871](https://github.com/nonebot/nonebot2/pull/1871))
|
||||
- Plugin: ROLL [@yanyongyu](https://github.com/yanyongyu) ([#1868](https://github.com/nonebot/nonebot2/pull/1868))
|
||||
|
||||
### 🍻 机器人发布
|
||||
|
||||
- Bot: 狐尾 [@noneflow](https://github.com/noneflow) ([#2009](https://github.com/nonebot/nonebot2/pull/2009))
|
||||
- Bot: ay 机器人 [@noneflow](https://github.com/noneflow) ([#1993](https://github.com/nonebot/nonebot2/pull/1993))
|
||||
- Bot: March7th [@noneflow](https://github.com/noneflow) ([#1978](https://github.com/nonebot/nonebot2/pull/1978))
|
||||
- Bot: XDbot2 [@noneflow](https://github.com/noneflow) ([#1932](https://github.com/nonebot/nonebot2/pull/1932))
|
||||
- Bot: CoolQBot [@noneflow](https://github.com/noneflow) ([#1894](https://github.com/nonebot/nonebot2/pull/1894))
|
||||
|
||||
### 🍻 适配器发布
|
||||
|
||||
- Adapter: Walle-Q [@yanyongyu](https://github.com/yanyongyu) ([#1889](https://github.com/nonebot/nonebot2/pull/1889))
|
||||
|
||||
## v2.0.0rc4
|
||||
|
||||
### 🚀 新功能
|
||||
|
@@ -19,31 +19,96 @@ function FooterCopyright() {
|
||||
Deployed by
|
||||
<Link to="https://www.netlify.com" className="ml-1 opacity-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 147 40"
|
||||
height="1rem"
|
||||
viewBox="0 0 256 105"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<radialGradient
|
||||
id="netlify-logo"
|
||||
cy="0%"
|
||||
r="100.11%"
|
||||
gradientTransform="matrix(0 .9989 -1.152 0 .5 -.5)"
|
||||
>
|
||||
<stop offset="0" stop-color="#20c6b7" />
|
||||
<stop offset="1" stop-color="#4d9abf" />
|
||||
</radialGradient>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g clip-path="url(#clip0_236_25)">
|
||||
<path
|
||||
fill="currentcolor"
|
||||
d="m53.37 12.978.123 2.198c1.403-1.7 3.245-2.55 5.525-2.55 3.951 0 5.962 2.268 6.032 6.804v12.568h-4.26v-12.322c0-1.207-.26-2.1-.78-2.681-.52-.58-1.371-.87-2.552-.87-1.719 0-3 .78-3.84 2.338v13.535h-4.262v-19.02h4.016zm24.378 19.372c-2.7 0-4.89-.852-6.567-2.557-1.678-1.705-2.517-3.976-2.517-6.812v-.527c0-1.898.365-3.595 1.096-5.089.73-1.494 1.757-2.657 3.078-3.49 1.321-.831 2.794-1.247 4.42-1.247 2.583 0 4.58.826 5.988 2.478 1.41 1.653 2.114 3.99 2.114 7.014v1.723h-12.4c.13 1.57.652 2.812 1.57 3.726s2.073 1.371 3.464 1.371c1.952 0 3.542-.79 4.77-2.373l2.297 2.198c-.76 1.136-1.774 2.018-3.042 2.645-1.269.627-2.692.94-4.27.94zm-.508-16.294c-1.17 0-2.113.41-2.832 1.23-.72.82-1.178 1.963-1.377 3.428h8.12v-.317c-.094-1.43-.474-2.51-1.14-3.243-.667-.732-1.59-1.098-2.771-1.098zm16.765-7.7v4.623h3.35v3.164h-3.35v10.617c0 .726.144 1.25.43 1.573.286.322.798.483 1.535.483a6.55 6.55 0 0 0 1.49-.176v3.305c-.97.27-1.905.404-2.806.404-3.273 0-4.91-1.81-4.91-5.431v-10.776h-3.124v-3.164h3.122v-4.623h4.261zm11.137 23.643h-4.262v-27h4.262zm9.172 0h-4.262v-19.02h4.262zm-4.525-23.96c0-.655.207-1.2.622-1.634.416-.433 1.009-.65 1.78-.65.772 0 1.368.217 1.79.65.42.434.63.979.63 1.635 0 .644-.21 1.18-.63 1.608-.422.428-1.018.642-1.79.642-.771 0-1.364-.214-1.78-.642-.415-.427-.622-.964-.622-1.608zm10.663 23.96v-15.857h-2.894v-3.164h2.894v-1.74c0-2.11.584-3.738 1.753-4.887 1.17-1.148 2.806-1.722 4.91-1.722.749 0 1.544.105 2.386.316l-.105 3.34a8.375 8.375 0 0 0 -1.631-.14c-2.035 0-3.052 1.048-3.052 3.146v1.687h3.858v3.164h-3.858v15.856h-4.261zm17.87-6.117 3.858-12.903h4.542l-7.54 21.903c-1.158 3.199-3.122 4.799-5.893 4.799-.62 0-1.304-.106-2.052-.317v-3.305l.807.053c1.075 0 1.885-.196 2.429-.589.543-.392.973-1.051 1.289-1.977l.613-1.635-6.664-18.932h4.595z"
|
||||
d="M58.4704 103.765V77.4144L59.0165 76.8683H65.6043L66.1504 77.4144V103.765L65.6043 104.311H59.0165L58.4704 103.765Z"
|
||||
fill="#05BDBA"
|
||||
/>
|
||||
<path
|
||||
fill="url(#netlify-logo)"
|
||||
fill-rule="nonzero"
|
||||
d="m28.589 14.135-.014-.006c-.008-.003-.016-.006-.023-.013a.11.11 0 0 1 -.028-.093l.773-4.726 3.625 3.626-3.77 1.604a.083.083 0 0 1 -.033.006h-.015c-.005-.003-.01-.007-.02-.017a1.716 1.716 0 0 0 -.495-.381zm5.258-.288 3.876 3.876c.805.806 1.208 1.208 1.355 1.674.022.069.04.138.054.209l-9.263-3.923a.728.728 0 0 0 -.015-.006c-.037-.015-.08-.032-.08-.07s.044-.056.081-.071l.012-.005zm5.127 7.003c-.2.376-.59.766-1.25 1.427l-4.37 4.369-5.652-1.177-.03-.006c-.05-.008-.103-.017-.103-.062a1.706 1.706 0 0 0 -.655-1.193c-.023-.023-.017-.059-.01-.092 0-.005 0-.01.002-.014l1.063-6.526.004-.022c.006-.05.015-.108.06-.108a1.73 1.73 0 0 0 1.16-.665c.009-.01.015-.021.027-.027.032-.015.07 0 .103.014l9.65 4.082zm-6.625 6.801-7.186 7.186 1.23-7.56.002-.01c.001-.01.003-.02.006-.029.01-.024.036-.034.061-.044l.012-.005a1.85 1.85 0 0 0 .695-.517c.024-.028.053-.055.09-.06a.09.09 0 0 1 .029 0l5.06 1.04zm-8.707 8.707-.81.81-8.955-12.942a.424.424 0 0 0 -.01-.014c-.014-.019-.029-.038-.026-.06 0-.016.011-.03.022-.042l.01-.013c.027-.04.05-.08.075-.123l.02-.035.003-.003c.014-.024.027-.047.051-.06.021-.01.05-.006.073-.001l9.921 2.046a.164.164 0 0 1 .076.033c.013.013.016.027.019.043a1.757 1.757 0 0 0 1.028 1.175c.028.014.016.045.003.078a.238.238 0 0 0 -.015.045c-.125.76-1.197 7.298-1.485 9.063zm-1.692 1.691c-.597.591-.949.904-1.347 1.03a2 2 0 0 1 -1.206 0c-.466-.148-.869-.55-1.674-1.356l-8.993-8.993 2.349-3.643c.011-.018.022-.034.04-.047.025-.018.061-.01.091 0a2.434 2.434 0 0 0 1.638-.083c.027-.01.054-.017.075.002a.19.19 0 0 1 .028.032l8.999 13.059zm-14.087-10.186-2.063-2.063 4.074-1.738a.084.084 0 0 1 .033-.007c.034 0 .054.034.072.065a2.91 2.91 0 0 0 .13.184l.013.016c.012.017.004.034-.008.05l-2.25 3.493zm-2.976-2.976-2.61-2.61c-.444-.444-.766-.766-.99-1.043l7.936 1.646a.84.84 0 0 0 .03.005c.049.008.103.017.103.063 0 .05-.059.073-.109.092l-.023.01zm-4.056-4.995a2 2 0 0 1 .09-.495c.148-.466.55-.868 1.356-1.674l3.34-3.34a2175.525 2175.525 0 0 0 4.626 6.687c.027.036.057.076.026.106-.146.161-.292.337-.395.528a.16.16 0 0 1 -.05.062c-.013.008-.027.005-.042.002h-.002l-8.949-1.877zm5.68-6.403 4.489-4.491c.423.185 1.96.834 3.333 1.414 1.04.44 1.988.84 2.286.97.03.012.057.024.07.054.008.018.004.041 0 .06a2.003 2.003 0 0 0 .523 1.828c.03.03 0 .073-.026.11l-.014.021-4.56 7.063c-.012.02-.023.037-.043.05-.024.015-.058.008-.086.001a2.274 2.274 0 0 0 -.543-.074c-.164 0-.342.03-.522.063h-.001c-.02.003-.038.007-.054-.005a.21.21 0 0 1 -.045-.051l-4.808-7.013zm5.398-5.398 5.814-5.814c.805-.805 1.208-1.208 1.674-1.355a2 2 0 0 1 1.206 0c.466.147.869.55 1.674 1.355l1.26 1.26-4.135 6.404a.155.155 0 0 1 -.041.048c-.025.017-.06.01-.09 0a2.097 2.097 0 0 0 -1.92.37c-.027.028-.067.012-.101-.003-.54-.235-4.74-2.01-5.341-2.265zm12.506-3.676 3.818 3.818-.92 5.698v.015a.135.135 0 0 1 -.008.038c-.01.02-.03.024-.05.03a1.83 1.83 0 0 0 -.548.273.154.154 0 0 0 -.02.017c-.011.012-.022.023-.04.025a.114.114 0 0 1 -.043-.007l-5.818-2.472-.011-.005c-.037-.015-.081-.033-.081-.071a2.198 2.198 0 0 0 -.31-.915c-.028-.046-.059-.094-.035-.141zm-3.932 8.606 5.454 2.31c.03.014.063.027.076.058a.106.106 0 0 1 0 .057c-.016.08-.03.171-.03.263v.153c0 .038-.039.054-.075.069l-.011.004c-.864.369-12.13 5.173-12.147 5.173s-.035 0-.052-.017c-.03-.03 0-.072.027-.11a.76.76 0 0 0 .014-.02l4.482-6.94.008-.012c.026-.042.056-.089.104-.089l.045.007c.102.014.192.027.283.027.68 0 1.31-.331 1.69-.897a.16.16 0 0 1 .034-.04c.027-.02.067-.01.098.004zm-6.246 9.185 12.28-5.237s.018 0 .035.017c.067.067.124.112.179.154l.027.017c.025.014.05.03.052.056 0 .01 0 .016-.002.025l-1.052 6.462-.004.026c-.007.05-.014.107-.061.107a1.729 1.729 0 0 0 -1.373.847l-.005.008c-.014.023-.027.045-.05.057-.021.01-.048.006-.07.001l-9.793-2.02c-.01-.002-.152-.519-.163-.52z"
|
||||
transform="translate(-.702)"
|
||||
d="M58.4704 26.8971V0.546133L59.0165 0H65.6043L66.1504 0.546133V26.8971L65.6043 27.4432H59.0165L58.4704 26.8971Z"
|
||||
fill="#05BDBA"
|
||||
/>
|
||||
<path
|
||||
d="M35.7973 85.2395H34.8928L30.3616 80.7083V79.8037L38.8523 71.3045L43.648 71.3131L44.288 71.9445V76.7403L35.7973 85.2395Z"
|
||||
fill="#05BDBA"
|
||||
/>
|
||||
<path
|
||||
d="M30.3616 24.7467V23.8336L34.8928 19.3109H35.7973L44.288 27.8016V32.5888L43.648 33.2373H38.8523L30.3616 24.7467Z"
|
||||
fill="#05BDBA"
|
||||
/>
|
||||
<path
|
||||
d="M0.546133 48.3072H37.8795L38.4256 48.8533V55.4496L37.8795 55.9958H0.546133L0 55.4496V48.8533L0.546133 48.3072Z"
|
||||
fill="#05BDBA"
|
||||
/>
|
||||
<path
|
||||
d="M255.445 48.3157L255.991 48.8619V55.4496L255.445 55.9957H217.566L217.02 55.4496L219.759 48.8619L220.305 48.3157H255.445Z"
|
||||
fill="#05BDBA"
|
||||
/>
|
||||
<path
|
||||
d="M74.6667 65.8859H68.0789L67.5328 65.3397V49.92C67.5328 47.1723 66.4576 45.0475 63.1467 44.9792C61.44 44.9365 59.4944 44.9792 57.4123 45.0645L57.0965 45.3803V65.3312L56.5504 65.8773H49.9627L49.4165 65.3312V38.9803L49.9627 38.4341H64.7851C70.5451 38.4341 75.2128 43.1019 75.2128 48.8619V65.3312L74.6667 65.8773V65.8859Z"
|
||||
fill="#014847"
|
||||
/>
|
||||
<path
|
||||
d="M106.573 54.3488L106.027 54.8949H88.9941L88.448 55.4411C88.448 56.5419 89.5488 59.8357 93.9435 59.8357C95.5904 59.8357 97.2373 59.2896 97.792 58.1888L98.3381 57.6427H104.926L105.472 58.1888C104.926 61.4827 102.178 66.432 93.9349 66.432C84.5995 66.432 80.2048 59.8443 80.2048 52.1472C80.2048 44.4501 84.5995 37.8624 93.3888 37.8624C102.178 37.8624 106.573 44.4501 106.573 52.1472V54.3488ZM98.3296 48.8533C98.3296 48.3072 97.7835 44.4587 93.3888 44.4587C88.9941 44.4587 88.448 48.3072 88.448 48.8533L88.9941 49.3995H97.7835L98.3296 48.8533Z"
|
||||
fill="#014847"
|
||||
/>
|
||||
<path
|
||||
d="M121.95 57.6427C121.95 58.7435 122.496 59.2896 123.597 59.2896H128.538L129.084 59.8358V65.3312L128.538 65.8773H123.597C118.656 65.8773 114.261 63.6758 114.261 57.6342V45.5509L113.715 45.0048H109.867L109.321 44.4587V38.9632L109.867 38.4171H113.715L114.261 37.8709V32.9301L114.807 32.384H121.395L121.941 32.9301V37.8709L122.487 38.4171H128.529L129.075 38.9632V44.4587L128.529 45.0048H122.487L121.941 45.5509V57.6342L121.95 57.6427Z"
|
||||
fill="#014847"
|
||||
/>
|
||||
<path
|
||||
d="M142.276 65.8859H135.689L135.142 65.3397V27.9808L135.689 27.4347H142.276L142.822 27.9808V65.3312L142.276 65.8773V65.8859Z"
|
||||
fill="#014847"
|
||||
/>
|
||||
<path
|
||||
d="M157.107 34.0224H150.519L149.973 33.4763V27.9808L150.519 27.4347H157.107L157.653 27.9808V33.4763L157.107 34.0224ZM157.107 65.8859H150.519L149.973 65.3397V38.9717L150.519 38.4256H157.107L157.653 38.9717V65.3397L157.107 65.8859Z"
|
||||
fill="#014847"
|
||||
/>
|
||||
<path
|
||||
d="M182.929 27.9808V33.4763L182.383 34.0224H177.442C176.341 34.0224 175.795 34.5685 175.795 35.6693V37.8709L176.341 38.4171H181.837L182.383 38.9632V44.4587L181.837 45.0048H176.341L175.795 45.5509V65.3227L175.249 65.8688H168.661L168.115 65.3227V45.5509L167.569 45.0048H163.721L163.174 44.4587V38.9632L163.721 38.4171H167.569L168.115 37.8709V35.6693C168.115 29.6277 172.51 27.4261 177.451 27.4261H182.391L182.938 27.9723L182.929 27.9808Z"
|
||||
fill="#014847"
|
||||
/>
|
||||
<path
|
||||
d="M203.247 66.432C201.045 71.9275 198.852 75.2213 191.164 75.2213H188.416L187.87 74.6752V69.1797L188.416 68.6336H191.164C193.911 68.6336 194.458 68.0875 195.012 66.4405V65.8944L186.223 44.4672V38.9717L186.769 38.4256H191.71L192.256 38.9717L198.844 57.6512H199.39L205.978 38.9717L206.524 38.4256H211.465L212.011 38.9717V44.4672L203.221 66.4405L203.247 66.432Z"
|
||||
fill="#014847"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_236_25">
|
||||
<rect width="256" height="104.311" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</Link>
|
||||
<Link to="https://www.cloudflare.com/" className="ml-1 opacity-100">
|
||||
<svg
|
||||
height="1rem"
|
||||
viewBox="0 0 651.29 94.76"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="#f78100"
|
||||
d="M143.05,93.42l1.07-3.71c1.27-4.41.8-8.48-1.34-11.48-2-2.76-5.26-4.38-9.25-4.57L58,72.7a1.47,1.47,0,0,1-1.35-2,2,2,0,0,1,1.75-1.34l76.26-1c9-.41,18.84-7.75,22.27-16.71l4.34-11.36a2.68,2.68,0,0,0,.18-1,3.31,3.31,0,0,0-.06-.54,49.67,49.67,0,0,0-95.49-5.14,22.35,22.35,0,0,0-35,23.42A31.73,31.73,0,0,0,.34,93.45a1.47,1.47,0,0,0,1.45,1.27l139.49,0h0A1.83,1.83,0,0,0,143.05,93.42Z"
|
||||
/>
|
||||
<path
|
||||
fill="#fcad32"
|
||||
d="M168.22,41.15q-1,0-2.1.06a.88.88,0,0,0-.32.07,1.17,1.17,0,0,0-.76.8l-3,10.26c-1.28,4.41-.81,8.48,1.34,11.48a11.65,11.65,0,0,0,9.24,4.57l16.11,1a1.44,1.44,0,0,1,1.14.62,1.5,1.5,0,0,1,.17,1.37,2,2,0,0,1-1.75,1.34l-16.73,1c-9.09.42-18.88,7.75-22.31,16.7l-1.21,3.16a.9.9,0,0,0,.79,1.22h57.63A1.55,1.55,0,0,0,208,93.63a41.34,41.34,0,0,0-39.76-52.48Z"
|
||||
/>
|
||||
<polygon points="273.03 59.66 282.56 59.66 282.56 85.72 299.23 85.72 299.23 94.07 273.03 94.07 273.03 59.66" />
|
||||
<path d="M309.11,77v-.09c0-9.88,8-17.9,18.58-17.9s18.48,7.92,18.48,17.8v.1c0,9.88-8,17.89-18.58,17.89S309.11,86.85,309.11,77m27.33,0v-.09c0-5-3.59-9.29-8.85-9.29s-8.7,4.22-8.7,9.19v.1c0,5,3.59,9.29,8.8,9.29s8.75-4.23,8.75-9.2" />
|
||||
<path d="M357.84,79V59.66h9.69V78.78c0,5,2.5,7.33,6.34,7.33s6.34-2.26,6.34-7.08V59.66h9.68V78.73c0,11.11-6.34,16-16.12,16s-15.93-5-15.93-15.73" />
|
||||
<path d="M404.49,59.66h13.27c12.29,0,19.42,7.08,19.42,17v.1c0,9.93-7.23,17.3-19.61,17.3H404.49Zm13.42,26c5.7,0,9.49-3.15,9.49-8.71v-.09c0-5.51-3.79-8.71-9.49-8.71H414V85.62Z" />
|
||||
<polygon points="451.04 59.66 478.56 59.66 478.56 68.02 460.58 68.02 460.58 73.87 476.85 73.87 476.85 81.78 460.58 81.78 460.58 94.07 451.04 94.07 451.04 59.66" />
|
||||
<polygon points="491.84 59.66 501.37 59.66 501.37 85.72 518.04 85.72 518.04 94.07 491.84 94.07 491.84 59.66" />
|
||||
<path d="M543,59.42h9.19L566.8,94.07H556.58l-2.51-6.14H540.79l-2.45,6.14h-10Zm8.35,21.08-3.83-9.78L543.6,80.5Z" />
|
||||
<path d="M579.08,59.66h16.27c5.27,0,8.9,1.38,11.21,3.74a10.64,10.64,0,0,1,3.05,8v.1a10.88,10.88,0,0,1-7.08,10.57l8.21,12h-11L592.8,83.65h-4.18V94.07h-9.54Zm15.83,16.52c3.25,0,5.12-1.58,5.12-4.08V72c0-2.71-2-4.08-5.17-4.08h-6.24v8.26Z" />
|
||||
<polygon points="623.37 59.66 651.05 59.66 651.05 67.77 632.81 67.77 632.81 72.98 649.33 72.98 649.33 80.5 632.81 80.5 632.81 85.96 651.29 85.96 651.29 94.07 623.37 94.07 623.37 59.66" />
|
||||
<path d="M252.15,81a8.44,8.44,0,0,1-7.88,5.16c-5.22,0-8.8-4.33-8.8-9.29v-.1c0-5,3.49-9.2,8.7-9.2a8.64,8.64,0,0,1,8.18,5.71h10C260.79,65.09,253.6,59,244.27,59c-10.62,0-18.58,8-18.58,17.9V77c0,9.88,7.86,17.8,18.48,17.8,9.08,0,16.18-5.88,18.05-13.76Z" />
|
||||
</svg>
|
||||
</Link>
|
||||
</span>
|
||||
|
@@ -115,12 +115,12 @@
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.spigot",
|
||||
"project_link": "nonebot-adapter-spigot",
|
||||
"name": "Spigot",
|
||||
"desc": "MineCraft通信适配",
|
||||
"module_name": "nonebot.adapters.minecraft",
|
||||
"project_link": "nonebot-adapter-minecraft",
|
||||
"name": "Minecraft",
|
||||
"desc": "MineCraft通信适配,支持Rcon",
|
||||
"author": "17TheWord",
|
||||
"homepage": "https://github.com/17TheWord/nonebot-adapter-spigot",
|
||||
"homepage": "https://github.com/17TheWord/nonebot-adapter-minecraft",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Minecraft",
|
||||
@@ -138,5 +138,20 @@
|
||||
"homepage": "https://github.com/wwweww/adapter-bilibili",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_adapter_walleq",
|
||||
"project_link": "nonebot-adapter-walleq",
|
||||
"name": "Walle-Q",
|
||||
"desc": "内置 QQ 协议实现",
|
||||
"author": "abrahum",
|
||||
"homepage": "https://github.com/onebot-walle/nonebot_adapter_walleq",
|
||||
"tags": [
|
||||
{
|
||||
"label": "QQ",
|
||||
"color": "#34a9cc"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
||||
|
@@ -454,5 +454,81 @@
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "CoolQBot",
|
||||
"desc": "基于 NoneBot2 的聊天机器人",
|
||||
"author": "he0119",
|
||||
"homepage": "https://github.com/he0119/CoolQBot",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "XDbot2",
|
||||
"desc": "简单的QQ功能型机器人",
|
||||
"author": "This-is-XiaoDeng",
|
||||
"homepage": "https://github.com/ITCraftDevelopmentTeam/XDbot2",
|
||||
"tags": [
|
||||
{
|
||||
"label": "a:onebot",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "March7th",
|
||||
"desc": "三月七 - 崩坏:星穹铁道机器人",
|
||||
"author": "mobyw",
|
||||
"homepage": "https://github.com/Mar-7th/March7th",
|
||||
"tags": [
|
||||
{
|
||||
"label": "StarRail",
|
||||
"color": "#5a8ccc"
|
||||
},
|
||||
{
|
||||
"label": "星穹铁道",
|
||||
"color": "#6faec6"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "ay机器人",
|
||||
"desc": "codeforces和洛谷卷王监视、股票监控、ai聊天",
|
||||
"author": "863109569",
|
||||
"homepage": "https://github.com/863109569/qqbot",
|
||||
"tags": [
|
||||
{
|
||||
"label": "acm",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "洛谷",
|
||||
"color": "#81ea52"
|
||||
},
|
||||
{
|
||||
"label": "codeforces",
|
||||
"color": "#5261ea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "狐尾",
|
||||
"desc": "一个整合了兽云祭api的机器人,支持账号令牌操作,以及上传兽图",
|
||||
"author": "bingqiu456",
|
||||
"homepage": "https://github.com/bingqiu456/shouyun",
|
||||
"tags": [
|
||||
{
|
||||
"label": "a:onebot",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "t:shouyun",
|
||||
"color": "#52ea7a"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
||||
|
File diff suppressed because it is too large
Load Diff
49
website/versioned_docs/version-2.0.0/README.md
Normal file
49
website/versioned_docs/version-2.0.0/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
id: index
|
||||
slug: /
|
||||
---
|
||||
|
||||
# 概览
|
||||
|
||||
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架(下称 NoneBot),它基于 Python 的类型注解和异步优先特性(兼容同步),能够为你的需求实现提供便捷灵活的支持。同时,NoneBot 拥有大量的开发者为其开发插件,用户无需编写任何代码,仅需完成环境配置及插件安装,就可以正常使用 NoneBot。
|
||||
|
||||
需要注意的是,NoneBot 仅支持 **Python 3.8 以上版本**
|
||||
|
||||
## 特色
|
||||
|
||||
### 异步优先
|
||||
|
||||
NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。
|
||||
|
||||
### 完整的类型注解
|
||||
|
||||
NoneBot 参考 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 等 PEP 完整实现了类型注解,通过 Pyright(Pylance) 检查。配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中([编辑器支持](./editor-support))。
|
||||
|
||||
### 开箱即用
|
||||
|
||||
NoneBot 提供了使用便捷、具有交互式功能的命令行工具--`nb-cli`,使得用户初次接触 NoneBot 时更容易上手。使用方法请阅读本文档[指南](./quick-start.mdx)以及 [CLI 文档](https://cli.nonebot.dev/)。
|
||||
|
||||
### 插件系统
|
||||
|
||||
插件系统是 NoneBot 的核心,通过它可以实现机器人的模块化以及功能扩展,便于维护和管理。
|
||||
|
||||
### 依赖注入系统
|
||||
|
||||
NoneBot 采用了一套自行定义的依赖注入系统,可以让事件的处理过程更加的简洁、清晰,增加代码的可读性,减少代码冗余。
|
||||
|
||||
#### 什么是依赖注入
|
||||
|
||||
[**『依赖注入』**](https://zh.m.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**『依赖』**。
|
||||
|
||||
系统(在这里是指 NoneBot)将负责做任何需要的事情,为你的代码提供这些必要依赖(即**『注入』**依赖性)
|
||||
|
||||
这在你有以下情形的需求时非常有用:
|
||||
|
||||
- 这部分代码拥有共享的逻辑(同样的代码逻辑多次重复)
|
||||
- 共享数据库以及网络请求连接会话
|
||||
- 比如 `httpx.AsyncClient`、`aiohttp.ClientSession` 和 `sqlalchemy.Session`
|
||||
- 机器人用户权限检查以及认证
|
||||
- 还有更多...
|
||||
|
||||
它在完成上述工作的同时,还能尽量减少代码的耦合和重复
|
161
website/versioned_docs/version-2.0.0/advanced/adapter.md
Normal file
161
website/versioned_docs/version-2.0.0/advanced/adapter.md
Normal file
@@ -0,0 +1,161 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: 注册适配器与指定平台交互
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 20
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 使用适配器
|
||||
|
||||
适配器 (Adapter) 是机器人与平台交互的核心桥梁,它负责在驱动器和机器人插件之间转换与传递消息。
|
||||
|
||||
## 适配器功能与组成
|
||||
|
||||
适配器通常有两种功能,分别是**接收事件**和**调用平台接口**。其中,接收事件是指将驱动器收到的事件消息转换为 NoneBot 定义的事件模型,然后交由机器人插件处理;调用平台接口是指将机器人插件调用平台接口的数据转换为平台指定的格式,然后交由驱动器发送,并接收接口返回数据。
|
||||
|
||||
为了实现这两种功能,适配器通常由四个部分组成:
|
||||
|
||||
- **Adapter**:负责转换事件和调用接口,正确创建 Bot 对象并注册到 NoneBot 中。
|
||||
- **Bot**:负责存储平台机器人相关信息,并提供回复事件的方法。
|
||||
- **Event**:负责定义事件内容,以及事件主体对象。
|
||||
- **Message**:负责正确序列化消息,以便机器人插件处理。
|
||||
|
||||
## 注册适配器
|
||||
|
||||
在使用适配器之前,我们需要先将适配器注册到驱动器中,这样适配器就可以通过驱动器接收事件和调用接口了。我们以 Console 适配器为例,来看看如何注册适配器:
|
||||
|
||||
```python {2,5} title=bot.py
|
||||
import nonebot
|
||||
from nonebot.adapters.console import Adapter
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(Adapter)
|
||||
```
|
||||
|
||||
我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。如果我们需要多平台支持,可以多次调用 `register_adapter` 方法来注册多个适配器。
|
||||
|
||||
## 获取已注册的适配器
|
||||
|
||||
NoneBot 提供了 `get_adapter` 方法来获取已注册的适配器,我们可以通过适配器的名称或类型来获取指定的适配器实例:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
from nonebot.adapters.console import Adapter
|
||||
|
||||
adapters = nonebot.get_adapters()
|
||||
console_adapter = nonebot.get_adapter(Adapter)
|
||||
console_adapter = nonebot.get_adapter(Adapter.get_name())
|
||||
```
|
||||
|
||||
## 获取 Bot 对象
|
||||
|
||||
当前所有适配器已连接的 Bot 对象可以通过 `get_bots` 方法获取,这是一个以机器人 ID 为键的字典:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
|
||||
bots = nonebot.get_bots()
|
||||
```
|
||||
|
||||
我们也可以通过 `get_bot` 方法获取指定 ID 的 Bot 对象。如果省略 ID 参数,将会返回所有 Bot 中的第一个:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
|
||||
bot = nonebot.get_bot("bot_id")
|
||||
```
|
||||
|
||||
如果需要获取指定适配器连接的 Bot 对象,我们可以通过适配器的 `bots` 属性获取,这也是一个以机器人 ID 为键的字典:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
from nonebot.adapters.console import Adapter
|
||||
|
||||
console_adapter = nonebot.get_adapter(Adapter)
|
||||
bots = console_adapter.bots
|
||||
```
|
||||
|
||||
Bot 对象都具有一个 `self_id` 属性,它是机器人的唯一 ID,由适配器填写,通常为机器人的帐号 ID 或者 APP ID。
|
||||
|
||||
## 获取事件通用信息
|
||||
|
||||
适配器的所有事件模型均继承自 `Event` 基类,在[事件类型与重载](../appendices/overload.md)一节中,我们也提到了如何使用基类抽象方法来获取事件通用信息。基类能提供如下信息:
|
||||
|
||||
### 事件类型
|
||||
|
||||
事件类型通常为 `meta_event`、`message`、`notice`、`request`。
|
||||
|
||||
```python
|
||||
type: str = event.get_type()
|
||||
```
|
||||
|
||||
### 事件名称
|
||||
|
||||
事件名称由适配器定义,通常用于日志记录。
|
||||
|
||||
```python
|
||||
name: str = event.get_event_name()
|
||||
```
|
||||
|
||||
### 事件描述
|
||||
|
||||
事件描述由适配器定义,通常用于日志记录。
|
||||
|
||||
```python
|
||||
description: str = event.get_event_description()
|
||||
```
|
||||
|
||||
### 事件日志字符串
|
||||
|
||||
事件日志字符串由事件名称和事件描述组成,用于日志记录。
|
||||
|
||||
```python
|
||||
log: str = event.get_log_string()
|
||||
```
|
||||
|
||||
### 事件主体 ID
|
||||
|
||||
事件主体 ID 通常为机器人用户 ID。
|
||||
|
||||
```python
|
||||
user_id: str = event.get_user_id()
|
||||
```
|
||||
|
||||
### 事件会话 ID
|
||||
|
||||
事件会话 ID 通常为机器人用户 ID 与群聊/频道 ID 组合而成。
|
||||
|
||||
```python
|
||||
session_id: str = event.get_session_id()
|
||||
```
|
||||
|
||||
### 事件消息
|
||||
|
||||
如果事件包含消息,则可以通过该方法获取,否则会产生异常。
|
||||
|
||||
```python
|
||||
message: Message = event.get_message()
|
||||
```
|
||||
|
||||
### 事件纯文本消息
|
||||
|
||||
通常为事件消息的纯文本内容,如果事件不包含消息,则会产生异常。
|
||||
|
||||
```python
|
||||
text: str = event.get_plaintext()
|
||||
```
|
||||
|
||||
### 事件是否与机器人有关
|
||||
|
||||
由适配器实现的判断,通常将事件目标主体为机器人、消息中包含“@机器人”或以“机器人的昵称”开始视为与机器人有关。
|
||||
|
||||
```python
|
||||
is_tome: bool = event.is_tome()
|
||||
```
|
||||
|
||||
## 更多
|
||||
|
||||
官方支持的适配器和社区贡献的适配器均可在[商店](/store)中查看。如果你想要开发自己的适配器,可以参考[开发文档](../developer/adapter-writing.md)。欢迎通过商店发布你的适配器。
|
1205
website/versioned_docs/version-2.0.0/advanced/dependency.mdx
Normal file
1205
website/versioned_docs/version-2.0.0/advanced/dependency.mdx
Normal file
File diff suppressed because it is too large
Load Diff
286
website/versioned_docs/version-2.0.0/advanced/driver.md
Normal file
286
website/versioned_docs/version-2.0.0/advanced/driver.md
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: 选择合适的驱动器运行机器人
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 10
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 选择驱动器
|
||||
|
||||
驱动器 (Driver) 是机器人运行的基石,它是机器人初始化的第一步,主要负责数据收发。
|
||||
|
||||
:::important 提示
|
||||
驱动器的选择通常与机器人所使用的协议适配器相关,如果不知道该选择哪个驱动器,可以先阅读相关协议适配器文档说明。
|
||||
:::
|
||||
|
||||
:::tip 提示
|
||||
如何**安装**驱动器请参考[安装驱动器](../tutorial/store.mdx#安装驱动器)。
|
||||
:::
|
||||
|
||||
## 驱动器类型
|
||||
|
||||
驱动器的类型有两种:
|
||||
|
||||
- `ForwardDriver`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。
|
||||
- `ReverseDriver`:即服务端型驱动器,多用于使用 WebHook,接收 WebSocket 客户端连接等情形。
|
||||
|
||||
客户端型驱动器具有以下两种功能:
|
||||
|
||||
1. 异步发送 HTTP 请求,自定义 `HTTP Method`、`URL`、`Header`、`Body`、`Cookie`、`Proxy`、`Timeout` 等。
|
||||
2. 异步建立 WebSocket 连接上下文,自定义 `WebSocket URL`、`Header`、`Cookie`、`Proxy`、`Timeout` 等。
|
||||
|
||||
服务端型驱动器通常为 ASGI 应用框架,具有以下功能:
|
||||
|
||||
1. 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
|
||||
2. 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
|
||||
3. 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。
|
||||
|
||||
## 配置驱动器
|
||||
|
||||
驱动器的配置方法已经在[配置](../appendices/config.mdx)章节中简单进行了介绍,这里将详细介绍驱动器配置的格式。
|
||||
|
||||
NoneBot 中的客户端和服务端型驱动器可以相互配合使用,但服务端型驱动器**仅能选择一个**。所有驱动器模块都会包含一个 `Driver` 子类,即驱动器类,他可以作为驱动器单独运行。同时,客户端驱动器模块中还会提供一个 `Mixin` 子类,用于在与其他驱动器配合使用时加载。因此,驱动器配置格式采用特殊语法:`<module>[:<Driver>][+<module>[:<Mixin>]]*`。
|
||||
|
||||
其中,`<module>` 代表**驱动器模块路径**;`<Driver>` 代表**驱动器类名**,默认为 `Driver`;`<Mixin>` 代表**驱动器混入类名**,默认为 `Mixin`。即,我们需要选择一个主要驱动器,然后在其基础上配合使用其他驱动器的功能。主要驱动器可以为客户端或服务端类型,但混入类驱动器只能为客户端类型。
|
||||
|
||||
特别的,为了简化内置驱动器模块路径,我们可以使用 `~` 符号作为内置驱动器模块路径的前缀,如 `~fastapi` 代表使用内置驱动器 `fastapi`。NoneBot 内置了多个驱动器适配,但需要安装额外依赖才能使用,具体请参考[安装驱动器](../tutorial/store.mdx#安装驱动器)。常见的驱动器配置如下:
|
||||
|
||||
```dotenv
|
||||
DRIVER=~fastapi
|
||||
DRIVER=~aiohttp
|
||||
DRIVER=~httpx+~websockets
|
||||
DRIVER=~fastapi+~httpx+~websockets
|
||||
```
|
||||
|
||||
## 获取驱动器
|
||||
|
||||
在 NoneBot 框架初始化完成后,我们就可以通过 `get_driver()` 方法获取全局驱动器实例:
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
|
||||
driver = get_driver()
|
||||
```
|
||||
|
||||
## 内置驱动器
|
||||
|
||||
### None
|
||||
|
||||
**类型:**服务端驱动器
|
||||
|
||||
NoneBot 内置的空驱动器,不提供任何收发数据功能,可以在不需要外部网络连接时使用。
|
||||
|
||||
```env
|
||||
DRIVER=~none
|
||||
```
|
||||
|
||||
### FastAPI(默认)
|
||||
|
||||
**类型:**服务端驱动器
|
||||
|
||||
> FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
|
||||
|
||||
[FastAPI](https://fastapi.tiangolo.com/) 是一个易上手、高性能的异步 Web 框架,具有极佳的编写体验。 FastAPI 可以通过类型注解、依赖注入等方式实现输入参数校验、自动生成 API 文档等功能,也可以挂载其他 ASGI、WSGI 应用。
|
||||
|
||||
```env
|
||||
DRIVER=~fastapi
|
||||
```
|
||||
|
||||
#### FastAPI 配置项
|
||||
|
||||
##### `fastapi_openapi_url`
|
||||
|
||||
类型:`str | None`
|
||||
默认值:`None`
|
||||
说明:`FastAPI` 提供的 `OpenAPI` JSON 定义地址,如果为 `None`,则不提供 `OpenAPI` JSON 定义。
|
||||
|
||||
##### `fastapi_docs_url`
|
||||
|
||||
类型:`str | None`
|
||||
默认值:`None`
|
||||
说明:`FastAPI` 提供的 `Swagger` 文档地址,如果为 `None`,则不提供 `Swagger` 文档。
|
||||
|
||||
##### `fastapi_redoc_url`
|
||||
|
||||
类型:`str | None`
|
||||
默认值:`None`
|
||||
说明:`FastAPI` 提供的 `ReDoc` 文档地址,如果为 `None`,则不提供 `ReDoc` 文档。
|
||||
|
||||
##### `fastapi_include_adapter_schema`
|
||||
|
||||
类型:`bool`
|
||||
默认值:`True`
|
||||
说明:`FastAPI` 提供的 `OpenAPI` JSON 定义中是否包含适配器路由的 `Schema`。
|
||||
|
||||
##### `fastapi_reload`
|
||||
|
||||
:::warning 警告
|
||||
不推荐开启该配置项,在 Windows 平台上开启该功能有可能会造成预料之外的影响!替代方案:使用 `nb-cli` 命令行工具以及参数 `--reload` 启动 NoneBot。
|
||||
|
||||
```bash
|
||||
nb run --reload
|
||||
```
|
||||
|
||||
开启该功能后,在 uvicorn 运行时(FastAPI 提供的 ASGI 底层,即 reload 功能的实际来源),asyncio 使用的事件循环会被 uvicorn 从默认的 `ProactorEventLoop` 强制切换到 `SelectorEventLoop`。
|
||||
|
||||
> 相关信息参考 [uvicorn#529](https://github.com/encode/uvicorn/issues/529),[uvicorn#1070](https://github.com/encode/uvicorn/pull/1070),[uvicorn#1257](https://github.com/encode/uvicorn/pull/1257)
|
||||
|
||||
后者(`SelectorEventLoop`)在 Windows 平台的可使用性不如前者(`ProactorEventLoop`),包括但不限于
|
||||
|
||||
1. 不支持创建子进程
|
||||
2. 最多只支持 512 个套接字
|
||||
3. ...
|
||||
|
||||
> 具体信息参考 [Python 文档](https://docs.python.org/zh-cn/3/library/asyncio-platforms.html#windows)
|
||||
|
||||
所以,一些使用了 asyncio 的库因此可能无法正常工作,如:
|
||||
|
||||
1. [playwright](https://playwright.dev/python/docs/library#incompatible-with-selectoreventloop-of-asyncio-on-windows)
|
||||
|
||||
如果在开启该功能后,原本**正常运行**的代码报错,且打印的异常堆栈信息和 asyncio 有关(异常一般为 `NotImplementedError`),
|
||||
你可能就需要考虑相关库对事件循环的支持,以及是否启用该功能。
|
||||
:::
|
||||
|
||||
类型:`bool`
|
||||
默认值:`False`
|
||||
说明:是否开启 `uvicorn` 的 `reload` 功能,需要在机器人入口文件提供 ASGI 应用路径。
|
||||
|
||||
```python title=bot.py
|
||||
app = nonebot.get_asgi()
|
||||
nonebot.run(app="bot:app")
|
||||
```
|
||||
|
||||
##### `fastapi_reload_dirs`
|
||||
|
||||
类型:`List[str] | None`
|
||||
默认值:`None`
|
||||
说明:重载监控文件夹列表,默认为 uvicorn 默认值
|
||||
|
||||
##### `fastapi_reload_delay`
|
||||
|
||||
类型:`float | None`
|
||||
默认值:`None`
|
||||
说明:重载延迟,默认为 uvicorn 默认值
|
||||
|
||||
##### `fastapi_reload_includes`
|
||||
|
||||
类型:`List[str] | None`
|
||||
默认值:`None`
|
||||
说明:要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
##### `fastapi_reload_excludes`
|
||||
|
||||
类型:`List[str] | None`
|
||||
默认值:`None`
|
||||
说明:不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
##### `fastapi_extra`
|
||||
|
||||
类型:`Dist[str, Any]`
|
||||
默认值:`{}`
|
||||
说明:传递给 `FastAPI` 的其他参数
|
||||
|
||||
### Quart
|
||||
|
||||
**类型:**`ReverseDriver`
|
||||
|
||||
> Quart is an asyncio reimplementation of the popular Flask microframework API.
|
||||
|
||||
[Quart](https://quart.palletsprojects.com/) 是一个类 Flask 的异步版本,拥有与 Flask 非常相似的接口和使用方法。
|
||||
|
||||
```env
|
||||
DRIVER=~quart
|
||||
```
|
||||
|
||||
#### Quart 配置项
|
||||
|
||||
##### `quart_reload`
|
||||
|
||||
:::warning 警告
|
||||
不推荐开启该配置项,在 Windows 平台上开启该功能有可能会造成预料之外的影响!替代方案:使用 `nb-cli` 命令行工具以及参数 `--reload` 启动 NoneBot。
|
||||
|
||||
```bash
|
||||
nb run --reload
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
类型:`bool`
|
||||
默认值:`False`
|
||||
说明:是否开启 `uvicorn` 的 `reload` 功能,需要在机器人入口文件提供 ASGI 应用路径。
|
||||
|
||||
```python title=bot.py
|
||||
app = nonebot.get_asgi()
|
||||
nonebot.run(app="bot:app")
|
||||
```
|
||||
|
||||
##### `quart_reload_dirs`
|
||||
|
||||
类型:`List[str] | None`
|
||||
默认值:`None`
|
||||
说明:重载监控文件夹列表,默认为 uvicorn 默认值
|
||||
|
||||
##### `quart_reload_delay`
|
||||
|
||||
类型:`float | None`
|
||||
默认值:`None`
|
||||
说明:重载延迟,默认为 uvicorn 默认值
|
||||
|
||||
##### `quart_reload_includes`
|
||||
|
||||
类型:`List[str] | None`
|
||||
默认值:`None`
|
||||
说明:要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
##### `quart_reload_excludes`
|
||||
|
||||
类型:`List[str] | None`
|
||||
默认值:`None`
|
||||
说明:不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
##### `quart_extra`
|
||||
|
||||
类型:`Dist[str, Any]`
|
||||
默认值:`{}`
|
||||
说明:传递给 `Quart` 的其他参数
|
||||
|
||||
### HTTPX
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
|
||||
:::warning 注意
|
||||
本驱动器仅支持 HTTP 请求,不支持 WebSocket 连接请求。
|
||||
:::
|
||||
|
||||
> [HTTPX](https://www.python-httpx.org/) is a fully featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.
|
||||
|
||||
```env
|
||||
DRIVER=~httpx
|
||||
```
|
||||
|
||||
### websockets
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
|
||||
:::warning 注意
|
||||
本驱动器仅支持 WebSocket 连接请求,不支持 HTTP 请求。
|
||||
:::
|
||||
|
||||
> [websockets](https://websockets.readthedocs.io/) is a library for building WebSocket servers and clients in Python with a focus on correctness, simplicity, robustness, and performance.
|
||||
|
||||
```env
|
||||
DRIVER=~websockets
|
||||
```
|
||||
|
||||
### AIOHTTP
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
|
||||
> [AIOHTTP](https://docs.aiohttp.org/): Asynchronous HTTP Client/Server for asyncio and Python.
|
||||
|
||||
```env
|
||||
DRIVER=~aiohttp
|
||||
```
|
@@ -0,0 +1,40 @@
|
||||
---
|
||||
sidebar_position: 10
|
||||
description: 自定义事件响应器存储
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 110
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 事件响应器存储
|
||||
|
||||
事件响应器是 NoneBot 处理事件的核心,它们默认存储在一个字典中。在进入会话状态后,事件响应器将会转为临时响应器,作为最高优先级同样存储于该字典中。因此,事件响应器的存储类似于会话存储,它决定了整个 NoneBot 对事件的处理行为。
|
||||
|
||||
NoneBot 默认使用 Python 的字典将事件响应器存储于内存中,但是我们也可以自定义事件响应器存储,将事件响应器存储于其他地方,例如 Redis 等。这样我们就可以实现持久化、在多实例间共享会话状态等功能。
|
||||
|
||||
## 编写存储提供者
|
||||
|
||||
事件响应器的存储提供者 `MatcherProvider` 抽象类继承自 `MutableMapping[int, list[type[Matcher]]]`,即以优先级为键,以事件响应器列表为值的映射。我们可以方便地进行逐优先级事件传播。
|
||||
|
||||
编写一个自定义的存储提供者,只需要继承并实现 `MatcherProvider` 抽象类:
|
||||
|
||||
```python
|
||||
from nonebot.matcher import MatcherProvider
|
||||
|
||||
class CustomProvider(MatcherProvider):
|
||||
...
|
||||
```
|
||||
|
||||
## 设置存储提供者
|
||||
|
||||
我们可以通过 `matchers.set_provider` 方法设置存储提供者:
|
||||
|
||||
```python {3}
|
||||
from nonebot.matcher import matchers
|
||||
|
||||
matchers.set_provider(CustomProvider)
|
||||
|
||||
assert isinstance(matchers.provider, CustomProvider)
|
||||
```
|
420
website/versioned_docs/version-2.0.0/advanced/matcher.md
Normal file
420
website/versioned_docs/version-2.0.0/advanced/matcher.md
Normal file
@@ -0,0 +1,420 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
description: 事件响应器组成与内置响应规则
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 60
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 事件响应器进阶
|
||||
|
||||
在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,内置的响应规则,与第三方响应规则拓展。
|
||||
|
||||
## 事件响应器组成
|
||||
|
||||
### 事件响应器类型
|
||||
|
||||
事件响应器类型 `type` 即是该响应器所要响应的事件类型,只有在接收到的事件类型与该响应器的类型相同时,才会触发该响应器。如果类型为空字符串 `""`,则响应器将会响应所有类型的事件。事件响应器类型的检查在所有其他检查(权限控制、响应规则)之前进行。
|
||||
|
||||
NoneBot 内置了四种常用事件类型:`meta_event`、`message`、`notice`、`request`,分别对应元事件、消息、通知、请求。通常情况下,协议适配器会将事件合理地分类至这四种类型中。如果有其他类型的事件需要响应,可以自行定义新的类型。
|
||||
|
||||
### 事件触发权限
|
||||
|
||||
事件触发权限 `permission` 是一个 `Permission` 对象,这在[权限控制](../appendices/permission.mdx)一节中已经介绍过。事件触发权限会在事件响应器的类型检查通过后进行检查,如果权限检查通过,则执行响应规则检查。
|
||||
|
||||
### 事件响应规则
|
||||
|
||||
事件响应规则 `rule` 是一个 `Rule` 对象,这在[响应规则](../appendices/rule.md)一节中已经介绍过。事件响应器的响应规则会在事件响应器的权限检查通过后进行匹配,如果响应规则检查通过,则触发该响应器。
|
||||
|
||||
### 响应优先级
|
||||
|
||||
响应优先级 `priority` 是一个正整数,用于指定响应器的优先级。响应器的优先级越小,越先被触发。如果响应器的优先级相同,则按照响应器的注册顺序进行触发。
|
||||
|
||||
### 阻断
|
||||
|
||||
阻断 `block` 是一个布尔值,用于指定响应器是否阻断事件的传播。如果阻断为 `True`,则在该响应器被触发后,事件将不会再传播给其他下一优先级的响应器。
|
||||
|
||||
NoneBot 内置的事件响应器中,所有非 `command` 规则的 `message` 类型的事件响应器都会阻断事件传递,其他则不会。
|
||||
|
||||
在部分情况中,可以使用 [`stop_propagation`](../appendices/session-control.mdx#stop_propagation) 方法动态阻止事件传播,该方法需要 handler 在参数中获取 matcher 实例后调用方法。
|
||||
|
||||
### 有效期
|
||||
|
||||
事件响应器的有效期分为 `temp` 和 `expire_time` 。`temp` 是一个布尔值,用于指定响应器是否为临时响应器。如果为 `True`,则该响应器在被触发后会被自动销毁。`expire_time` 是一个 `datetime` 对象,用于指定响应器的过期时间。如果 `expire_time` 不为 `None`,则在该时间点后,该响应器会被自动销毁。
|
||||
|
||||
### 默认状态
|
||||
|
||||
事件响应器的默认状态 `default_state` 是一个 `dict` 对象,用于指定响应器的默认状态。在响应器被触发时,响应器将会初始化默认状态然后开始执行事件处理流程。
|
||||
|
||||
## 基本辅助函数
|
||||
|
||||
NoneBot 为四种类型的事件响应器提供了五个基本的辅助函数:
|
||||
|
||||
- `on`:创建任何类型的事件响应器。
|
||||
- `on_metaevent`:创建元事件响应器。
|
||||
- `on_message`:创建消息事件响应器。
|
||||
- `on_request`:创建请求事件响应器。
|
||||
- `on_notice`:创建通知事件响应器。
|
||||
|
||||
除了 `on` 函数具有一个 `type` 参数外,其余参数均相同:
|
||||
|
||||
- `rule`:响应规则,可以是 `Rule` 对象或者 `RuleChecker` 函数。
|
||||
- `permission`:事件触发权限,可以是 `Permission` 对象或者 `PermissionChecker` 函数。
|
||||
- `handlers`:事件处理函数列表。
|
||||
- `temp`:是否为临时响应器。
|
||||
- `expire_time`:响应器的过期时间。
|
||||
- `priority`:响应器的优先级。
|
||||
- `block`:是否阻断事件传播。
|
||||
- `state`:响应器的默认状态。
|
||||
|
||||
在消息类型的事件响应器的基础上,NoneBot 还内置了一些常用的响应规则,并结合为辅助函数来方便我们快速创建指定功能的响应器。下面我们逐个介绍。
|
||||
|
||||
## 内置响应规则
|
||||
|
||||
### `startswith`
|
||||
|
||||
`startswith` 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。
|
||||
|
||||
例如,我们可以创建一个匹配消息开头为 `!` 或者 `/` 的规则:
|
||||
|
||||
```python
|
||||
from nonebot.rule import startswith
|
||||
|
||||
rule = startswith(("!", "/"), ignorecase=False)
|
||||
```
|
||||
|
||||
也可以直接使用辅助函数新建一个响应器:
|
||||
|
||||
```python
|
||||
from nonebot import on_startswith
|
||||
|
||||
matcher = on_startswith(("!", "/"), ignorecase=False)
|
||||
```
|
||||
|
||||
### `endswith`
|
||||
|
||||
`endswith` 响应规则用于匹配消息纯文本部分的结尾是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。
|
||||
|
||||
例如,我们可以创建一个匹配消息结尾为 `.` 或者 `。` 的规则:
|
||||
|
||||
```python
|
||||
from nonebot.rule import endswith
|
||||
|
||||
rule = endswith((".", "。"), ignorecase=False)
|
||||
```
|
||||
|
||||
也可以直接使用辅助函数新建一个响应器:
|
||||
|
||||
```python
|
||||
from nonebot import on_endswith
|
||||
|
||||
matcher = on_endswith((".", "。"), ignorecase=False)
|
||||
```
|
||||
|
||||
### `fullmatch`
|
||||
|
||||
`fullmatch` 响应规则用于匹配消息纯文本部分是否与指定字符串(或一系列字符串)完全相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。
|
||||
|
||||
例如,我们可以创建一个匹配消息为 `ping` 或者 `pong` 的规则:
|
||||
|
||||
```python
|
||||
from nonebot.rule import fullmatch
|
||||
|
||||
rule = fullmatch(("ping", "pong"), ignorecase=False)
|
||||
```
|
||||
|
||||
也可以直接使用辅助函数新建一个响应器:
|
||||
|
||||
```python
|
||||
from nonebot import on_fullmatch
|
||||
|
||||
matcher = on_fullmatch(("ping", "pong"), ignorecase=False)
|
||||
```
|
||||
|
||||
### `keyword`
|
||||
|
||||
`keyword` 响应规则用于匹配消息纯文本部分是否包含指定字符串(或一系列字符串)。
|
||||
|
||||
例如,我们可以创建一个匹配消息中包含 `hello` 或者 `hi` 的规则:
|
||||
|
||||
```python
|
||||
from nonebot.rule import keyword
|
||||
|
||||
rule = keyword("hello", "hi")
|
||||
```
|
||||
|
||||
也可以直接使用辅助函数新建一个响应器:
|
||||
|
||||
```python
|
||||
from nonebot import on_keyword
|
||||
|
||||
matcher = on_keyword("hello", "hi")
|
||||
```
|
||||
|
||||
### `command`
|
||||
|
||||
`command` 是最常用的响应规则,它用于匹配消息是否为命令。它会根据配置中的 [Command Start 和 Command Separator](../appendices/config.mdx#command-start-和-command-separator) 来判断消息是否为命令。
|
||||
|
||||
例如,当我们配置了 `Command Start` 为 `/`,`Command Separator` 为 `.` 时:
|
||||
|
||||
```python
|
||||
from nonebot.rule import command
|
||||
|
||||
# 匹配 "/help" 或者 "/帮助" 开头的消息
|
||||
rule = command("help", "帮助")
|
||||
# 匹配 "/help.cmd" 开头的消息
|
||||
rule = command(("help", "cmd"))
|
||||
```
|
||||
|
||||
也可以直接使用辅助函数新建一个响应器:
|
||||
|
||||
```python
|
||||
from nonebot import on_command
|
||||
|
||||
matcher = on_command("help", aliases={"帮助"})
|
||||
```
|
||||
|
||||
此外,`command` 响应规则默认允许消息命令与参数间不加空格,如果需要严格匹配命令与参数间的空白符,可以使用 `command` 函数的 `force_whitespace` 参数。`force_whitespace` 参数可以是 bool 类型或者具体的字符串,默认为 `False`。如果为 `True`,则命令与参数间必须有任意个数的空白符;如果为字符串,则命令与参数间必须有且与给定字符串一致的空白符。
|
||||
|
||||
```python
|
||||
rule = command("help", force_whitespace=True)
|
||||
rule = command("help", force_whitespace=" ")
|
||||
```
|
||||
|
||||
命令解析后的结果可以通过 [`Command`](./dependency.mdx#command)、[`RawCommand`](./dependency.mdx#rawcommand)、[`CommandArg`](./dependency.mdx#commandarg)、[`CommandStart`](./dependency.mdx#commandstart)、[`CommandWhitespace`](./dependency.mdx#commandwhitespace) 依赖注入获取。
|
||||
|
||||
### `shell_command`
|
||||
|
||||
`shell_command` 响应规则用于匹配类 shell 命令形式的消息。它首先与 [`command`](#command) 响应规则一样进行命令匹配,如果匹配成功,则会进行进一步的参数解析。参数解析采用 `argparse` 标准库进行,在此基础上添加了消息序列 `Message` 支持。
|
||||
|
||||
例如,我们可以创建一个匹配 `/cmd` 命令并且带有 `-v` 选项与默认 `-h` 帮助选项的规则:
|
||||
|
||||
```python
|
||||
from nonebot.rule import shell_command, ArgumentParser
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
|
||||
rule = shell_command("cmd", parser=parser)
|
||||
```
|
||||
|
||||
更多关于 `argparse` 的使用方法请参考 [argparse 文档](https://docs.python.org/zh-cn/3/library/argparse.html)。我们也可以选择不提供 `parser` 参数,这样 `shell_command` 将不会解析参数,但会提供参数列表 `argv`。
|
||||
|
||||
直接使用辅助函数新建一个响应器:
|
||||
|
||||
```python
|
||||
from nonebot import on_shell_command
|
||||
from nonebot.rule import ArgumentParser
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
|
||||
matcher = on_shell_command("cmd", parser=parser)
|
||||
```
|
||||
|
||||
参数解析后的结果可以通过 [`ShellCommandArgv`](./dependency.mdx#shellcommandargv)、[`ShellCommandArgs`](./dependency.mdx#shellcommandargs) 依赖注入获取。
|
||||
|
||||
### `regex`
|
||||
|
||||
`regex` 响应规则用于匹配消息是否与指定正则表达式匹配。
|
||||
|
||||
:::tip 提示
|
||||
正则表达式匹配使用 search 而非 match,如需从头匹配请使用 `r"^xxx"` 模式来确保匹配开头。
|
||||
:::
|
||||
|
||||
例如,我们可以创建一个匹配消息中包含字母并且忽略大小写的规则:
|
||||
|
||||
```python
|
||||
from nonebot.rule import regex
|
||||
|
||||
rule = regex(r"[a-z]+", flags=re.IGNORECASE)
|
||||
```
|
||||
|
||||
也可以直接使用辅助函数新建一个响应器:
|
||||
|
||||
```python
|
||||
from nonebot import on_regex
|
||||
|
||||
matcher = on_regex(r"[a-z]+", flags=re.IGNORECASE)
|
||||
```
|
||||
|
||||
正则匹配后的结果可以通过 [`RegexStr`](./dependency.mdx#regexstr)、[`RegexGroup`](./dependency.mdx#regexgroup)、[`RegexDict`](./dependency.mdx#regexdict) 依赖注入获取。
|
||||
|
||||
### `to_me`
|
||||
|
||||
`to_me` 响应规则用于匹配事件是否与机器人相关。
|
||||
|
||||
例如:
|
||||
|
||||
```python
|
||||
from nonebot.rule import to_me
|
||||
|
||||
rule = to_me()
|
||||
```
|
||||
|
||||
### `is_type`
|
||||
|
||||
`is_type` 响应规则用于匹配事件类型是否为指定类型(或者一系列类型)。
|
||||
|
||||
例如,我们可以创建一个匹配 OneBot v11 私聊和群聊消息事件的规则:
|
||||
|
||||
```python
|
||||
from nonebot.rule import is_type
|
||||
from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent
|
||||
|
||||
rule = is_type(PrivateMessageEvent, GroupMessageEvent)
|
||||
```
|
||||
|
||||
## 响应器组
|
||||
|
||||
为了更方便的管理一系列功能相近的响应器,NoneBot 提供了两种响应器组,它们可以帮助我们进行响应器的统一管理。
|
||||
|
||||
### `CommandGroup`
|
||||
|
||||
`CommandGroup` 可以用于管理一系列具有相同前置命令的子命令响应器。
|
||||
|
||||
例如,我们创建 `/cmd`、`/cmd.sub`、`/cmd.help` 三个命令,他们具有相同的优先级:
|
||||
|
||||
```python
|
||||
from nonebot import CommandGroup
|
||||
|
||||
group = CommandGroup("cmd", priority=10)
|
||||
|
||||
cmd = group.command(tuple())
|
||||
sub_cmd = group.command("sub")
|
||||
help_cmd = group.command("help")
|
||||
```
|
||||
|
||||
### `MatcherGroup`
|
||||
|
||||
`MatcherGroup` 可以用于管理一系列具有相同属性的响应器。
|
||||
|
||||
例如,我们创建一个具有相同响应规则的响应器组:
|
||||
|
||||
```python
|
||||
from nonebot.rule import to_me
|
||||
from nonebot import MatcherGroup
|
||||
|
||||
group = MatcherGroup(rule=to_me())
|
||||
|
||||
matcher1 = group.on_message()
|
||||
matcher2 = group.on_message()
|
||||
```
|
||||
|
||||
## 第三方响应规则
|
||||
|
||||
### Alconna
|
||||
|
||||
[`nonebot-plugin-alconna`](https://github.com/ArcletProject/nonebot-plugin-alconna) 是一类提供了拓展响应规则的插件。
|
||||
该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
|
||||
是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||
|
||||
特点包括:
|
||||
|
||||
- 高效
|
||||
- 直观的命令组件创建方式
|
||||
- 强大的类型解析与类型转换功能
|
||||
- 自定义的帮助信息格式
|
||||
- 多语言支持
|
||||
- 易用的快捷命令创建与使用
|
||||
- 可创建命令补全会话, 以实现多轮连续的补全提示
|
||||
- 可嵌套的多级子命令
|
||||
- 正则匹配支持
|
||||
|
||||
该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。
|
||||
|
||||
同时,基于 [Annotated 支持](https://github.com/nonebot/nonebot2/pull/1832), 添加了两类注解 `AlcMatches` 与 `AlcResult`
|
||||
|
||||
该插件还可以通过 `handle(parameterless)` 来控制一个具体的响应函数是否在不满足条件时跳过响应:
|
||||
|
||||
- `pip.handle([Check(assign("add.name", "nb"))])` 表示仅在命令为 `role-group add` 并且 name 为 `nb` 时响应
|
||||
- `pip.handle([Check(assign("list"))])` 表示仅在命令为 `role-group list` 时响应
|
||||
- `pip.handle([Check(assign("add"))])` 表示仅在命令为 `role-group add` 时响应
|
||||
|
||||
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
|
||||
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
|
||||
|
||||
#### 插件安装
|
||||
|
||||
```shell
|
||||
nb plugin install nonebot-plugin-alconna
|
||||
```
|
||||
|
||||
或
|
||||
|
||||
```shell
|
||||
pip install nonebot-plugin-alconna
|
||||
```
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.adapters import At
|
||||
from nonebot.adapters.onebot.v12 import Message
|
||||
from nonebot_plugin_alconna.adapters.onebot12 import Image
|
||||
from nonebot_plugin_alconna import AlconnaMatches, on_alconna
|
||||
from nonebot.adapters.onebot.v12 import MessageSegment as Ob12MS
|
||||
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
|
||||
|
||||
alc = Alconna(
|
||||
"role-group",
|
||||
Subcommand(
|
||||
"add",
|
||||
Args["name", str],
|
||||
Option("member", Args["target", MultiVar(At)]),
|
||||
),
|
||||
Option("list"),
|
||||
)
|
||||
rg = on_alconna(alc, auto_send_output=True)
|
||||
|
||||
|
||||
@rg.handle()
|
||||
async def _(result: Arparma = AlconnaMatches()):
|
||||
if result.find("list"):
|
||||
img = await gen_role_group_list_image()
|
||||
await rg.finish(Message([Image(img)]))
|
||||
if result.find("add"):
|
||||
group = await create_role_group(result["add.name"])
|
||||
if result.find("add.member"):
|
||||
ats: tuple[Ob12MS, ...] = result["add.member.target"]
|
||||
group.extend(member.data["user_id"] for member in ats)
|
||||
await rg.finish("添加成功")
|
||||
```
|
||||
|
||||
我们可以看到主要的两大组件:`Option` 与 `Subcommand`。
|
||||
|
||||
`Option` 可以传入一组别名,如 `Option("--foo|-F|--FOO|-f")` 或 `Option("--foo", alias=["-F"]`
|
||||
|
||||
`Subcommand` 则可以传入自己的 `Option` 与 `Subcommand`:
|
||||
|
||||
他们拥有如下共同参数:
|
||||
|
||||
- `help_text`: 传入该组件的帮助信息
|
||||
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
|
||||
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
|
||||
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
|
||||
|
||||
其次使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
|
||||
|
||||
`on_alconna` 的所有参数如下:
|
||||
|
||||
- `command: Alconna | str`: Alconna 命令
|
||||
- `skip_for_unmatch: bool = True`: 是否在命令不匹配时跳过该响应
|
||||
- `auto_send_output: bool = False`: 是否自动发送输出信息并跳过响应
|
||||
- `output_converter: TConvert | None = None`: 输出信息字符串转换为消息序列方法
|
||||
- `aliases: set[str | tuple[str, ...]] | None = None`: 命令别名, 作用类似于 `on_command` 中的 aliases
|
||||
- `comp_config: CompConfig | None = None`: 补全会话配置, 不传入则不启用补全会话
|
||||
|
||||
`AlconnaMatches` 是一个依赖注入函数,可注入 `Alconna` 命令解析结果。
|
||||
|
||||
#### 参考
|
||||
|
||||
插件文档: [📦 这里](https://github.com/ArcletProject/nonebot-plugin-alconna/blob/master/docs.md)
|
||||
|
||||
官方文档: [👉 指路](https://arclet.top/)
|
||||
|
||||
QQ 交流群: [🔗 链接](https://jq.qq.com/?_wv=1027&k=PUPOnCSH)
|
||||
|
||||
友链: [📚 文档](https://graiax.cn/guide/message_parser/alconna.html)
|
97
website/versioned_docs/version-2.0.0/advanced/plugin-info.md
Normal file
97
website/versioned_docs/version-2.0.0/advanced/plugin-info.md
Normal file
@@ -0,0 +1,97 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: 填写与获取插件相关的信息
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 30
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 插件信息
|
||||
|
||||
NoneBot 是一个插件化的框架,可以通过加载插件来扩展功能。同时,我们也可以通过 NoneBot 的插件系统来获取相关信息,例如插件的名称、使用方法,用于收集帮助信息等。下面我们将介绍如何为插件添加元数据,以及如何获取插件信息。
|
||||
|
||||
## 插件元数据
|
||||
|
||||
在 NoneBot 中,插件 [`Plugin`](../api/plugin/plugin.md#Plugin) 对象中存储了插件系统所需要的一系列信息。包括插件的索引名称、插件模块、插件中的事件响应器、插件父子关系等。通常,只有插件开发者才需要关心这些信息,而插件使用者或者机器人用户想要看到的是插件使用方法等帮助信息。因此,我们可以为插件添加插件元数据 `PluginMetadata`,它允许插件开发者为插件添加一些额外的信息。这些信息编写于插件模块的顶层,可以直接通过源码查看,或者通过 NoneBot 插件系统获取收集到的信息,通过其他方式发送给机器人用户等。
|
||||
|
||||
现在,假设我们有一个插件 `example`, 它的模块结构如下:
|
||||
|
||||
```tree {4-6} title=Project
|
||||
📦 awesome-bot
|
||||
├── 📂 awesome_bot
|
||||
│ └── 📂 plugins
|
||||
| └── 📂 example
|
||||
| ├── 📜 __init__.py
|
||||
| └── 📜 config.py
|
||||
├── 📜 pyproject.toml
|
||||
└── 📜 README.md
|
||||
```
|
||||
|
||||
我们需要在插件顶层模块 `example/__init__.py` 中添加插件元数据,如下所示:
|
||||
|
||||
```python {1,5-11} title=example/__init__.py
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .config import Config
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="示例插件",
|
||||
description="这是一个示例插件",
|
||||
usage="没什么用",
|
||||
config=Config,
|
||||
extra={},
|
||||
)
|
||||
```
|
||||
|
||||
我们可以看到,插件元数据 `PluginMetadata` 有三个基本属性:插件名称、插件描述、插件使用方法。除此之外,还有两个可选的属性。`config` 属性用于指定插件的[配置类](../appendices/config.mdx#插件配置),`extra` 属性,它是一个字典,可以用于存储任意信息。其他插件可以通过约定 `extra` 字典的键名来达成收集某些特殊信息的目的。
|
||||
|
||||
请注意,这里的**插件名称**是供使用者或机器人用户查看的,与插件索引名称无关。**插件索引名称(插件模块名称)**仅用于 NoneBot 插件系统**内部索引**。
|
||||
|
||||
## 获取插件信息
|
||||
|
||||
NoneBot 提供了多种获取插件对象的方法,例如获取当前所有已导入的插件:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
|
||||
plugins: set[Plugin] = nonebot.get_loaded_plugins()
|
||||
```
|
||||
|
||||
也可以通过插件索引名称获取插件对象:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
|
||||
plugin: Plugin | None = nonebot.get_plugin("example")
|
||||
```
|
||||
|
||||
或者通过模块路径获取插件对象:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
|
||||
plugin: Plugin | None = nonebot.get_plugin_by_module_name("awesome_bot.plugins.example")
|
||||
```
|
||||
|
||||
如果需要获取所有当前声明的插件名称(可能还未加载),可以使用 `get_available_plugin_names` 函数:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
|
||||
plugin_names: set[str] = nonebot.get_available_plugin_names()
|
||||
```
|
||||
|
||||
插件对象 `Plugin` 中包含了多个属性:
|
||||
|
||||
- `name`:插件索引名称
|
||||
- `module`:插件模块
|
||||
- `module_name`:插件模块路径
|
||||
- `manager`:插件管理器
|
||||
- `matcher`:插件中定义的事件响应器
|
||||
- `parent_plugin`:插件的父插件
|
||||
- `sub_plugins`:插件的子插件集合
|
||||
- `metadata`:插件元数据
|
||||
|
||||
通过这些属性以及插件元数据,我们就可以收集所需要的插件信息了。
|
@@ -0,0 +1,41 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
description: 编写与加载嵌套插件
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 40
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 嵌套插件
|
||||
|
||||
NoneBot 支持嵌套插件,即一个插件可以包含其他插件。通过这种方式,我们可以将一个大型插件拆分成多个功能子插件,使得插件更加清晰、易于维护。我们可以直接在插件中使用 NoneBot 加载插件的方法来加载子插件。
|
||||
|
||||
## 创建嵌套插件
|
||||
|
||||
我们可以在使用 `nb-cli` 命令[创建插件](../tutorial/create-plugin.md#创建插件)时,选择直接通过模板创建一个嵌套插件:
|
||||
|
||||
```bash
|
||||
$ nb plugin create
|
||||
[?] 插件名称: parent
|
||||
[?] 使用嵌套插件? (y/N) Y
|
||||
[?] 输出目录: awesome_bot/plugins
|
||||
```
|
||||
|
||||
或者使用 `nb plugin create --sub-plugin` 选项直接创建一个嵌套插件。
|
||||
|
||||
## 已有插件
|
||||
|
||||
如果你已经有一个插件,想要在其中嵌套加载子插件,可以在插件的 `__init__.py` 中添加如下代码:
|
||||
|
||||
```python title=parent/__init__.py
|
||||
import nonebot
|
||||
from pathlib import Path
|
||||
|
||||
sub_plugins = nonebot.load_plugins(
|
||||
str(Path(__file__).parent.joinpath("plugins").resolve())
|
||||
)
|
||||
```
|
||||
|
||||
这样,`parent` 插件就会加载 `parent/plugins` 目录下的所有插件。NoneBot 会正确识别这些插件的父子关系,你可以在 `parent` 的插件信息中看到这些子插件的信息,也可以在子插件信息中看到它们的父插件信息。
|
37
website/versioned_docs/version-2.0.0/advanced/requiring.md
Normal file
37
website/versioned_docs/version-2.0.0/advanced/requiring.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
description: 使用其他插件提供的功能
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 50
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 跨插件访问
|
||||
|
||||
NoneBot 插件化系统的设计使得插件之间可以功能独立、各司其职,我们可以更好地维护和扩展插件。但是,有时候我们可能需要在不同插件之间调用功能。NoneBot 生态中就有一类插件,它们专为其他插件提供功能支持,如:[定时任务插件](../best-practice/scheduler.md)、[数据存储插件](../best-practice/data-storing.md)等。这时候我们就需要在插件之间进行跨插件访问。
|
||||
|
||||
## 插件跟踪
|
||||
|
||||
由于 NoneBot 插件系统通过 [Import Hooks](https://docs.python.org/3/reference/import.html#import-hooks) 的方式实现插件加载与跟踪管理,因此我们**不能**在 NoneBot 跟踪插件前进行模块 import,这会导致插件加载失败。即,我们不能在使用 NoneBot 提供的加载插件方法前,直接使用 `import` 语句导入插件。
|
||||
|
||||
对于在项目目录下的插件,我们通常直接使用 `load_from_toml` 等方法一次性加载所有插件。由于这些插件已经被声明,即便插件导入顺序不同,NoneBot 也能正确跟踪插件。此时,我们不需要对跨插件访问进行特殊处理。但当我们使用了外部插件,如果没有事先声明或加载插件,NoneBot 并不会将其当作插件进行跟踪,可能会出现意料之外的错误出现。
|
||||
|
||||
简单来说,我们必须在 `import` 外部插件之前,确保依赖的外部插件已经被声明或加载。
|
||||
|
||||
## 插件依赖声明
|
||||
|
||||
NoneBot 提供了一种方法来确保我们依赖的插件已经被正确加载,即使用 `require` 函数。通过 `require` 函数,我们可以在当前插件中声明依赖的插件,NoneBot 会在加载当前插件时,检查依赖的插件是否已经被加载,如果没有,会尝试优先加载依赖的插件。
|
||||
|
||||
假设我们有一个插件 `a` 依赖于插件 `b`,我们可以在插件 `a` 中使用 `require` 函数声明其依赖于插件 `b`:
|
||||
|
||||
```python {3} title=a/__init__.py
|
||||
from nonebot import require
|
||||
|
||||
require("b")
|
||||
|
||||
from b import some_function
|
||||
```
|
||||
|
||||
其中,`require` 函数的参数为插件索引名称或者外部插件的模块名称。在完成依赖声明后,我们可以在插件 `a` 中直接导入插件 `b` 所提供的功能。
|
134
website/versioned_docs/version-2.0.0/advanced/routing.md
Normal file
134
website/versioned_docs/version-2.0.0/advanced/routing.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
sidebar_position: 9
|
||||
description: 添加服务端路由规则
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 100
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 添加路由
|
||||
|
||||
在[驱动器](./driver.md)一节中,我们了解了驱动器的两种类型。既然驱动器可以作为服务端运行,那么我们就可以向驱动器添加路由规则,从而实现自定义的 API 接口等功能。在添加路由规则时,我们需要注意驱动器的类型,详情可以参考[选择驱动器](./driver.md#配置驱动器)。
|
||||
|
||||
NoneBot 中,我们可以通过两种途径向驱动器添加路由规则:
|
||||
|
||||
1. 通过 NoneBot 的兼容层建立路由规则。
|
||||
2. 直接向 ASGI 应用添加路由规则。
|
||||
|
||||
这两种途径各有优劣,前者可以在各种服务端型驱动器下运行,但并不能直接使用 ASGI 应用框架提供的特性与功能;后者直接使用 ASGI 应用,更自由、功能完整,但只能在特定类型驱动器下运行。
|
||||
|
||||
在向驱动器添加路由规则时,我们需要注意驱动器是否为服务端类型,我们可以通过以下方式判断:
|
||||
|
||||
```python {3}
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import ReverseDriver
|
||||
|
||||
can_use = isinstance(get_driver(), ReverseDriver)
|
||||
```
|
||||
|
||||
## 通过兼容层添加路由
|
||||
|
||||
NoneBot 兼容层定义了两个数据类 `HTTPServerSetup` 和 `WebSocketServerSetup`,分别用于定义 HTTP 服务端和 WebSocket 服务端的路由规则。
|
||||
|
||||
### HTTP 路由
|
||||
|
||||
`HTTPServerSetup` 具有四个属性:
|
||||
|
||||
- `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。
|
||||
- `method`:请求方法。类型为 `str`。
|
||||
- `name`:路由名称,不可重复。类型为 `str`。
|
||||
- `handle_func`:路由处理函数。类型为 `Callable[[Request], Awaitable[Response]]`。
|
||||
|
||||
例如,我们添加一个 `/hello` 的路由,当请求方法为 `GET` 时,返回 `200 OK` 以及返回体信息:
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import URL, Request, Response, HTTPServerSetup
|
||||
|
||||
async def hello(request: Request) -> Response:
|
||||
return Response(200, content="Hello, world!")
|
||||
|
||||
if isinstance((driver := get_driver()), ReverseDriver):
|
||||
driver.setup_http_server(
|
||||
HTTPServerSetup(
|
||||
path=URL("/hello"),
|
||||
method="GET",
|
||||
name="hello",
|
||||
handle_func=hello,
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
对于 `Request` 和 `Response` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。
|
||||
|
||||
### WebSocket 路由
|
||||
|
||||
`WebSocketServerSetup` 具有三个属性:
|
||||
|
||||
- `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。
|
||||
- `name`:路由名称,不可重复。类型为 `str`。
|
||||
- `handle_func`:路由处理函数。类型为 `Callable[[WebSocket], Awaitable[Any]]`。
|
||||
|
||||
例如,我们添加一个 `/ws` 的路由,发送所有接收到的数据:
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import URL, WebSocket, WebSocketServerSetup
|
||||
|
||||
async def ws_handler(ws: WebSocket):
|
||||
await ws.accept()
|
||||
try:
|
||||
while True:
|
||||
data = await ws.receive()
|
||||
await ws.send(data)
|
||||
except WebSocketClosed as e:
|
||||
# handle closed
|
||||
...
|
||||
finally:
|
||||
with contextlib.suppress(Exception):
|
||||
await websocket.close()
|
||||
# do some cleanup
|
||||
|
||||
if isinstance((driver := get_driver()), ReverseDriver):
|
||||
driver.setup_websocket_server(
|
||||
WebSocketServerSetup(
|
||||
path=URL("/ws"),
|
||||
name="ws",
|
||||
handle_func=ws_handler,
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
对于 `WebSocket` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。
|
||||
|
||||
## 使用 ASGI 应用添加路由
|
||||
|
||||
### 获取 ASGI 应用
|
||||
|
||||
NoneBot 服务端类型的驱动器具有两个属性 `server_app` 和 `asgi`,分别对应驱动框架应用和 ASGI 应用。通常情况下,这两个应用是同一个对象。我们可以通过 `get_app()` 方法快速获取:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
|
||||
app = nonebot.get_app()
|
||||
asgi = nonebot.get_asgi()
|
||||
```
|
||||
|
||||
### 添加路由规则
|
||||
|
||||
在获取到了 ASGI 应用后,我们就可以直接使用 ASGI 应用框架提供的功能来添加路由规则了。这里我们以 [FastAPI](./driver.md#fastapi默认) 为例,演示如何添加路由规则。
|
||||
|
||||
在下面的代码中,我们添加了一个 `GET` 类型的 `/api` 路由,具体方法参考 [FastAPI 文档](https://fastapi.tiangolo.com/)。
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
from fastapi import FastAPI
|
||||
|
||||
app: FastAPI = nonebot.get_app()
|
||||
|
||||
@app.get("/api")
|
||||
async def custom_api():
|
||||
return {"message": "Hello, world!"}
|
||||
```
|
141
website/versioned_docs/version-2.0.0/advanced/runtime-hook.md
Normal file
141
website/versioned_docs/version-2.0.0/advanced/runtime-hook.md
Normal file
@@ -0,0 +1,141 @@
|
||||
---
|
||||
sidebar_position: 8
|
||||
description: 在特定的生命周期中执行代码
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 90
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 钩子函数
|
||||
|
||||
> [钩子编程](https://zh.wikipedia.org/wiki/%E9%92%A9%E5%AD%90%E7%BC%96%E7%A8%8B)(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。
|
||||
|
||||
在 NoneBot 中有一系列预定义的钩子函数,可以分为两类:**全局钩子函数**和**事件处理钩子函数**,这些钩子函数可以用装饰器的形式来使用。
|
||||
|
||||
## 全局钩子函数
|
||||
|
||||
全局钩子函数是指 NoneBot 针对其本身运行过程的钩子函数。
|
||||
|
||||
这些钩子函数是由驱动器来运行的,故需要先[获得全局驱动器](./driver.md#获取驱动器)。
|
||||
|
||||
### 启动准备
|
||||
|
||||
这个钩子函数会在 NoneBot 启动时运行。很多时候,我们并不希望在模块被导入时就执行一些耗时操作,如:连接数据库,这时候我们可以在这个钩子函数中进行这些操作。
|
||||
|
||||
```python
|
||||
@driver.on_startup
|
||||
async def do_something():
|
||||
pass
|
||||
```
|
||||
|
||||
### 终止处理
|
||||
|
||||
这个钩子函数会在 NoneBot 终止时运行。我们可以在这个钩子函数中进行一些清理工作,如:关闭数据库连接。
|
||||
|
||||
```python
|
||||
@driver.on_shutdown
|
||||
async def do_something():
|
||||
pass
|
||||
```
|
||||
|
||||
### Bot 连接处理
|
||||
|
||||
这个钩子函数会在任何协议适配器连接 `Bot` 对象至 NoneBot 时运行。支持依赖注入,可以直接注入 `Bot` 对象。
|
||||
|
||||
```python
|
||||
@driver.on_bot_connect
|
||||
async def do_something(bot: Bot):
|
||||
pass
|
||||
```
|
||||
|
||||
### Bot 断开处理
|
||||
|
||||
这个钩子函数会在 `Bot` 断开与 NoneBot 的连接时运行。支持依赖注入,可以直接注入 `Bot` 对象。
|
||||
|
||||
```python
|
||||
@driver.on_bot_disconnect
|
||||
async def do_something(bot: Bot):
|
||||
pass
|
||||
```
|
||||
|
||||
## 事件处理钩子函数
|
||||
|
||||
这些钩子函数指的是影响 NoneBot 进行**事件处理**的函数, 这些函数可以跟普通的事件处理函数一样接受相应的参数。
|
||||
|
||||
### 事件预处理
|
||||
|
||||
这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。
|
||||
|
||||
```python
|
||||
from nonebot.message import event_preprocessor
|
||||
|
||||
@event_preprocessor
|
||||
async def do_something(event: Event):
|
||||
pass
|
||||
```
|
||||
|
||||
### 事件后处理
|
||||
|
||||
这个钩子函数会在 NoneBot 处理事件完成后运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。
|
||||
|
||||
```python
|
||||
from nonebot.message import event_postprocessor
|
||||
@event_postprocessor
|
||||
async def do_something(event: Event):
|
||||
pass
|
||||
```
|
||||
|
||||
### 运行预处理
|
||||
|
||||
这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。
|
||||
|
||||
```python
|
||||
from nonebot.message import run_preprocessor
|
||||
@run_preprocessor
|
||||
async def do_something(event: Event, matcher: Matcher):
|
||||
pass
|
||||
```
|
||||
|
||||
### 运行后处理
|
||||
|
||||
这个钩子函数会在 NoneBot 运行事件响应器后运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态、运行中产生的异常。
|
||||
|
||||
```python
|
||||
from nonebot.message import run_postprocessor
|
||||
|
||||
@run_postprocessor
|
||||
async def do_something(event: Event, matcher: Matcher, exception: Optional[Exception]):
|
||||
pass
|
||||
```
|
||||
|
||||
### 平台接口调用钩子
|
||||
|
||||
这个钩子函数会在 `Bot` 对象调用平台接口时运行。在这个钩子函数中,我们可以通过引起 `MockApiException` 异常来阻止 `Bot` 对象调用平台接口并返回指定的结果。
|
||||
|
||||
```python
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.exception import MockApiException
|
||||
|
||||
@Bot.on_calling_api
|
||||
async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
if api == "send_msg":
|
||||
raise MockApiException(result={"message_id": 123})
|
||||
```
|
||||
|
||||
### 平台接口调用后钩子
|
||||
|
||||
这个钩子函数会在 `Bot` 对象调用平台接口后运行。在这个钩子函数中,我们可以通过引起 `MockApiException` 异常来忽略平台接口返回的结果并返回指定的结果。
|
||||
|
||||
```python
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.exception import MockApiException
|
||||
|
||||
@Bot.on_called_api
|
||||
async def handle_api_result(
|
||||
bot: Bot, exception: Optional[Exception], api: str, data: Dict[str, Any], result: Any
|
||||
):
|
||||
if not exception and api == "send_msg":
|
||||
raise MockApiException(result={**result, "message_id": 123})
|
||||
```
|
@@ -0,0 +1,59 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
description: 控制会话响应对象
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 80
|
||||
category: advanced
|
||||
---
|
||||
|
||||
# 会话更新
|
||||
|
||||
在 NoneBot 中,在某个事件响应器对事件响应后,即是进入了会话状态,会话状态会持续到整个事件响应流程结束。会话过程中,机器人可以与用户进行多次交互。每次需要等待用户事件时,NoneBot 将会复制一个新的临时事件响应器,并更新该事件响应器使其响应当前会话主体的消息,这个过程称为会话更新。
|
||||
|
||||
会话更新分为两部分:**更新[事件响应器类型](./matcher.md#事件响应器类型)**和**更新[事件触发权限](./matcher.md#事件触发权限)**。
|
||||
|
||||
## 更新事件响应器类型
|
||||
|
||||
通常情况下,与机器人用户进行的会话都是通过消息事件进行的,因此会话更新后的默认响应事件类型为 `message`。如果希望接收一个特定类型的消息,比如 `notice` 等,我们需要自定义响应事件类型更新函数。响应事件类型更新函数是一个 `Dependent`,可以使用依赖注入。
|
||||
|
||||
```python {3-5}
|
||||
foo = on_message()
|
||||
|
||||
@foo.type_updater
|
||||
async def _() -> str:
|
||||
return "notice"
|
||||
```
|
||||
|
||||
在注册了上述响应事件类型更新函数后,当我们需要等待用户事件时,将只会响应 `notice` 类型的事件。如果希望在会话过程中的不同阶段响应不同类型的事件,我们就需要使用更复杂的逻辑来更新响应事件类型(如:根据会话状态),这里将不再展示。
|
||||
|
||||
## 更新事件触发权限
|
||||
|
||||
会话通常是由机器人与用户进行的一对一交互,因此会话更新后的默认触发权限为当前事件的会话 ID。这个会话 ID 由协议适配器生成,通常由用户 ID 和群 ID 等组成。如果希望实现更复杂的会话功能(如:多用户同时参与的会话),我们需要自定义触发权限更新函数。触发权限更新函数是一个 `Dependent`,可以使用依赖注入。
|
||||
|
||||
```python {5-7}
|
||||
from nonebot.permission import User
|
||||
|
||||
foo = on_message()
|
||||
|
||||
@foo.permission_updater
|
||||
async def _(event: Event, matcher: Matcher) -> Permission:
|
||||
return Permission(User.from_event(event, perm=matcher.permission))
|
||||
```
|
||||
|
||||
上述权限更新函数是默认的权限更新函数,它将会话的触发权限更新为当前事件的会话 ID。如果我们希望响应多个用户的消息,我们可以如下修改:
|
||||
|
||||
```python {5-7}
|
||||
from nonebot.permission import USER
|
||||
|
||||
foo = on_message()
|
||||
|
||||
@foo.permission_updater
|
||||
async def _(matcher: Matcher) -> Permission:
|
||||
return USER("session1", "session2", perm=matcher.permission)
|
||||
```
|
||||
|
||||
请注意,此处为全大写字母的 `USER` 权限,它可以匹配多个会话 ID。通过这种方式,我们可以实现多用户同时参与的会话。
|
||||
|
||||
我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store)。
|
0
website/versioned_docs/version-2.0.0/api/.gitkeep
Normal file
0
website/versioned_docs/version-2.0.0/api/.gitkeep
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"position": 15
|
||||
}
|
871
website/versioned_docs/version-2.0.0/api/adapters/index.md
Normal file
871
website/versioned_docs/version-2.0.0/api/adapters/index.md
Normal file
@@ -0,0 +1,871 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: nonebot.adapters 模块
|
||||
---
|
||||
|
||||
# nonebot.adapters
|
||||
|
||||
本模块定义了协议适配基类,各协议请继承以下基类。
|
||||
|
||||
使用 [Driver.register_adapter](../drivers/index.md#Driver-register_adapter) 注册适配器。
|
||||
|
||||
## _abstract class_ `Bot(adapter, self_id)` {#Bot}
|
||||
|
||||
- **说明**
|
||||
|
||||
Bot 基类。
|
||||
|
||||
用于处理上报消息,并提供 API 调用接口。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `adapter` ([Adapter](#Adapter)): 协议适配器实例
|
||||
|
||||
- `self_id` (str): 机器人 ID
|
||||
|
||||
### _instance-var_ `adapter` {#Bot-adapter}
|
||||
|
||||
- **类型:** [Adapter](#Adapter)
|
||||
|
||||
- **说明:** 协议适配器实例
|
||||
|
||||
### _instance-var_ `self_id` {#Bot-self_id}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 机器人 ID
|
||||
|
||||
### _property_ `type` {#Bot-type}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 协议适配器名称
|
||||
|
||||
### _property_ `config` {#Bot-config}
|
||||
|
||||
- **类型:** [Config](../config.md#Config)
|
||||
|
||||
- **说明:** 全局 NoneBot 配置
|
||||
|
||||
### _async method_ `call_api(api, **data)` {#Bot-call_api}
|
||||
|
||||
- **说明:** 调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
|
||||
|
||||
- **参数**
|
||||
|
||||
- `api` (str): API 名称
|
||||
|
||||
- `**data` (Any): API 数据
|
||||
|
||||
- **返回**
|
||||
|
||||
- Any
|
||||
|
||||
- **用法**
|
||||
|
||||
```python
|
||||
await bot.call_api("send_msg", message="hello world")
|
||||
await bot.send_msg(message="hello world")
|
||||
```
|
||||
|
||||
### _abstract async method_ `send(event, message, **kwargs)` {#Bot-send}
|
||||
|
||||
- **说明:** 调用机器人基础发送消息接口
|
||||
|
||||
- **参数**
|
||||
|
||||
- `event` ([Event](#Event)): 上报事件
|
||||
|
||||
- `message` (str | [Message](#Message) | [MessageSegment](#MessageSegment)): 要发送的消息
|
||||
|
||||
- `**kwargs` (Any): 任意额外参数
|
||||
|
||||
- **返回**
|
||||
|
||||
- Any
|
||||
|
||||
### _classmethod_ `on_calling_api(func)` {#Bot-on_calling_api}
|
||||
|
||||
- **说明**
|
||||
|
||||
调用 api 预处理。
|
||||
|
||||
钩子函数参数:
|
||||
|
||||
- bot: 当前 bot 对象
|
||||
- api: 调用的 api 名称
|
||||
- data: api 调用的参数字典
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_CallingAPIHook](../typing.md#T_CallingAPIHook))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_CallingAPIHook](../typing.md#T_CallingAPIHook)
|
||||
|
||||
### _classmethod_ `on_called_api(func)` {#Bot-on_called_api}
|
||||
|
||||
- **说明**
|
||||
|
||||
调用 api 后处理。
|
||||
|
||||
钩子函数参数:
|
||||
|
||||
- bot: 当前 bot 对象
|
||||
- exception: 调用 api 时发生的错误
|
||||
- api: 调用的 api 名称
|
||||
- data: api 调用的参数字典
|
||||
- result: api 调用的返回
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_CalledAPIHook](../typing.md#T_CalledAPIHook))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_CalledAPIHook](../typing.md#T_CalledAPIHook)
|
||||
|
||||
## _abstract class_ `Event(<auto>)` {#Event}
|
||||
|
||||
- **说明:** Event 基类。提供获取关键信息的方法,其余信息可直接获取。
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _classmethod_ `validate(value)` {#Event-validate}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- E
|
||||
|
||||
### _abstract method_ `get_type()` {#Event-get_type}
|
||||
|
||||
- **说明:** 获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `get_event_name()` {#Event-get_event_name}
|
||||
|
||||
- **说明:** 获取事件名称的方法。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `get_event_description()` {#Event-get_event_description}
|
||||
|
||||
- **说明:** 获取事件描述的方法,通常为事件具体内容。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _method_ `get_log_string()` {#Event-get_log_string}
|
||||
|
||||
- **说明**
|
||||
|
||||
获取事件日志信息的方法。
|
||||
|
||||
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,可以抛出 `NoLogException` 异常。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
- **异常**
|
||||
|
||||
- NoLogException
|
||||
|
||||
### _abstract method_ `get_user_id()` {#Event-get_user_id}
|
||||
|
||||
- **说明:** 获取事件主体 id 的方法,通常是用户 id 。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `get_session_id()` {#Event-get_session_id}
|
||||
|
||||
- **说明:** 获取会话 id 的方法,用于判断当前事件属于哪一个会话,通常是用户 id、群组 id 组合。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `get_message()` {#Event-get_message}
|
||||
|
||||
- **说明:** 获取事件消息内容的方法。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- [Message](#Message)
|
||||
|
||||
### _method_ `get_plaintext()` {#Event-get_plaintext}
|
||||
|
||||
- **说明**
|
||||
|
||||
获取消息纯文本的方法。
|
||||
|
||||
通常不需要修改,默认通过 `get_message().extract_plain_text` 获取。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `is_tome()` {#Event-is_tome}
|
||||
|
||||
- **说明:** 获取事件是否与机器人有关的方法。
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- bool
|
||||
|
||||
## _abstract class_ `Adapter(driver, **kwargs)` {#Adapter}
|
||||
|
||||
- **说明**
|
||||
|
||||
协议适配器基类。
|
||||
|
||||
通常,在 Adapter 中编写协议通信相关代码,如: 建立通信连接、处理接收与发送 data 等。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `driver` ([Driver](../drivers/index.md#Driver)): [Driver](../drivers/index.md#Driver) 实例
|
||||
|
||||
- `**kwargs` (Any): 其他由 [Driver.register_adapter](../drivers/index.md#Driver-register_adapter) 传入的额外参数
|
||||
|
||||
### _instance-var_ `driver` {#Adapter-driver}
|
||||
|
||||
- **类型:** [Driver](../drivers/index.md#Driver)
|
||||
|
||||
- **说明:** 实例
|
||||
|
||||
### _instance-var_ `bots` {#Adapter-bots}
|
||||
|
||||
- **类型:** dict[str, [Bot](#Bot)]
|
||||
|
||||
- **说明:** 本协议适配器已建立连接的 [Bot](#Bot) 实例
|
||||
|
||||
### _abstract classmethod_ `get_name()` {#Adapter-get_name}
|
||||
|
||||
- **说明:** 当前协议适配器的名称
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _property_ `config` {#Adapter-config}
|
||||
|
||||
- **类型:** [Config](../config.md#Config)
|
||||
|
||||
- **说明:** 全局 NoneBot 配置
|
||||
|
||||
### _method_ `bot_connect(bot)` {#Adapter-bot_connect}
|
||||
|
||||
- **说明**
|
||||
|
||||
告知 NoneBot 建立了一个新的 [Bot](#Bot) 连接。
|
||||
|
||||
当有新的 [Bot](#Bot) 实例连接建立成功时调用。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `bot` ([Bot](#Bot)): [Bot](#Bot) 实例
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `bot_disconnect(bot)` {#Adapter-bot_disconnect}
|
||||
|
||||
- **说明**
|
||||
|
||||
告知 NoneBot [Bot](#Bot) 连接已断开。
|
||||
|
||||
当有 [Bot](#Bot) 实例连接断开时调用。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `bot` ([Bot](#Bot)): [Bot](#Bot) 实例
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `setup_http_server(setup)` {#Adapter-setup_http_server}
|
||||
|
||||
- **说明:** 设置一个 HTTP 服务器路由配置
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([HTTPServerSetup](../drivers/index.md#HTTPServerSetup))
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `setup_websocket_server(setup)` {#Adapter-setup_websocket_server}
|
||||
|
||||
- **说明:** 设置一个 WebSocket 服务器路由配置
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([WebSocketServerSetup](../drivers/index.md#WebSocketServerSetup))
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _async method_ `request(setup)` {#Adapter-request}
|
||||
|
||||
- **说明:** 进行一个 HTTP 客户端请求
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([Request](../drivers/index.md#Request))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [Response](../drivers/index.md#Response)
|
||||
|
||||
### _method_ `websocket(setup)` {#Adapter-websocket}
|
||||
|
||||
- **说明:** 建立一个 WebSocket 客户端连接请求
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([Request](../drivers/index.md#Request))
|
||||
|
||||
- **返回**
|
||||
|
||||
- AsyncGenerator[[WebSocket](../drivers/index.md#WebSocket), None]
|
||||
|
||||
## _abstract class_ `Message(<auto>)` {#Message}
|
||||
|
||||
- **说明:** 消息数组
|
||||
|
||||
- **参数**
|
||||
|
||||
- `message`: 消息内容
|
||||
|
||||
### _classmethod_ `template(format_string)` {#Message-template}
|
||||
|
||||
- **说明**
|
||||
|
||||
创建消息模板。
|
||||
|
||||
用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板
|
||||
|
||||
并且提供了拓展的格式化控制符, 可以用适用于该消息类型的 `MessageSegment` 的工厂方法创建消息
|
||||
|
||||
- **参数**
|
||||
|
||||
- `format_string` (str | TM): 格式化模板
|
||||
|
||||
- **返回**
|
||||
|
||||
- [MessageTemplate](#MessageTemplate)[Self]: 消息格式化器
|
||||
|
||||
### _abstract classmethod_ `get_segment_class()` {#Message-get_segment_class}
|
||||
|
||||
- **说明:** 获取消息段类型
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- type[TMS]
|
||||
|
||||
### _abstract staticmethod_ `_construct(msg)` {#Message-\_construct}
|
||||
|
||||
- **说明:** 构造消息数组
|
||||
|
||||
- **参数**
|
||||
|
||||
- `msg` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- Iterable[TMS]
|
||||
|
||||
### _method_ `__getitem__(args)` {#Message-**getitem**}
|
||||
|
||||
- **重载**
|
||||
|
||||
**1.** `(self, args) -> Self`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `self`
|
||||
|
||||
- `args` (str): 消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 所有类型为 `args` 的消息段
|
||||
|
||||
**2.** `(self, args) -> TMS`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `self`
|
||||
|
||||
- `args` (tuple[str, int]): 消息段类型和索引
|
||||
|
||||
- **返回**
|
||||
|
||||
- TMS: 类型为 `args[0]` 的消息段第 `args[1]` 个
|
||||
|
||||
**3.** `(self, args) -> Self`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `self`
|
||||
|
||||
- `args` (tuple[str, slice]): 消息段类型和切片
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 类型为 `args[0]` 的消息段切片 `args[1]`
|
||||
|
||||
**4.** `(self, args) -> TMS`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `self`
|
||||
|
||||
- `args` (int): 索引
|
||||
|
||||
- **返回**
|
||||
|
||||
- TMS: 第 `args` 个消息段
|
||||
|
||||
**5.** `(self, args) -> Self`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `self`
|
||||
|
||||
- `args` (slice): 切片
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 消息切片 `args`
|
||||
|
||||
### _method_ `__contains__(value)` {#Message-**contains**}
|
||||
|
||||
- **说明:** 检查消息段是否存在
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str): 消息段或消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- bool: 消息内是否存在给定消息段或给定类型的消息段
|
||||
|
||||
### _method_ `has(value)` {#Message-has}
|
||||
|
||||
- **说明:** 与 [`__contains__`](#Message-__contains__) 相同
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- bool
|
||||
|
||||
### _method_ `index(value, *args)` {#Message-index}
|
||||
|
||||
- **说明:** 索引消息段
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str): 消息段或者消息段类型
|
||||
|
||||
- `*args` (SupportsIndex)
|
||||
|
||||
- `arg`: start 与 end
|
||||
|
||||
- **返回**
|
||||
|
||||
- int: 索引 index
|
||||
|
||||
- **异常**
|
||||
|
||||
- ValueError: 消息段不存在
|
||||
|
||||
### _method_ `get(type_, count=None)` {#Message-get}
|
||||
|
||||
- **说明:** 获取指定类型的消息段
|
||||
|
||||
- **参数**
|
||||
|
||||
- `type_` (str): 消息段类型
|
||||
|
||||
- `count` (int | None): 获取个数
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 构建的新消息
|
||||
|
||||
### _method_ `count(value)` {#Message-count}
|
||||
|
||||
- **说明:** 计算指定消息段的个数
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str): 消息段或消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- int: 个数
|
||||
|
||||
### _method_ `only(value)` {#Message-only}
|
||||
|
||||
- **说明:** 检查消息中是否仅包含指定消息段
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str): 指定消息段或消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- bool: 是否仅包含指定消息段
|
||||
|
||||
### _method_ `append(obj)` {#Message-append}
|
||||
|
||||
- **说明:** 添加一个消息段到消息数组末尾。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `obj` (str | TMS): 要添加的消息段
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self
|
||||
|
||||
### _method_ `extend(obj)` {#Message-extend}
|
||||
|
||||
- **说明:** 拼接一个消息数组或多个消息段到消息数组末尾。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `obj` (Self | Iterable[TMS]): 要添加的消息数组
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self
|
||||
|
||||
### _method_ `join(iterable)` {#Message-join}
|
||||
|
||||
- **说明:** 将多个消息连接并将自身作为分割
|
||||
|
||||
- **参数**
|
||||
|
||||
- `iterable` (Iterable[TMS | Self]): 要连接的消息
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 连接后的消息
|
||||
|
||||
### _method_ `copy()` {#Message-copy}
|
||||
|
||||
- **说明:** 深拷贝消息
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self
|
||||
|
||||
### _method_ `include(*types)` {#Message-include}
|
||||
|
||||
- **说明:** 过滤消息
|
||||
|
||||
- **参数**
|
||||
|
||||
- `*types` (str): 包含的消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 新构造的消息
|
||||
|
||||
### _method_ `exclude(*types)` {#Message-exclude}
|
||||
|
||||
- **说明:** 过滤消息
|
||||
|
||||
- **参数**
|
||||
|
||||
- `*types` (str): 不包含的消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 新构造的消息
|
||||
|
||||
### _method_ `extract_plain_text()` {#Message-extract_plain_text}
|
||||
|
||||
- **说明:** 提取消息内纯文本消息
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
## _abstract class_ `MessageSegment(<auto>)` {#MessageSegment}
|
||||
|
||||
- **说明:** 消息段基类
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _instance-var_ `type` {#MessageSegment-type}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 消息段类型
|
||||
|
||||
### _class-var_ `data` {#MessageSegment-data}
|
||||
|
||||
- **类型:** dict[str, Any]
|
||||
|
||||
- **说明:** 消息段数据
|
||||
|
||||
### _abstract classmethod_ `get_message_class()` {#MessageSegment-get_message_class}
|
||||
|
||||
- **说明:** 获取消息数组类型
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- type[TM]
|
||||
|
||||
### _abstract method_ `__str__()` {#MessageSegment-**str**}
|
||||
|
||||
- **说明:** 该消息段所代表的 str,在命令匹配部分使用
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _method_ `__add__(other)` {#MessageSegment-**add**}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `other` (str | TMS | Iterable[TMS])
|
||||
|
||||
- **返回**
|
||||
|
||||
- TM
|
||||
|
||||
### _method_ `get(key, default=None)` {#MessageSegment-get}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `key` (str)
|
||||
|
||||
- `default` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `keys()` {#MessageSegment-keys}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `values()` {#MessageSegment-values}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `items()` {#MessageSegment-items}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `join(iterable)` {#MessageSegment-join}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `iterable` (Iterable[TMS | TM])
|
||||
|
||||
- **返回**
|
||||
|
||||
- TM
|
||||
|
||||
### _method_ `copy()` {#MessageSegment-copy}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self
|
||||
|
||||
### _abstract method_ `is_text()` {#MessageSegment-is_text}
|
||||
|
||||
- **说明:** 当前消息段是否为纯文本
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- bool
|
||||
|
||||
## _class_ `MessageTemplate(template, factory=str)` {#MessageTemplate}
|
||||
|
||||
- **说明:** 消息模板格式化实现类。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `template` (str | TM): 模板
|
||||
|
||||
- `factory` (type[str] | type[TM]): 消息类型工厂,默认为 `str`
|
||||
|
||||
### _method_ `add_format_spec(spec, name=None)` {#MessageTemplate-add_format_spec}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `spec` (FormatSpecFunc_T)
|
||||
|
||||
- `name` (str | None)
|
||||
|
||||
- **返回**
|
||||
|
||||
- FormatSpecFunc_T
|
||||
|
||||
### _method_ `format(*args, **kwargs)` {#MessageTemplate-format}
|
||||
|
||||
- **说明:** 根据传入参数和模板生成消息对象
|
||||
|
||||
- **参数**
|
||||
|
||||
- `*args`
|
||||
|
||||
- `**kwargs`
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `format_map(mapping)` {#MessageTemplate-format_map}
|
||||
|
||||
- **说明:** 根据传入字典和模板生成消息对象, 在传入字段名不是有效标识符时有用
|
||||
|
||||
- **参数**
|
||||
|
||||
- `mapping` (Mapping[str, Any])
|
||||
|
||||
- **返回**
|
||||
|
||||
- TF
|
||||
|
||||
### _method_ `vformat(format_string, args, kwargs)` {#MessageTemplate-vformat}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `format_string` (str)
|
||||
|
||||
- `args` (Sequence[Any])
|
||||
|
||||
- `kwargs` (Mapping[str, Any])
|
||||
|
||||
- **返回**
|
||||
|
||||
- TF
|
||||
|
||||
### _method_ `format_field(value, format_spec)` {#MessageTemplate-format_field}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (Any)
|
||||
|
||||
- `format_spec` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- Any
|
156
website/versioned_docs/version-2.0.0/api/config.md
Normal file
156
website/versioned_docs/version-2.0.0/api/config.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: nonebot.config 模块
|
||||
---
|
||||
|
||||
# nonebot.config
|
||||
|
||||
本模块定义了 NoneBot 本身运行所需的配置项。
|
||||
|
||||
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
|
||||
|
||||
配置项需符合特殊格式或 json 序列化格式。详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
||||
|
||||
## _class_ `Env(<auto>)` {#Env}
|
||||
|
||||
- **说明**
|
||||
|
||||
运行环境配置。大小写不敏感。
|
||||
|
||||
将会从 `环境变量` > `.env 环境配置文件` 的优先级读取环境信息。
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _class-var_ `environment` {#Env-environment}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明**
|
||||
|
||||
当前环境名。
|
||||
|
||||
NoneBot 将从 `.env.{environment}` 文件中加载配置。
|
||||
|
||||
## _class_ `Config(<auto>)` {#Config}
|
||||
|
||||
- **说明**
|
||||
|
||||
NoneBot 主要配置。大小写不敏感。
|
||||
|
||||
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
|
||||
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
|
||||
|
||||
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _class-var_ `driver` {#Config-driver}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明**
|
||||
|
||||
NoneBot 运行所使用的 `Driver` 。继承自 [Driver](drivers/index.md#Driver) 。
|
||||
|
||||
配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`。
|
||||
|
||||
`~` 为 `nonebot.drivers.` 的缩写。
|
||||
|
||||
### _class-var_ `host` {#Config-host}
|
||||
|
||||
- **类型:** IPvAnyAddress
|
||||
|
||||
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的 IP/主机名。
|
||||
|
||||
### _class-var_ `port` {#Config-port}
|
||||
|
||||
- **类型:** int
|
||||
|
||||
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的端口。
|
||||
|
||||
### _class-var_ `log_level` {#Config-log_level}
|
||||
|
||||
- **类型:** int | str
|
||||
|
||||
- **说明**
|
||||
|
||||
NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称
|
||||
|
||||
参考 [`loguru 日志等级`](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
||||
|
||||
:::tip 提示
|
||||
日志等级名称应为大写,如 `INFO`。
|
||||
:::
|
||||
|
||||
- **用法**
|
||||
|
||||
```conf
|
||||
LOG_LEVEL=25
|
||||
LOG_LEVEL=INFO
|
||||
```
|
||||
|
||||
### _class-var_ `api_timeout` {#Config-api_timeout}
|
||||
|
||||
- **类型:** float | None
|
||||
|
||||
- **说明:** API 请求超时时间,单位: 秒。
|
||||
|
||||
### _class-var_ `superusers` {#Config-superusers}
|
||||
|
||||
- **类型:** set[str]
|
||||
|
||||
- **说明:** 机器人超级用户。
|
||||
|
||||
- **用法**
|
||||
|
||||
```conf
|
||||
SUPERUSERS=["12345789"]
|
||||
```
|
||||
|
||||
### _class-var_ `nickname` {#Config-nickname}
|
||||
|
||||
- **类型:** set[str]
|
||||
|
||||
- **说明:** 机器人昵称。
|
||||
|
||||
### _class-var_ `command_start` {#Config-command_start}
|
||||
|
||||
- **类型:** set[str]
|
||||
|
||||
- **说明:** 命令的起始标记,用于判断一条消息是不是命令。
|
||||
|
||||
- **用法**
|
||||
|
||||
```conf
|
||||
COMMAND_START=["/", ""]
|
||||
```
|
||||
|
||||
### _class-var_ `command_sep` {#Config-command_sep}
|
||||
|
||||
- **类型:** set[str]
|
||||
|
||||
- **说明:** 命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
||||
|
||||
- **用法**
|
||||
|
||||
```conf
|
||||
COMMAND_SEP=["."]
|
||||
```
|
||||
|
||||
### _class-var_ `session_expire_timeout` {#Config-session_expire_timeout}
|
||||
|
||||
- **类型:** timedelta
|
||||
|
||||
- **说明:** 等待用户回复的超时时间。
|
||||
|
||||
- **用法**
|
||||
|
||||
```conf
|
||||
SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒
|
||||
SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff]
|
||||
SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
|
||||
```
|
116
website/versioned_docs/version-2.0.0/api/consts.md
Normal file
116
website/versioned_docs/version-2.0.0/api/consts.md
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
sidebar_position: 9
|
||||
description: nonebot.consts 模块
|
||||
---
|
||||
|
||||
# nonebot.consts
|
||||
|
||||
本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
||||
|
||||
## _var_ `RECEIVE_KEY` {#RECEIVE_KEY}
|
||||
|
||||
- **类型:** Literal['_receive_{id}']
|
||||
|
||||
- **说明:** `receive` 存储 key
|
||||
|
||||
## _var_ `LAST_RECEIVE_KEY` {#LAST_RECEIVE_KEY}
|
||||
|
||||
- **类型:** Literal['_last_receive']
|
||||
|
||||
- **说明:** `last_receive` 存储 key
|
||||
|
||||
## _var_ `ARG_KEY` {#ARG_KEY}
|
||||
|
||||
- **类型:** Literal['{key}']
|
||||
|
||||
- **说明:** `arg` 存储 key
|
||||
|
||||
## _var_ `REJECT_TARGET` {#REJECT_TARGET}
|
||||
|
||||
- **类型:** Literal['_current_target']
|
||||
|
||||
- **说明:** 当前 `reject` 目标存储 key
|
||||
|
||||
## _var_ `REJECT_CACHE_TARGET` {#REJECT_CACHE_TARGET}
|
||||
|
||||
- **类型:** Literal['_next_target']
|
||||
|
||||
- **说明:** 下一个 `reject` 目标存储 key
|
||||
|
||||
## _var_ `PREFIX_KEY` {#PREFIX_KEY}
|
||||
|
||||
- **类型:** Literal['_prefix']
|
||||
|
||||
- **说明:** 命令前缀存储 key
|
||||
|
||||
## _var_ `CMD_KEY` {#CMD_KEY}
|
||||
|
||||
- **类型:** Literal['command']
|
||||
|
||||
- **说明:** 命令元组存储 key
|
||||
|
||||
## _var_ `RAW_CMD_KEY` {#RAW_CMD_KEY}
|
||||
|
||||
- **类型:** Literal['raw_command']
|
||||
|
||||
- **说明:** 命令文本存储 key
|
||||
|
||||
## _var_ `CMD_ARG_KEY` {#CMD_ARG_KEY}
|
||||
|
||||
- **类型:** Literal['command_arg']
|
||||
|
||||
- **说明:** 命令参数存储 key
|
||||
|
||||
## _var_ `CMD_START_KEY` {#CMD_START_KEY}
|
||||
|
||||
- **类型:** Literal['command_start']
|
||||
|
||||
- **说明:** 命令开头存储 key
|
||||
|
||||
## _var_ `CMD_WHITESPACE_KEY` {#CMD_WHITESPACE_KEY}
|
||||
|
||||
- **类型:** Literal['command_whitespace']
|
||||
|
||||
- **说明:** 命令与参数间空白符存储 key
|
||||
|
||||
## _var_ `SHELL_ARGS` {#SHELL_ARGS}
|
||||
|
||||
- **类型:** Literal['_args']
|
||||
|
||||
- **说明:** shell 命令 parse 后参数字典存储 key
|
||||
|
||||
## _var_ `SHELL_ARGV` {#SHELL_ARGV}
|
||||
|
||||
- **类型:** Literal['_argv']
|
||||
|
||||
- **说明:** shell 命令原始参数列表存储 key
|
||||
|
||||
## _var_ `REGEX_MATCHED` {#REGEX_MATCHED}
|
||||
|
||||
- **类型:** Literal['_matched']
|
||||
|
||||
- **说明:** 正则匹配结果存储 key
|
||||
|
||||
## _var_ `STARTSWITH_KEY` {#STARTSWITH_KEY}
|
||||
|
||||
- **类型:** Literal['_startswith']
|
||||
|
||||
- **说明:** 响应触发前缀 key
|
||||
|
||||
## _var_ `ENDSWITH_KEY` {#ENDSWITH_KEY}
|
||||
|
||||
- **类型:** Literal['_endswith']
|
||||
|
||||
- **说明:** 响应触发后缀 key
|
||||
|
||||
## _var_ `FULLMATCH_KEY` {#FULLMATCH_KEY}
|
||||
|
||||
- **类型:** Literal['_fullmatch']
|
||||
|
||||
- **说明:** 响应触发完整消息 key
|
||||
|
||||
## _var_ `KEYWORD_KEY` {#KEYWORD_KEY}
|
||||
|
||||
- **类型:** Literal['_keyword']
|
||||
|
||||
- **说明:** 响应触发关键字 key
|
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"position": 13
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: nonebot.dependencies 模块
|
||||
---
|
||||
|
||||
# nonebot.dependencies
|
||||
|
||||
本模块模块实现了依赖注入的定义与处理。
|
||||
|
||||
## _abstract class_ `Param(<auto>)` {#Param}
|
||||
|
||||
- **说明**
|
||||
|
||||
依赖注入的基本单元 —— 参数。
|
||||
|
||||
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
## _class_ `Dependent(<auto>)` {#Dependent}
|
||||
|
||||
- **说明:** 依赖注入容器
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call`: 依赖注入的可调用对象,可以是任何 Callable 对象
|
||||
|
||||
- `pre_checkers`: 依赖注入解析前的参数检查
|
||||
|
||||
- `params`: 具名参数列表
|
||||
|
||||
- `parameterless`: 匿名参数列表
|
||||
|
||||
- `allow_types`: 允许的参数类型
|
||||
|
||||
### _staticmethod_ `parse_params(call, allow_types)` {#Dependent-parse_params}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call` (\_DependentCallable[R])
|
||||
|
||||
- `allow_types` (tuple[type[Param], ...])
|
||||
|
||||
- **返回**
|
||||
|
||||
- tuple[ModelField]
|
||||
|
||||
### _staticmethod_ `parse_parameterless(parameterless, allow_types)` {#Dependent-parse_parameterless}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `parameterless` (tuple[Any, ...])
|
||||
|
||||
- `allow_types` (tuple[type[Param], ...])
|
||||
|
||||
- **返回**
|
||||
|
||||
- tuple[Param, ...]
|
||||
|
||||
### _classmethod_ `parse(*, call, parameterless=None, allow_types)` {#Dependent-parse}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call` (\_DependentCallable[R])
|
||||
|
||||
- `parameterless` (Iterable[Any] | None)
|
||||
|
||||
- `allow_types` (Iterable[type[Param]])
|
||||
|
||||
- **返回**
|
||||
|
||||
- Dependent[R]
|
||||
|
||||
### _async method_ `check(**params)` {#Dependent-check}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `**params` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _async method_ `solve(**params)` {#Dependent-solve}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `**params` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- dict[str, Any]
|
@@ -0,0 +1,44 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: nonebot.dependencies.utils 模块
|
||||
---
|
||||
|
||||
# nonebot.dependencies.utils
|
||||
|
||||
## _def_ `get_typed_signature(call)` {#get_typed_signature}
|
||||
|
||||
- **说明:** 获取可调用对象签名
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call` ((...) -> Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- inspect.Signature
|
||||
|
||||
## _def_ `get_typed_annotation(param, globalns)` {#get_typed_annotation}
|
||||
|
||||
- **说明:** 获取参数的类型注解
|
||||
|
||||
- **参数**
|
||||
|
||||
- `param` (inspect.Parameter)
|
||||
|
||||
- `globalns` (dict[str, Any])
|
||||
|
||||
- **返回**
|
||||
|
||||
- Any
|
||||
|
||||
## _def_ `check_field_type(field, value)` {#check_field_type}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `field` (ModelField)
|
||||
|
||||
- `value` (V)
|
||||
|
||||
- **返回**
|
||||
|
||||
- V
|
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"position": 14
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
# nonebot.drivers.\_lifespan
|
||||
|
||||
## _class_ `Lifespan()` {#Lifespan}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
### _method_ `on_startup(func)` {#Lifespan-on_startup}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (LIFESPAN_FUNC)
|
||||
|
||||
- **返回**
|
||||
|
||||
- LIFESPAN_FUNC
|
||||
|
||||
### _method_ `on_shutdown(func)` {#Lifespan-on_shutdown}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (LIFESPAN_FUNC)
|
||||
|
||||
- **返回**
|
||||
|
||||
- LIFESPAN_FUNC
|
||||
|
||||
### _async method_ `startup()` {#Lifespan-startup}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _async method_ `shutdown()` {#Lifespan-shutdown}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
134
website/versioned_docs/version-2.0.0/api/drivers/aiohttp.md
Normal file
134
website/versioned_docs/version-2.0.0/api/drivers/aiohttp.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: nonebot.drivers.aiohttp 模块
|
||||
---
|
||||
|
||||
# nonebot.drivers.aiohttp
|
||||
|
||||
[AIOHTTP](https://aiohttp.readthedocs.io/en/stable/) 驱动适配器。
|
||||
|
||||
```bash
|
||||
nb driver install aiohttp
|
||||
# 或者
|
||||
pip install nonebot2[aiohttp]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持客户端连接
|
||||
:::
|
||||
|
||||
## _class_ `Mixin(<auto>)` {#Mixin}
|
||||
|
||||
- **说明:** AIOHTTP Mixin
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _async method_ `request(setup)` {#Mixin-request}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([Request](index.md#Request))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [Response](index.md#Response)
|
||||
|
||||
### _method_ `websocket(setup)` {#Mixin-websocket}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([Request](index.md#Request))
|
||||
|
||||
- **返回**
|
||||
|
||||
- AsyncGenerator[[WebSocket](index.md#WebSocket), None]
|
||||
|
||||
## _class_ `WebSocket(*, request, session, websocket)` {#WebSocket}
|
||||
|
||||
- **说明:** AIOHTTP Websocket Wrapper
|
||||
|
||||
- **参数**
|
||||
|
||||
- `request` ([Request](index.md#Request))
|
||||
|
||||
- `session` (aiohttp.ClientSession)
|
||||
|
||||
- `websocket` (aiohttp.ClientWebSocketResponse)
|
||||
|
||||
### _async method_ `accept()` {#WebSocket-accept}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _async method_ `close(code=1000)` {#WebSocket-close}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `code` (int)
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _async method_ `receive()` {#WebSocket-receive}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _async method_ `receive_text()` {#WebSocket-receive_text}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _async method_ `receive_bytes()` {#WebSocket-receive_bytes}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- bytes
|
||||
|
||||
### _async method_ `send_text(data)` {#WebSocket-send_text}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _async method_ `send_bytes(data)` {#WebSocket-send_bytes}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (bytes)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
## _var_ `Driver` {#Driver}
|
||||
|
||||
- **类型:** type[[ForwardDriver](index.md#ForwardDriver)]
|
||||
|
||||
- **说明:** AIOHTTP Driver
|
260
website/versioned_docs/version-2.0.0/api/drivers/fastapi.md
Normal file
260
website/versioned_docs/version-2.0.0/api/drivers/fastapi.md
Normal file
@@ -0,0 +1,260 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: nonebot.drivers.fastapi 模块
|
||||
---
|
||||
|
||||
# nonebot.drivers.fastapi
|
||||
|
||||
[FastAPI](https://fastapi.tiangolo.com/) 驱动适配
|
||||
|
||||
```bash
|
||||
nb driver install fastapi
|
||||
# 或者
|
||||
pip install nonebot2[fastapi]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持服务端连接
|
||||
:::
|
||||
|
||||
## _class_ `Config(<auto>)` {#Config}
|
||||
|
||||
- **说明:** FastAPI 驱动框架设置,详情参考 FastAPI 文档
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _class-var_ `fastapi_openapi_url` {#Config-fastapi_openapi_url}
|
||||
|
||||
- **类型:** str | None
|
||||
|
||||
- **说明:** `openapi.json` 地址,默认为 `None` 即关闭
|
||||
|
||||
### _class-var_ `fastapi_docs_url` {#Config-fastapi_docs_url}
|
||||
|
||||
- **类型:** str | None
|
||||
|
||||
- **说明:** `swagger` 地址,默认为 `None` 即关闭
|
||||
|
||||
### _class-var_ `fastapi_redoc_url` {#Config-fastapi_redoc_url}
|
||||
|
||||
- **类型:** str | None
|
||||
|
||||
- **说明:** `redoc` 地址,默认为 `None` 即关闭
|
||||
|
||||
### _class-var_ `fastapi_include_adapter_schema` {#Config-fastapi_include_adapter_schema}
|
||||
|
||||
- **类型:** bool
|
||||
|
||||
- **说明:** 是否包含适配器路由的 schema,默认为 `True`
|
||||
|
||||
### _class-var_ `fastapi_reload` {#Config-fastapi_reload}
|
||||
|
||||
- **类型:** bool
|
||||
|
||||
- **说明:** 开启/关闭冷重载
|
||||
|
||||
### _class-var_ `fastapi_reload_dirs` {#Config-fastapi_reload_dirs}
|
||||
|
||||
- **类型:** list[str] | None
|
||||
|
||||
- **说明:** 重载监控文件夹列表,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `fastapi_reload_delay` {#Config-fastapi_reload_delay}
|
||||
|
||||
- **类型:** float
|
||||
|
||||
- **说明:** 重载延迟,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `fastapi_reload_includes` {#Config-fastapi_reload_includes}
|
||||
|
||||
- **类型:** list[str] | None
|
||||
|
||||
- **说明:** 要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `fastapi_reload_excludes` {#Config-fastapi_reload_excludes}
|
||||
|
||||
- **类型:** list[str] | None
|
||||
|
||||
- **说明:** 不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `fastapi_extra` {#Config-fastapi_extra}
|
||||
|
||||
- **类型:** dict[str, Any]
|
||||
|
||||
- **说明:** 传递给 `FastAPI` 的其他参数。
|
||||
|
||||
## _class_ `Driver(env, config)` {#Driver}
|
||||
|
||||
- **说明:** FastAPI 驱动框架。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `env` ([Env](../config.md#Env))
|
||||
|
||||
- `config` (NoneBotConfig)
|
||||
|
||||
### _property_ `type` {#Driver-type}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 驱动名称: `fastapi`
|
||||
|
||||
### _property_ `server_app` {#Driver-server_app}
|
||||
|
||||
- **类型:** FastAPI
|
||||
|
||||
- **说明:** `FastAPI APP` 对象
|
||||
|
||||
### _property_ `asgi` {#Driver-asgi}
|
||||
|
||||
- **类型:** FastAPI
|
||||
|
||||
- **说明:** `FastAPI APP` 对象
|
||||
|
||||
### _property_ `logger` {#Driver-logger}
|
||||
|
||||
- **类型:** logging.Logger
|
||||
|
||||
- **说明:** fastapi 使用的 logger
|
||||
|
||||
### _method_ `setup_http_server(setup)` {#Driver-setup_http_server}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([HTTPServerSetup](index.md#HTTPServerSetup))
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `setup_websocket_server(setup)` {#Driver-setup_websocket_server}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([WebSocketServerSetup](index.md#WebSocketServerSetup))
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `on_startup(func)` {#Driver-on_startup}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (LIFESPAN_FUNC)
|
||||
|
||||
- **返回**
|
||||
|
||||
- LIFESPAN_FUNC
|
||||
|
||||
### _method_ `on_shutdown(func)` {#Driver-on_shutdown}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (LIFESPAN_FUNC)
|
||||
|
||||
- **返回**
|
||||
|
||||
- LIFESPAN_FUNC
|
||||
|
||||
### _method_ `run(host=None, port=None, *, app=None, **kwargs)` {#Driver-run}
|
||||
|
||||
- **说明:** 使用 `uvicorn` 启动 FastAPI
|
||||
|
||||
- **参数**
|
||||
|
||||
- `host` (str | None)
|
||||
|
||||
- `port` (int | None)
|
||||
|
||||
- `app` (str | None)
|
||||
|
||||
- `**kwargs`
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
## _class_ `FastAPIWebSocket(*, request, websocket)` {#FastAPIWebSocket}
|
||||
|
||||
- **说明:** FastAPI WebSocket Wrapper
|
||||
|
||||
- **参数**
|
||||
|
||||
- `request` (BaseRequest)
|
||||
|
||||
- `websocket` ([WebSocket](index.md#WebSocket))
|
||||
|
||||
### _async method_ `accept()` {#FastAPIWebSocket-accept}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _async method_ `close(code=status.WS_1000_NORMAL_CLOSURE, reason="")` {#FastAPIWebSocket-close}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `code` (int)
|
||||
|
||||
- `reason` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _async method_ `receive()` {#FastAPIWebSocket-receive}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str | bytes
|
||||
|
||||
### _async method_ `receive_text()` {#FastAPIWebSocket-receive_text}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _async method_ `receive_bytes()` {#FastAPIWebSocket-receive_bytes}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- bytes
|
||||
|
||||
### _async method_ `send_text(data)` {#FastAPIWebSocket-send_text}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _async method_ `send_bytes(data)` {#FastAPIWebSocket-send_bytes}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (bytes)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
52
website/versioned_docs/version-2.0.0/api/drivers/httpx.md
Normal file
52
website/versioned_docs/version-2.0.0/api/drivers/httpx.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
description: nonebot.drivers.httpx 模块
|
||||
---
|
||||
|
||||
# nonebot.drivers.httpx
|
||||
|
||||
[HTTPX](https://www.python-httpx.org/) 驱动适配
|
||||
|
||||
```bash
|
||||
nb driver install httpx
|
||||
# 或者
|
||||
pip install nonebot2[httpx]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持客户端 HTTP 连接
|
||||
:::
|
||||
|
||||
## _class_ `Mixin(<auto>)` {#Mixin}
|
||||
|
||||
- **说明:** HTTPX Mixin
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _async method_ `request(setup)` {#Mixin-request}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([Request](index.md#Request))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [Response](index.md#Response)
|
||||
|
||||
### _method_ `websocket(setup)` {#Mixin-websocket}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([Request](index.md#Request))
|
||||
|
||||
- **返回**
|
||||
|
||||
- AsyncGenerator[[WebSocket](index.md#WebSocket), None]
|
||||
|
||||
## _var_ `Driver` {#Driver}
|
||||
|
||||
- **类型:** type[[ForwardDriver](index.md#ForwardDriver)]
|
||||
|
||||
- **说明:** HTTPX Driver
|
510
website/versioned_docs/version-2.0.0/api/drivers/index.md
Normal file
510
website/versioned_docs/version-2.0.0/api/drivers/index.md
Normal file
@@ -0,0 +1,510 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: nonebot.drivers 模块
|
||||
---
|
||||
|
||||
# nonebot.drivers
|
||||
|
||||
本模块定义了驱动适配器基类。
|
||||
|
||||
各驱动请继承以下基类。
|
||||
|
||||
## _abstract class_ `Driver(env, config)` {#Driver}
|
||||
|
||||
- **说明:** Driver 基类。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `env` ([Env](../config.md#Env)): 包含环境信息的 Env 对象
|
||||
|
||||
- `config` ([Config](../config.md#Config)): 包含配置信息的 Config 对象
|
||||
|
||||
### _instance-var_ `env` {#Driver-env}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 环境名称
|
||||
|
||||
### _instance-var_ `config` {#Driver-config}
|
||||
|
||||
- **类型:** [Config](../config.md#Config)
|
||||
|
||||
- **说明:** 全局配置对象
|
||||
|
||||
### _property_ `bots` {#Driver-bots}
|
||||
|
||||
- **类型:** dict[str, [Bot](../adapters/index.md#Bot)]
|
||||
|
||||
- **说明:** 获取当前所有已连接的 Bot
|
||||
|
||||
### _method_ `register_adapter(adapter, **kwargs)` {#Driver-register_adapter}
|
||||
|
||||
- **说明:** 注册一个协议适配器
|
||||
|
||||
- **参数**
|
||||
|
||||
- `adapter` (type[[Adapter](../adapters/index.md#Adapter)]): 适配器类
|
||||
|
||||
- `**kwargs`: 其他传递给适配器的参数
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _abstract property_ `type` {#Driver-type}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 驱动类型名称
|
||||
|
||||
### _abstract property_ `logger` {#Driver-logger}
|
||||
|
||||
- **类型:**
|
||||
|
||||
- **说明:** 驱动专属 logger 日志记录器
|
||||
|
||||
### _abstract method_ `run(*args, **kwargs)` {#Driver-run}
|
||||
|
||||
- **说明:** 启动驱动框架
|
||||
|
||||
- **参数**
|
||||
|
||||
- `*args`
|
||||
|
||||
- `**kwargs`
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _abstract method_ `on_startup(func)` {#Driver-on_startup}
|
||||
|
||||
- **说明:** 注册一个在驱动器启动时执行的函数
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (Callable)
|
||||
|
||||
- **返回**
|
||||
|
||||
- Callable
|
||||
|
||||
### _abstract method_ `on_shutdown(func)` {#Driver-on_shutdown}
|
||||
|
||||
- **说明:** 注册一个在驱动器停止时执行的函数
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (Callable)
|
||||
|
||||
- **返回**
|
||||
|
||||
- Callable
|
||||
|
||||
### _classmethod_ `on_bot_connect(func)` {#Driver-on_bot_connect}
|
||||
|
||||
- **说明**
|
||||
|
||||
装饰一个函数使他在 bot 连接成功时执行。
|
||||
|
||||
钩子函数参数:
|
||||
|
||||
- bot: 当前连接上的 Bot 对象
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_BotConnectionHook](../typing.md#T_BotConnectionHook))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_BotConnectionHook](../typing.md#T_BotConnectionHook)
|
||||
|
||||
### _classmethod_ `on_bot_disconnect(func)` {#Driver-on_bot_disconnect}
|
||||
|
||||
- **说明**
|
||||
|
||||
装饰一个函数使他在 bot 连接断开时执行。
|
||||
|
||||
钩子函数参数:
|
||||
|
||||
- bot: 当前连接上的 Bot 对象
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_BotDisconnectionHook](../typing.md#T_BotDisconnectionHook))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_BotDisconnectionHook](../typing.md#T_BotDisconnectionHook)
|
||||
|
||||
## _class_ `Cookies(cookies=None)` {#Cookies}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `cookies` (CookieTypes)
|
||||
|
||||
### _method_ `set(name, value, domain="", path="/")` {#Cookies-set}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `name` (str)
|
||||
|
||||
- `value` (str)
|
||||
|
||||
- `domain` (str)
|
||||
|
||||
- `path` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `get(name, default=None, domain=None, path=None)` {#Cookies-get}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `name` (str)
|
||||
|
||||
- `default` (str | None)
|
||||
|
||||
- `domain` (str | None)
|
||||
|
||||
- `path` (str | None)
|
||||
|
||||
- **返回**
|
||||
|
||||
- str | None
|
||||
|
||||
### _method_ `delete(name, domain=None, path=None)` {#Cookies-delete}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `name` (str)
|
||||
|
||||
- `domain` (str | None)
|
||||
|
||||
- `path` (str | None)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `clear(domain=None, path=None)` {#Cookies-clear}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `domain` (str | None)
|
||||
|
||||
- `path` (str | None)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `update(cookies=None)` {#Cookies-update}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `cookies` (CookieTypes)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `as_header(request)` {#Cookies-as_header}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `request` (Request)
|
||||
|
||||
- **返回**
|
||||
|
||||
- dict[str, str]
|
||||
|
||||
## _class_ `Request(method, url, *, params=None, headers=None, cookies=None, content=None, data=None, json=None, files=None, version=HTTPVersion.H11, timeout=None, proxy=None)` {#Request}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `method` (str | bytes)
|
||||
|
||||
- `url` (URL | str | RawURL)
|
||||
|
||||
- `params` (QueryTypes)
|
||||
|
||||
- `headers` (HeaderTypes)
|
||||
|
||||
- `cookies` (CookieTypes)
|
||||
|
||||
- `content` (ContentTypes)
|
||||
|
||||
- `data` (DataTypes)
|
||||
|
||||
- `json` (Any)
|
||||
|
||||
- `files` (FilesTypes)
|
||||
|
||||
- `version` (str | HTTPVersion)
|
||||
|
||||
- `timeout` (float | None)
|
||||
|
||||
- `proxy` (str | None)
|
||||
|
||||
## _class_ `Response(status_code, *, headers=None, content=None, request=None)` {#Response}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `status_code` (int)
|
||||
|
||||
- `headers` (HeaderTypes)
|
||||
|
||||
- `content` (ContentTypes)
|
||||
|
||||
- `request` (Request | None)
|
||||
|
||||
## _abstract class_ `WebSocket(*, request)` {#WebSocket}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `request` (Request)
|
||||
|
||||
### _abstract property_ `closed` {#WebSocket-closed}
|
||||
|
||||
- **类型:** bool
|
||||
|
||||
- **说明:** 连接是否已经关闭
|
||||
|
||||
### _abstract async method_ `accept()` {#WebSocket-accept}
|
||||
|
||||
- **说明:** 接受 WebSocket 连接请求
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _abstract async method_ `close(code=1000, reason="")` {#WebSocket-close}
|
||||
|
||||
- **说明:** 关闭 WebSocket 连接请求
|
||||
|
||||
- **参数**
|
||||
|
||||
- `code` (int)
|
||||
|
||||
- `reason` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _abstract async method_ `receive()` {#WebSocket-receive}
|
||||
|
||||
- **说明:** 接收一条 WebSocket text/bytes 信息
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str | bytes
|
||||
|
||||
### _abstract async method_ `receive_text()` {#WebSocket-receive_text}
|
||||
|
||||
- **说明:** 接收一条 WebSocket text 信息
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _abstract async method_ `receive_bytes()` {#WebSocket-receive_bytes}
|
||||
|
||||
- **说明:** 接收一条 WebSocket binary 信息
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- bytes
|
||||
|
||||
### _async method_ `send(data)` {#WebSocket-send}
|
||||
|
||||
- **说明:** 发送一条 WebSocket text/bytes 信息
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (str | bytes)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _abstract async method_ `send_text(data)` {#WebSocket-send_text}
|
||||
|
||||
- **说明:** 发送一条 WebSocket text 信息
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _abstract async method_ `send_bytes(data)` {#WebSocket-send_bytes}
|
||||
|
||||
- **说明:** 发送一条 WebSocket binary 信息
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (bytes)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
## _enum_ `HTTPVersion` {#HTTPVersion}
|
||||
|
||||
- **说明:** An enumeration.
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
- `H10: '1.0'`
|
||||
|
||||
- `H11: '1.1'`
|
||||
|
||||
- `H2: '2'`
|
||||
|
||||
## _abstract class_ `ForwardMixin(<auto>)` {#ForwardMixin}
|
||||
|
||||
- **说明:** 客户端混入基类。
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _abstract property_ `type` {#ForwardMixin-type}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 客户端驱动类型名称
|
||||
|
||||
### _abstract async method_ `request(setup)` {#ForwardMixin-request}
|
||||
|
||||
- **说明:** 发送一个 HTTP 请求
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([Request](#Request))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [Response](#Response)
|
||||
|
||||
### _abstract method_ `websocket(setup)` {#ForwardMixin-websocket}
|
||||
|
||||
- **说明:** 发起一个 WebSocket 连接
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([Request](#Request))
|
||||
|
||||
- **返回**
|
||||
|
||||
- AsyncGenerator[[WebSocket](#WebSocket), None]
|
||||
|
||||
## _abstract class_ `ForwardDriver(env, config)` {#ForwardDriver}
|
||||
|
||||
- **说明:** 客户端基类。将客户端框架封装,以满足适配器使用。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `env` ([Env](../config.md#Env))
|
||||
|
||||
- `config` ([Config](../config.md#Config))
|
||||
|
||||
## _abstract class_ `ReverseDriver(env, config)` {#ReverseDriver}
|
||||
|
||||
- **说明:** 服务端基类。将后端框架封装,以满足适配器使用。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `env` ([Env](../config.md#Env))
|
||||
|
||||
- `config` ([Config](../config.md#Config))
|
||||
|
||||
### _abstract property_ `server_app` {#ReverseDriver-server_app}
|
||||
|
||||
- **类型:** Any
|
||||
|
||||
- **说明:** 驱动 APP 对象
|
||||
|
||||
### _abstract property_ `asgi` {#ReverseDriver-asgi}
|
||||
|
||||
- **类型:** Any
|
||||
|
||||
- **说明:** 驱动 ASGI 对象
|
||||
|
||||
### _abstract method_ `setup_http_server(setup)` {#ReverseDriver-setup_http_server}
|
||||
|
||||
- **说明:** 设置一个 HTTP 服务器路由配置
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([HTTPServerSetup](#HTTPServerSetup))
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _abstract method_ `setup_websocket_server(setup)` {#ReverseDriver-setup_websocket_server}
|
||||
|
||||
- **说明:** 设置一个 WebSocket 服务器路由配置
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([WebSocketServerSetup](#WebSocketServerSetup))
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
## _def_ `combine_driver(driver, *mixins)` {#combine_driver}
|
||||
|
||||
- **说明:** 将一个驱动器和多个混入类合并。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `driver` (type[Driver])
|
||||
|
||||
- `*mixins` (type[ForwardMixin])
|
||||
|
||||
- **返回**
|
||||
|
||||
- type[Driver]
|
||||
|
||||
## _class_ `HTTPServerSetup(<auto>)` {#HTTPServerSetup}
|
||||
|
||||
- **说明:** HTTP 服务器路由配置。
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
## _class_ `WebSocketServerSetup(<auto>)` {#WebSocketServerSetup}
|
||||
|
||||
- **说明:** WebSocket 服务器路由配置。
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
84
website/versioned_docs/version-2.0.0/api/drivers/none.md
Normal file
84
website/versioned_docs/version-2.0.0/api/drivers/none.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
description: nonebot.drivers.none 模块
|
||||
---
|
||||
|
||||
# nonebot.drivers.none
|
||||
|
||||
None 驱动适配
|
||||
|
||||
:::tip 提示
|
||||
本驱动不支持任何服务器或客户端连接
|
||||
:::
|
||||
|
||||
## _class_ `Driver(env, config)` {#Driver}
|
||||
|
||||
- **说明:** None 驱动框架
|
||||
|
||||
- **参数**
|
||||
|
||||
- `env` ([Env](../config.md#Env))
|
||||
|
||||
- `config` ([Config](../config.md#Config))
|
||||
|
||||
### _property_ `type` {#Driver-type}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 驱动名称: `none`
|
||||
|
||||
### _property_ `logger` {#Driver-logger}
|
||||
|
||||
- **类型:**
|
||||
|
||||
- **说明:** none driver 使用的 logger
|
||||
|
||||
### _method_ `on_startup(func)` {#Driver-on_startup}
|
||||
|
||||
- **说明:** 注册一个启动时执行的函数
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (LIFESPAN_FUNC)
|
||||
|
||||
- **返回**
|
||||
|
||||
- LIFESPAN_FUNC
|
||||
|
||||
### _method_ `on_shutdown(func)` {#Driver-on_shutdown}
|
||||
|
||||
- **说明:** 注册一个停止时执行的函数
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (LIFESPAN_FUNC)
|
||||
|
||||
- **返回**
|
||||
|
||||
- LIFESPAN_FUNC
|
||||
|
||||
### _method_ `run(*args, **kwargs)` {#Driver-run}
|
||||
|
||||
- **说明:** 启动 none driver
|
||||
|
||||
- **参数**
|
||||
|
||||
- `*args`
|
||||
|
||||
- `**kwargs`
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `exit(force=False)` {#Driver-exit}
|
||||
|
||||
- **说明:** 退出 none driver
|
||||
|
||||
- **参数**
|
||||
|
||||
- `force` (bool): 强制退出
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
240
website/versioned_docs/version-2.0.0/api/drivers/quart.md
Normal file
240
website/versioned_docs/version-2.0.0/api/drivers/quart.md
Normal file
@@ -0,0 +1,240 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
description: nonebot.drivers.quart 模块
|
||||
---
|
||||
|
||||
# nonebot.drivers.quart
|
||||
|
||||
[Quart](https://pgjones.gitlab.io/quart/index.html) 驱动适配
|
||||
|
||||
```bash
|
||||
nb driver install quart
|
||||
# 或者
|
||||
pip install nonebot2[quart]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持服务端连接
|
||||
:::
|
||||
|
||||
## _class_ `Config(<auto>)` {#Config}
|
||||
|
||||
- **说明:** Quart 驱动框架设置
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
|
||||
### _class-var_ `quart_reload` {#Config-quart_reload}
|
||||
|
||||
- **类型:** bool
|
||||
|
||||
- **说明:** 开启/关闭冷重载
|
||||
|
||||
### _class-var_ `quart_reload_dirs` {#Config-quart_reload_dirs}
|
||||
|
||||
- **类型:** list[str] | None
|
||||
|
||||
- **说明:** 重载监控文件夹列表,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `quart_reload_delay` {#Config-quart_reload_delay}
|
||||
|
||||
- **类型:** float
|
||||
|
||||
- **说明:** 重载延迟,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `quart_reload_includes` {#Config-quart_reload_includes}
|
||||
|
||||
- **类型:** list[str] | None
|
||||
|
||||
- **说明:** 要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `quart_reload_excludes` {#Config-quart_reload_excludes}
|
||||
|
||||
- **类型:** list[str] | None
|
||||
|
||||
- **说明:** 不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `quart_extra` {#Config-quart_extra}
|
||||
|
||||
- **类型:** dict[str, Any]
|
||||
|
||||
- **说明:** 传递给 `Quart` 的其他参数。
|
||||
|
||||
## _class_ `Driver(env, config)` {#Driver}
|
||||
|
||||
- **说明:** Quart 驱动框架
|
||||
|
||||
- **参数**
|
||||
|
||||
- `env` ([Env](../config.md#Env))
|
||||
|
||||
- `config` (NoneBotConfig)
|
||||
|
||||
### _property_ `type` {#Driver-type}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
- **说明:** 驱动名称: `quart`
|
||||
|
||||
### _property_ `server_app` {#Driver-server_app}
|
||||
|
||||
- **类型:** Quart
|
||||
|
||||
- **说明:** `Quart` 对象
|
||||
|
||||
### _property_ `asgi` {#Driver-asgi}
|
||||
|
||||
- **类型:**
|
||||
|
||||
- **说明:** `Quart` 对象
|
||||
|
||||
### _property_ `logger` {#Driver-logger}
|
||||
|
||||
- **类型:**
|
||||
|
||||
- **说明:** Quart 使用的 logger
|
||||
|
||||
### _method_ `setup_http_server(setup)` {#Driver-setup_http_server}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([HTTPServerSetup](index.md#HTTPServerSetup))
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `setup_websocket_server(setup)` {#Driver-setup_websocket_server}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `setup` ([WebSocketServerSetup](index.md#WebSocketServerSetup))
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `on_startup(func)` {#Driver-on_startup}
|
||||
|
||||
- **说明:** 参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (\_AsyncCallable)
|
||||
|
||||
- **返回**
|
||||
|
||||
- \_AsyncCallable
|
||||
|
||||
### _method_ `on_shutdown(func)` {#Driver-on_shutdown}
|
||||
|
||||
- **说明:** 参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` (\_AsyncCallable)
|
||||
|
||||
- **返回**
|
||||
|
||||
- \_AsyncCallable
|
||||
|
||||
### _method_ `run(host=None, port=None, *, app=None, **kwargs)` {#Driver-run}
|
||||
|
||||
- **说明:** 使用 `uvicorn` 启动 Quart
|
||||
|
||||
- **参数**
|
||||
|
||||
- `host` (str | None)
|
||||
|
||||
- `port` (int | None)
|
||||
|
||||
- `app` (str | None)
|
||||
|
||||
- `**kwargs`
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
## _class_ `WebSocket(*, request, websocket)` {#WebSocket}
|
||||
|
||||
- **说明:** Quart WebSocket Wrapper
|
||||
|
||||
- **参数**
|
||||
|
||||
- `request` (BaseRequest)
|
||||
|
||||
- `websocket` (QuartWebSocket)
|
||||
|
||||
### _async method_ `accept()` {#WebSocket-accept}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _async method_ `close(code=1000, reason="")` {#WebSocket-close}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `code` (int)
|
||||
|
||||
- `reason` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _async method_ `receive()` {#WebSocket-receive}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str | bytes
|
||||
|
||||
### _async method_ `receive_text()` {#WebSocket-receive_text}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _async method_ `receive_bytes()` {#WebSocket-receive_bytes}
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- bytes
|
||||
|
||||
### _async method_ `send_text(data)` {#WebSocket-send_text}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
||||
|
||||
### _async method_ `send_bytes(data)` {#WebSocket-send_bytes}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `data` (bytes)
|
||||
|
||||
- **返回**
|
||||
|
||||
- untyped
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user