mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 11:16:43 +00:00
Compare commits
257 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5e86d53e0b | ||
|
a50a3398de | ||
|
950930a275 | ||
|
c7af169a94 | ||
|
e17096d8d7 | ||
|
00e9e74dfc | ||
|
934954d985 | ||
|
a4a4991473 | ||
|
60acb71033 | ||
|
f8b4dfb1b1 | ||
|
ee5561046f | ||
|
6660c3b471 | ||
|
bd5ba84737 | ||
|
15c5464069 | ||
|
7b136548a9 | ||
|
36ed8030d3 | ||
|
eff1fe455f | ||
|
e3cb4c7907 | ||
|
be732cf9d8 | ||
|
88a5966a40 | ||
|
bdde496332 | ||
|
a989a895e4 | ||
|
7fc51e9227 | ||
|
571fd007ba | ||
|
599ef3b253 | ||
|
c0c7d141ef | ||
|
3be68895e5 | ||
|
ff21ceb946 | ||
|
bd9befbb55 | ||
|
ed91ec9bf5 | ||
|
a00def5d86 | ||
|
973282587e | ||
|
8a6d209942 | ||
|
fcd226031b | ||
|
fb3f0d5e30 | ||
|
56518748d9 | ||
|
474398ee3d | ||
|
597b104111 | ||
|
acec8945ac | ||
|
06b5f09371 | ||
|
15470cd3bb | ||
|
c1c5f57e0b | ||
|
533e8794b2 | ||
|
05c20a7a86 | ||
|
edb416736b | ||
|
2a68bb1b6e | ||
|
29ffbc630a | ||
|
5cf6b93984 | ||
|
30011e3fb4 | ||
|
36606ab05a | ||
|
0aba6b4bb4 | ||
|
fab51d9605 | ||
|
d7e2cc608b | ||
|
b2c5ab3235 | ||
|
1668568d1a | ||
|
4385934a6b | ||
|
4830182050 | ||
|
d86a86d4b2 | ||
|
f175bc9e80 | ||
|
40c2bc636a | ||
|
8ad5a8d4d1 | ||
|
aed91dcc48 | ||
|
de8ffb6c97 | ||
|
990cf32304 | ||
|
09b3f13e7e | ||
|
c39b13b782 | ||
|
3ec4611a29 | ||
|
9d6832303d | ||
|
9fc9f7c384 | ||
|
2021e81ed2 | ||
|
ada6e1ab64 | ||
|
2723a372da | ||
|
a56c93cbcc | ||
|
230476d8ae | ||
|
31a13551be | ||
|
82dbacda83 | ||
|
badd53b4bb | ||
|
94052b5bf7 | ||
|
72a6914980 | ||
|
a2a604dd85 | ||
|
d3d0779d30 | ||
|
a45e7d3854 | ||
|
4dadef3e51 | ||
|
ee643544f1 | ||
|
4598a3de9a | ||
|
59ad3d4b17 | ||
|
ba78c3aef8 | ||
|
3ce2b69431 | ||
|
d47722d87c | ||
|
c4ddfc3df1 | ||
|
58c8879cbb | ||
|
eb1342b78d | ||
|
feb619a85c | ||
|
a9ec70e798 | ||
|
c96e9dbcb6 | ||
|
8742d867e8 | ||
|
ed14dcd090 | ||
|
7956b53530 | ||
|
1140d668b6 | ||
|
9351b074b1 | ||
|
61dc206935 | ||
|
70f62bf4da | ||
|
812c0cd624 | ||
|
5107729290 | ||
|
9b59c16b04 | ||
|
fdc1dcace7 | ||
|
c6c22e3c29 | ||
|
0291b10560 | ||
|
6f07ce0060 | ||
|
b375575792 | ||
|
88765711f3 | ||
|
17ba8d70e1 | ||
|
e373251092 | ||
|
63dcc658da | ||
|
3aaf86e9a9 | ||
|
2cabeb658e | ||
|
0c187cd8c3 | ||
|
c0c8e1aa02 | ||
|
a8586d7990 | ||
|
91b3d3d5e0 | ||
|
8ef51154fd | ||
|
86c83064e4 | ||
|
a4be2c465f | ||
|
be4f36036c | ||
|
f6c7fb6da6 | ||
|
c9b4c3f3c0 | ||
|
4af4412cd7 | ||
|
4e7f7fb722 | ||
|
f01f692fde | ||
|
6e94dade69 | ||
|
eaef8dfc19 | ||
|
dec8b26b89 | ||
|
e8c39f9cc8 | ||
|
2a8644de81 | ||
|
5aaa0d3f12 | ||
|
22bb377fcf | ||
|
0c0ad0dd5e | ||
|
c947bdfef5 | ||
|
b197802d9a | ||
|
8019a570cc | ||
|
25a85330a7 | ||
|
35fb4fc18d | ||
|
ff50e997d0 | ||
|
6cda981aa2 | ||
|
dcfbb32363 | ||
|
d8a1a0ab38 | ||
|
6efd01a575 | ||
|
b2f7846eb4 | ||
|
8d2a284fe2 | ||
|
cce13f682d | ||
|
6c1d7ad74b | ||
|
4b837343ff | ||
|
d1904ba156 | ||
|
3ed1bde38a | ||
|
5cd82df580 | ||
|
fdd0e82099 | ||
|
f540245aec | ||
|
7b3ca228ef | ||
|
aa23adfd8a | ||
|
95ee5d54e8 | ||
|
eac0e7a656 | ||
|
e41ec29867 | ||
|
42a922deb6 | ||
|
8e70d55d77 | ||
|
eeaf823ea9 | ||
|
2195e07998 | ||
|
70fb8fc8c6 | ||
|
b78ae1ef0d | ||
|
6af5566466 | ||
|
8ceca0a90d | ||
|
08fa6dbfc8 | ||
|
967aa758d3 | ||
|
e3b10fbdc2 | ||
|
2115e5c6ec | ||
|
41dc908032 | ||
|
2b2f24628d | ||
|
1cc5d1af33 | ||
|
88074cf5c3 | ||
|
5d637eed95 | ||
|
362c43ce5f | ||
|
622b8eb51e | ||
|
c369dcf781 | ||
|
53d1e1dee9 | ||
|
75f5825cff | ||
|
d05c90787c | ||
|
e07ba36a4a | ||
|
f7c05d9a08 | ||
|
59c5a1a35d | ||
|
3eb653821e | ||
|
214bc838c2 | ||
|
79c7ea5bab | ||
|
b59b1be6ff | ||
|
aeb75a6ce3 | ||
|
847325a119 | ||
|
26eabfaf6f | ||
|
40a7b97220 | ||
|
91b40748c4 | ||
|
013a2f94d6 | ||
|
74d280ed75 | ||
|
b7d46de10e | ||
|
c37b5bbbca | ||
|
5e08e73698 | ||
|
b27bb92d03 | ||
|
6bf8858cc6 | ||
|
c97a780645 | ||
|
976c1cd8e0 | ||
|
26fd6f8a6c | ||
|
0020ad28ba | ||
|
ba9ca63f10 | ||
|
28b5b732c2 | ||
|
b944da8445 | ||
|
5cab166d6b | ||
|
546cdb4229 | ||
|
77790fad1f | ||
|
bcf849c98f | ||
|
f7b3c8af02 | ||
|
cced60589c | ||
|
62adb32c94 | ||
|
6ab752dcdb | ||
|
4d6f071739 | ||
|
bd140c2ceb | ||
|
59d9991aa4 | ||
|
55e7f59e40 | ||
|
bb83483020 | ||
|
5300ef5119 | ||
|
5a50d4203c | ||
|
01a96f3086 | ||
|
0570d779ee | ||
|
18d0bc2c81 | ||
|
87e0d8148f | ||
|
53d8989145 | ||
|
5433b4ebdf | ||
|
f10cecf16a | ||
|
60a3f6f4cc | ||
|
f70ae89098 | ||
|
2f60c5e9b4 | ||
|
015ddd9517 | ||
|
f1539d9ec4 | ||
|
2d0444ba75 | ||
|
ed2c222e83 | ||
|
ed048913a4 | ||
|
121ba17698 | ||
|
d0f5a76c47 | ||
|
f809f1d089 | ||
|
070ad18781 | ||
|
56119ef1cc | ||
|
30195a35dc | ||
|
0500b7baab | ||
|
08473a5c25 | ||
|
37ad14c277 | ||
|
3e8c6ce541 | ||
|
3dd5539dc7 | ||
|
559a0320a8 | ||
|
8646d885f0 | ||
|
84c008cdce | ||
|
2671cb5b72 | ||
|
379440708f |
@@ -9,13 +9,12 @@
|
|||||||
"vscode": {
|
"vscode": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"python.analysis.diagnosticMode": "workspace",
|
"python.analysis.diagnosticMode": "workspace",
|
||||||
"python.analysis.typeCheckingMode": "basic",
|
|
||||||
"ruff.organizeImports": false,
|
"ruff.organizeImports": false,
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter",
|
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.ruff": true,
|
"source.fixAll.ruff": "explicit",
|
||||||
"source.organizeImports": true
|
"source.organizeImports": "explicit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
|||||||
open_collective: nonebot
|
open_collective: nonebot
|
||||||
custom: ["https://afdian.net/@nonebot"]
|
custom: ["https://afdian.com/@nonebot"]
|
||||||
|
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@@ -35,3 +35,12 @@ updates:
|
|||||||
actions:
|
actions:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
|
|
||||||
|
- package-ecosystem: devcontainers
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
groups:
|
||||||
|
devcontainers:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
2
.github/workflows/release-drafter.yml
vendored
2
.github/workflows/release-drafter.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Update Changelog
|
- name: Update Changelog
|
||||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||||
with:
|
with:
|
||||||
changelog_file: website/src/pages/changelog.md
|
changelog_file: website/src/changelog/changelog.md
|
||||||
latest_changes_position: '# 更新日志\n\n'
|
latest_changes_position: '# 更新日志\n\n'
|
||||||
latest_changes_title: "## 最近更新"
|
latest_changes_title: "## 最近更新"
|
||||||
replace_regex: '(?<=## 最近更新\n)[\s\S]*?(?=\n## )'
|
replace_regex: '(?<=## 最近更新\n)[\s\S]*?(?=\n## )'
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
- name: Archive Changelog
|
- name: Archive Changelog
|
||||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||||
with:
|
with:
|
||||||
changelog_file: website/src/pages/changelog.md
|
changelog_file: website/src/changelog/changelog.md
|
||||||
archive_regex: '(?<=## )最近更新(?=\n)'
|
archive_regex: '(?<=## )最近更新(?=\n)'
|
||||||
archive_title: ${{ env.TAG_NAME }}
|
archive_title: ${{ env.TAG_NAME }}
|
||||||
commit_and_push: false
|
commit_and_push: false
|
||||||
|
99
.github/workflows/website-preview-cd.yml
vendored
Normal file
99
.github/workflows/website-preview-cd.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
name: Site Deploy (Preview CD)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Site Deploy (Preview CI)"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
preview-cd:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: pull-request-preview-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
|
||||||
|
environment: pull request
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
statuses: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set Commit Status
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.repos.createCommitStatus({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
sha: context.payload.workflow_run.head_sha,
|
||||||
|
context: 'Website Preview',
|
||||||
|
description: 'Deploying...',
|
||||||
|
state: 'pending',
|
||||||
|
})
|
||||||
|
|
||||||
|
- name: Download Artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: website-preview
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|
||||||
|
- name: Restore Context
|
||||||
|
run: |
|
||||||
|
cat action.env >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set Deploy Name
|
||||||
|
run: |
|
||||||
|
echo "DEPLOY_NAME=deploy-preview-${{ env.PR_NUMBER }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Deploy to Netlify
|
||||||
|
id: deploy
|
||||||
|
uses: nwtgck/actions-netlify@v3
|
||||||
|
with:
|
||||||
|
publish-dir: ./website/build
|
||||||
|
production-deploy: false
|
||||||
|
deploy-message: "Deploy ${{ env.DEPLOY_NAME }}@${{ github.event.workflow_run.head_sha }}"
|
||||||
|
alias: ${{ env.DEPLOY_NAME }}
|
||||||
|
env:
|
||||||
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
NETLIFY_SITE_ID: ${{ secrets.SITE_ID }}
|
||||||
|
|
||||||
|
# action netlify has no pull request context, so we need to comment by ourselves
|
||||||
|
- name: Comment on Pull Request
|
||||||
|
uses: marocchino/sticky-pull-request-comment@v2
|
||||||
|
with:
|
||||||
|
header: website
|
||||||
|
number: ${{ env.PR_NUMBER }}
|
||||||
|
message: |
|
||||||
|
:rocket: Deployed to ${{ steps.deploy.outputs.deploy-url }}
|
||||||
|
|
||||||
|
- name: Set Commit Status
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
if (`${{ job.status }}` === 'success') {
|
||||||
|
github.rest.repos.createCommitStatus({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
sha: context.payload.workflow_run.head_sha,
|
||||||
|
context: 'Website Preview',
|
||||||
|
description: `Deployed to ${{ steps.deploy.outputs.deploy-url }}`,
|
||||||
|
state: 'success',
|
||||||
|
target_url: `${{ steps.deploy.outputs.deploy-url }}`,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
github.rest.repos.createCommitStatus({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
sha: context.payload.workflow_run.head_sha,
|
||||||
|
context: 'Website Preview',
|
||||||
|
description: `Deploy ${{ job.status }}`,
|
||||||
|
state: 'failure',
|
||||||
|
})
|
||||||
|
}
|
42
.github/workflows/website-preview-ci.yml
vendored
Normal file
42
.github/workflows/website-preview-ci.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Site Deploy (Preview CI)
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
preview-ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: pull-request-preview-${{ github.event.number }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Python Environment
|
||||||
|
uses: ./.github/actions/setup-python
|
||||||
|
|
||||||
|
- name: Setup Node Environment
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
|
||||||
|
- name: Build API Doc
|
||||||
|
uses: ./.github/actions/build-api-doc
|
||||||
|
|
||||||
|
- name: Build Doc
|
||||||
|
run: yarn build
|
||||||
|
|
||||||
|
- name: Export Context
|
||||||
|
run: |
|
||||||
|
echo "PR_NUMBER=${{ github.event.number }}" >> ./action.env
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: website-preview
|
||||||
|
path: |
|
||||||
|
./website/build
|
||||||
|
./action.env
|
||||||
|
retention-days: 1
|
46
.github/workflows/website-preview.yml
vendored
46
.github/workflows/website-preview.yml
vendored
@@ -1,46 +0,0 @@
|
|||||||
name: Site Deploy(Preview)
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
preview:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: pull-request-preview-${{ github.event.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Python Environment
|
|
||||||
uses: ./.github/actions/setup-python
|
|
||||||
|
|
||||||
- name: Setup Node Environment
|
|
||||||
uses: ./.github/actions/setup-node
|
|
||||||
|
|
||||||
- name: Build API Doc
|
|
||||||
uses: ./.github/actions/build-api-doc
|
|
||||||
|
|
||||||
- name: Build Doc
|
|
||||||
run: yarn build
|
|
||||||
|
|
||||||
- name: Get Deploy Name
|
|
||||||
run: |
|
|
||||||
echo "DEPLOY_NAME=deploy-preview-${{ github.event.number }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
uses: nwtgck/actions-netlify@v3
|
|
||||||
with:
|
|
||||||
publish-dir: "./website/build"
|
|
||||||
production-deploy: false
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
deploy-message: "Deploy ${{ env.DEPLOY_NAME }}@${{ github.sha }}"
|
|
||||||
enable-commit-comment: false
|
|
||||||
alias: ${{ env.DEPLOY_NAME }}
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.SITE_ID }}
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ docs_build/_build
|
|||||||
!tests/.env
|
!tests/.env
|
||||||
.docusaurus
|
.docusaurus
|
||||||
website/docs/api/**/*.md
|
website/docs/api/**/*.md
|
||||||
|
website/src/pages/changelog/**/*
|
||||||
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/python,node,visualstudiocode,jetbrains,macos,windows,linux
|
# Created by https://www.toptal.com/developers/gitignore/api/python,node,visualstudiocode,jetbrains,macos,windows,linux
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python,node,visualstudiocode,jetbrains,macos,windows,linux
|
# Edit at https://www.toptal.com/developers/gitignore?templates=python,node,visualstudiocode,jetbrains,macos,windows,linux
|
||||||
|
@@ -7,30 +7,23 @@ ci:
|
|||||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.5.0
|
rev: v0.7.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
stages: [commit]
|
stages: [pre-commit]
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 5.13.2
|
rev: 5.13.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
stages: [commit]
|
stages: [pre-commit]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 24.4.2
|
rev: 24.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
stages: [commit]
|
stages: [pre-commit]
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
|
||||||
rev: v4.0.0-alpha.8
|
|
||||||
hooks:
|
|
||||||
- id: prettier
|
|
||||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
|
||||||
stages: [commit]
|
|
||||||
|
|
||||||
- repo: https://github.com/nonebot/nonemoji
|
- repo: https://github.com/nonebot/nonemoji
|
||||||
rev: v0.1.4
|
rev: v0.1.4
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
See [changelog.md](./website/src/pages/changelog.md) or <https://nonebot.dev/changelog>
|
See [changelog.md](./website/src/changelog/changelog.md) or <https://nonebot.dev/changelog>
|
||||||
|
@@ -126,7 +126,6 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
| Mirai([仓库](https://github.com/nonebot/adapter-mirai),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ✅ | QQ 协议 |
|
| Mirai([仓库](https://github.com/nonebot/adapter-mirai),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ✅ | QQ 协议 |
|
||||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
| 开黑啦([仓库](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)) | ↗️ | 微信协议,由社区贡献 |
|
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||||
|
@@ -69,16 +69,6 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"module_name": "nonebot.adapters.mirai2",
|
|
||||||
"project_link": "nonebot_adapter_mirai2",
|
|
||||||
"name": "mirai2",
|
|
||||||
"desc": "为 nonebot2 添加 mirai_api_http2.x的兼容适配器",
|
|
||||||
"author": "ieew",
|
|
||||||
"homepage": "https://github.com/ieew/nonebot_adapter_mirai2",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"module_name": "nonebot.adapters.onebot.v12",
|
"module_name": "nonebot.adapters.onebot.v12",
|
||||||
"project_link": "nonebot-adapter-onebot",
|
"project_link": "nonebot-adapter-onebot",
|
||||||
|
@@ -607,5 +607,55 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "星辰 Bot",
|
||||||
|
"desc": "欢迎使用 星辰 Bot 项目!这是一款基于 NoneBot2 打造的智能 QQ 机器人,旨在为用户提供丰富的功能体验。无论是获取一言的灵感,探索历史上的今天,还是穿梭60s世界,星辰 Bot 为您打开了全新的交流之门。快来尝试吧!",
|
||||||
|
"author": "StarXinXin",
|
||||||
|
"homepage": "https://github.com/StarXinXin/StarsBot",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Minecraft_QQBot",
|
||||||
|
"desc": "基于 NoneBot2 的 Minecraft 群服互联 QQ 机器人,支持多服务器多种方式连接。",
|
||||||
|
"author": "Lonely-Sails",
|
||||||
|
"homepage": "https://github.com/Minecraft-QQBot/BotServer",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "Minecraft",
|
||||||
|
"color": "#ea5252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "娱乐",
|
||||||
|
"color": "#37a7e7"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "小安提Bot",
|
||||||
|
"desc": "服务于音游 舞萌DX 的多功能Bot",
|
||||||
|
"author": "Ant1816",
|
||||||
|
"homepage": "https://github.com/Ant1816/Ant1Bot",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "maimaiDX",
|
||||||
|
"color": "#52ea9a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "音游",
|
||||||
|
"color": "#f74b18"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CanrotBot",
|
||||||
|
"desc": "有很多实用功能的bot,也有很多没什么用的娱乐功能;接入了大模型,并且有一部分功能可以被大模型调用。主打一个全都有(",
|
||||||
|
"author": "wangyw15",
|
||||||
|
"homepage": "https://github.com/wangyw15/CanrotBot",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
1291
assets/plugins.json
1291
assets/plugins.json
File diff suppressed because it is too large
Load Diff
2634
envs/pydantic-v1/poetry.lock
generated
2634
envs/pydantic-v1/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
2737
envs/pydantic-v2/poetry.lock
generated
2737
envs/pydantic-v2/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
1384
envs/test/poetry.lock
generated
1384
envs/test/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -8,11 +8,11 @@ packages = [{ include = "nonebot-test.py" }]
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
nonebug = "^0.3.7"
|
trio = "^0.27.0"
|
||||||
|
nonebug = "^0.4.1"
|
||||||
wsproto = "^1.2.0"
|
wsproto = "^1.2.0"
|
||||||
pytest-cov = "^5.0.0"
|
pytest-cov = "^5.0.0"
|
||||||
pytest-xdist = "^3.0.2"
|
pytest-xdist = "^3.0.2"
|
||||||
pytest-asyncio = "^0.23.2"
|
|
||||||
werkzeug = ">=2.3.6,<4.0.0"
|
werkzeug = ">=2.3.6,<4.0.0"
|
||||||
coverage-conditional-plugin = "^0.9.0"
|
coverage-conditional-plugin = "^0.9.0"
|
||||||
|
|
||||||
|
@@ -39,6 +39,8 @@
|
|||||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot 模块
|
description: nonebot 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器。
|
使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.adapters 模块
|
description: nonebot.adapters 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -3,22 +3,27 @@
|
|||||||
为兼容 Pydantic V1 与 V2 版本,定义了一系列兼容函数与类供使用。
|
为兼容 Pydantic V1 与 V2 版本,定义了一系列兼容函数与类供使用。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 16
|
sidebar_position: 16
|
||||||
description: nonebot.compat 模块
|
description: nonebot.compat 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from functools import cached_property
|
||||||
from dataclasses import dataclass, is_dataclass
|
from dataclasses import dataclass, is_dataclass
|
||||||
from typing_extensions import Self, get_args, get_origin, is_typeddict
|
from typing_extensions import Self, get_args, get_origin, is_typeddict
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Union,
|
Union,
|
||||||
|
Generic,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Callable,
|
Callable,
|
||||||
Optional,
|
Optional,
|
||||||
Protocol,
|
Protocol,
|
||||||
Annotated,
|
Annotated,
|
||||||
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydantic import VERSION, BaseModel
|
from pydantic import VERSION, BaseModel
|
||||||
@@ -46,8 +51,8 @@ __all__ = (
|
|||||||
"DEFAULT_CONFIG",
|
"DEFAULT_CONFIG",
|
||||||
"FieldInfo",
|
"FieldInfo",
|
||||||
"ModelField",
|
"ModelField",
|
||||||
|
"TypeAdapter",
|
||||||
"extract_field_info",
|
"extract_field_info",
|
||||||
"model_field_validate",
|
|
||||||
"model_fields",
|
"model_fields",
|
||||||
"model_config",
|
"model_config",
|
||||||
"model_dump",
|
"model_dump",
|
||||||
@@ -63,9 +68,10 @@ __autodoc__ = {
|
|||||||
|
|
||||||
|
|
||||||
if PYDANTIC_V2: # pragma: pydantic-v2
|
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||||
|
from pydantic import GetCoreSchemaHandler
|
||||||
|
from pydantic import TypeAdapter as TypeAdapter
|
||||||
from pydantic_core import CoreSchema, core_schema
|
from pydantic_core import CoreSchema, core_schema
|
||||||
from pydantic._internal._repr import display_as_type
|
from pydantic._internal._repr import display_as_type
|
||||||
from pydantic import TypeAdapter, GetCoreSchemaHandler
|
|
||||||
from pydantic.fields import FieldInfo as BaseFieldInfo
|
from pydantic.fields import FieldInfo as BaseFieldInfo
|
||||||
|
|
||||||
Required = Ellipsis
|
Required = Ellipsis
|
||||||
@@ -125,6 +131,25 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
|||||||
"""Construct a ModelField from given infos."""
|
"""Construct a ModelField from given infos."""
|
||||||
return cls._construct(name, annotation, field_info or FieldInfo())
|
return cls._construct(name, annotation, field_info or FieldInfo())
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
# Each ModelField is unique for our purposes,
|
||||||
|
# to allow store them in a set.
|
||||||
|
return id(self)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def type_adapter(self) -> TypeAdapter:
|
||||||
|
"""TypeAdapter of the field.
|
||||||
|
|
||||||
|
Cache the TypeAdapter to avoid creating it multiple times.
|
||||||
|
Pydantic v2 uses too much cpu time to create TypeAdapter.
|
||||||
|
|
||||||
|
See: https://github.com/pydantic/pydantic/issues/9834
|
||||||
|
"""
|
||||||
|
return TypeAdapter(
|
||||||
|
Annotated[self.annotation, self.field_info],
|
||||||
|
config=None if self._annotation_has_config() else DEFAULT_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
def _annotation_has_config(self) -> bool:
|
def _annotation_has_config(self) -> bool:
|
||||||
"""Check if the annotation has config.
|
"""Check if the annotation has config.
|
||||||
|
|
||||||
@@ -152,10 +177,9 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
|||||||
"""Get the display of the type of the field."""
|
"""Get the display of the type of the field."""
|
||||||
return display_as_type(self.annotation)
|
return display_as_type(self.annotation)
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def validate_value(self, value: Any) -> Any:
|
||||||
# Each ModelField is unique for our purposes,
|
"""Validate the value pass to the field."""
|
||||||
# to allow store them in a set.
|
return self.type_adapter.validate_python(value)
|
||||||
return id(self)
|
|
||||||
|
|
||||||
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
|
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
|
||||||
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
||||||
@@ -164,15 +188,6 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
|||||||
kwargs["annotation"] = field_info.rebuild_annotation()
|
kwargs["annotation"] = field_info.rebuild_annotation()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def model_field_validate(
|
|
||||||
model_field: ModelField, value: Any, config: Optional[ConfigDict] = None
|
|
||||||
) -> Any:
|
|
||||||
"""Validate the value pass to the field."""
|
|
||||||
type: Any = Annotated[model_field.annotation, model_field.field_info]
|
|
||||||
return TypeAdapter(
|
|
||||||
type, config=None if model_field._annotation_has_config() else config
|
|
||||||
).validate_python(value)
|
|
||||||
|
|
||||||
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
||||||
"""Get field list of a model."""
|
"""Get field list of a model."""
|
||||||
|
|
||||||
@@ -305,6 +320,45 @@ else: # pragma: pydantic-v1
|
|||||||
)
|
)
|
||||||
return cls._construct(name, annotation, field_info or FieldInfo())
|
return cls._construct(name, annotation, field_info or FieldInfo())
|
||||||
|
|
||||||
|
def validate_value(self, value: Any) -> Any:
|
||||||
|
"""Validate the value pass to the field."""
|
||||||
|
v, errs_ = self.validate(value, {}, loc=())
|
||||||
|
if errs_:
|
||||||
|
raise ValueError(value, self)
|
||||||
|
return v
|
||||||
|
|
||||||
|
class TypeAdapter(Generic[T]):
|
||||||
|
@overload
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
type: type[T],
|
||||||
|
*,
|
||||||
|
config: Optional[ConfigDict] = ...,
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
type: Any,
|
||||||
|
*,
|
||||||
|
config: Optional[ConfigDict] = ...,
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
type: Any,
|
||||||
|
*,
|
||||||
|
config: Optional[ConfigDict] = None,
|
||||||
|
) -> None:
|
||||||
|
self.type = type
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def validate_python(self, value: Any) -> T:
|
||||||
|
return type_validate_python(self.type, value)
|
||||||
|
|
||||||
|
def validate_json(self, value: Union[str, bytes]) -> T:
|
||||||
|
return type_validate_json(self.type, value)
|
||||||
|
|
||||||
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
|
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
|
||||||
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
||||||
|
|
||||||
@@ -314,22 +368,6 @@ else: # pragma: pydantic-v1
|
|||||||
kwargs.update(field_info.extra)
|
kwargs.update(field_info.extra)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def model_field_validate(
|
|
||||||
model_field: ModelField, value: Any, config: Optional[type[ConfigDict]] = None
|
|
||||||
) -> Any:
|
|
||||||
"""Validate the value pass to the field.
|
|
||||||
|
|
||||||
Set config before validate to ensure validate correctly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if model_field.model_config is not config:
|
|
||||||
model_field.set_config(config or ConfigDict)
|
|
||||||
|
|
||||||
v, errs_ = model_field.validate(value, {}, loc=())
|
|
||||||
if errs_:
|
|
||||||
raise ValueError(value, model_field)
|
|
||||||
return v
|
|
||||||
|
|
||||||
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
||||||
"""Get field list of a model."""
|
"""Get field list of a model."""
|
||||||
|
|
||||||
|
@@ -7,6 +7,8 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
|
|||||||
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.config 模块
|
description: nonebot.config 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 9
|
sidebar_position: 9
|
||||||
description: nonebot.consts 模块
|
description: nonebot.consts 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,22 +1,32 @@
|
|||||||
"""本模块模块实现了依赖注入的定义与处理。
|
"""本模块模块实现了依赖注入的定义与处理。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.dependencies 模块
|
description: nonebot.dependencies 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import asyncio
|
|
||||||
import inspect
|
import inspect
|
||||||
|
from functools import partial
|
||||||
from dataclasses import field, dataclass
|
from dataclasses import field, dataclass
|
||||||
from collections.abc import Iterable, Awaitable
|
from collections.abc import Iterable, Awaitable
|
||||||
from typing import Any, Generic, TypeVar, Callable, Optional, cast
|
from typing import Any, Generic, TypeVar, Callable, Optional, cast
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.typing import _DependentCallable
|
from nonebot.typing import _DependentCallable
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
|
||||||
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
|
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
|
||||||
|
from nonebot.utils import (
|
||||||
|
run_sync,
|
||||||
|
run_coro_with_shield,
|
||||||
|
is_coroutine_callable,
|
||||||
|
flatten_exception_group,
|
||||||
|
)
|
||||||
|
|
||||||
from .utils import check_field_type, get_typed_signature
|
from .utils import check_field_type, get_typed_signature
|
||||||
|
|
||||||
@@ -82,7 +92,16 @@ class Dependent(Generic[R]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def __call__(self, **kwargs: Any) -> R:
|
async def __call__(self, **kwargs: Any) -> R:
|
||||||
try:
|
exception: Optional[BaseExceptionGroup[SkippedException]] = None
|
||||||
|
|
||||||
|
def _handle_skipped(exc_group: BaseExceptionGroup[SkippedException]):
|
||||||
|
nonlocal exception
|
||||||
|
exception = exc_group
|
||||||
|
# raise one of the exceptions instead
|
||||||
|
excs = list(flatten_exception_group(exc_group))
|
||||||
|
logger.trace(f"{self} skipped due to {excs}")
|
||||||
|
|
||||||
|
with catch({SkippedException: _handle_skipped}):
|
||||||
# do pre-check
|
# do pre-check
|
||||||
await self.check(**kwargs)
|
await self.check(**kwargs)
|
||||||
|
|
||||||
@@ -94,9 +113,8 @@ class Dependent(Generic[R]):
|
|||||||
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
|
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
|
||||||
else:
|
else:
|
||||||
return await run_sync(cast(Callable[..., R], self.call))(**values)
|
return await run_sync(cast(Callable[..., R], self.call))(**values)
|
||||||
except SkippedException as e:
|
|
||||||
logger.trace(f"{self} skipped due to {e}")
|
raise exception
|
||||||
raise
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_params(
|
def parse_params(
|
||||||
@@ -164,10 +182,17 @@ class Dependent(Generic[R]):
|
|||||||
return cls(call, params, parameterless_params)
|
return cls(call, params, parameterless_params)
|
||||||
|
|
||||||
async def check(self, **params: Any) -> None:
|
async def check(self, **params: Any) -> None:
|
||||||
await asyncio.gather(*(param._check(**params) for param in self.parameterless))
|
if self.parameterless:
|
||||||
await asyncio.gather(
|
async with anyio.create_task_group() as tg:
|
||||||
*(cast(Param, param.field_info)._check(**params) for param in self.params)
|
for param in self.parameterless:
|
||||||
)
|
tg.start_soon(partial(param._check, **params))
|
||||||
|
|
||||||
|
if self.params:
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for param in self.params:
|
||||||
|
tg.start_soon(
|
||||||
|
partial(cast(Param, param.field_info)._check, **params)
|
||||||
|
)
|
||||||
|
|
||||||
async def _solve_field(self, field: ModelField, params: dict[str, Any]) -> Any:
|
async def _solve_field(self, field: ModelField, params: dict[str, Any]) -> Any:
|
||||||
param = cast(Param, field.field_info)
|
param = cast(Param, field.field_info)
|
||||||
@@ -183,10 +208,22 @@ class Dependent(Generic[R]):
|
|||||||
await param._solve(**params)
|
await param._solve(**params)
|
||||||
|
|
||||||
# solve param values
|
# solve param values
|
||||||
values = await asyncio.gather(
|
result: dict[str, Any] = {}
|
||||||
*(self._solve_field(field, params) for field in self.params)
|
if not self.params:
|
||||||
)
|
return result
|
||||||
return {field.name: value for field, value in zip(self.params, values)}
|
|
||||||
|
async def _solve_field(field: ModelField, params: dict[str, Any]) -> None:
|
||||||
|
value = await self._solve_field(field, params)
|
||||||
|
result[field.name] = value
|
||||||
|
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for field in self.params:
|
||||||
|
# shield the task to prevent cancellation
|
||||||
|
# when one of the tasks raises an exception
|
||||||
|
# this will improve the dependency cache reusability
|
||||||
|
tg.start_soon(run_coro_with_shield, _solve_field(field, params))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {"CustomConfig": False}
|
__autodoc__ = {"CustomConfig": False}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.dependencies.utils 模块
|
description: nonebot.dependencies.utils 模块
|
||||||
"""
|
"""
|
||||||
@@ -9,9 +11,9 @@ from typing import Any, Callable, ForwardRef
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from nonebot.compat import ModelField
|
||||||
from nonebot.exception import TypeMisMatch
|
from nonebot.exception import TypeMisMatch
|
||||||
from nonebot.typing import evaluate_forwardref
|
from nonebot.typing import evaluate_forwardref
|
||||||
from nonebot.compat import DEFAULT_CONFIG, ModelField, model_field_validate
|
|
||||||
|
|
||||||
|
|
||||||
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||||
@@ -51,6 +53,6 @@ def check_field_type(field: ModelField, value: Any) -> Any:
|
|||||||
"""检查字段类型是否匹配"""
|
"""检查字段类型是否匹配"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return model_field_validate(field, value, DEFAULT_CONFIG)
|
return field.validate_value(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise TypeMisMatch(field, value)
|
raise TypeMisMatch(field, value)
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
各驱动请继承以下基类。
|
各驱动请继承以下基类。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.drivers 模块
|
description: nonebot.drivers 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -11,6 +11,8 @@ pip install nonebot2[aiohttp]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.drivers.aiohttp 模块
|
description: nonebot.drivers.aiohttp 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -11,6 +11,8 @@ pip install nonebot2[fastapi]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.drivers.fastapi 模块
|
description: nonebot.drivers.fastapi 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -11,6 +11,8 @@ pip install nonebot2[httpx]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.drivers.httpx 模块
|
description: nonebot.drivers.httpx 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -5,19 +5,25 @@
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 6
|
sidebar_position: 6
|
||||||
description: nonebot.drivers.none 模块
|
description: nonebot.drivers.none 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import signal
|
import signal
|
||||||
import asyncio
|
from typing import Optional
|
||||||
import threading
|
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from anyio.abc import TaskGroup
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.consts import WINDOWS
|
from nonebot.consts import WINDOWS
|
||||||
from nonebot.config import Env, Config
|
from nonebot.config import Env, Config
|
||||||
from nonebot.drivers import Driver as BaseDriver
|
from nonebot.drivers import Driver as BaseDriver
|
||||||
|
from nonebot.utils import flatten_exception_group
|
||||||
|
|
||||||
HANDLED_SIGNALS = (
|
HANDLED_SIGNALS = (
|
||||||
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
||||||
@@ -33,8 +39,8 @@ class Driver(BaseDriver):
|
|||||||
def __init__(self, env: Env, config: Config):
|
def __init__(self, env: Env, config: Config):
|
||||||
super().__init__(env, config)
|
super().__init__(env, config)
|
||||||
|
|
||||||
self.should_exit: asyncio.Event = asyncio.Event()
|
self.should_exit: anyio.Event = anyio.Event()
|
||||||
self.force_exit: bool = False
|
self.force_exit: anyio.Event = anyio.Event()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
@@ -52,85 +58,98 @@ class Driver(BaseDriver):
|
|||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
"""启动 none driver"""
|
"""启动 none driver"""
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
loop = asyncio.get_event_loop()
|
anyio.run(self._serve)
|
||||||
loop.run_until_complete(self._serve())
|
|
||||||
|
|
||||||
async def _serve(self):
|
async def _serve(self):
|
||||||
self._install_signal_handlers()
|
async with anyio.create_task_group() as driver_tg:
|
||||||
await self._startup()
|
driver_tg.start_soon(self._handle_signals)
|
||||||
if self.should_exit.is_set():
|
driver_tg.start_soon(self._listen_force_exit, driver_tg)
|
||||||
return
|
driver_tg.start_soon(self._handle_lifespan, driver_tg)
|
||||||
await self._main_loop()
|
|
||||||
await self._shutdown()
|
|
||||||
|
|
||||||
async def _startup(self):
|
async def _handle_signals(self):
|
||||||
try:
|
try:
|
||||||
await self._lifespan.startup()
|
with anyio.open_signal_receiver(*HANDLED_SIGNALS) as signal_receiver:
|
||||||
except Exception as e:
|
async for sig in signal_receiver:
|
||||||
logger.opt(colors=True, exception=e).error(
|
self.exit(force=self.should_exit.is_set())
|
||||||
"<r><bg #f8bbd0>Application startup failed. "
|
|
||||||
"Exiting.</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
self.should_exit.set()
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info("Application startup completed.")
|
|
||||||
|
|
||||||
async def _main_loop(self):
|
|
||||||
await self.should_exit.wait()
|
|
||||||
|
|
||||||
async def _shutdown(self):
|
|
||||||
logger.info("Shutting down")
|
|
||||||
|
|
||||||
logger.info("Waiting for application shutdown.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self._lifespan.shutdown()
|
|
||||||
except Exception as e:
|
|
||||||
logger.opt(colors=True, exception=e).error(
|
|
||||||
"<r><bg #f8bbd0>Error when running shutdown function. "
|
|
||||||
"Ignored!</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
|
|
||||||
for task in asyncio.all_tasks():
|
|
||||||
if task is not asyncio.current_task() and not task.done():
|
|
||||||
task.cancel()
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
|
|
||||||
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
|
||||||
if tasks and not self.force_exit:
|
|
||||||
logger.info("Waiting for tasks to finish. (CTRL+C to force quit)")
|
|
||||||
while tasks and not self.force_exit:
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
|
||||||
|
|
||||||
for task in tasks:
|
|
||||||
task.cancel()
|
|
||||||
|
|
||||||
await asyncio.gather(*tasks, return_exceptions=True)
|
|
||||||
|
|
||||||
logger.info("Application shutdown complete.")
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.stop()
|
|
||||||
|
|
||||||
def _install_signal_handlers(self) -> None:
|
|
||||||
if threading.current_thread() is not threading.main_thread():
|
|
||||||
# Signals can only be listened to from the main thread.
|
|
||||||
return
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
|
|
||||||
try:
|
|
||||||
for sig in HANDLED_SIGNALS:
|
|
||||||
loop.add_signal_handler(sig, self._handle_exit, sig, None)
|
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
# Windows
|
# Windows
|
||||||
for sig in HANDLED_SIGNALS:
|
for sig in HANDLED_SIGNALS:
|
||||||
signal.signal(sig, self._handle_exit)
|
signal.signal(sig, self._handle_legacy_signal)
|
||||||
|
|
||||||
def _handle_exit(self, sig, frame):
|
# backport for Windows signal handling
|
||||||
|
def _handle_legacy_signal(self, sig, frame):
|
||||||
self.exit(force=self.should_exit.is_set())
|
self.exit(force=self.should_exit.is_set())
|
||||||
|
|
||||||
|
async def _handle_lifespan(self, tg: TaskGroup):
|
||||||
|
try:
|
||||||
|
await self._startup()
|
||||||
|
|
||||||
|
if self.should_exit.is_set():
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._listen_exit()
|
||||||
|
|
||||||
|
await self._shutdown()
|
||||||
|
finally:
|
||||||
|
tg.cancel_scope.cancel()
|
||||||
|
|
||||||
|
async def _startup(self):
|
||||||
|
def handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||||
|
self.should_exit.set()
|
||||||
|
|
||||||
|
for exc in flatten_exception_group(exc_group):
|
||||||
|
logger.opt(colors=True, exception=exc).error(
|
||||||
|
"<r><bg #f8bbd0>Error occurred while running startup hook."
|
||||||
|
"</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
logger.error(
|
||||||
|
"<r><bg #f8bbd0>Application startup failed. "
|
||||||
|
"Exiting.</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
|
with catch({Exception: handle_exception}):
|
||||||
|
await self._lifespan.startup()
|
||||||
|
|
||||||
|
if not self.should_exit.is_set():
|
||||||
|
logger.info("Application startup completed.")
|
||||||
|
|
||||||
|
async def _listen_exit(self, tg: Optional[TaskGroup] = None):
|
||||||
|
await self.should_exit.wait()
|
||||||
|
|
||||||
|
if tg is not None:
|
||||||
|
tg.cancel_scope.cancel()
|
||||||
|
|
||||||
|
async def _shutdown(self):
|
||||||
|
logger.info("Shutting down")
|
||||||
|
logger.info("Waiting for application shutdown. (CTRL+C to force quit)")
|
||||||
|
|
||||||
|
error_occurred: bool = False
|
||||||
|
|
||||||
|
def handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||||
|
nonlocal error_occurred
|
||||||
|
|
||||||
|
error_occurred = True
|
||||||
|
|
||||||
|
for exc in flatten_exception_group(exc_group):
|
||||||
|
logger.opt(colors=True, exception=exc).error(
|
||||||
|
"<r><bg #f8bbd0>Error occurred while running shutdown hook."
|
||||||
|
"</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
logger.error(
|
||||||
|
"<r><bg #f8bbd0>Application shutdown failed. "
|
||||||
|
"Exiting.</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
|
with catch({Exception: handle_exception}):
|
||||||
|
await self._lifespan.shutdown()
|
||||||
|
|
||||||
|
if not error_occurred:
|
||||||
|
logger.info("Application shutdown complete.")
|
||||||
|
|
||||||
|
async def _listen_force_exit(self, tg: TaskGroup):
|
||||||
|
await self.force_exit.wait()
|
||||||
|
tg.cancel_scope.cancel()
|
||||||
|
|
||||||
def exit(self, force: bool = False):
|
def exit(self, force: bool = False):
|
||||||
"""退出 none driver
|
"""退出 none driver
|
||||||
|
|
||||||
@@ -140,4 +159,4 @@ class Driver(BaseDriver):
|
|||||||
if not self.should_exit.is_set():
|
if not self.should_exit.is_set():
|
||||||
self.should_exit.set()
|
self.should_exit.set()
|
||||||
if force:
|
if force:
|
||||||
self.force_exit = True
|
self.force_exit.set()
|
||||||
|
@@ -11,6 +11,8 @@ pip install nonebot2[quart]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.drivers.quart 模块
|
description: nonebot.drivers.quart 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -11,6 +11,8 @@ pip install nonebot2[websockets]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 4
|
sidebar_position: 4
|
||||||
description: nonebot.drivers.websockets 模块
|
description: nonebot.drivers.websockets 模块
|
||||||
"""
|
"""
|
||||||
@@ -69,6 +71,8 @@ class Mixin(WebSocketClientMixin):
|
|||||||
@override
|
@override
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
||||||
|
if setup.proxy is not None:
|
||||||
|
logger.warning("proxy is not supported by websockets driver")
|
||||||
connection = Connect(
|
connection = Connect(
|
||||||
str(setup.url),
|
str(setup.url),
|
||||||
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
|
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
|
||||||
|
@@ -25,6 +25,8 @@ NoneBotException
|
|||||||
```
|
```
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 10
|
sidebar_position: 10
|
||||||
description: nonebot.exception 模块
|
description: nonebot.exception 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,11 +1,14 @@
|
|||||||
import abc
|
import abc
|
||||||
import asyncio
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional, Protocol
|
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional, Protocol
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.config import Config
|
from nonebot.config import Config
|
||||||
from nonebot.exception import MockApiException
|
from nonebot.exception import MockApiException
|
||||||
|
from nonebot.utils import flatten_exception_group
|
||||||
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
|
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -76,47 +79,98 @@ class Bot(abc.ABC):
|
|||||||
skip_calling_api: bool = False
|
skip_calling_api: bool = False
|
||||||
exception: Optional[Exception] = None
|
exception: Optional[Exception] = None
|
||||||
|
|
||||||
if coros := [hook(self, api, data) for hook in self._calling_api_hook]:
|
if self._calling_api_hook:
|
||||||
try:
|
logger.debug("Running CallingAPI hooks...")
|
||||||
logger.debug("Running CallingAPI hooks...")
|
|
||||||
await asyncio.gather(*coros)
|
def _handle_mock_api_exception(
|
||||||
except MockApiException as e:
|
exc_group: BaseExceptionGroup[MockApiException],
|
||||||
|
) -> None:
|
||||||
|
nonlocal skip_calling_api, result
|
||||||
|
|
||||||
|
excs = [
|
||||||
|
exc
|
||||||
|
for exc in flatten_exception_group(exc_group)
|
||||||
|
if isinstance(exc, MockApiException)
|
||||||
|
]
|
||||||
|
if not excs:
|
||||||
|
return
|
||||||
|
elif len(excs) > 1:
|
||||||
|
logger.warning(
|
||||||
|
"Multiple hooks want to mock API result. Use the first one."
|
||||||
|
)
|
||||||
|
|
||||||
skip_calling_api = True
|
skip_calling_api = True
|
||||||
result = e.result
|
result = excs[0].result
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Calling API {api} is cancelled. Return {result} instead."
|
f"Calling API {api} is cancelled. Return {result!r} instead."
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.opt(colors=True, exception=e).error(
|
|
||||||
"<r><bg #f8bbd0>Error when running CallingAPI hook. "
|
|
||||||
"Running cancelled!</bg #f8bbd0></r>"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||||
|
for exc in flatten_exception_group(exc_group):
|
||||||
|
logger.opt(colors=True, exception=exc).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running CallingAPI hook. "
|
||||||
|
"Running cancelled!</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
|
with catch(
|
||||||
|
{
|
||||||
|
MockApiException: _handle_mock_api_exception,
|
||||||
|
Exception: _handle_exception,
|
||||||
|
}
|
||||||
|
):
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for hook in self._calling_api_hook:
|
||||||
|
tg.start_soon(hook, self, api, data)
|
||||||
|
|
||||||
if not skip_calling_api:
|
if not skip_calling_api:
|
||||||
try:
|
try:
|
||||||
result = await self.adapter._call_api(self, api, **data)
|
result = await self.adapter._call_api(self, api, **data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
exception = e
|
exception = e
|
||||||
|
|
||||||
if coros := [
|
if self._called_api_hook:
|
||||||
hook(self, exception, api, data, result) for hook in self._called_api_hook
|
logger.debug("Running CalledAPI hooks...")
|
||||||
]:
|
|
||||||
try:
|
def _handle_mock_api_exception(
|
||||||
logger.debug("Running CalledAPI hooks...")
|
exc_group: BaseExceptionGroup[MockApiException],
|
||||||
await asyncio.gather(*coros)
|
) -> None:
|
||||||
except MockApiException as e:
|
nonlocal result, exception
|
||||||
# mock api result
|
|
||||||
result = e.result
|
excs = [
|
||||||
# ignore exception
|
exc
|
||||||
|
for exc in flatten_exception_group(exc_group)
|
||||||
|
if isinstance(exc, MockApiException)
|
||||||
|
]
|
||||||
|
if not excs:
|
||||||
|
return
|
||||||
|
elif len(excs) > 1:
|
||||||
|
logger.warning(
|
||||||
|
"Multiple hooks want to mock API result. Use the first one."
|
||||||
|
)
|
||||||
|
|
||||||
|
result = excs[0].result
|
||||||
exception = None
|
exception = None
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Calling API {api} result is mocked. Return {result} instead."
|
f"Calling API {api} result is mocked. Return {result} instead."
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
logger.opt(colors=True, exception=e).error(
|
def _handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||||
"<r><bg #f8bbd0>Error when running CalledAPI hook. "
|
for exc in flatten_exception_group(exc_group):
|
||||||
"Running cancelled!</bg #f8bbd0></r>"
|
logger.opt(colors=True, exception=exc).error(
|
||||||
)
|
"<r><bg #f8bbd0>Error when running CalledAPI hook. "
|
||||||
|
"Running cancelled!</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
|
with catch(
|
||||||
|
{
|
||||||
|
MockApiException: _handle_mock_api_exception,
|
||||||
|
Exception: _handle_exception,
|
||||||
|
}
|
||||||
|
):
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for hook in self._called_api_hook:
|
||||||
|
tg.start_soon(hook, self, exception, api, data, result)
|
||||||
|
|
||||||
if exception:
|
if exception:
|
||||||
raise exception
|
raise exception
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
from collections.abc import Awaitable
|
from types import TracebackType
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import TypeAlias
|
||||||
from typing import Any, Union, Callable, cast
|
from collections.abc import Iterable, Awaitable
|
||||||
|
from typing import Any, Union, Callable, Optional, cast
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from anyio.abc import TaskGroup
|
||||||
|
from exceptiongroup import suppress
|
||||||
|
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
from nonebot.utils import run_sync, is_coroutine_callable
|
||||||
|
|
||||||
@@ -11,10 +16,24 @@ LIFESPAN_FUNC: TypeAlias = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
|
|||||||
|
|
||||||
class Lifespan:
|
class Lifespan:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
self._task_group: Optional[TaskGroup] = None
|
||||||
|
|
||||||
self._startup_funcs: list[LIFESPAN_FUNC] = []
|
self._startup_funcs: list[LIFESPAN_FUNC] = []
|
||||||
self._ready_funcs: list[LIFESPAN_FUNC] = []
|
self._ready_funcs: list[LIFESPAN_FUNC] = []
|
||||||
self._shutdown_funcs: list[LIFESPAN_FUNC] = []
|
self._shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def task_group(self) -> TaskGroup:
|
||||||
|
if self._task_group is None:
|
||||||
|
raise RuntimeError("Lifespan not started")
|
||||||
|
return self._task_group
|
||||||
|
|
||||||
|
@task_group.setter
|
||||||
|
def task_group(self, task_group: TaskGroup) -> None:
|
||||||
|
if self._task_group is not None:
|
||||||
|
raise RuntimeError("Lifespan already started")
|
||||||
|
self._task_group = task_group
|
||||||
|
|
||||||
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
self._startup_funcs.append(func)
|
self._startup_funcs.append(func)
|
||||||
return func
|
return func
|
||||||
@@ -29,7 +48,7 @@ class Lifespan:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _run_lifespan_func(
|
async def _run_lifespan_func(
|
||||||
funcs: list[LIFESPAN_FUNC],
|
funcs: Iterable[LIFESPAN_FUNC],
|
||||||
) -> None:
|
) -> None:
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
if is_coroutine_callable(func):
|
if is_coroutine_callable(func):
|
||||||
@@ -38,18 +57,44 @@ class Lifespan:
|
|||||||
await run_sync(cast(SYNC_LIFESPAN_FUNC, func))()
|
await run_sync(cast(SYNC_LIFESPAN_FUNC, func))()
|
||||||
|
|
||||||
async def startup(self) -> None:
|
async def startup(self) -> None:
|
||||||
|
# create background task group
|
||||||
|
self.task_group = anyio.create_task_group()
|
||||||
|
await self.task_group.__aenter__()
|
||||||
|
|
||||||
|
# run startup funcs
|
||||||
if self._startup_funcs:
|
if self._startup_funcs:
|
||||||
await self._run_lifespan_func(self._startup_funcs)
|
await self._run_lifespan_func(self._startup_funcs)
|
||||||
|
|
||||||
|
# run ready funcs
|
||||||
if self._ready_funcs:
|
if self._ready_funcs:
|
||||||
await self._run_lifespan_func(self._ready_funcs)
|
await self._run_lifespan_func(self._ready_funcs)
|
||||||
|
|
||||||
async def shutdown(self) -> None:
|
async def shutdown(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
exc_type: Optional[type[BaseException]] = None,
|
||||||
|
exc_val: Optional[BaseException] = None,
|
||||||
|
exc_tb: Optional[TracebackType] = None,
|
||||||
|
) -> None:
|
||||||
if self._shutdown_funcs:
|
if self._shutdown_funcs:
|
||||||
await self._run_lifespan_func(self._shutdown_funcs)
|
# reverse shutdown funcs to ensure stack order
|
||||||
|
await self._run_lifespan_func(reversed(self._shutdown_funcs))
|
||||||
|
|
||||||
|
# shutdown background task group
|
||||||
|
self.task_group.cancel_scope.cancel()
|
||||||
|
|
||||||
|
with suppress(Exception):
|
||||||
|
await self.task_group.__aexit__(exc_type, exc_val, exc_tb)
|
||||||
|
|
||||||
|
self._task_group = None
|
||||||
|
|
||||||
async def __aenter__(self) -> None:
|
async def __aenter__(self) -> None:
|
||||||
await self.startup()
|
await self.startup()
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
async def __aexit__(
|
||||||
await self.shutdown()
|
self,
|
||||||
|
exc_type: Optional[type[BaseException]],
|
||||||
|
exc_val: Optional[BaseException],
|
||||||
|
exc_tb: Optional[TracebackType],
|
||||||
|
) -> None:
|
||||||
|
await self.shutdown(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb)
|
||||||
|
@@ -1,17 +1,20 @@
|
|||||||
import abc
|
import abc
|
||||||
import asyncio
|
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing_extensions import Self, TypeAlias
|
from typing_extensions import Self, TypeAlias
|
||||||
from contextlib import AsyncExitStack, asynccontextmanager
|
from contextlib import AsyncExitStack, asynccontextmanager
|
||||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional
|
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional
|
||||||
|
|
||||||
|
from anyio.abc import TaskGroup
|
||||||
|
from anyio import CancelScope, create_task_group
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.config import Env, Config
|
from nonebot.config import Env, Config
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
from nonebot.utils import escape_tag, run_coro_with_catch
|
|
||||||
from nonebot.internal.params import BotParam, DependParam, DefaultParam
|
from nonebot.internal.params import BotParam, DependParam, DefaultParam
|
||||||
|
from nonebot.utils import escape_tag, run_coro_with_catch, flatten_exception_group
|
||||||
from nonebot.typing import (
|
from nonebot.typing import (
|
||||||
T_DependencyCache,
|
T_DependencyCache,
|
||||||
T_BotConnectionHook,
|
T_BotConnectionHook,
|
||||||
@@ -61,7 +64,6 @@ class Driver(abc.ABC):
|
|||||||
self.config: Config = config
|
self.config: Config = config
|
||||||
"""全局配置对象"""
|
"""全局配置对象"""
|
||||||
self._bots: dict[str, "Bot"] = {}
|
self._bots: dict[str, "Bot"] = {}
|
||||||
self._bot_tasks: set[asyncio.Task] = set()
|
|
||||||
self._lifespan = Lifespan()
|
self._lifespan = Lifespan()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@@ -75,6 +77,10 @@ class Driver(abc.ABC):
|
|||||||
"""获取当前所有已连接的 Bot"""
|
"""获取当前所有已连接的 Bot"""
|
||||||
return self._bots
|
return self._bots
|
||||||
|
|
||||||
|
@property
|
||||||
|
def task_group(self) -> TaskGroup:
|
||||||
|
return self._lifespan.task_group
|
||||||
|
|
||||||
def register_adapter(self, adapter: type["Adapter"], **kwargs) -> None:
|
def register_adapter(self, adapter: type["Adapter"], **kwargs) -> None:
|
||||||
"""注册一个协议适配器
|
"""注册一个协议适配器
|
||||||
|
|
||||||
@@ -112,8 +118,6 @@ class Driver(abc.ABC):
|
|||||||
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
|
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.on_shutdown(self._cleanup)
|
|
||||||
|
|
||||||
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
"""注册一个启动时执行的函数"""
|
"""注册一个启动时执行的函数"""
|
||||||
return self._lifespan.on_startup(func)
|
return self._lifespan.on_startup(func)
|
||||||
@@ -154,66 +158,63 @@ class Driver(abc.ABC):
|
|||||||
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
||||||
self._bots[bot.self_id] = bot
|
self._bots[bot.self_id] = bot
|
||||||
|
|
||||||
|
if not self._bot_connection_hook:
|
||||||
|
return
|
||||||
|
|
||||||
|
def handle_exception(exc_group: BaseExceptionGroup) -> None:
|
||||||
|
for exc in flatten_exception_group(exc_group):
|
||||||
|
logger.opt(colors=True, exception=exc).error(
|
||||||
|
"<r><bg #f8bbd0>"
|
||||||
|
"Error when running WebSocketConnection hook:"
|
||||||
|
"</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
async def _run_hook(bot: "Bot") -> None:
|
async def _run_hook(bot: "Bot") -> None:
|
||||||
dependency_cache: T_DependencyCache = {}
|
dependency_cache: T_DependencyCache = {}
|
||||||
async with AsyncExitStack() as stack:
|
with CancelScope(shield=True), catch({Exception: handle_exception}):
|
||||||
if coros := [
|
async with AsyncExitStack() as stack, create_task_group() as tg:
|
||||||
run_coro_with_catch(
|
for hook in self._bot_connection_hook:
|
||||||
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
|
tg.start_soon(
|
||||||
(SkippedException,),
|
run_coro_with_catch,
|
||||||
)
|
hook(
|
||||||
for hook in self._bot_connection_hook
|
bot=bot, stack=stack, dependency_cache=dependency_cache
|
||||||
]:
|
),
|
||||||
try:
|
(SkippedException,),
|
||||||
await asyncio.gather(*coros)
|
|
||||||
except Exception as e:
|
|
||||||
logger.opt(colors=True, exception=e).error(
|
|
||||||
"<r><bg #f8bbd0>"
|
|
||||||
"Error when running WebSocketConnection hook. "
|
|
||||||
"Running cancelled!"
|
|
||||||
"</bg #f8bbd0></r>"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
task = asyncio.create_task(_run_hook(bot))
|
self.task_group.start_soon(_run_hook, bot)
|
||||||
task.add_done_callback(self._bot_tasks.discard)
|
|
||||||
self._bot_tasks.add(task)
|
|
||||||
|
|
||||||
def _bot_disconnect(self, bot: "Bot") -> None:
|
def _bot_disconnect(self, bot: "Bot") -> None:
|
||||||
"""在连接断开后,调用该函数来注销 bot 对象"""
|
"""在连接断开后,调用该函数来注销 bot 对象"""
|
||||||
if bot.self_id in self._bots:
|
if bot.self_id in self._bots:
|
||||||
del self._bots[bot.self_id]
|
del self._bots[bot.self_id]
|
||||||
|
|
||||||
|
if not self._bot_disconnection_hook:
|
||||||
|
return
|
||||||
|
|
||||||
|
def handle_exception(exc_group: BaseExceptionGroup) -> None:
|
||||||
|
for exc in flatten_exception_group(exc_group):
|
||||||
|
logger.opt(colors=True, exception=exc).error(
|
||||||
|
"<r><bg #f8bbd0>"
|
||||||
|
"Error when running WebSocketDisConnection hook:"
|
||||||
|
"</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
async def _run_hook(bot: "Bot") -> None:
|
async def _run_hook(bot: "Bot") -> None:
|
||||||
dependency_cache: T_DependencyCache = {}
|
dependency_cache: T_DependencyCache = {}
|
||||||
async with AsyncExitStack() as stack:
|
# shield cancellation to ensure bot disconnect hooks are always run
|
||||||
if coros := [
|
with CancelScope(shield=True), catch({Exception: handle_exception}):
|
||||||
run_coro_with_catch(
|
async with create_task_group() as tg, AsyncExitStack() as stack:
|
||||||
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
|
for hook in self._bot_disconnection_hook:
|
||||||
(SkippedException,),
|
tg.start_soon(
|
||||||
)
|
run_coro_with_catch,
|
||||||
for hook in self._bot_disconnection_hook
|
hook(
|
||||||
]:
|
bot=bot, stack=stack, dependency_cache=dependency_cache
|
||||||
try:
|
),
|
||||||
await asyncio.gather(*coros)
|
(SkippedException,),
|
||||||
except Exception as e:
|
|
||||||
logger.opt(colors=True, exception=e).error(
|
|
||||||
"<r><bg #f8bbd0>"
|
|
||||||
"Error when running WebSocketDisConnection hook. "
|
|
||||||
"Running cancelled!"
|
|
||||||
"</bg #f8bbd0></r>"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
task = asyncio.create_task(_run_hook(bot))
|
self.task_group.start_soon(_run_hook, bot)
|
||||||
task.add_done_callback(self._bot_tasks.discard)
|
|
||||||
self._bot_tasks.add(task)
|
|
||||||
|
|
||||||
async def _cleanup(self) -> None:
|
|
||||||
"""清理驱动器资源"""
|
|
||||||
if self._bot_tasks:
|
|
||||||
logger.opt(colors=True).debug(
|
|
||||||
"<y>Waiting for running bot connection hooks...</y>"
|
|
||||||
)
|
|
||||||
await asyncio.gather(*self._bot_tasks, return_exceptions=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Mixin(abc.ABC):
|
class Mixin(abc.ABC):
|
||||||
|
@@ -15,7 +15,7 @@ def combine_driver(driver: type[D]) -> type[D]: ...
|
|||||||
|
|
||||||
@overload
|
@overload
|
||||||
def combine_driver(
|
def combine_driver(
|
||||||
driver: type[D], _m: type[Mixin], *mixins: type[Mixin]
|
driver: type[D], __m: type[Mixin], /, *mixins: type[Mixin]
|
||||||
) -> type["CombinedDriver"]: ...
|
) -> type["CombinedDriver"]: ...
|
||||||
|
|
||||||
|
|
||||||
|
@@ -22,11 +22,13 @@ from typing import ( # noqa: UP035
|
|||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.internal.rule import Rule
|
from nonebot.internal.rule import Rule
|
||||||
from nonebot.utils import classproperty
|
|
||||||
from nonebot.dependencies import Param, Dependent
|
from nonebot.dependencies import Param, Dependent
|
||||||
from nonebot.internal.permission import User, Permission
|
from nonebot.internal.permission import User, Permission
|
||||||
|
from nonebot.utils import classproperty, flatten_exception_group
|
||||||
from nonebot.internal.adapter import (
|
from nonebot.internal.adapter import (
|
||||||
Bot,
|
Bot,
|
||||||
Event,
|
Event,
|
||||||
@@ -76,7 +78,7 @@ T = TypeVar("T")
|
|||||||
current_bot: ContextVar[Bot] = ContextVar("current_bot")
|
current_bot: ContextVar[Bot] = ContextVar("current_bot")
|
||||||
current_event: ContextVar[Event] = ContextVar("current_event")
|
current_event: ContextVar[Event] = ContextVar("current_event")
|
||||||
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
||||||
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
current_handler: ContextVar[Dependent[Any]] = ContextVar("current_handler")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -812,28 +814,34 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
f"bot={bot}, event={event!r}, state={state!r}"
|
f"bot={bot}, event={event!r}, state={state!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _handle_stop_propagation(exc_group: BaseExceptionGroup[StopPropagation]):
|
||||||
|
self.block = True
|
||||||
|
|
||||||
with self.ensure_context(bot, event):
|
with self.ensure_context(bot, event):
|
||||||
try:
|
try:
|
||||||
# Refresh preprocess state
|
with catch({StopPropagation: _handle_stop_propagation}):
|
||||||
self.state.update(state)
|
# Refresh preprocess state
|
||||||
|
self.state.update(state)
|
||||||
|
|
||||||
while self.remain_handlers:
|
while self.remain_handlers:
|
||||||
handler = self.remain_handlers.pop(0)
|
handler = self.remain_handlers.pop(0)
|
||||||
current_handler.set(handler)
|
current_handler.set(handler)
|
||||||
logger.debug(f"Running handler {handler}")
|
logger.debug(f"Running handler {handler}")
|
||||||
try:
|
|
||||||
await handler(
|
def _handle_skipped(
|
||||||
matcher=self,
|
exc_group: BaseExceptionGroup[SkippedException],
|
||||||
bot=bot,
|
):
|
||||||
event=event,
|
logger.debug(f"Handler {handler} skipped")
|
||||||
state=self.state,
|
|
||||||
stack=stack,
|
with catch({SkippedException: _handle_skipped}):
|
||||||
dependency_cache=dependency_cache,
|
await handler(
|
||||||
)
|
matcher=self,
|
||||||
except SkippedException:
|
bot=bot,
|
||||||
logger.debug(f"Handler {handler} skipped")
|
event=event,
|
||||||
except StopPropagation:
|
state=self.state,
|
||||||
self.block = True
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
logger.info(f"{self} running complete")
|
logger.info(f"{self} running complete")
|
||||||
|
|
||||||
@@ -846,10 +854,54 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
stack: Optional[AsyncExitStack] = None,
|
stack: Optional[AsyncExitStack] = None,
|
||||||
dependency_cache: Optional[T_DependencyCache] = None,
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
):
|
):
|
||||||
try:
|
exc: Optional[Union[FinishedException, RejectedException, PausedException]] = (
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_special_exception(
|
||||||
|
exc_group: BaseExceptionGroup[
|
||||||
|
Union[FinishedException, RejectedException, PausedException]
|
||||||
|
]
|
||||||
|
):
|
||||||
|
nonlocal exc
|
||||||
|
excs = list(flatten_exception_group(exc_group))
|
||||||
|
if len(excs) > 1:
|
||||||
|
logger.warning(
|
||||||
|
"Multiple session control exceptions occurred. "
|
||||||
|
"NoneBot will choose the proper one."
|
||||||
|
)
|
||||||
|
finished_exc = next(
|
||||||
|
(e for e in excs if isinstance(e, FinishedException)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
rejected_exc = next(
|
||||||
|
(e for e in excs if isinstance(e, RejectedException)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
paused_exc = next(
|
||||||
|
(e for e in excs if isinstance(e, PausedException)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
exc = finished_exc or rejected_exc or paused_exc
|
||||||
|
elif isinstance(
|
||||||
|
excs[0], (FinishedException, RejectedException, PausedException)
|
||||||
|
):
|
||||||
|
exc = excs[0]
|
||||||
|
|
||||||
|
with catch(
|
||||||
|
{
|
||||||
|
(
|
||||||
|
FinishedException,
|
||||||
|
RejectedException,
|
||||||
|
PausedException,
|
||||||
|
): _handle_special_exception
|
||||||
|
}
|
||||||
|
):
|
||||||
await self.simple_run(bot, event, state, stack, dependency_cache)
|
await self.simple_run(bot, event, state, stack, dependency_cache)
|
||||||
|
|
||||||
except RejectedException:
|
if isinstance(exc, FinishedException):
|
||||||
|
pass
|
||||||
|
elif isinstance(exc, RejectedException):
|
||||||
await self.resolve_reject()
|
await self.resolve_reject()
|
||||||
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
||||||
permission = await self.update_permission(
|
permission = await self.update_permission(
|
||||||
@@ -870,7 +922,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
default_type_updater=self.__class__._default_type_updater,
|
default_type_updater=self.__class__._default_type_updater,
|
||||||
default_permission_updater=self.__class__._default_permission_updater,
|
default_permission_updater=self.__class__._default_permission_updater,
|
||||||
)
|
)
|
||||||
except PausedException:
|
elif isinstance(exc, PausedException):
|
||||||
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
||||||
permission = await self.update_permission(
|
permission = await self.update_permission(
|
||||||
bot, event, stack, dependency_cache
|
bot, event, stack, dependency_cache
|
||||||
@@ -890,5 +942,3 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
default_type_updater=self.__class__._default_type_updater,
|
default_type_updater=self.__class__._default_type_updater,
|
||||||
default_permission_updater=self.__class__._default_permission_updater,
|
default_permission_updater=self.__class__._default_permission_updater,
|
||||||
)
|
)
|
||||||
except FinishedException:
|
|
||||||
pass
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import asyncio
|
|
||||||
import inspect
|
import inspect
|
||||||
|
from enum import Enum
|
||||||
from typing_extensions import Self, get_args, override, get_origin
|
from typing_extensions import Self, get_args, override, get_origin
|
||||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||||
from typing import (
|
from typing import (
|
||||||
@@ -13,8 +13,11 @@ from typing import (
|
|||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
||||||
|
|
||||||
|
from nonebot.exception import SkippedException
|
||||||
from nonebot.dependencies import Param, Dependent
|
from nonebot.dependencies import Param, Dependent
|
||||||
from nonebot.dependencies.utils import check_field_type
|
from nonebot.dependencies.utils import check_field_type
|
||||||
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
|
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
|
||||||
@@ -93,6 +96,78 @@ def Depends(
|
|||||||
return DependsInner(dependency, use_cache=use_cache, validate=validate)
|
return DependsInner(dependency, use_cache=use_cache, validate=validate)
|
||||||
|
|
||||||
|
|
||||||
|
class CacheState(str, Enum):
|
||||||
|
"""子依赖缓存状态"""
|
||||||
|
|
||||||
|
PENDING = "PENDING"
|
||||||
|
FINISHED = "FINISHED"
|
||||||
|
|
||||||
|
|
||||||
|
class DependencyCache:
|
||||||
|
"""子依赖结果缓存。
|
||||||
|
|
||||||
|
用于缓存子依赖的结果,以避免重复计算。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._state = CacheState.PENDING
|
||||||
|
self._result: Any = None
|
||||||
|
self._exception: Optional[BaseException] = None
|
||||||
|
self._waiter = anyio.Event()
|
||||||
|
|
||||||
|
def done(self) -> bool:
|
||||||
|
return self._state == CacheState.FINISHED
|
||||||
|
|
||||||
|
def result(self) -> Any:
|
||||||
|
"""获取子依赖结果"""
|
||||||
|
|
||||||
|
if self._state != CacheState.FINISHED:
|
||||||
|
raise RuntimeError("Result is not ready")
|
||||||
|
|
||||||
|
if self._exception is not None:
|
||||||
|
raise self._exception
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
def exception(self) -> Optional[BaseException]:
|
||||||
|
"""获取子依赖异常"""
|
||||||
|
|
||||||
|
if self._state != CacheState.FINISHED:
|
||||||
|
raise RuntimeError("Result is not ready")
|
||||||
|
|
||||||
|
return self._exception
|
||||||
|
|
||||||
|
def set_result(self, result: Any) -> None:
|
||||||
|
"""设置子依赖结果"""
|
||||||
|
|
||||||
|
if self._state != CacheState.PENDING:
|
||||||
|
raise RuntimeError(f"Cache state invalid: {self._state}")
|
||||||
|
|
||||||
|
self._result = result
|
||||||
|
self._state = CacheState.FINISHED
|
||||||
|
self._waiter.set()
|
||||||
|
|
||||||
|
def set_exception(self, exception: BaseException) -> None:
|
||||||
|
"""设置子依赖异常"""
|
||||||
|
|
||||||
|
if self._state != CacheState.PENDING:
|
||||||
|
raise RuntimeError(f"Cache state invalid: {self._state}")
|
||||||
|
|
||||||
|
self._exception = exception
|
||||||
|
self._state = CacheState.FINISHED
|
||||||
|
self._waiter.set()
|
||||||
|
|
||||||
|
async def wait(self):
|
||||||
|
"""等待子依赖结果"""
|
||||||
|
await self._waiter.wait()
|
||||||
|
if self._state != CacheState.FINISHED:
|
||||||
|
raise RuntimeError("Invalid cache state")
|
||||||
|
|
||||||
|
if self._exception is not None:
|
||||||
|
raise self._exception
|
||||||
|
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
|
||||||
class DependParam(Param):
|
class DependParam(Param):
|
||||||
"""子依赖注入参数。
|
"""子依赖注入参数。
|
||||||
|
|
||||||
@@ -102,7 +177,7 @@ class DependParam(Param):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, *args, dependent: Dependent, use_cache: bool, **kwargs: Any
|
self, *args, dependent: Dependent[Any], use_cache: bool, **kwargs: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.dependent = dependent
|
self.dependent = dependent
|
||||||
@@ -114,7 +189,7 @@ class DependParam(Param):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _from_field(
|
def _from_field(
|
||||||
cls,
|
cls,
|
||||||
sub_dependent: Dependent,
|
sub_dependent: Dependent[Any],
|
||||||
use_cache: bool,
|
use_cache: bool,
|
||||||
validate: Union[bool, PydanticFieldInfo],
|
validate: Union[bool, PydanticFieldInfo],
|
||||||
) -> Self:
|
) -> Self:
|
||||||
@@ -190,21 +265,31 @@ class DependParam(Param):
|
|||||||
use_cache: bool = self.use_cache
|
use_cache: bool = self.use_cache
|
||||||
dependency_cache = {} if dependency_cache is None else dependency_cache
|
dependency_cache = {} if dependency_cache is None else dependency_cache
|
||||||
|
|
||||||
sub_dependent: Dependent = self.dependent
|
sub_dependent = self.dependent
|
||||||
call = cast(Callable[..., Any], sub_dependent.call)
|
call = cast(Callable[..., Any], sub_dependent.call)
|
||||||
|
|
||||||
# solve sub dependency with current cache
|
# solve sub dependency with current cache
|
||||||
sub_values = await sub_dependent.solve(
|
exc: Optional[BaseExceptionGroup[SkippedException]] = None
|
||||||
stack=stack,
|
|
||||||
dependency_cache=dependency_cache,
|
def _handle_skipped(exc_group: BaseExceptionGroup[SkippedException]):
|
||||||
**kwargs,
|
nonlocal exc
|
||||||
)
|
exc = exc_group
|
||||||
|
|
||||||
|
with catch({SkippedException: _handle_skipped}):
|
||||||
|
sub_values = await sub_dependent.solve(
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
if exc is not None:
|
||||||
|
raise exc
|
||||||
|
|
||||||
# run dependency function
|
# run dependency function
|
||||||
task: asyncio.Task[Any]
|
|
||||||
if use_cache and call in dependency_cache:
|
if use_cache and call in dependency_cache:
|
||||||
return await dependency_cache[call]
|
return await dependency_cache[call].wait()
|
||||||
elif is_gen_callable(call) or is_async_gen_callable(call):
|
|
||||||
|
if is_gen_callable(call) or is_async_gen_callable(call):
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
stack, AsyncExitStack
|
stack, AsyncExitStack
|
||||||
), "Generator dependency should be called in context"
|
), "Generator dependency should be called in context"
|
||||||
@@ -212,17 +297,28 @@ class DependParam(Param):
|
|||||||
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
||||||
else:
|
else:
|
||||||
cm = asynccontextmanager(call)(**sub_values)
|
cm = asynccontextmanager(call)(**sub_values)
|
||||||
task = asyncio.create_task(stack.enter_async_context(cm))
|
|
||||||
dependency_cache[call] = task
|
target = stack.enter_async_context(cm)
|
||||||
return await task
|
|
||||||
elif is_coroutine_callable(call):
|
elif is_coroutine_callable(call):
|
||||||
task = asyncio.create_task(call(**sub_values))
|
target = call(**sub_values)
|
||||||
dependency_cache[call] = task
|
|
||||||
return await task
|
|
||||||
else:
|
else:
|
||||||
task = asyncio.create_task(run_sync(call)(**sub_values))
|
target = run_sync(call)(**sub_values)
|
||||||
dependency_cache[call] = task
|
|
||||||
return await task
|
dependency_cache[call] = cache = DependencyCache()
|
||||||
|
try:
|
||||||
|
result = await target
|
||||||
|
except Exception as e:
|
||||||
|
cache.set_exception(e)
|
||||||
|
raise
|
||||||
|
except BaseException as e:
|
||||||
|
cache.set_exception(e)
|
||||||
|
# remove cache when base exception occurs
|
||||||
|
# e.g. CancelledError
|
||||||
|
dependency_cache.pop(call, None)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
cache.set_result(result)
|
||||||
|
return result
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _check(self, **kwargs: Any) -> None:
|
async def _check(self, **kwargs: Any) -> None:
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import asyncio
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import Union, ClassVar, NoReturn, Optional
|
from typing import Union, ClassVar, NoReturn, Optional
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.utils import run_coro_with_catch
|
from nonebot.utils import run_coro_with_catch
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
@@ -70,22 +71,26 @@ class Permission:
|
|||||||
"""
|
"""
|
||||||
if not self.checkers:
|
if not self.checkers:
|
||||||
return True
|
return True
|
||||||
results = await asyncio.gather(
|
|
||||||
*(
|
result = False
|
||||||
run_coro_with_catch(
|
|
||||||
checker(
|
async def _run_checker(checker: Dependent[bool]) -> None:
|
||||||
bot=bot,
|
nonlocal result
|
||||||
event=event,
|
# calculate the result first to avoid data racing
|
||||||
stack=stack,
|
is_passed = await run_coro_with_catch(
|
||||||
dependency_cache=dependency_cache,
|
checker(
|
||||||
),
|
bot=bot, event=event, stack=stack, dependency_cache=dependency_cache
|
||||||
(SkippedException,),
|
),
|
||||||
False,
|
(SkippedException,),
|
||||||
)
|
False,
|
||||||
for checker in self.checkers
|
)
|
||||||
),
|
result |= is_passed
|
||||||
)
|
|
||||||
return any(results)
|
async with anyio.create_task_group() as tg:
|
||||||
|
for checker in self.checkers:
|
||||||
|
tg.start_soon(_run_checker, checker)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def __and__(self, other: object) -> NoReturn:
|
def __and__(self, other: object) -> NoReturn:
|
||||||
raise RuntimeError("And operation between Permissions is not allowed.")
|
raise RuntimeError("And operation between Permissions is not allowed.")
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import asyncio
|
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import Union, ClassVar, NoReturn, Optional
|
from typing import Union, ClassVar, NoReturn, Optional
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
||||||
@@ -71,22 +73,33 @@ class Rule:
|
|||||||
"""
|
"""
|
||||||
if not self.checkers:
|
if not self.checkers:
|
||||||
return True
|
return True
|
||||||
try:
|
|
||||||
results = await asyncio.gather(
|
result = True
|
||||||
*(
|
|
||||||
checker(
|
def _handle_skipped_exception(
|
||||||
bot=bot,
|
exc_group: BaseExceptionGroup[SkippedException],
|
||||||
event=event,
|
) -> None:
|
||||||
state=state,
|
nonlocal result
|
||||||
stack=stack,
|
result = False
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
)
|
async def _run_checker(checker: Dependent[bool]) -> None:
|
||||||
for checker in self.checkers
|
nonlocal result
|
||||||
)
|
# calculate the result first to avoid data racing
|
||||||
|
is_passed = await checker(
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
state=state,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
)
|
)
|
||||||
except SkippedException:
|
result &= is_passed
|
||||||
return False
|
|
||||||
return all(results)
|
with catch({SkippedException: _handle_skipped_exception}):
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for checker in self.checkers:
|
||||||
|
tg.start_soon(_run_checker, checker)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
|
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
|
||||||
if other is None:
|
if other is None:
|
||||||
|
@@ -8,6 +8,8 @@ NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
|
|||||||
[loguru]: https://github.com/Delgan/loguru
|
[loguru]: https://github.com/Delgan/loguru
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 7
|
sidebar_position: 7
|
||||||
description: nonebot.log 模块
|
description: nonebot.log 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.matcher 模块
|
description: nonebot.matcher 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -3,27 +3,36 @@
|
|||||||
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
|
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.message 模块
|
description: nonebot.message 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import contextlib
|
import contextlib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Callable, Optional
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.rule import TrieRule
|
from nonebot.rule import TrieRule
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.matcher import Matcher, matchers
|
from nonebot.matcher import Matcher, matchers
|
||||||
from nonebot.utils import escape_tag, run_coro_with_catch
|
|
||||||
from nonebot.exception import (
|
from nonebot.exception import (
|
||||||
NoLogException,
|
NoLogException,
|
||||||
StopPropagation,
|
StopPropagation,
|
||||||
IgnoredException,
|
IgnoredException,
|
||||||
SkippedException,
|
SkippedException,
|
||||||
)
|
)
|
||||||
|
from nonebot.utils import (
|
||||||
|
escape_tag,
|
||||||
|
run_coro_with_catch,
|
||||||
|
run_coro_with_shield,
|
||||||
|
flatten_exception_group,
|
||||||
|
)
|
||||||
from nonebot.typing import (
|
from nonebot.typing import (
|
||||||
T_State,
|
T_State,
|
||||||
T_DependencyCache,
|
T_DependencyCache,
|
||||||
@@ -123,6 +132,21 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
|||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_ignored_exception(msg: str) -> Callable[[BaseExceptionGroup], None]:
|
||||||
|
def _handle(exc_group: BaseExceptionGroup[IgnoredException]) -> None:
|
||||||
|
logger.opt(colors=True).info(msg)
|
||||||
|
|
||||||
|
return _handle
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_exception(msg: str) -> Callable[[BaseExceptionGroup], None]:
|
||||||
|
def _handle(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||||
|
for exc in flatten_exception_group(exc_group):
|
||||||
|
logger.opt(colors=True, exception=exc).error(msg)
|
||||||
|
|
||||||
|
return _handle
|
||||||
|
|
||||||
|
|
||||||
async def _apply_event_preprocessors(
|
async def _apply_event_preprocessors(
|
||||||
bot: "Bot",
|
bot: "Bot",
|
||||||
event: "Event",
|
event: "Event",
|
||||||
@@ -150,10 +174,21 @@ async def _apply_event_preprocessors(
|
|||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Running PreProcessors...")
|
logger.debug("Running PreProcessors...")
|
||||||
|
|
||||||
try:
|
with catch(
|
||||||
await asyncio.gather(
|
{
|
||||||
*(
|
IgnoredException: _handle_ignored_exception(
|
||||||
run_coro_with_catch(
|
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
||||||
|
),
|
||||||
|
Exception: _handle_exception(
|
||||||
|
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||||
|
"Event ignored!</bg #f8bbd0></r>"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
):
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for proc in _event_preprocessors:
|
||||||
|
tg.start_soon(
|
||||||
|
run_coro_with_catch,
|
||||||
proc(
|
proc(
|
||||||
bot=bot,
|
bot=bot,
|
||||||
event=event,
|
event=event,
|
||||||
@@ -163,22 +198,10 @@ async def _apply_event_preprocessors(
|
|||||||
),
|
),
|
||||||
(SkippedException,),
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
for proc in _event_preprocessors
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except IgnoredException:
|
|
||||||
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
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def _apply_event_postprocessors(
|
async def _apply_event_postprocessors(
|
||||||
@@ -205,10 +228,17 @@ async def _apply_event_postprocessors(
|
|||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Running PostProcessors...")
|
logger.debug("Running PostProcessors...")
|
||||||
|
|
||||||
try:
|
with catch(
|
||||||
await asyncio.gather(
|
{
|
||||||
*(
|
Exception: _handle_exception(
|
||||||
run_coro_with_catch(
|
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
):
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for proc in _event_postprocessors:
|
||||||
|
tg.start_soon(
|
||||||
|
run_coro_with_catch,
|
||||||
proc(
|
proc(
|
||||||
bot=bot,
|
bot=bot,
|
||||||
event=event,
|
event=event,
|
||||||
@@ -218,13 +248,6 @@ async def _apply_event_postprocessors(
|
|||||||
),
|
),
|
||||||
(SkippedException,),
|
(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(
|
async def _apply_run_preprocessors(
|
||||||
@@ -252,35 +275,38 @@ async def _apply_run_preprocessors(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# ensure matcher function can be correctly called
|
# ensure matcher function can be correctly called
|
||||||
with matcher.ensure_context(bot, event):
|
with (
|
||||||
try:
|
matcher.ensure_context(bot, event),
|
||||||
await asyncio.gather(
|
catch(
|
||||||
*(
|
{
|
||||||
run_coro_with_catch(
|
IgnoredException: _handle_ignored_exception(
|
||||||
proc(
|
f"{matcher} running is <b>cancelled</b>"
|
||||||
matcher=matcher,
|
),
|
||||||
bot=bot,
|
Exception: _handle_exception(
|
||||||
event=event,
|
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
||||||
state=state,
|
"Running cancelled!</bg #f8bbd0></r>"
|
||||||
stack=stack,
|
),
|
||||||
dependency_cache=dependency_cache,
|
}
|
||||||
),
|
),
|
||||||
(SkippedException,),
|
):
|
||||||
)
|
async with anyio.create_task_group() as tg:
|
||||||
for proc in _run_preprocessors
|
for proc in _run_preprocessors:
|
||||||
|
tg.start_soon(
|
||||||
|
run_coro_with_catch,
|
||||||
|
proc(
|
||||||
|
matcher=matcher,
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
state=state,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
),
|
||||||
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
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
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def _apply_run_postprocessors(
|
async def _apply_run_postprocessors(
|
||||||
@@ -304,29 +330,32 @@ async def _apply_run_postprocessors(
|
|||||||
if not _run_postprocessors:
|
if not _run_postprocessors:
|
||||||
return
|
return
|
||||||
|
|
||||||
with matcher.ensure_context(bot, event):
|
with (
|
||||||
try:
|
matcher.ensure_context(bot, event),
|
||||||
await asyncio.gather(
|
catch(
|
||||||
*(
|
{
|
||||||
run_coro_with_catch(
|
Exception: _handle_exception(
|
||||||
proc(
|
"<r><bg #f8bbd0>Error when running RunPostProcessors"
|
||||||
matcher=matcher,
|
"</bg #f8bbd0></r>"
|
||||||
exception=exception,
|
)
|
||||||
bot=bot,
|
}
|
||||||
event=event,
|
),
|
||||||
state=matcher.state,
|
):
|
||||||
stack=stack,
|
async with anyio.create_task_group() as tg:
|
||||||
dependency_cache=dependency_cache,
|
for proc in _run_postprocessors:
|
||||||
),
|
tg.start_soon(
|
||||||
(SkippedException,),
|
run_coro_with_catch,
|
||||||
)
|
proc(
|
||||||
for proc in _run_postprocessors
|
matcher=matcher,
|
||||||
|
exception=exception,
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
state=matcher.state,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
),
|
||||||
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
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(
|
async def _check_matcher(
|
||||||
@@ -423,8 +452,9 @@ async def _run_matcher(
|
|||||||
|
|
||||||
exception = None
|
exception = None
|
||||||
|
|
||||||
|
logger.debug(f"Running {matcher}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Running {matcher}")
|
|
||||||
await matcher.run(bot, event, state, stack, dependency_cache)
|
await matcher.run(bot, event, state, stack, dependency_cache)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
@@ -492,8 +522,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
import asyncio
|
driver.task_group.start_soon(handle_event, bot, event)
|
||||||
asyncio.create_task(handle_event(bot, event))
|
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
show_log = True
|
show_log = True
|
||||||
@@ -528,6 +557,13 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
break_flag = False
|
break_flag = False
|
||||||
|
|
||||||
|
def _handle_stop_propagation(exc_group: BaseExceptionGroup) -> None:
|
||||||
|
nonlocal break_flag
|
||||||
|
|
||||||
|
break_flag = True
|
||||||
|
logger.debug("Stop event propagation")
|
||||||
|
|
||||||
# iterate through all priority until stop propagation
|
# iterate through all priority until stop propagation
|
||||||
for priority in sorted(matchers.keys()):
|
for priority in sorted(matchers.keys()):
|
||||||
if break_flag:
|
if break_flag:
|
||||||
@@ -536,23 +572,30 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
if show_log:
|
if show_log:
|
||||||
logger.debug(f"Checking for matchers in priority {priority}...")
|
logger.debug(f"Checking for matchers in priority {priority}...")
|
||||||
|
|
||||||
pending_tasks = [
|
if not (priority_matchers := matchers[priority]):
|
||||||
check_and_run_matcher(
|
continue
|
||||||
matcher, bot, event, state.copy(), stack, dependency_cache
|
|
||||||
)
|
with catch(
|
||||||
for matcher in matchers[priority]
|
{
|
||||||
]
|
StopPropagation: _handle_stop_propagation,
|
||||||
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
|
Exception: _handle_exception(
|
||||||
for result in results:
|
|
||||||
if not isinstance(result, Exception):
|
|
||||||
continue
|
|
||||||
if isinstance(result, StopPropagation):
|
|
||||||
break_flag = True
|
|
||||||
logger.debug("Stop event propagation")
|
|
||||||
else:
|
|
||||||
logger.opt(colors=True, exception=result).error(
|
|
||||||
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
||||||
)
|
),
|
||||||
|
}
|
||||||
|
):
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for matcher in priority_matchers:
|
||||||
|
tg.start_soon(
|
||||||
|
run_coro_with_shield,
|
||||||
|
check_and_run_matcher(
|
||||||
|
matcher,
|
||||||
|
bot,
|
||||||
|
event,
|
||||||
|
state.copy(),
|
||||||
|
stack,
|
||||||
|
dependency_cache,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Checking for matchers completed")
|
logger.debug("Checking for matchers completed")
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""本模块定义了依赖注入的各类参数。
|
"""本模块定义了依赖注入的各类参数。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 4
|
sidebar_position: 4
|
||||||
description: nonebot.params 模块
|
description: nonebot.params 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -5,6 +5,8 @@
|
|||||||
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 6
|
sidebar_position: 6
|
||||||
description: nonebot.permission 模块
|
description: nonebot.permission 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -32,6 +32,8 @@
|
|||||||
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.model.PluginMetadata>`
|
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.model.PluginMetadata>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.plugin 模块
|
description: nonebot.plugin 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""本模块定义插件加载接口。
|
"""本模块定义插件加载接口。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.plugin.load 模块
|
description: nonebot.plugin.load 模块
|
||||||
"""
|
"""
|
||||||
@@ -20,7 +22,7 @@ from . import _managers, get_plugin, _module_name_to_plugin_id
|
|||||||
try: # pragma: py-gte-311
|
try: # pragma: py-gte-311
|
||||||
import tomllib # pyright: ignore[reportMissingImports]
|
import tomllib # pyright: ignore[reportMissingImports]
|
||||||
except ModuleNotFoundError: # pragma: py-lt-311
|
except ModuleNotFoundError: # pragma: py-lt-311
|
||||||
import tomli as tomllib
|
import tomli as tomllib # pyright: ignore[reportMissingImports]
|
||||||
|
|
||||||
|
|
||||||
def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
参考: [import hooks](https://docs.python.org/3/reference/import.html#import-hooks), [PEP302](https://www.python.org/dev/peps/pep-0302/)
|
参考: [import hooks](https://docs.python.org/3/reference/import.html#import-hooks), [PEP302](https://www.python.org/dev/peps/pep-0302/)
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.plugin.manager 模块
|
description: nonebot.plugin.manager 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""本模块定义插件相关信息。
|
"""本模块定义插件相关信息。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.plugin.model 模块
|
description: nonebot.plugin.model 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""本模块定义事件响应器便携定义函数。
|
"""本模块定义事件响应器便携定义函数。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.plugin.on 模块
|
description: nonebot.plugin.on 模块
|
||||||
"""
|
"""
|
||||||
@@ -114,7 +116,7 @@ def on(
|
|||||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = None,
|
permission: Optional[Union[Permission, T_PermissionChecker]] = None,
|
||||||
*,
|
*,
|
||||||
handlers: Optional[list[Union[T_Handler, Dependent]]] = None,
|
handlers: Optional[list[Union[T_Handler, Dependent[Any]]]] = None,
|
||||||
temp: bool = False,
|
temp: bool = False,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = None,
|
expire_time: Optional[Union[datetime, timedelta]] = None,
|
||||||
priority: int = 1,
|
priority: int = 1,
|
||||||
|
@@ -21,7 +21,7 @@ def on(
|
|||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -32,7 +32,7 @@ def on_metaevent(
|
|||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -43,7 +43,7 @@ def on_message(
|
|||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -54,7 +54,7 @@ def on_notice(
|
|||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -65,7 +65,7 @@ def on_request(
|
|||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -78,7 +78,7 @@ def on_startswith(
|
|||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
*,
|
*,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -91,7 +91,7 @@ def on_endswith(
|
|||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
*,
|
*,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -104,7 +104,7 @@ def on_fullmatch(
|
|||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
*,
|
*,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -116,7 +116,7 @@ def on_keyword(
|
|||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -130,7 +130,7 @@ def on_command(
|
|||||||
force_whitespace: str | bool | None = ...,
|
force_whitespace: str | bool | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -144,7 +144,7 @@ def on_shell_command(
|
|||||||
parser: ArgumentParser | None = ...,
|
parser: ArgumentParser | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -157,7 +157,7 @@ def on_regex(
|
|||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -169,7 +169,7 @@ def on_type(
|
|||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -194,7 +194,7 @@ class CommandGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -209,7 +209,7 @@ class CommandGroup(_Group):
|
|||||||
aliases: set[str | tuple[str, ...]] | None = ...,
|
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||||
force_whitespace: str | bool | None = ...,
|
force_whitespace: str | bool | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -224,7 +224,7 @@ class CommandGroup(_Group):
|
|||||||
aliases: set[str | tuple[str, ...]] | None = ...,
|
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||||
parser: ArgumentParser | None = ...,
|
parser: ArgumentParser | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -239,7 +239,7 @@ class MatcherGroup(_Group):
|
|||||||
type: str = ...,
|
type: str = ...,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -252,7 +252,7 @@ class MatcherGroup(_Group):
|
|||||||
type: str = ...,
|
type: str = ...,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -264,7 +264,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -276,7 +276,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -288,7 +288,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -300,7 +300,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -314,7 +314,7 @@ class MatcherGroup(_Group):
|
|||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -328,7 +328,7 @@ class MatcherGroup(_Group):
|
|||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -342,7 +342,7 @@ class MatcherGroup(_Group):
|
|||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -355,7 +355,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -370,7 +370,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -385,7 +385,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -399,7 +399,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
@@ -412,7 +412,7 @@ class MatcherGroup(_Group):
|
|||||||
*,
|
*,
|
||||||
rule: Rule | T_RuleChecker | None = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Permission | T_PermissionChecker | None = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: list[T_Handler | Dependent] | None = ...,
|
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: datetime | timedelta | None = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
|
@@ -5,6 +5,8 @@
|
|||||||
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.rule 模块
|
description: nonebot.rule 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
[`typing`](https://docs.python.org/3/library/typing.html)。
|
[`typing`](https://docs.python.org/3/library/typing.html)。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 11
|
sidebar_position: 11
|
||||||
description: nonebot.typing 模块
|
description: nonebot.typing 模块
|
||||||
"""
|
"""
|
||||||
@@ -13,17 +15,15 @@ FrontMatter:
|
|||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
import contextlib
|
|
||||||
import typing as t
|
import typing as t
|
||||||
import typing_extensions as t_ext
|
import typing_extensions as t_ext
|
||||||
from typing import TYPE_CHECKING, TypeVar
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
from typing_extensions import ParamSpec, TypeAlias, get_args, override, get_origin
|
from typing_extensions import ParamSpec, TypeAlias, get_args, override, get_origin
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from asyncio import Task
|
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
|
from nonebot.internal.params import DependencyCache
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
@@ -86,9 +86,7 @@ def all_literal_values(type_: type[t.Any]) -> list[t.Any]:
|
|||||||
|
|
||||||
def origin_is_annotated(origin: t.Optional[type[t.Any]]) -> bool:
|
def origin_is_annotated(origin: t.Optional[type[t.Any]]) -> bool:
|
||||||
"""判断是否是 Annotated 类型"""
|
"""判断是否是 Annotated 类型"""
|
||||||
with contextlib.suppress(TypeError):
|
return origin is t_ext.Annotated
|
||||||
return origin is not None and issubclass(origin, t_ext.Annotated)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None]}
|
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None]}
|
||||||
@@ -259,5 +257,5 @@ T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "Task[t.Any]"]
|
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "DependencyCache"]
|
||||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||||
|
@@ -1,27 +1,30 @@
|
|||||||
"""本模块包含了 NoneBot 的一些工具函数
|
"""本模块包含了 NoneBot 的一些工具函数
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 8
|
sidebar_position: 8
|
||||||
description: nonebot.utils 模块
|
description: nonebot.utils 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import asyncio
|
|
||||||
import inspect
|
import inspect
|
||||||
import importlib
|
import importlib
|
||||||
import contextlib
|
import contextlib
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from contextvars import copy_context
|
|
||||||
from functools import wraps, partial
|
from functools import wraps, partial
|
||||||
from contextlib import AbstractContextManager, asynccontextmanager
|
from contextlib import AbstractContextManager, asynccontextmanager
|
||||||
from typing_extensions import ParamSpec, get_args, override, get_origin
|
from typing_extensions import ParamSpec, get_args, override, get_origin
|
||||||
from collections.abc import Mapping, Sequence, Coroutine, AsyncGenerator
|
|
||||||
from typing import Any, Union, Generic, TypeVar, Callable, Optional, overload
|
from typing import Any, Union, Generic, TypeVar, Callable, Optional, overload
|
||||||
|
from collections.abc import Mapping, Sequence, Coroutine, Generator, AsyncGenerator
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
import anyio.to_thread
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.typing import (
|
from nonebot.typing import (
|
||||||
@@ -37,6 +40,7 @@ R = TypeVar("R")
|
|||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
K = TypeVar("K")
|
K = TypeVar("K")
|
||||||
V = TypeVar("V")
|
V = TypeVar("V")
|
||||||
|
E = TypeVar("E", bound=BaseException)
|
||||||
|
|
||||||
|
|
||||||
def escape_tag(s: str) -> str:
|
def escape_tag(s: str) -> str:
|
||||||
@@ -176,11 +180,9 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
|
|||||||
|
|
||||||
@wraps(call)
|
@wraps(call)
|
||||||
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
loop = asyncio.get_running_loop()
|
return await anyio.to_thread.run_sync(
|
||||||
pfunc = partial(call, *args, **kwargs)
|
partial(call, *args, **kwargs), abandon_on_cancel=True
|
||||||
context = copy_context()
|
)
|
||||||
result = await loop.run_in_executor(None, partial(context.run, pfunc))
|
|
||||||
return result
|
|
||||||
|
|
||||||
return _wrapper
|
return _wrapper
|
||||||
|
|
||||||
@@ -232,10 +234,34 @@ async def run_coro_with_catch(
|
|||||||
协程的返回值或发生异常时的指定值
|
协程的返回值或发生异常时的指定值
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
with catch({exc: lambda exc_group: None}):
|
||||||
return await coro
|
return await coro
|
||||||
except exc:
|
|
||||||
return return_on_err
|
return return_on_err
|
||||||
|
|
||||||
|
|
||||||
|
async def run_coro_with_shield(coro: Coroutine[Any, Any, T]) -> T:
|
||||||
|
"""运行协程并在取消时屏蔽取消异常。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
coro: 要运行的协程
|
||||||
|
|
||||||
|
返回:
|
||||||
|
协程的返回值
|
||||||
|
"""
|
||||||
|
|
||||||
|
with anyio.CancelScope(shield=True):
|
||||||
|
return await coro
|
||||||
|
|
||||||
|
|
||||||
|
def flatten_exception_group(
|
||||||
|
exc_group: BaseExceptionGroup[E],
|
||||||
|
) -> Generator[E, None, None]:
|
||||||
|
for exc in exc_group.exceptions:
|
||||||
|
if isinstance(exc, BaseExceptionGroup):
|
||||||
|
yield from flatten_exception_group(exc)
|
||||||
|
else:
|
||||||
|
yield exc
|
||||||
|
|
||||||
|
|
||||||
def get_name(obj: Any) -> str:
|
def get_name(obj: Any) -> str:
|
||||||
|
2890
poetry.lock
generated
2890
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "nonebot2"
|
name = "nonebot2"
|
||||||
version = "2.3.2"
|
version = "2.4.0"
|
||||||
description = "An asynchronous python bot framework."
|
description = "An asynchronous python bot framework."
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -22,12 +22,14 @@ include = ["nonebot/py.typed"]
|
|||||||
[tool.poetry.urls]
|
[tool.poetry.urls]
|
||||||
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
||||||
"Changelog" = "https://nonebot.dev/changelog"
|
"Changelog" = "https://nonebot.dev/changelog"
|
||||||
"Funding" = "https://afdian.net/@nonebot"
|
"Funding" = "https://afdian.com/@nonebot"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
yarl = "^1.7.2"
|
yarl = "^1.7.2"
|
||||||
|
anyio = "^4.4.0"
|
||||||
pygtrie = "^2.4.1"
|
pygtrie = "^2.4.1"
|
||||||
|
exceptiongroup = "^1.2.2"
|
||||||
loguru = ">=0.6.0,<1.0.0"
|
loguru = ">=0.6.0,<1.0.0"
|
||||||
python-dotenv = ">=0.21.0,<2.0.0"
|
python-dotenv = ">=0.21.0,<2.0.0"
|
||||||
typing-extensions = ">=4.4.0,<5.0.0"
|
typing-extensions = ">=4.4.0,<5.0.0"
|
||||||
@@ -44,7 +46,7 @@ uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
|||||||
], optional = true }
|
], optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "^0.4.0"
|
ruff = "^0.7.0"
|
||||||
isort = "^5.10.1"
|
isort = "^5.10.1"
|
||||||
black = "^24.0.0"
|
black = "^24.0.0"
|
||||||
nonemoji = "^0.1.2"
|
nonemoji = "^0.1.2"
|
||||||
@@ -65,7 +67,6 @@ fastapi = ["fastapi", "uvicorn"]
|
|||||||
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
asyncio_mode = "strict"
|
|
||||||
addopts = "--cov=nonebot --cov-report=term-missing"
|
addopts = "--cov=nonebot --cov-report=term-missing"
|
||||||
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
||||||
|
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from functools import wraps
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from typing_extensions import ParamSpec
|
||||||
|
from typing import TYPE_CHECKING, TypeVar, Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import NONEBOT_INIT_KWARGS
|
from nonebug import NONEBOT_INIT_KWARGS
|
||||||
@@ -20,6 +22,9 @@ os.environ["CONFIG_OVERRIDE"] = "new"
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from nonebot.plugin import Plugin
|
from nonebot.plugin import Plugin
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
collect_ignore = ["plugins/", "dynamic/", "bad_plugins/"]
|
collect_ignore = ["plugins/", "dynamic/", "bad_plugins/"]
|
||||||
|
|
||||||
|
|
||||||
@@ -38,14 +43,36 @@ def load_driver(request: pytest.FixtureRequest) -> Driver:
|
|||||||
return DriverClass(Env(environment=global_driver.env), global_driver.config)
|
return DriverClass(Env(environment=global_driver.env), global_driver.config)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", params=[pytest.param("asyncio"), pytest.param("trio")])
|
||||||
|
def anyio_backend(request: pytest.FixtureRequest):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
|
def run_once(func: Callable[P, R]) -> Callable[P, R]:
|
||||||
|
result = ...
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
|
nonlocal result
|
||||||
|
if result is not Ellipsis:
|
||||||
|
return result
|
||||||
|
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def load_plugin(nonebug_init: None) -> set["Plugin"]:
|
@run_once
|
||||||
|
def load_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
|
||||||
# preload global plugins
|
# preload global plugins
|
||||||
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def load_builtin_plugin(nonebug_init: None) -> set["Plugin"]:
|
@run_once
|
||||||
|
def load_builtin_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
|
||||||
# preload builtin plugins
|
# preload builtin plugins
|
||||||
return nonebot.load_builtin_plugins("echo", "single_session")
|
return nonebot.load_builtin_plugins("echo", "single_session")
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import anyio
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from nonebot import on_message
|
from nonebot import on_message
|
||||||
@@ -105,3 +106,26 @@ async def validate_field(x: int = Depends(lambda: "1", validate=Field(gt=0))):
|
|||||||
|
|
||||||
async def validate_field_fail(x: int = Depends(lambda: "0", validate=Field(gt=0))):
|
async def validate_field_fail(x: int = Depends(lambda: "0", validate=Field(gt=0))):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
async def _dep():
|
||||||
|
await anyio.sleep(1)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def _dep_mismatch():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
async def cache_exception_func1(
|
||||||
|
dep: int = Depends(_dep),
|
||||||
|
mismatch: dict = Depends(_dep_mismatch),
|
||||||
|
):
|
||||||
|
raise RuntimeError("Never reach here")
|
||||||
|
|
||||||
|
|
||||||
|
async def cache_exception_func2(
|
||||||
|
dep: int = Depends(_dep),
|
||||||
|
match: int = Depends(_dep_mismatch),
|
||||||
|
):
|
||||||
|
return dep
|
||||||
|
@@ -17,7 +17,7 @@ from nonebot.drivers import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_adapter_connect(app: App, driver: Driver):
|
async def test_adapter_connect(app: App, driver: Driver):
|
||||||
last_connect_bot: Optional[Bot] = None
|
last_connect_bot: Optional[Bot] = None
|
||||||
last_disconnect_bot: Optional[Bot] = None
|
last_disconnect_bot: Optional[Bot] = None
|
||||||
@@ -45,7 +45,6 @@ async def test_adapter_connect(app: App, driver: Driver):
|
|||||||
assert bot.self_id not in adapter.bots
|
assert bot.self_id not in adapter.bots
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -75,7 +74,7 @@ async def test_adapter_connect(app: App, driver: Driver):
|
|||||||
],
|
],
|
||||||
indirect=True,
|
indirect=True,
|
||||||
)
|
)
|
||||||
async def test_adapter_server(driver: Driver):
|
def test_adapter_server(driver: Driver):
|
||||||
last_http_setup: Optional[HTTPServerSetup] = None
|
last_http_setup: Optional[HTTPServerSetup] = None
|
||||||
last_ws_setup: Optional[WebSocketServerSetup] = None
|
last_ws_setup: Optional[WebSocketServerSetup] = None
|
||||||
|
|
||||||
@@ -112,7 +111,7 @@ async def test_adapter_server(driver: Driver):
|
|||||||
assert last_ws_setup is setup
|
assert last_ws_setup is setup
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -159,7 +158,7 @@ async def test_adapter_http_client(driver: Driver):
|
|||||||
assert last_request is request
|
assert last_request is request
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
import anyio
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@ from nonebot.adapters import Bot
|
|||||||
from nonebot.exception import MockApiException
|
from nonebot.exception import MockApiException
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_bot_call_api(app: App):
|
async def test_bot_call_api(app: App):
|
||||||
async with app.test_api() as ctx:
|
async with app.test_api() as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
@@ -23,7 +24,7 @@ async def test_bot_call_api(app: App):
|
|||||||
await bot.call_api("test")
|
await bot.call_api("test")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_bot_calling_api_hook_simple(app: App):
|
async def test_bot_calling_api_hook_simple(app: App):
|
||||||
runned: bool = False
|
runned: bool = False
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ async def test_bot_calling_api_hook_simple(app: App):
|
|||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_bot_calling_api_hook_mock(app: App):
|
async def test_bot_calling_api_hook_mock(app: App):
|
||||||
runned: bool = False
|
runned: bool = False
|
||||||
|
|
||||||
@@ -76,7 +77,47 @@ async def test_bot_calling_api_hook_mock(app: App):
|
|||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
|
async def test_bot_calling_api_hook_multi_mock(app: App):
|
||||||
|
runned1: bool = False
|
||||||
|
runned2: bool = False
|
||||||
|
event = anyio.Event()
|
||||||
|
|
||||||
|
async def calling_api_hook1(bot: Bot, api: str, data: dict[str, Any]):
|
||||||
|
nonlocal runned1
|
||||||
|
runned1 = True
|
||||||
|
event.set()
|
||||||
|
|
||||||
|
raise MockApiException(1)
|
||||||
|
|
||||||
|
async def calling_api_hook2(bot: Bot, api: str, data: dict[str, Any]):
|
||||||
|
nonlocal runned2
|
||||||
|
runned2 = True
|
||||||
|
with anyio.fail_after(1):
|
||||||
|
await event.wait()
|
||||||
|
|
||||||
|
raise MockApiException(2)
|
||||||
|
|
||||||
|
hooks = set()
|
||||||
|
|
||||||
|
with pytest.MonkeyPatch.context() as m:
|
||||||
|
m.setattr(Bot, "_calling_api_hook", hooks)
|
||||||
|
|
||||||
|
Bot.on_calling_api(calling_api_hook1)
|
||||||
|
Bot.on_calling_api(calling_api_hook2)
|
||||||
|
|
||||||
|
assert hooks == {calling_api_hook1, calling_api_hook2}
|
||||||
|
|
||||||
|
async with app.test_api() as ctx:
|
||||||
|
bot = ctx.create_bot()
|
||||||
|
result = await bot.call_api("test")
|
||||||
|
|
||||||
|
assert runned1 is True
|
||||||
|
assert runned2 is True
|
||||||
|
assert result == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
async def test_bot_called_api_hook_simple(app: App):
|
async def test_bot_called_api_hook_simple(app: App):
|
||||||
runned: bool = False
|
runned: bool = False
|
||||||
|
|
||||||
@@ -108,7 +149,7 @@ async def test_bot_called_api_hook_simple(app: App):
|
|||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_bot_called_api_hook_mock(app: App):
|
async def test_bot_called_api_hook_mock(app: App):
|
||||||
runned: bool = False
|
runned: bool = False
|
||||||
|
|
||||||
@@ -150,3 +191,56 @@ async def test_bot_called_api_hook_mock(app: App):
|
|||||||
|
|
||||||
assert runned is True
|
assert runned is True
|
||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_bot_called_api_hook_multi_mock(app: App):
|
||||||
|
runned1: bool = False
|
||||||
|
runned2: bool = False
|
||||||
|
event = anyio.Event()
|
||||||
|
|
||||||
|
async def called_api_hook1(
|
||||||
|
bot: Bot,
|
||||||
|
exception: Optional[Exception],
|
||||||
|
api: str,
|
||||||
|
data: dict[str, Any],
|
||||||
|
result: Any,
|
||||||
|
):
|
||||||
|
nonlocal runned1
|
||||||
|
runned1 = True
|
||||||
|
event.set()
|
||||||
|
|
||||||
|
raise MockApiException(1)
|
||||||
|
|
||||||
|
async def called_api_hook2(
|
||||||
|
bot: Bot,
|
||||||
|
exception: Optional[Exception],
|
||||||
|
api: str,
|
||||||
|
data: dict[str, Any],
|
||||||
|
result: Any,
|
||||||
|
):
|
||||||
|
nonlocal runned2
|
||||||
|
runned2 = True
|
||||||
|
with anyio.fail_after(1):
|
||||||
|
await event.wait()
|
||||||
|
|
||||||
|
raise MockApiException(2)
|
||||||
|
|
||||||
|
hooks = set()
|
||||||
|
|
||||||
|
with pytest.MonkeyPatch.context() as m:
|
||||||
|
m.setattr(Bot, "_called_api_hook", hooks)
|
||||||
|
|
||||||
|
Bot.on_called_api(called_api_hook1)
|
||||||
|
Bot.on_called_api(called_api_hook2)
|
||||||
|
|
||||||
|
assert hooks == {called_api_hook1, called_api_hook2}
|
||||||
|
|
||||||
|
async with app.test_api() as ctx:
|
||||||
|
bot = ctx.create_bot()
|
||||||
|
ctx.should_call_api("test", {}, True)
|
||||||
|
result = await bot.call_api("test")
|
||||||
|
|
||||||
|
assert runned1 is True
|
||||||
|
assert runned2 is True
|
||||||
|
assert result == 1
|
||||||
|
@@ -25,7 +25,7 @@ async def _dependency() -> int:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_event_preprocessors", set())
|
m.setattr(message, "_event_preprocessors", set())
|
||||||
@@ -58,7 +58,7 @@ async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned, "event_preprocessor should runned"
|
assert runned, "event_preprocessor should runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_event_preprocessors", set())
|
m.setattr(message, "_event_preprocessors", set())
|
||||||
@@ -88,7 +88,7 @@ async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPat
|
|||||||
assert not runned, "matcher should not runned"
|
assert not runned, "matcher should not runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_event_preprocessor_exception(
|
async def test_event_preprocessor_exception(
|
||||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
):
|
):
|
||||||
@@ -132,7 +132,7 @@ async def test_event_preprocessor_exception(
|
|||||||
assert "RuntimeError: test" in capsys.readouterr().out
|
assert "RuntimeError: test" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_event_postprocessors", set())
|
m.setattr(message, "_event_postprocessors", set())
|
||||||
@@ -165,7 +165,7 @@ async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned, "event_postprocessor should runned"
|
assert runned, "event_postprocessor should runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_event_postprocessor_exception(
|
async def test_event_postprocessor_exception(
|
||||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
):
|
):
|
||||||
@@ -202,7 +202,7 @@ async def test_event_postprocessor_exception(
|
|||||||
assert "RuntimeError: test" in capsys.readouterr().out
|
assert "RuntimeError: test" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_run_preprocessors", set())
|
m.setattr(message, "_run_preprocessors", set())
|
||||||
@@ -239,7 +239,7 @@ async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned, "run_preprocessor should runned"
|
assert runned, "run_preprocessor should runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_run_preprocessors", set())
|
m.setattr(message, "_run_preprocessors", set())
|
||||||
@@ -269,7 +269,7 @@ async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch
|
|||||||
assert not runned, "matcher should not runned"
|
assert not runned, "matcher should not runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_run_preprocessor_exception(
|
async def test_run_preprocessor_exception(
|
||||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
):
|
):
|
||||||
@@ -313,7 +313,7 @@ async def test_run_preprocessor_exception(
|
|||||||
assert "RuntimeError: test" in capsys.readouterr().out
|
assert "RuntimeError: test" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_run_postprocessors", set())
|
m.setattr(message, "_run_postprocessors", set())
|
||||||
@@ -351,7 +351,7 @@ async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned, "run_postprocessor should runned"
|
assert runned, "run_postprocessor should runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_run_postprocessor_exception(
|
async def test_run_postprocessor_exception(
|
||||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
):
|
):
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
from typing import Any, Optional
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Optional, Annotated
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, ValidationError
|
||||||
|
|
||||||
from nonebot.compat import (
|
from nonebot.compat import (
|
||||||
DEFAULT_CONFIG,
|
DEFAULT_CONFIG,
|
||||||
Required,
|
Required,
|
||||||
FieldInfo,
|
FieldInfo,
|
||||||
|
TypeAdapter,
|
||||||
PydanticUndefined,
|
PydanticUndefined,
|
||||||
model_dump,
|
model_dump,
|
||||||
custom_validation,
|
custom_validation,
|
||||||
@@ -16,14 +17,12 @@ from nonebot.compat import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_default_config():
|
||||||
async def test_default_config():
|
|
||||||
assert DEFAULT_CONFIG.get("extra") == "allow"
|
assert DEFAULT_CONFIG.get("extra") == "allow"
|
||||||
assert DEFAULT_CONFIG.get("arbitrary_types_allowed") is True
|
assert DEFAULT_CONFIG.get("arbitrary_types_allowed") is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_field_info():
|
||||||
async def test_field_info():
|
|
||||||
# required should be convert to PydanticUndefined
|
# required should be convert to PydanticUndefined
|
||||||
assert FieldInfo(Required).default is PydanticUndefined
|
assert FieldInfo(Required).default is PydanticUndefined
|
||||||
|
|
||||||
@@ -31,8 +30,21 @@ async def test_field_info():
|
|||||||
assert FieldInfo(test="test").extra["test"] == "test"
|
assert FieldInfo(test="test").extra["test"] == "test"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_type_adapter():
|
||||||
async def test_model_dump():
|
t = TypeAdapter(Annotated[int, FieldInfo(ge=1)])
|
||||||
|
|
||||||
|
assert t.validate_python(2) == 2
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
t.validate_python(0)
|
||||||
|
|
||||||
|
assert t.validate_json("2") == 2
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
t.validate_json("0")
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_dump():
|
||||||
class TestModel(BaseModel):
|
class TestModel(BaseModel):
|
||||||
test1: int
|
test1: int
|
||||||
test2: int
|
test2: int
|
||||||
@@ -41,8 +53,7 @@ async def test_model_dump():
|
|||||||
assert model_dump(TestModel(test1=1, test2=2), exclude={"test1"}) == {"test2": 2}
|
assert model_dump(TestModel(test1=1, test2=2), exclude={"test1"}) == {"test2": 2}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_custom_validation():
|
||||||
async def test_custom_validation():
|
|
||||||
called = []
|
called = []
|
||||||
|
|
||||||
@custom_validation
|
@custom_validation
|
||||||
@@ -69,8 +80,7 @@ async def test_custom_validation():
|
|||||||
assert called == [1, 2]
|
assert called == [1, 2]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_validate_json():
|
||||||
async def test_validate_json():
|
|
||||||
class TestModel(BaseModel):
|
class TestModel(BaseModel):
|
||||||
test1: int
|
test1: int
|
||||||
test2: str
|
test2: str
|
||||||
|
@@ -50,16 +50,14 @@ class ExampleWithoutDelimiter(Example):
|
|||||||
env_nested_delimiter = None
|
env_nested_delimiter = None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_config_no_env():
|
||||||
async def test_config_no_env():
|
|
||||||
config = Example(_env_file=None)
|
config = Example(_env_file=None)
|
||||||
assert config.simple == ""
|
assert config.simple == ""
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
config.common_config
|
config.common_config
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_config_with_env():
|
||||||
async def test_config_with_env():
|
|
||||||
config = Example(_env_file=(".env", ".env.example"))
|
config = Example(_env_file=(".env", ".env.example"))
|
||||||
assert config.simple == "simple"
|
assert config.simple == "simple"
|
||||||
|
|
||||||
@@ -102,8 +100,7 @@ async def test_config_with_env():
|
|||||||
config.other_nested_inner__b
|
config.other_nested_inner__b
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_config_error_env():
|
||||||
async def test_config_error_env():
|
|
||||||
with pytest.MonkeyPatch().context() as m:
|
with pytest.MonkeyPatch().context() as m:
|
||||||
m.setenv("COMPLEX", "not json")
|
m.setenv("COMPLEX", "not json")
|
||||||
|
|
||||||
@@ -111,8 +108,7 @@ async def test_config_error_env():
|
|||||||
Example(_env_file=(".env", ".env.example"))
|
Example(_env_file=(".env", ".env.example"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_config_without_delimiter():
|
||||||
async def test_config_without_delimiter():
|
|
||||||
config = ExampleWithoutDelimiter()
|
config = ExampleWithoutDelimiter()
|
||||||
assert config.nested.a == 1
|
assert config.nested.a == 1
|
||||||
assert config.nested.b == 0
|
assert config.nested.b == 0
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
import asyncio
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
|
|
||||||
|
import anyio
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ from nonebot.drivers import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver", [pytest.param("nonebot.drivers.none:Driver", id="none")], indirect=True
|
"driver", [pytest.param("nonebot.drivers.none:Driver", id="none")], indirect=True
|
||||||
)
|
)
|
||||||
@@ -59,22 +59,22 @@ async def test_lifespan(driver: Driver):
|
|||||||
|
|
||||||
@driver.on_shutdown
|
@driver.on_shutdown
|
||||||
async def _shutdown1():
|
async def _shutdown1():
|
||||||
assert shutdown_log == []
|
assert shutdown_log == [2]
|
||||||
shutdown_log.append(1)
|
shutdown_log.append(1)
|
||||||
|
|
||||||
@driver.on_shutdown
|
@driver.on_shutdown
|
||||||
async def _shutdown2():
|
async def _shutdown2():
|
||||||
assert shutdown_log == [1]
|
assert shutdown_log == []
|
||||||
shutdown_log.append(2)
|
shutdown_log.append(2)
|
||||||
|
|
||||||
async with driver._lifespan:
|
async with driver._lifespan:
|
||||||
assert start_log == [1, 2]
|
assert start_log == [1, 2]
|
||||||
assert ready_log == [1, 2]
|
assert ready_log == [1, 2]
|
||||||
|
|
||||||
assert shutdown_log == [1, 2]
|
assert shutdown_log == [2, 1]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -99,10 +99,10 @@ async def test_http_server(app: App, driver: Driver):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.text == "test"
|
assert response.text == "test"
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -155,10 +155,10 @@ async def test_websocket_server(app: App, driver: Driver):
|
|||||||
|
|
||||||
await ws.close(code=1000)
|
await ws.close(code=1000)
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -171,9 +171,10 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
assert isinstance(driver, ASGIMixin)
|
assert isinstance(driver, ASGIMixin)
|
||||||
|
|
||||||
ws: Optional[WebSocket] = None
|
ws: Optional[WebSocket] = None
|
||||||
ws_ready = asyncio.Event()
|
ws_ready = anyio.Event()
|
||||||
ws_should_close = asyncio.Event()
|
ws_should_close = anyio.Event()
|
||||||
|
|
||||||
|
# create a background task before the ws connection established
|
||||||
async def background_task():
|
async def background_task():
|
||||||
try:
|
try:
|
||||||
await ws_ready.wait()
|
await ws_ready.wait()
|
||||||
@@ -185,8 +186,6 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
finally:
|
finally:
|
||||||
ws_should_close.set()
|
ws_should_close.set()
|
||||||
|
|
||||||
task = asyncio.create_task(background_task())
|
|
||||||
|
|
||||||
async def _handle_ws(websocket: WebSocket) -> None:
|
async def _handle_ws(websocket: WebSocket) -> None:
|
||||||
nonlocal ws
|
nonlocal ws
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
@@ -199,7 +198,9 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
ws_setup = WebSocketServerSetup(URL("/ws_test"), "ws_test", _handle_ws)
|
ws_setup = WebSocketServerSetup(URL("/ws_test"), "ws_test", _handle_ws)
|
||||||
driver.setup_websocket_server(ws_setup)
|
driver.setup_websocket_server(ws_setup)
|
||||||
|
|
||||||
async with app.test_server(driver.asgi) as ctx:
|
async with anyio.create_task_group() as tg, app.test_server(driver.asgi) as ctx:
|
||||||
|
tg.start_soon(background_task)
|
||||||
|
|
||||||
client = ctx.get_client()
|
client = ctx.get_client()
|
||||||
|
|
||||||
async with client.websocket_connect("/ws_test") as websocket:
|
async with client.websocket_connect("/ws_test") as websocket:
|
||||||
@@ -211,11 +212,10 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
if not e.args or "websocket.close" not in str(e.args[0]):
|
if not e.args or "websocket.close" not in str(e.args[0]):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
await task
|
await anyio.sleep(1)
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -304,10 +304,10 @@ async def test_http_client(driver: Driver, server_url: URL):
|
|||||||
"test3": "test",
|
"test3": "test",
|
||||||
}, "file parsing error"
|
}, "file parsing error"
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -419,10 +419,10 @@ async def test_http_client_session(driver: Driver, server_url: URL):
|
|||||||
"test3": "test",
|
"test3": "test",
|
||||||
}, "file parsing error"
|
}, "file parsing error"
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -452,10 +452,9 @@ async def test_websocket_client(driver: Driver, server_url: URL):
|
|||||||
with pytest.raises(WebSocketClosed, match=r"code=1000"):
|
with pytest.raises(WebSocketClosed, match=r"code=1000"):
|
||||||
await ws.receive()
|
await ws.receive()
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("driver", "driver_type"),
|
("driver", "driver_type"),
|
||||||
[
|
[
|
||||||
@@ -472,11 +471,11 @@ async def test_websocket_client(driver: Driver, server_url: URL):
|
|||||||
],
|
],
|
||||||
indirect=["driver"],
|
indirect=["driver"],
|
||||||
)
|
)
|
||||||
async def test_combine_driver(driver: Driver, driver_type: str):
|
def test_combine_driver(driver: Driver, driver_type: str):
|
||||||
assert driver.type == driver_type
|
assert driver.type == driver_type
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_bot_connect_hook(app: App, driver: Driver):
|
async def test_bot_connect_hook(app: App, driver: Driver):
|
||||||
with pytest.MonkeyPatch.context() as m:
|
with pytest.MonkeyPatch.context() as m:
|
||||||
conn_hooks: set[Dependent[Any]] = set()
|
conn_hooks: set[Dependent[Any]] = set()
|
||||||
@@ -533,7 +532,7 @@ async def test_bot_connect_hook(app: App, driver: Driver):
|
|||||||
async with app.test_api() as ctx:
|
async with app.test_api() as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
if not conn_should_be_called:
|
if not conn_should_be_called:
|
||||||
pytest.fail("on_bot_connect hook not called")
|
pytest.fail("on_bot_connect hook not called")
|
||||||
|
@@ -4,7 +4,7 @@ from nonebug import App
|
|||||||
from utils import FakeMessage, FakeMessageSegment, make_fake_event
|
from utils import FakeMessage, FakeMessageSegment, make_fake_event
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_echo(app: App):
|
async def test_echo(app: App):
|
||||||
from nonebot.plugins.echo import echo
|
from nonebot.plugins.echo import echo
|
||||||
|
|
||||||
|
@@ -14,8 +14,7 @@ from nonebot import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_init():
|
||||||
async def test_init():
|
|
||||||
env = nonebot.get_driver().env
|
env = nonebot.get_driver().env
|
||||||
assert env == "test"
|
assert env == "test"
|
||||||
|
|
||||||
@@ -35,31 +34,28 @@ async def test_init():
|
|||||||
assert config.not_nested == "some string"
|
assert config.not_nested == "some string"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_get_driver(monkeypatch: pytest.MonkeyPatch):
|
||||||
async def test_get_driver(app: App, monkeypatch: pytest.MonkeyPatch):
|
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(nonebot, "_driver", None)
|
m.setattr(nonebot, "_driver", None)
|
||||||
with pytest.raises(ValueError, match="initialized"):
|
with pytest.raises(ValueError, match="initialized"):
|
||||||
get_driver()
|
get_driver()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_get_asgi():
|
||||||
async def test_get_asgi(app: App, monkeypatch: pytest.MonkeyPatch):
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
assert isinstance(driver, ReverseDriver)
|
assert isinstance(driver, ReverseDriver)
|
||||||
assert isinstance(driver, ASGIMixin)
|
assert isinstance(driver, ASGIMixin)
|
||||||
assert get_asgi() == driver.asgi
|
assert get_asgi() == driver.asgi
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_get_app():
|
||||||
async def test_get_app(app: App, monkeypatch: pytest.MonkeyPatch):
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
assert isinstance(driver, ReverseDriver)
|
assert isinstance(driver, ReverseDriver)
|
||||||
assert isinstance(driver, ASGIMixin)
|
assert isinstance(driver, ASGIMixin)
|
||||||
assert get_app() == driver.server_app
|
assert get_app() == driver.server_app
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_get_adapter(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_get_adapter(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
async with app.test_api() as ctx:
|
async with app.test_api() as ctx:
|
||||||
adapter = ctx.create_adapter()
|
adapter = ctx.create_adapter()
|
||||||
@@ -74,8 +70,7 @@ async def test_get_adapter(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
get_adapter("not exist")
|
get_adapter("not exist")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_run(monkeypatch: pytest.MonkeyPatch):
|
||||||
async def test_run(app: App, monkeypatch: pytest.MonkeyPatch):
|
|
||||||
runned = False
|
runned = False
|
||||||
|
|
||||||
def mock_run(*args, **kwargs):
|
def mock_run(*args, **kwargs):
|
||||||
@@ -93,8 +88,7 @@ async def test_run(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned
|
assert runned
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_get_bot(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
async def test_get_bot(app: App, monkeypatch: pytest.MonkeyPatch):
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="no bots"):
|
with pytest.raises(ValueError, match="no bots"):
|
||||||
|
@@ -12,8 +12,7 @@ from nonebot.permission import User, Permission
|
|||||||
from nonebot.message import _check_matcher, check_and_run_matcher
|
from nonebot.message import _check_matcher, check_and_run_matcher
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_matcher_info(app: App):
|
||||||
async def test_matcher_info(app: App):
|
|
||||||
from plugins.matcher.matcher_info import matcher
|
from plugins.matcher.matcher_info import matcher
|
||||||
|
|
||||||
assert issubclass(matcher, Matcher)
|
assert issubclass(matcher, Matcher)
|
||||||
@@ -43,7 +42,7 @@ async def test_matcher_info(app: App):
|
|||||||
assert matcher._source.lineno == 3
|
assert matcher._source.lineno == 3
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_check(app: App):
|
async def test_matcher_check(app: App):
|
||||||
async def falsy():
|
async def falsy():
|
||||||
return False
|
return False
|
||||||
@@ -87,7 +86,7 @@ async def test_matcher_check(app: App):
|
|||||||
assert await _check_matcher(test_rule_error, bot, event, {}) is False
|
assert await _check_matcher(test_rule_error, bot, event, {}) is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_handle(app: App):
|
async def test_matcher_handle(app: App):
|
||||||
from plugins.matcher.matcher_process import test_handle
|
from plugins.matcher.matcher_process import test_handle
|
||||||
|
|
||||||
@@ -102,7 +101,7 @@ async def test_matcher_handle(app: App):
|
|||||||
ctx.should_finished()
|
ctx.should_finished()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_got(app: App):
|
async def test_matcher_got(app: App):
|
||||||
from plugins.matcher.matcher_process import test_got
|
from plugins.matcher.matcher_process import test_got
|
||||||
|
|
||||||
@@ -124,7 +123,7 @@ async def test_matcher_got(app: App):
|
|||||||
ctx.receive_event(bot, event_next)
|
ctx.receive_event(bot, event_next)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_receive(app: App):
|
async def test_matcher_receive(app: App):
|
||||||
from plugins.matcher.matcher_process import test_receive
|
from plugins.matcher.matcher_process import test_receive
|
||||||
|
|
||||||
@@ -141,7 +140,7 @@ async def test_matcher_receive(app: App):
|
|||||||
ctx.should_paused()
|
ctx.should_paused()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_combine(app: App):
|
async def test_matcher_combine(app: App):
|
||||||
from plugins.matcher.matcher_process import test_combine
|
from plugins.matcher.matcher_process import test_combine
|
||||||
|
|
||||||
@@ -164,7 +163,7 @@ async def test_matcher_combine(app: App):
|
|||||||
ctx.receive_event(bot, event_next)
|
ctx.receive_event(bot, event_next)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_preset(app: App):
|
async def test_matcher_preset(app: App):
|
||||||
from plugins.matcher.matcher_process import test_preset
|
from plugins.matcher.matcher_process import test_preset
|
||||||
|
|
||||||
@@ -182,7 +181,7 @@ async def test_matcher_preset(app: App):
|
|||||||
ctx.receive_event(bot, event_next)
|
ctx.receive_event(bot, event_next)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_overload(app: App):
|
async def test_matcher_overload(app: App):
|
||||||
from plugins.matcher.matcher_process import test_overload
|
from plugins.matcher.matcher_process import test_overload
|
||||||
|
|
||||||
@@ -196,7 +195,7 @@ async def test_matcher_overload(app: App):
|
|||||||
ctx.should_finished()
|
ctx.should_finished()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_destroy(app: App):
|
async def test_matcher_destroy(app: App):
|
||||||
from plugins.matcher.matcher_process import test_destroy
|
from plugins.matcher.matcher_process import test_destroy
|
||||||
|
|
||||||
@@ -210,7 +209,7 @@ async def test_matcher_destroy(app: App):
|
|||||||
assert len(matchers[test_destroy.priority]) == 0
|
assert len(matchers[test_destroy.priority]) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_type_updater(app: App):
|
async def test_type_updater(app: App):
|
||||||
from plugins.matcher.matcher_type import test_type_updater, test_custom_updater
|
from plugins.matcher.matcher_type import test_type_updater, test_custom_updater
|
||||||
|
|
||||||
@@ -231,7 +230,7 @@ async def test_type_updater(app: App):
|
|||||||
assert new_type == "custom"
|
assert new_type == "custom"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_default_permission_updater(app: App):
|
async def test_default_permission_updater(app: App):
|
||||||
from plugins.matcher.matcher_permission import (
|
from plugins.matcher.matcher_permission import (
|
||||||
default_permission,
|
default_permission,
|
||||||
@@ -252,7 +251,7 @@ async def test_default_permission_updater(app: App):
|
|||||||
assert checker.perm is default_permission
|
assert checker.perm is default_permission
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_user_permission_updater(app: App):
|
async def test_user_permission_updater(app: App):
|
||||||
from plugins.matcher.matcher_permission import (
|
from plugins.matcher.matcher_permission import (
|
||||||
default_permission,
|
default_permission,
|
||||||
@@ -274,7 +273,7 @@ async def test_user_permission_updater(app: App):
|
|||||||
assert checker.perm is default_permission
|
assert checker.perm is default_permission
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_custom_permission_updater(app: App):
|
async def test_custom_permission_updater(app: App):
|
||||||
from plugins.matcher.matcher_permission import (
|
from plugins.matcher.matcher_permission import (
|
||||||
new_permission,
|
new_permission,
|
||||||
@@ -291,7 +290,7 @@ async def test_custom_permission_updater(app: App):
|
|||||||
assert new_perm is new_permission
|
assert new_perm is new_permission
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_run(app: App):
|
async def test_run(app: App):
|
||||||
with app.provider.context({}):
|
with app.provider.context({}):
|
||||||
assert not matchers
|
assert not matchers
|
||||||
@@ -322,37 +321,46 @@ async def test_run(app: App):
|
|||||||
assert len(matchers[0][0].handlers) == 0
|
assert len(matchers[0][0].handlers) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_temp(app: App):
|
async def test_temp(app: App):
|
||||||
from plugins.matcher.matcher_expire import test_temp_matcher
|
from plugins.matcher.matcher_expire import test_temp_matcher
|
||||||
|
|
||||||
event = make_fake_event(_type="test")()
|
event = make_fake_event(_type="test")()
|
||||||
async with app.test_api() as ctx:
|
with app.provider.context({test_temp_matcher.priority: [test_temp_matcher]}):
|
||||||
bot = ctx.create_bot()
|
async with app.test_api() as ctx:
|
||||||
assert test_temp_matcher in matchers[test_temp_matcher.priority]
|
bot = ctx.create_bot()
|
||||||
await check_and_run_matcher(test_temp_matcher, bot, event, {})
|
assert test_temp_matcher in matchers[test_temp_matcher.priority]
|
||||||
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
|
await check_and_run_matcher(test_temp_matcher, bot, event, {})
|
||||||
|
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_datetime_expire(app: App):
|
async def test_datetime_expire(app: App):
|
||||||
from plugins.matcher.matcher_expire import test_datetime_matcher
|
from plugins.matcher.matcher_expire import test_datetime_matcher
|
||||||
|
|
||||||
event = make_fake_event()()
|
event = make_fake_event()()
|
||||||
async with app.test_api() as ctx:
|
with app.provider.context(
|
||||||
bot = ctx.create_bot()
|
{test_datetime_matcher.priority: [test_datetime_matcher]}
|
||||||
assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
|
):
|
||||||
await check_and_run_matcher(test_datetime_matcher, bot, event, {})
|
async with app.test_matcher(test_datetime_matcher) as ctx:
|
||||||
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
|
bot = ctx.create_bot()
|
||||||
|
assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
|
||||||
|
await check_and_run_matcher(test_datetime_matcher, bot, event, {})
|
||||||
|
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_timedelta_expire(app: App):
|
async def test_timedelta_expire(app: App):
|
||||||
from plugins.matcher.matcher_expire import test_timedelta_matcher
|
from plugins.matcher.matcher_expire import test_timedelta_matcher
|
||||||
|
|
||||||
event = make_fake_event()()
|
event = make_fake_event()()
|
||||||
async with app.test_api() as ctx:
|
with app.provider.context(
|
||||||
bot = ctx.create_bot()
|
{test_timedelta_matcher.priority: [test_timedelta_matcher]}
|
||||||
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
|
):
|
||||||
await check_and_run_matcher(test_timedelta_matcher, bot, event, {})
|
async with app.test_api() as ctx:
|
||||||
assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
|
bot = ctx.create_bot()
|
||||||
|
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
|
||||||
|
await check_and_run_matcher(test_timedelta_matcher, bot, event, {})
|
||||||
|
assert (
|
||||||
|
test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
|
||||||
|
)
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
import pytest
|
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
|
||||||
from nonebot.matcher import DEFAULT_PROVIDER_CLASS, matchers
|
from nonebot.matcher import DEFAULT_PROVIDER_CLASS, matchers
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_manager(app: App):
|
||||||
async def test_manager(app: App):
|
|
||||||
try:
|
try:
|
||||||
default_provider = matchers.provider
|
default_provider = matchers.provider
|
||||||
matchers.set_provider(DEFAULT_PROVIDER_CLASS)
|
matchers.set_provider(DEFAULT_PROVIDER_CLASS)
|
||||||
|
@@ -2,6 +2,7 @@ import re
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
from exceptiongroup import BaseExceptionGroup
|
||||||
|
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
@@ -36,7 +37,7 @@ from nonebot.consts import (
|
|||||||
UNKNOWN_PARAM = "Unknown parameter"
|
UNKNOWN_PARAM = "Unknown parameter"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_depend(app: App):
|
async def test_depend(app: App):
|
||||||
from plugins.param.param_depend import (
|
from plugins.param.param_depend import (
|
||||||
ClassDependency,
|
ClassDependency,
|
||||||
@@ -50,6 +51,8 @@ async def test_depend(app: App):
|
|||||||
annotated_depend,
|
annotated_depend,
|
||||||
sub_type_mismatch,
|
sub_type_mismatch,
|
||||||
validate_field_fail,
|
validate_field_fail,
|
||||||
|
cache_exception_func1,
|
||||||
|
cache_exception_func2,
|
||||||
annotated_class_depend,
|
annotated_class_depend,
|
||||||
annotated_multi_depend,
|
annotated_multi_depend,
|
||||||
annotated_prior_depend,
|
annotated_prior_depend,
|
||||||
@@ -90,36 +93,67 @@ async def test_depend(app: App):
|
|||||||
|
|
||||||
assert runned == [1, 1, 1]
|
assert runned == [1, 1, 1]
|
||||||
|
|
||||||
|
runned.clear()
|
||||||
|
|
||||||
async with app.test_dependent(
|
async with app.test_dependent(
|
||||||
annotated_class_depend, allow_types=[DependParam]
|
annotated_class_depend, allow_types=[DependParam]
|
||||||
) as ctx:
|
) as ctx:
|
||||||
ctx.should_return(ClassDependency(x=1, y=2))
|
ctx.should_return(ClassDependency(x=1, y=2))
|
||||||
|
|
||||||
with pytest.raises(TypeMisMatch): # noqa: PT012
|
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info: # noqa: PT012
|
||||||
async with app.test_dependent(
|
async with app.test_dependent(
|
||||||
sub_type_mismatch, allow_types=[DependParam, BotParam]
|
sub_type_mismatch, allow_types=[DependParam, BotParam]
|
||||||
) as ctx:
|
) as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
ctx.pass_params(bot=bot)
|
ctx.pass_params(bot=bot)
|
||||||
|
|
||||||
|
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||||
|
assert exc_info.group_contains(TypeMisMatch)
|
||||||
|
|
||||||
async with app.test_dependent(validate, allow_types=[DependParam]) as ctx:
|
async with app.test_dependent(validate, allow_types=[DependParam]) as ctx:
|
||||||
ctx.should_return(1)
|
ctx.should_return(1)
|
||||||
|
|
||||||
with pytest.raises(TypeMisMatch):
|
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
||||||
async with app.test_dependent(validate_fail, allow_types=[DependParam]) as ctx:
|
async with app.test_dependent(validate_fail, allow_types=[DependParam]) as ctx:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||||
|
assert exc_info.group_contains(TypeMisMatch)
|
||||||
|
|
||||||
async with app.test_dependent(validate_field, allow_types=[DependParam]) as ctx:
|
async with app.test_dependent(validate_field, allow_types=[DependParam]) as ctx:
|
||||||
ctx.should_return(1)
|
ctx.should_return(1)
|
||||||
|
|
||||||
with pytest.raises(TypeMisMatch):
|
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
||||||
async with app.test_dependent(
|
async with app.test_dependent(
|
||||||
validate_field_fail, allow_types=[DependParam]
|
validate_field_fail, allow_types=[DependParam]
|
||||||
) as ctx:
|
) as ctx:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||||
|
assert exc_info.group_contains(TypeMisMatch)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
# test cache reuse when exception raised
|
||||||
|
dependency_cache = {}
|
||||||
|
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
||||||
|
async with app.test_dependent(
|
||||||
|
cache_exception_func1, allow_types=[DependParam]
|
||||||
|
) as ctx:
|
||||||
|
ctx.pass_params(dependency_cache=dependency_cache)
|
||||||
|
|
||||||
|
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||||
|
assert exc_info.group_contains(TypeMisMatch)
|
||||||
|
|
||||||
|
# dependency solve tasks should be shielded even if one of them raises an exception
|
||||||
|
assert len(dependency_cache) == 2
|
||||||
|
|
||||||
|
async with app.test_dependent(
|
||||||
|
cache_exception_func2, allow_types=[DependParam]
|
||||||
|
) as ctx:
|
||||||
|
ctx.pass_params(dependency_cache=dependency_cache)
|
||||||
|
ctx.should_return(1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
async def test_bot(app: App):
|
async def test_bot(app: App):
|
||||||
from plugins.param.param_bot import (
|
from plugins.param.param_bot import (
|
||||||
FooBot,
|
FooBot,
|
||||||
@@ -157,11 +191,14 @@ async def test_bot(app: App):
|
|||||||
ctx.pass_params(bot=bot)
|
ctx.pass_params(bot=bot)
|
||||||
ctx.should_return(bot)
|
ctx.should_return(bot)
|
||||||
|
|
||||||
with pytest.raises(TypeMisMatch): # noqa: PT012
|
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info: # noqa: PT012
|
||||||
async with app.test_dependent(sub_bot, allow_types=[BotParam]) as ctx:
|
async with app.test_dependent(sub_bot, allow_types=[BotParam]) as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
ctx.pass_params(bot=bot)
|
ctx.pass_params(bot=bot)
|
||||||
|
|
||||||
|
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||||
|
assert exc_info.group_contains(TypeMisMatch)
|
||||||
|
|
||||||
async with app.test_dependent(union_bot, allow_types=[BotParam]) as ctx:
|
async with app.test_dependent(union_bot, allow_types=[BotParam]) as ctx:
|
||||||
bot = ctx.create_bot(base=FooBot)
|
bot = ctx.create_bot(base=FooBot)
|
||||||
ctx.pass_params(bot=bot)
|
ctx.pass_params(bot=bot)
|
||||||
@@ -181,7 +218,7 @@ async def test_bot(app: App):
|
|||||||
app.test_dependent(not_bot, allow_types=[BotParam])
|
app.test_dependent(not_bot, allow_types=[BotParam])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_event(app: App):
|
async def test_event(app: App):
|
||||||
from plugins.param.param_event import (
|
from plugins.param.param_event import (
|
||||||
FooEvent,
|
FooEvent,
|
||||||
@@ -223,10 +260,13 @@ async def test_event(app: App):
|
|||||||
ctx.pass_params(event=fake_fooevent)
|
ctx.pass_params(event=fake_fooevent)
|
||||||
ctx.should_return(fake_fooevent)
|
ctx.should_return(fake_fooevent)
|
||||||
|
|
||||||
with pytest.raises(TypeMisMatch):
|
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
||||||
async with app.test_dependent(sub_event, allow_types=[EventParam]) as ctx:
|
async with app.test_dependent(sub_event, allow_types=[EventParam]) as ctx:
|
||||||
ctx.pass_params(event=fake_event)
|
ctx.pass_params(event=fake_event)
|
||||||
|
|
||||||
|
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||||
|
assert exc_info.group_contains(TypeMisMatch)
|
||||||
|
|
||||||
async with app.test_dependent(union_event, allow_types=[EventParam]) as ctx:
|
async with app.test_dependent(union_event, allow_types=[EventParam]) as ctx:
|
||||||
ctx.pass_params(event=fake_fooevent)
|
ctx.pass_params(event=fake_fooevent)
|
||||||
ctx.should_return(fake_fooevent)
|
ctx.should_return(fake_fooevent)
|
||||||
@@ -267,7 +307,7 @@ async def test_event(app: App):
|
|||||||
ctx.should_return(fake_event.is_tome())
|
ctx.should_return(fake_event.is_tome())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_state(app: App):
|
async def test_state(app: App):
|
||||||
from plugins.param.param_state import (
|
from plugins.param.param_state import (
|
||||||
state,
|
state,
|
||||||
@@ -418,7 +458,7 @@ async def test_state(app: App):
|
|||||||
ctx.should_return(fake_state[KEYWORD_KEY])
|
ctx.should_return(fake_state[KEYWORD_KEY])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher(app: App):
|
async def test_matcher(app: App):
|
||||||
from plugins.param.param_matcher import (
|
from plugins.param.param_matcher import (
|
||||||
FooMatcher,
|
FooMatcher,
|
||||||
@@ -457,10 +497,13 @@ async def test_matcher(app: App):
|
|||||||
ctx.pass_params(matcher=foo_matcher)
|
ctx.pass_params(matcher=foo_matcher)
|
||||||
ctx.should_return(foo_matcher)
|
ctx.should_return(foo_matcher)
|
||||||
|
|
||||||
with pytest.raises(TypeMisMatch):
|
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
||||||
async with app.test_dependent(sub_matcher, allow_types=[MatcherParam]) as ctx:
|
async with app.test_dependent(sub_matcher, allow_types=[MatcherParam]) as ctx:
|
||||||
ctx.pass_params(matcher=fake_matcher)
|
ctx.pass_params(matcher=fake_matcher)
|
||||||
|
|
||||||
|
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||||
|
assert exc_info.group_contains(TypeMisMatch)
|
||||||
|
|
||||||
async with app.test_dependent(union_matcher, allow_types=[MatcherParam]) as ctx:
|
async with app.test_dependent(union_matcher, allow_types=[MatcherParam]) as ctx:
|
||||||
ctx.pass_params(matcher=foo_matcher)
|
ctx.pass_params(matcher=foo_matcher)
|
||||||
ctx.should_return(foo_matcher)
|
ctx.should_return(foo_matcher)
|
||||||
@@ -496,7 +539,7 @@ async def test_matcher(app: App):
|
|||||||
ctx.should_return(event_next)
|
ctx.should_return(event_next)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_arg(app: App):
|
async def test_arg(app: App):
|
||||||
from plugins.param.param_arg import (
|
from plugins.param.param_arg import (
|
||||||
arg,
|
arg,
|
||||||
@@ -548,7 +591,7 @@ async def test_arg(app: App):
|
|||||||
ctx.should_return(message.extract_plain_text())
|
ctx.should_return(message.extract_plain_text())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_exception(app: App):
|
async def test_exception(app: App):
|
||||||
from plugins.param.param_exception import exc, legacy_exc
|
from plugins.param.param_exception import exc, legacy_exc
|
||||||
|
|
||||||
@@ -562,7 +605,7 @@ async def test_exception(app: App):
|
|||||||
ctx.should_return(exception)
|
ctx.should_return(exception)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_default(app: App):
|
async def test_default(app: App):
|
||||||
from plugins.param.param_default import default
|
from plugins.param.param_default import default
|
||||||
|
|
||||||
@@ -570,11 +613,10 @@ async def test_default(app: App):
|
|||||||
ctx.should_return(1)
|
ctx.should_return(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_priority():
|
||||||
async def test_priority():
|
|
||||||
from plugins.param.priority import complex_priority
|
from plugins.param.priority import complex_priority
|
||||||
|
|
||||||
dependent = Dependent.parse(
|
dependent = Dependent[None].parse(
|
||||||
call=complex_priority,
|
call=complex_priority,
|
||||||
allow_types=[
|
allow_types=[
|
||||||
DependParam,
|
DependParam,
|
||||||
|
@@ -22,7 +22,7 @@ from nonebot.permission import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_permission(app: App):
|
async def test_permission(app: App):
|
||||||
async def falsy():
|
async def falsy():
|
||||||
return False
|
return False
|
||||||
@@ -54,7 +54,7 @@ async def test_permission(app: App):
|
|||||||
assert await Permission(truthy, skipped)(bot, event) is True
|
assert await Permission(truthy, skipped)(bot, event) is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(("type", "expected"), [("message", True), ("notice", False)])
|
@pytest.mark.parametrize(("type", "expected"), [("message", True), ("notice", False)])
|
||||||
async def test_message(type: str, expected: bool):
|
async def test_message(type: str, expected: bool):
|
||||||
dependent = next(iter(MESSAGE.checkers))
|
dependent = next(iter(MESSAGE.checkers))
|
||||||
@@ -66,7 +66,7 @@ async def test_message(type: str, expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("notice", True)])
|
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("notice", True)])
|
||||||
async def test_notice(type: str, expected: bool):
|
async def test_notice(type: str, expected: bool):
|
||||||
dependent = next(iter(NOTICE.checkers))
|
dependent = next(iter(NOTICE.checkers))
|
||||||
@@ -78,7 +78,7 @@ async def test_notice(type: str, expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("request", True)])
|
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("request", True)])
|
||||||
async def test_request(type: str, expected: bool):
|
async def test_request(type: str, expected: bool):
|
||||||
dependent = next(iter(REQUEST.checkers))
|
dependent = next(iter(REQUEST.checkers))
|
||||||
@@ -90,7 +90,7 @@ async def test_request(type: str, expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("type", "expected"), [("message", False), ("meta_event", True)]
|
("type", "expected"), [("message", False), ("meta_event", True)]
|
||||||
)
|
)
|
||||||
@@ -104,7 +104,7 @@ async def test_metaevent(type: str, expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("type", "user_id", "expected"),
|
("type", "user_id", "expected"),
|
||||||
[
|
[
|
||||||
@@ -128,7 +128,7 @@ async def test_superuser(app: App, type: str, user_id: str, expected: bool):
|
|||||||
assert await dependent(bot=bot, event=event) == expected
|
assert await dependent(bot=bot, event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("session_ids", "session_id", "expected"),
|
("session_ids", "session_id", "expected"),
|
||||||
[
|
[
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
import pytest
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.plugin import PluginManager, _managers
|
from nonebot.plugin import PluginManager, _managers
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_get_plugin():
|
||||||
async def test_get_plugin():
|
|
||||||
# check simple plugin
|
# check simple plugin
|
||||||
plugin = nonebot.get_plugin("export")
|
plugin = nonebot.get_plugin("export")
|
||||||
assert plugin
|
assert plugin
|
||||||
@@ -22,8 +20,7 @@ async def test_get_plugin():
|
|||||||
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_get_plugin_by_module_name():
|
||||||
async def test_get_plugin_by_module_name():
|
|
||||||
# check get plugin by exact module name
|
# check get plugin by exact module name
|
||||||
plugin = nonebot.get_plugin_by_module_name("plugins.nested")
|
plugin = nonebot.get_plugin_by_module_name("plugins.nested")
|
||||||
assert plugin
|
assert plugin
|
||||||
@@ -48,8 +45,7 @@ async def test_get_plugin_by_module_name():
|
|||||||
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_get_available_plugin():
|
||||||
async def test_get_available_plugin():
|
|
||||||
old_managers = _managers.copy()
|
old_managers = _managers.copy()
|
||||||
_managers.clear()
|
_managers.clear()
|
||||||
try:
|
try:
|
||||||
@@ -63,8 +59,7 @@ async def test_get_available_plugin():
|
|||||||
_managers.extend(old_managers)
|
_managers.extend(old_managers)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_get_plugin_config():
|
||||||
async def test_get_plugin_config():
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
plugin_config: int
|
plugin_config: int
|
||||||
|
|
||||||
|
@@ -1,15 +1,44 @@
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from functools import wraps
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
|
from typing import TypeVar, Callable
|
||||||
|
from typing_extensions import ParamSpec
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.plugin import Plugin, PluginManager, _managers, inherit_supported_adapters
|
from nonebot.plugin import (
|
||||||
|
Plugin,
|
||||||
|
PluginManager,
|
||||||
|
_plugins,
|
||||||
|
_managers,
|
||||||
|
inherit_supported_adapters,
|
||||||
|
)
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def _recover(func: Callable[P, R]) -> Callable[P, R]:
|
||||||
async def test_load_plugin():
|
|
||||||
|
@wraps(func)
|
||||||
|
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
|
origin_managers = _managers.copy()
|
||||||
|
origin_plugins = _plugins.copy()
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
_managers.clear()
|
||||||
|
_managers.extend(origin_managers)
|
||||||
|
_plugins.clear()
|
||||||
|
_plugins.update(origin_plugins)
|
||||||
|
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@_recover
|
||||||
|
def test_load_plugin():
|
||||||
# check regular
|
# check regular
|
||||||
assert nonebot.load_plugin("dynamic.simple")
|
assert nonebot.load_plugin("dynamic.simple")
|
||||||
|
|
||||||
@@ -20,8 +49,7 @@ async def test_load_plugin():
|
|||||||
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[Plugin]):
|
||||||
async def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[Plugin]):
|
|
||||||
loaded_plugins = {
|
loaded_plugins = {
|
||||||
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
||||||
}
|
}
|
||||||
@@ -44,8 +72,7 @@ async def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[P
|
|||||||
PluginManager(search_path=["plugins"]).load_all_plugins()
|
PluginManager(search_path=["plugins"]).load_all_plugins()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_load_nested_plugin():
|
||||||
async def test_load_nested_plugin():
|
|
||||||
parent_plugin = nonebot.get_plugin("nested")
|
parent_plugin = nonebot.get_plugin("nested")
|
||||||
sub_plugin = nonebot.get_plugin("nested:nested_subplugin")
|
sub_plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||||
sub_plugin2 = nonebot.get_plugin("nested:nested_subplugin2")
|
sub_plugin2 = nonebot.get_plugin("nested:nested_subplugin2")
|
||||||
@@ -57,16 +84,16 @@ async def test_load_nested_plugin():
|
|||||||
assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}
|
assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@_recover
|
||||||
async def test_load_json():
|
def test_load_json():
|
||||||
nonebot.load_from_json("./plugins.json")
|
nonebot.load_from_json("./plugins.json")
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
nonebot.load_from_json("./plugins.invalid.json")
|
nonebot.load_from_json("./plugins.invalid.json")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@_recover
|
||||||
async def test_load_toml():
|
def test_load_toml():
|
||||||
nonebot.load_from_toml("./plugins.toml")
|
nonebot.load_from_toml("./plugins.toml")
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Cannot find"):
|
with pytest.raises(ValueError, match="Cannot find"):
|
||||||
@@ -76,52 +103,54 @@ async def test_load_toml():
|
|||||||
nonebot.load_from_toml("./plugins.invalid.toml")
|
nonebot.load_from_toml("./plugins.invalid.toml")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@_recover
|
||||||
async def test_bad_plugin():
|
def test_bad_plugin():
|
||||||
nonebot.load_plugins("bad_plugins")
|
nonebot.load_plugins("bad_plugins")
|
||||||
|
|
||||||
assert nonebot.get_plugin("bad_plugin") is None
|
assert nonebot.get_plugin("bad_plugin") is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@_recover
|
||||||
async def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||||
def _patched_find(name: str):
|
def _patched_find(name: str):
|
||||||
pytest.fail("require existing plugin should not call find_manager_by_name")
|
pytest.fail("require existing plugin should not call find_manager_by_name")
|
||||||
|
|
||||||
monkeypatch.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
with monkeypatch.context() as m:
|
||||||
|
m.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
||||||
|
|
||||||
# require use module name
|
# require use module name
|
||||||
nonebot.require("plugins.export")
|
nonebot.require("plugins.export")
|
||||||
# require use plugin id
|
# require use plugin id
|
||||||
nonebot.require("export")
|
nonebot.require("export")
|
||||||
nonebot.require("nested:nested_subplugin")
|
nonebot.require("nested:nested_subplugin")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@_recover
|
||||||
async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||||
m = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
|
pm = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
|
||||||
_managers.append(m)
|
_managers.append(pm)
|
||||||
num_managers = len(_managers)
|
num_managers = len(_managers)
|
||||||
|
|
||||||
origin_load = PluginManager.load_plugin
|
origin_load = PluginManager.load_plugin
|
||||||
|
|
||||||
def _patched_load(self: PluginManager, name: str):
|
def _patched_load(self: PluginManager, name: str):
|
||||||
assert self is m
|
assert self is pm
|
||||||
return origin_load(self, name)
|
return origin_load(self, name)
|
||||||
|
|
||||||
monkeypatch.setattr(PluginManager, "load_plugin", _patched_load)
|
with monkeypatch.context() as m:
|
||||||
|
m.setattr(PluginManager, "load_plugin", _patched_load)
|
||||||
|
|
||||||
# require standalone plugin
|
# require standalone plugin
|
||||||
nonebot.require("dynamic.require_not_loaded")
|
nonebot.require("dynamic.require_not_loaded")
|
||||||
# require searched plugin
|
# require searched plugin
|
||||||
nonebot.require("dynamic.require_not_loaded.subplugin1")
|
nonebot.require("dynamic.require_not_loaded.subplugin1")
|
||||||
nonebot.require("require_not_loaded:subplugin2")
|
nonebot.require("require_not_loaded:subplugin2")
|
||||||
|
|
||||||
assert len(_managers) == num_managers
|
assert len(_managers) == num_managers
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@_recover
|
||||||
async def test_require_not_declared():
|
def test_require_not_declared():
|
||||||
num_managers = len(_managers)
|
num_managers = len(_managers)
|
||||||
|
|
||||||
nonebot.require("dynamic.require_not_declared")
|
nonebot.require("dynamic.require_not_declared")
|
||||||
@@ -130,14 +159,13 @@ async def test_require_not_declared():
|
|||||||
assert _managers[-1].plugins == {"dynamic.require_not_declared"}
|
assert _managers[-1].plugins == {"dynamic.require_not_declared"}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@_recover
|
||||||
async def test_require_not_found():
|
def test_require_not_found():
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
nonebot.require("some_plugin_not_exist")
|
nonebot.require("some_plugin_not_exist")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_plugin_metadata():
|
||||||
async def test_plugin_metadata():
|
|
||||||
from plugins.metadata import Config, FakeAdapter
|
from plugins.metadata import Config, FakeAdapter
|
||||||
|
|
||||||
plugin = nonebot.get_plugin("metadata")
|
plugin = nonebot.get_plugin("metadata")
|
||||||
@@ -157,8 +185,7 @@ async def test_plugin_metadata():
|
|||||||
assert plugin.metadata.get_supported_adapters() == {FakeAdapter}
|
assert plugin.metadata.get_supported_adapters() == {FakeAdapter}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_inherit_supported_adapters_not_found():
|
||||||
async def test_inherit_supported_adapters_not_found():
|
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
inherit_supported_adapters("some_plugin_not_exist")
|
inherit_supported_adapters("some_plugin_not_exist")
|
||||||
|
|
||||||
@@ -166,7 +193,6 @@ async def test_inherit_supported_adapters_not_found():
|
|||||||
inherit_supported_adapters("export")
|
inherit_supported_adapters("export")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("inherit_plugins", "expected"),
|
("inherit_plugins", "expected"),
|
||||||
[
|
[
|
||||||
@@ -233,7 +259,7 @@ async def test_inherit_supported_adapters_not_found():
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_inherit_supported_adapters_combine(
|
def test_inherit_supported_adapters_combine(
|
||||||
inherit_plugins: tuple[str], expected: set[str]
|
inherit_plugins: tuple[str], expected: set[str]
|
||||||
):
|
):
|
||||||
assert inherit_supported_adapters(*inherit_plugins) == expected
|
assert inherit_supported_adapters(*inherit_plugins) == expected
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from nonebot.plugin import PluginManager, _managers
|
from nonebot.plugin import PluginManager, _managers
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_load_plugin_name():
|
||||||
async def test_load_plugin_name():
|
|
||||||
m = PluginManager(plugins=["dynamic.manager"])
|
m = PluginManager(plugins=["dynamic.manager"])
|
||||||
_managers.append(m)
|
try:
|
||||||
|
_managers.append(m)
|
||||||
|
|
||||||
# load by plugin id
|
# load by plugin id
|
||||||
module1 = m.load_plugin("manager")
|
module1 = m.load_plugin("manager")
|
||||||
# load by module name
|
# load by module name
|
||||||
module2 = m.load_plugin("dynamic.manager")
|
module2 = m.load_plugin("dynamic.manager")
|
||||||
assert module1
|
assert module1
|
||||||
assert module2
|
assert module2
|
||||||
assert module1 is module2
|
assert module1 is module2
|
||||||
|
finally:
|
||||||
|
_managers.remove(m)
|
||||||
|
@@ -18,7 +18,6 @@ from nonebot.rule import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("matcher_name", "pre_rule_factory", "has_permission"),
|
("matcher_name", "pre_rule_factory", "has_permission"),
|
||||||
[
|
[
|
||||||
@@ -102,7 +101,7 @@ from nonebot.rule import (
|
|||||||
pytest.param("matcher_group_on_type", lambda e: IsTypeRule(e), True),
|
pytest.param("matcher_group_on_type", lambda e: IsTypeRule(e), True),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_on(
|
def test_on(
|
||||||
matcher_name: str,
|
matcher_name: str,
|
||||||
pre_rule_factory: Optional[Callable[[type[Event]], T_RuleChecker]],
|
pre_rule_factory: Optional[Callable[[type[Event]], T_RuleChecker]],
|
||||||
has_permission: bool,
|
has_permission: bool,
|
||||||
@@ -150,8 +149,7 @@ async def test_on(
|
|||||||
assert matcher.module_name == "plugins.plugin.matchers"
|
assert matcher.module_name == "plugins.plugin.matchers"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_runtime_on():
|
||||||
async def test_runtime_on():
|
|
||||||
import plugins.plugin.matchers as module
|
import plugins.plugin.matchers as module
|
||||||
from plugins.plugin.matchers import matcher_on_factory
|
from plugins.plugin.matchers import matcher_on_factory
|
||||||
|
|
||||||
|
@@ -49,7 +49,7 @@ from nonebot.rule import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_rule(app: App):
|
async def test_rule(app: App):
|
||||||
async def falsy():
|
async def falsy():
|
||||||
return False
|
return False
|
||||||
@@ -81,7 +81,7 @@ async def test_rule(app: App):
|
|||||||
assert await Rule(truthy, skipped)(bot, event, {}) is False
|
assert await Rule(truthy, skipped)(bot, event, {}) is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_trie(app: App):
|
async def test_trie(app: App):
|
||||||
TrieRule.add_prefix("/fake-prefix", TRIE_VALUE("/", ("fake-prefix",)))
|
TrieRule.add_prefix("/fake-prefix", TRIE_VALUE("/", ("fake-prefix",)))
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ async def test_trie(app: App):
|
|||||||
del TrieRule.prefix["/fake-prefix"]
|
del TrieRule.prefix["/fake-prefix"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("msg", "ignorecase", "type", "text", "expected"),
|
("msg", "ignorecase", "type", "text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -186,7 +186,7 @@ async def test_startswith(
|
|||||||
assert await dependent(event=event, state=state) == expected
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("msg", "ignorecase", "type", "text", "expected"),
|
("msg", "ignorecase", "type", "text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -226,7 +226,7 @@ async def test_endswith(
|
|||||||
assert await dependent(event=event, state=state) == expected
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("msg", "ignorecase", "type", "text", "expected"),
|
("msg", "ignorecase", "type", "text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -266,7 +266,7 @@ async def test_fullmatch(
|
|||||||
assert await dependent(event=event, state=state) == expected
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("kws", "type", "text", "expected"),
|
("kws", "type", "text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -298,7 +298,7 @@ async def test_keyword(
|
|||||||
assert await dependent(event=event, state=state) == expected
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("cmds", "force_whitespace", "cmd", "whitespace", "arg_text", "expected"),
|
("cmds", "force_whitespace", "cmd", "whitespace", "arg_text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -344,7 +344,7 @@ async def test_command(
|
|||||||
assert await dependent(state=state) == expected
|
assert await dependent(state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_shell_command():
|
async def test_shell_command():
|
||||||
state: T_State
|
state: T_State
|
||||||
CMD = ("test",)
|
CMD = ("test",)
|
||||||
@@ -451,7 +451,7 @@ async def test_shell_command():
|
|||||||
assert state[SHELL_ARGS].status != 0
|
assert state[SHELL_ARGS].status != 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("pattern", "type", "text", "expected", "matched"),
|
("pattern", "type", "text", "expected", "matched"),
|
||||||
[
|
[
|
||||||
@@ -494,7 +494,7 @@ async def test_regex(
|
|||||||
assert result.span() == matched.span()
|
assert result.span() == matched.span()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.parametrize("expected", [True, False])
|
@pytest.mark.parametrize("expected", [True, False])
|
||||||
async def test_to_me(expected: bool):
|
async def test_to_me(expected: bool):
|
||||||
test_to_me = to_me()
|
test_to_me = to_me()
|
||||||
@@ -507,7 +507,7 @@ async def test_to_me(expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_is_type():
|
async def test_is_type():
|
||||||
Event1 = make_fake_event()
|
Event1 = make_fake_event()
|
||||||
Event2 = make_fake_event()
|
Event2 = make_fake_event()
|
||||||
|
@@ -5,7 +5,7 @@ import pytest
|
|||||||
from utils import make_fake_event
|
from utils import make_fake_event
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.anyio
|
||||||
async def test_matcher_mutex():
|
async def test_matcher_mutex():
|
||||||
from nonebot.plugins.single_session import matcher_mutex, _running_matcher
|
from nonebot.plugins.single_session import matcher_mutex, _running_matcher
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
|
|
||||||
### 异步优先
|
### 异步优先
|
||||||
|
|
||||||
NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。
|
NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) / [trio](https://trio.readthedocs.io/en/stable/) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。
|
||||||
|
|
||||||
### 完整的类型注解
|
### 完整的类型注解
|
||||||
|
|
||||||
|
@@ -77,6 +77,10 @@ NoneBot 为四种类型的事件响应器提供了五个基本的辅助函数:
|
|||||||
|
|
||||||
## 内置响应规则
|
## 内置响应规则
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
响应规则的使用方法可以参考 [深入 - 响应规则](../appendices/rule.md)。
|
||||||
|
:::
|
||||||
|
|
||||||
### `startswith`
|
### `startswith`
|
||||||
|
|
||||||
`startswith` 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。
|
`startswith` 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。
|
||||||
|
@@ -82,14 +82,16 @@ async def do_something(bot: Bot):
|
|||||||
|
|
||||||
### 事件预处理
|
### 事件预处理
|
||||||
|
|
||||||
这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。
|
这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。在这个钩子函数内抛出 `nonebot.exception.IgnoredException` 会使 NoneBot 忽略该事件。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from nonebot.exception import IgnoredException
|
||||||
from nonebot.message import event_preprocessor
|
from nonebot.message import event_preprocessor
|
||||||
|
|
||||||
@event_preprocessor
|
@event_preprocessor
|
||||||
async def do_something(event: Event):
|
async def do_something(event: Event):
|
||||||
pass
|
if not event.is_tome():
|
||||||
|
raise IgnoredException("some reason")
|
||||||
```
|
```
|
||||||
|
|
||||||
### 事件后处理
|
### 事件后处理
|
||||||
@@ -106,14 +108,16 @@ async def do_something(event: Event):
|
|||||||
|
|
||||||
### 运行预处理
|
### 运行预处理
|
||||||
|
|
||||||
这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。
|
这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。在这个钩子函数内抛出 `nonebot.exception.IgnoredException` 也会使 NoneBot 忽略本次运行。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot.message import run_preprocessor
|
from nonebot.message import run_preprocessor
|
||||||
|
from nonebot.exception import IgnoredException
|
||||||
|
|
||||||
@run_preprocessor
|
@run_preprocessor
|
||||||
async def do_something(event: Event, matcher: Matcher):
|
async def do_something(event: Event, matcher: Matcher):
|
||||||
pass
|
if not event.is_tome():
|
||||||
|
raise IgnoredException("some reason")
|
||||||
```
|
```
|
||||||
|
|
||||||
### 运行后处理
|
### 运行后处理
|
||||||
|
@@ -73,10 +73,12 @@ CUSTOM_CONFIG=config in dotenv
|
|||||||
同时,设置环境变量:
|
同时,设置环境变量:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set CUSTOM_CONFIG "config in environment variables"
|
set CUSTOM_CONFIG 'config in environment variables'
|
||||||
|
# windows powershell
|
||||||
|
$Env:CUSTOM_CONFIG='config in environment variables'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export CUSTOM_CONFIG="config in environment variables"
|
export CUSTOM_CONFIG='config in environment variables'
|
||||||
```
|
```
|
||||||
|
|
||||||
那最终 NoneBot 所读取的内容为环境变量中的内容,即 `config in environment variables`。
|
那最终 NoneBot 所读取的内容为环境变量中的内容,即 `config in environment variables`。
|
||||||
@@ -164,7 +166,7 @@ COMMON_CONFIG=common config # 这个配置项在任何环境中都会被加载
|
|||||||
在生产环境中,可以通过设置环境变量 `ENVIRONMENT=prod` 来确保 NoneBot 读取正确的环境配置。
|
在生产环境中,可以通过设置环境变量 `ENVIRONMENT=prod` 来确保 NoneBot 读取正确的环境配置。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
#### .env.{ENVIRONMENT} 文件
|
#### .env.\{ENVIRONMENT\} 文件
|
||||||
|
|
||||||
`.env.{ENVIRONMENT}` 文件类似于预设,可以让我们在多套不同的配置方案中灵活切换,默认 NoneBot 会读取 `.env.prod` 配置。如果你使用了 `nb-cli` 创建 `simple` 项目,那么将含有两套预设配置:`.env.dev` 和 `.env.prod`。
|
`.env.{ENVIRONMENT}` 文件类似于预设,可以让我们在多套不同的配置方案中灵活切换,默认 NoneBot 会读取 `.env.prod` 配置。如果你使用了 `nb-cli` 创建 `simple` 项目,那么将含有两套预设配置:`.env.dev` 和 `.env.prod`。
|
||||||
|
|
||||||
@@ -176,7 +178,7 @@ nonebot.init(_env_file=".env.dev")
|
|||||||
|
|
||||||
这将忽略在 `.env` 文件或环境变量中指定的 `ENVIRONMENT` 配置项。
|
这将忽略在 `.env` 文件或环境变量中指定的 `ENVIRONMENT` 配置项。
|
||||||
|
|
||||||
## 读取配置项
|
## 读取全局配置项
|
||||||
|
|
||||||
NoneBot 的全局配置对象可以通过 `driver` 获取,如:
|
NoneBot 的全局配置对象可以通过 `driver` 获取,如:
|
||||||
|
|
||||||
@@ -196,7 +198,7 @@ superusers = config.superusers
|
|||||||
|
|
||||||
## 插件配置
|
## 插件配置
|
||||||
|
|
||||||
在一个涉及大量配置项的项目中,通过直接读取配置项的方式显然并不高效。同时,由于额外的全局配置项没有预先定义,开发时编辑器将无法提示字段与类型,并且运行时没有对配置项直接进行合法性检查。那么就需要一种方式来规范定义插件配置项。
|
在一个涉及大量配置项的项目中,通过直接读取全局配置项的方式显然并不高效。同时,由于额外的全局配置项没有预先定义,开发时编辑器将无法提示字段与类型,并且运行时没有对配置项直接进行合法性检查。那么就需要一种方式来规范定义插件配置项。
|
||||||
|
|
||||||
在 NoneBot 中,我们使用强大高效的 `pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在 `weather` 插件目录中新建 `config.py` 来定义一个模型:
|
在 NoneBot 中,我们使用强大高效的 `pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在 `weather` 插件目录中新建 `config.py` 来定义一个模型:
|
||||||
|
|
||||||
@@ -218,7 +220,7 @@ class Config(BaseModel):
|
|||||||
|
|
||||||
在 `config.py` 中,我们定义了一个 `Config` 类,它继承自 `pydantic.BaseModel`,并定义了一些配置项。在 `Config` 类中,我们还定义了一个 `check_priority` 方法,它用于检查 `weather_command_priority` 配置项的合法性。更多关于 `pydantic` 的编写方式,可以参考 [pydantic 官方文档](https://docs.pydantic.dev/)。
|
在 `config.py` 中,我们定义了一个 `Config` 类,它继承自 `pydantic.BaseModel`,并定义了一些配置项。在 `Config` 类中,我们还定义了一个 `check_priority` 方法,它用于检查 `weather_command_priority` 配置项的合法性。更多关于 `pydantic` 的编写方式,可以参考 [pydantic 官方文档](https://docs.pydantic.dev/)。
|
||||||
|
|
||||||
在定义好配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型并使用:
|
在定义好配置模型后,我们可以在插件加载时通过配置模型获取插件配置:
|
||||||
|
|
||||||
```python {5,11} title=weather/__init__.py
|
```python {5,11} title=weather/__init__.py
|
||||||
from nonebot import get_plugin_config
|
from nonebot import get_plugin_config
|
||||||
@@ -295,8 +297,10 @@ DRIVER=~fastapi+~httpx+~websockets
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set DRIVER '~fastapi+~httpx+~websockets'
|
set DRIVER '~fastapi+~httpx+~websockets'
|
||||||
|
# windows powershell
|
||||||
|
$Env:DRIVER='~fastapi+~httpx+~websockets'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export DRIVER='~fastapi+~httpx+~websockets'
|
export DRIVER='~fastapi+~httpx+~websockets'
|
||||||
```
|
```
|
||||||
@@ -331,8 +335,10 @@ HOST=127.0.0.1
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set HOST '127.0.0.1'
|
set HOST '127.0.0.1'
|
||||||
|
# windows powershell
|
||||||
|
$Env:HOST='127.0.0.1'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export HOST='127.0.0.1'
|
export HOST='127.0.0.1'
|
||||||
```
|
```
|
||||||
@@ -367,8 +373,10 @@ PORT=8080
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set PORT '8080'
|
set PORT '8080'
|
||||||
|
# windows powershell
|
||||||
|
$Env:PORT='8080'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export PORT='8080'
|
export PORT='8080'
|
||||||
```
|
```
|
||||||
@@ -407,8 +415,10 @@ LOG_LEVEL=DEBUG
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set LOG_LEVEL 'DEBUG'
|
set LOG_LEVEL 'DEBUG'
|
||||||
|
# windows powershell
|
||||||
|
$Env:LOG_LEVEL='DEBUG'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export LOG_LEVEL='DEBUG'
|
export LOG_LEVEL='DEBUG'
|
||||||
```
|
```
|
||||||
@@ -443,8 +453,10 @@ API_TIMEOUT=10.0
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set API_TIMEOUT '10.0'
|
set API_TIMEOUT '10.0'
|
||||||
|
# windows powershell
|
||||||
|
$Env:API_TIMEOUT='10.0'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export API_TIMEOUT='10.0'
|
export API_TIMEOUT='10.0'
|
||||||
```
|
```
|
||||||
@@ -479,8 +491,10 @@ SUPERUSERS=["123123123"]
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set SUPERUSERS '["123123123"]'
|
set SUPERUSERS '["123123123"]'
|
||||||
|
# windows powershell
|
||||||
|
$Env:SUPERUSERS='["123123123"]'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export SUPERUSERS='["123123123"]'
|
export SUPERUSERS='["123123123"]'
|
||||||
```
|
```
|
||||||
@@ -515,8 +529,10 @@ NICKNAME=["bot"]
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set NICKNAME '["bot"]'
|
set NICKNAME '["bot"]'
|
||||||
|
# windows powershell
|
||||||
|
$Env:NICKNAME='["bot"]'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export NICKNAME='["bot"]'
|
export NICKNAME='["bot"]'
|
||||||
```
|
```
|
||||||
@@ -554,9 +570,12 @@ COMMAND_SEP=[".", " "]
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set COMMAND_START '["/", ""]'
|
set COMMAND_START '["/", ""]'
|
||||||
set COMMAND_SEP '[".", " "]'
|
set COMMAND_SEP '[".", " "]'
|
||||||
|
# windows powershell
|
||||||
|
$Env:COMMAND_START='["/", ""]'
|
||||||
|
$Env:COMMAND_SEP='[".", " "]'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export COMMAND_START='["/", ""]'
|
export COMMAND_START='["/", ""]'
|
||||||
export COMMAND_SEP='[".", " "]'
|
export COMMAND_SEP='[".", " "]'
|
||||||
@@ -592,8 +611,10 @@ SESSION_EXPIRE_TIMEOUT=00:02:00
|
|||||||
<TabItem value="env" label="系统环境变量">
|
<TabItem value="env" label="系统环境变量">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows cmd
|
||||||
set SESSION_EXPIRE_TIMEOUT '00:02:00'
|
set SESSION_EXPIRE_TIMEOUT '00:02:00'
|
||||||
|
# windows powershell
|
||||||
|
$Env:SESSION_EXPIRE_TIMEOUT='00:02:00'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export SESSION_EXPIRE_TIMEOUT='00:02:00'
|
export SESSION_EXPIRE_TIMEOUT='00:02:00'
|
||||||
```
|
```
|
||||||
|
@@ -29,8 +29,9 @@ import Messenger from "@site/src/components/Messenger";
|
|||||||
|
|
||||||
例如,我们可以在 `weather` 插件中添加一个超级用户可用的指令:
|
例如,我们可以在 `weather` 插件中添加一个超级用户可用的指令:
|
||||||
|
|
||||||
```python {2,8} title=weather/__init__.py
|
```python {3,9} title=weather/__init__.py
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
from nonebot.params import Command
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
|
|
||||||
manage = on_command(
|
manage = on_command(
|
||||||
|
@@ -20,7 +20,11 @@ options:
|
|||||||
|
|
||||||
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
||||||
|
|
||||||
```python {3,4} title=weather/__init__.py
|
```python {7,8} title=weather/__init__.py
|
||||||
|
from nonebot import get_plugin_config
|
||||||
|
|
||||||
|
from .config import Config
|
||||||
|
|
||||||
plugin_config = get_plugin_config(Config)
|
plugin_config = get_plugin_config(Config)
|
||||||
|
|
||||||
async def is_enable() -> bool:
|
async def is_enable() -> bool:
|
||||||
@@ -54,8 +58,11 @@ weather = on_command("天气", rule=rule)
|
|||||||
|
|
||||||
在定义响应规则时,我们可以将规则进行细分,来更好地复用规则。而在使用时,我们需要合并多个规则。除了使用 `Rule` 对象来组合多个 `RuleChecker` 外,我们还可以对 `Rule` 对象进行合并。在原 `weather` 插件中,我们可以将 `rule=to_me()` 与 `rule=is_enable` 使用 `&` 运算符合并:
|
在定义响应规则时,我们可以将规则进行细分,来更好地复用规则。而在使用时,我们需要合并多个规则。除了使用 `Rule` 对象来组合多个 `RuleChecker` 外,我们还可以对 `Rule` 对象进行合并。在原 `weather` 插件中,我们可以将 `rule=to_me()` 与 `rule=is_enable` 使用 `&` 运算符合并:
|
||||||
|
|
||||||
```python {10} title=weather/__init__.py
|
```python {13} title=weather/__init__.py
|
||||||
from nonebot.rule import to_me
|
from nonebot.rule import to_me
|
||||||
|
from nonebot import get_plugin_config
|
||||||
|
|
||||||
|
from .config import Config
|
||||||
|
|
||||||
plugin_config = get_plugin_config(Config)
|
plugin_config = get_plugin_config(Config)
|
||||||
|
|
||||||
@@ -66,7 +73,7 @@ weather = on_command(
|
|||||||
"天气",
|
"天气",
|
||||||
rule=to_me() & is_enable,
|
rule=to_me() & is_enable,
|
||||||
aliases={"weather", "查天气"},
|
aliases={"weather", "查天气"},
|
||||||
priority=plugin_config.weather_command_priority
|
priority=plugin_config.weather_command_priority,
|
||||||
block=True,
|
block=True,
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
@@ -71,14 +71,14 @@ alc = Alconna(".rd{roll:int}")
|
|||||||
assert alc.parse(".rd123").header["roll"] == 123
|
assert alc.parse(".rd123").header["roll"] == 123
|
||||||
```
|
```
|
||||||
|
|
||||||
Bracket Header 类似 python 里的 f-string 写法,通过 "{}" 声明匹配类型
|
Bracket Header 类似 python 里的 f-string 写法,通过 `"{}"` 声明匹配类型
|
||||||
|
|
||||||
"{}" 中的内容为 "name:type or pat":
|
`"{}"` 中的内容为 "name:type or pat":
|
||||||
|
|
||||||
- "{}", "{:}" ⇔ "(.+)", 占位符
|
- `"{}"`, `"{:}"` ⇔ `"(.+)"`, 占位符
|
||||||
- "{foo}" ⇔ "(?P<foo>.+)"
|
- `"{foo}"` ⇔ `"(?P<foo>.+)"`
|
||||||
- "{:\d+}" ⇔ "(\d+)"
|
- `"{:\d+}"` ⇔ `"(\d+)"`
|
||||||
- "{foo:int}" ⇔ "(?P<foo>\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
|
- `"{foo:int}"` ⇔ `"(?P<foo>\d+)"`,其中 `"int"` 部分若能转为 `BasePattern` 则读取里面的表达式
|
||||||
|
|
||||||
## 参数声明(Args)
|
## 参数声明(Args)
|
||||||
|
|
||||||
@@ -321,7 +321,7 @@ opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
|||||||
- `keep_crlf`: 命令解析时是否保留换行字符
|
- `keep_crlf`: 命令解析时是否保留换行字符
|
||||||
- `compact`: 命令是否允许第一个参数紧随头部
|
- `compact`: 命令是否允许第一个参数紧随头部
|
||||||
- `strict`: 命令是否严格匹配,若为 False 则未知参数将作为名为 $extra 的参数
|
- `strict`: 命令是否严格匹配,若为 False 则未知参数将作为名为 $extra 的参数
|
||||||
- `context_style`: 命令上下文插值的风格,None 为关闭,bracket 为 {...},parentheses 为 $(...)
|
- `context_style`: 命令上下文插值的风格,None 为关闭,bracket 为 `{...}`,parentheses 为 `$(...)`
|
||||||
- `extra`: 命令的自定义额外信息
|
- `extra`: 命令的自定义额外信息
|
||||||
|
|
||||||
元数据一定使用 `meta=...` 形式传入:
|
元数据一定使用 `meta=...` 形式传入:
|
||||||
|
@@ -96,7 +96,7 @@ class Other(Segment):
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tips
|
:::tip
|
||||||
|
|
||||||
或许你注意到了 `Segment` 上有一个 `children` 属性。
|
或许你注意到了 `Segment` 上有一个 `children` 属性。
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ msg.extend([Text("text")])
|
|||||||
|
|
||||||
这里额外说明 `UniMessage.template` 的拓展控制符
|
这里额外说明 `UniMessage.template` 的拓展控制符
|
||||||
|
|
||||||
相比 `Message`,UniMessage 对于 {:XXX} 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
|
相比 `Message`,UniMessage 对于 `{:XXX}` 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
|
||||||
|
|
||||||
以 At(...) 为例:
|
以 At(...) 为例:
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ UniMessage(At("user", "123"))
|
|||||||
UniMessage(At("user", "123"))
|
UniMessage(At("user", "123"))
|
||||||
```
|
```
|
||||||
|
|
||||||
而在 `AlconnaMatcher` 中,{:XXX} 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
|
而在 `AlconnaMatcher` 中,`{:XXX}` 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
|
||||||
|
|
||||||
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
|
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
|
||||||
from arclet.alconna import Alconna, Args
|
from arclet.alconna import Alconna, Args
|
||||||
|
@@ -31,17 +31,17 @@ require("nonebot_plugin_localstore")
|
|||||||
import nonebot_plugin_localstore as store
|
import nonebot_plugin_localstore as store
|
||||||
|
|
||||||
# 获取插件缓存目录
|
# 获取插件缓存目录
|
||||||
cache_dir = store.get_cache_dir("plugin_name")
|
cache_dir = store.get_plugin_cache_dir()
|
||||||
# 获取插件缓存文件
|
# 获取插件缓存文件
|
||||||
cache_file = store.get_cache_file("plugin_name", "file_name")
|
cache_file = store.get_plugin_cache_file("file_name")
|
||||||
# 获取插件数据目录
|
# 获取插件数据目录
|
||||||
data_dir = store.get_data_dir("plugin_name")
|
data_dir = store.get_plugin_data_dir()
|
||||||
# 获取插件数据文件
|
# 获取插件数据文件
|
||||||
data_file = store.get_data_file("plugin_name", "file_name")
|
data_file = store.get_plugin_data_file("file_name")
|
||||||
# 获取插件配置目录
|
# 获取插件配置目录
|
||||||
config_dir = store.get_config_dir("plugin_name")
|
config_dir = store.get_plugin_config_dir()
|
||||||
# 获取插件配置文件
|
# 获取插件配置文件
|
||||||
config_file = store.get_config_file("plugin_name", "file_name")
|
config_file = store.get_plugin_config_file("file_name")
|
||||||
```
|
```
|
||||||
|
|
||||||
:::danger 警告
|
:::danger 警告
|
||||||
@@ -53,9 +53,61 @@ config_file = store.get_config_file("plugin_name", "file_name")
|
|||||||
```python
|
```python
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
data_file = store.get_data_file("plugin_name", "file_name")
|
data_file = store.get_plugin_data_file("file_name")
|
||||||
# 写入文件内容
|
# 写入文件内容
|
||||||
data_file.write_text("Hello World!")
|
data_file.write_text("Hello World!")
|
||||||
# 读取文件内容
|
# 读取文件内容
|
||||||
data = data_file.read_text()
|
data = data_file.read_text()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::note 提示
|
||||||
|
|
||||||
|
对于嵌套插件,子插件的存储目录将位于父插件存储目录下。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 配置项
|
||||||
|
|
||||||
|
### localstore_cache_dir
|
||||||
|
|
||||||
|
自定义缓存目录
|
||||||
|
|
||||||
|
默认值:
|
||||||
|
|
||||||
|
- macOS: `~/Library/Caches/<AppName>`
|
||||||
|
- Unix: `~/.cache/<AppName>` (XDG default)
|
||||||
|
- Windows: `C:\Users\<username>\AppData\Local\<AppName>\Cache`
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
LOCALSTORE_CACHE_DIR=/tmp/cache
|
||||||
|
```
|
||||||
|
|
||||||
|
### localstore_data_dir
|
||||||
|
|
||||||
|
自定义数据目录
|
||||||
|
|
||||||
|
默认值:
|
||||||
|
|
||||||
|
- macOS: `~/Library/Application Support/<AppName>`
|
||||||
|
- Unix: `~/.local/share/<AppName>` or in $XDG_DATA_HOME, if defined
|
||||||
|
- Win XP (not roaming): `C:\Documents and Settings\<username>\Application Data\<AppName>`
|
||||||
|
- Win 7 (not roaming): `C:\Users\<username>\AppData\Local\<AppName>`
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
LOCALSTORE_DATA_DIR=/tmp/data
|
||||||
|
```
|
||||||
|
|
||||||
|
### localstore_config_dir
|
||||||
|
|
||||||
|
自定义配置目录
|
||||||
|
|
||||||
|
默认值:
|
||||||
|
|
||||||
|
- macOS: same as user_data_dir
|
||||||
|
- Unix: `~/.config/<AppName>`
|
||||||
|
- Win XP (roaming): `C:\Documents and Settings\<username>\Local Settings\Application Data\<AppName>`
|
||||||
|
- Win 7 (roaming): `C:\Users\<username>\AppData\Roaming\<AppName>`
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
LOCALSTORE_CONFIG_DIR=/tmp/config
|
||||||
|
```
|
||||||
|
@@ -74,7 +74,17 @@ pip install pytest-asyncio
|
|||||||
|
|
||||||
## 配置测试
|
## 配置测试
|
||||||
|
|
||||||
在开始测试之前,我们需要对测试进行一些配置,以正确启动我们的机器人。在 `tests` 目录下新建 `conftest.py` 文件,添加以下内容:
|
在开始测试之前,我们需要对测试进行一些配置,以正确启动我们的机器人。
|
||||||
|
|
||||||
|
首先我们需要配置 pytest-asyncio,在 `pyproject.toml` 的 pytest 配置部分添加:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
asyncio_default_fixture_loop_scope = "session"
|
||||||
|
```
|
||||||
|
|
||||||
|
然后,我们在 `tests` 目录下新建 `conftest.py` 文件,添加以下内容:
|
||||||
|
|
||||||
```python title=tests/conftest.py
|
```python title=tests/conftest.py
|
||||||
import pytest
|
import pytest
|
||||||
@@ -83,7 +93,7 @@ import nonebot
|
|||||||
from nonebot.adapters.console import Adapter as ConsoleAdapter
|
from nonebot.adapters.console import Adapter as ConsoleAdapter
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def load_bot():
|
async def after_nonebot_init(after_nonebot_init: None):
|
||||||
# 加载适配器
|
# 加载适配器
|
||||||
driver = nonebot.get_driver()
|
driver = nonebot.get_driver()
|
||||||
driver.register_adapter(ConsoleAdapter)
|
driver.register_adapter(ConsoleAdapter)
|
||||||
@@ -94,9 +104,10 @@ def load_bot():
|
|||||||
|
|
||||||
这样,我们就可以在测试中使用机器人的插件了。通常,我们不需要自行初始化 NoneBot,NoneBug 已经为我们运行了 `nonebot.init()`。如果需要自定义 NoneBot 初始化的参数,我们可以在 `conftest.py` 中添加 `pytest_configure` 钩子函数。例如,我们可以修改 NoneBot 配置环境为 `test` 并从环境变量中输入配置:
|
这样,我们就可以在测试中使用机器人的插件了。通常,我们不需要自行初始化 NoneBot,NoneBug 已经为我们运行了 `nonebot.init()`。如果需要自定义 NoneBot 初始化的参数,我们可以在 `conftest.py` 中添加 `pytest_configure` 钩子函数。例如,我们可以修改 NoneBot 配置环境为 `test` 并从环境变量中输入配置:
|
||||||
|
|
||||||
```python {3,5,7-9} title=tests/conftest.py
|
```python {4,6,8-10} title=tests/conftest.py
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
from nonebug import NONEBOT_INIT_KWARGS
|
from nonebug import NONEBOT_INIT_KWARGS
|
||||||
|
|
||||||
os.environ["ENVIRONMENT"] = "test"
|
os.environ["ENVIRONMENT"] = "test"
|
||||||
@@ -105,6 +116,16 @@ def pytest_configure(config: pytest.Config):
|
|||||||
config.stash[NONEBOT_INIT_KWARGS] = {"secret": os.getenv("INPUT_SECRET")}
|
config.stash[NONEBOT_INIT_KWARGS] = {"secret": os.getenv("INPUT_SECRET")}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
NoneBug 默认也会为我们管理 lifespan 的 startup 与 shutdown。如果不希望 NoneBug 管理 lifespan,你可以在 `pytest_configure` 里添加以下配置:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
from nonebug import NONEBOT_START_LIFESPAN
|
||||||
|
|
||||||
|
def pytest_configure(config: pytest.Config):
|
||||||
|
config.stash[NONEBOT_START_LIFESPAN] = False
|
||||||
|
```
|
||||||
|
|
||||||
## 编写插件测试
|
## 编写插件测试
|
||||||
|
|
||||||
在配置完成插件加载后,我们就可以在测试中使用插件了。NoneBug 通过 pytest fixture `app` 提供各种测试方法,我们可以在测试中使用它来测试插件。现在,我们创建一个测试脚本来测试[深入指南](../../appendices/session-control.mdx)中编写的天气插件。首先,我们先要导入我们需要的模块:
|
在配置完成插件加载后,我们就可以在测试中使用插件了。NoneBug 通过 pytest fixture `app` 提供各种测试方法,我们可以在测试中使用它来测试插件。现在,我们创建一个测试脚本来测试[深入指南](../../appendices/session-control.mdx)中编写的天气插件。首先,我们先要导入我们需要的模块:
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: 开源软件供应链点亮计划 - 暑期 2021
|
description: 开源软件供应链点亮计划 - 暑期 2021
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
---
|
---
|
||||||
|
|
||||||
# 暑期 2021
|
# 暑期 2021
|
||||||
|
|
||||||
**开源软件供应链点亮计划 - 暑期 2021** 是**中国科学院软件研究所**与 **openEuler 社区**共同举办的一项面向高校学生的暑期活动,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer.iscas.ac.cn/) 和 [帮助文档](https://summer.iscas.ac.cn/help/)。
|
**开源软件供应链点亮计划 - 暑期 2021** 是**中国科学院软件研究所**与 **openEuler 社区**共同举办的一项面向高校学生的暑期活动,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer.iscas.ac.cn/) 和 [帮助文档](https://summer.iscas.ac.cn/help/)。
|
||||||
|
|
||||||
NoneBot 社区有幸作为开源社区参与了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学在上面给出的活动官网报名,或通过 <contact@nonebot.dev> 联系我们。
|
NoneBot 社区有幸作为开源社区参与了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学在上面给出的活动官网报名,或通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
|
||||||
|
|
||||||
## NoneBot v1
|
## NoneBot v1
|
||||||
|
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: 开源之夏 - 暑期 2022
|
description: 开源之夏 - 暑期 2022
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
---
|
---
|
||||||
|
|
||||||
# 暑期 2022
|
# 暑期 2022
|
||||||
|
|
||||||
**开源之夏 - 暑期 2022** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
**开源之夏 - 暑期 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> 联系我们。
|
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](mailto:contact@nonebot.dev) 联系我们。
|
||||||
|
|
||||||
## NoneBot2 命令行 CLI 交互体验升级
|
## NoneBot2 命令行 CLI 交互体验升级
|
||||||
|
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: 开源之夏 - 暑期 2023
|
description: 开源之夏 - 暑期 2023
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
---
|
---
|
||||||
|
|
||||||
# 暑期 2023
|
# 暑期 2023
|
||||||
|
|
||||||
**开源之夏 - 暑期 2023** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
**开源之夏 - 暑期 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 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
|
||||||
|
|
||||||
## NoneBot 项目管理图形化面板
|
## NoneBot 项目管理图形化面板
|
||||||
|
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: 开源之夏 - 暑期 2024
|
description: 开源之夏 - 暑期 2024
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
---
|
---
|
||||||
|
|
||||||
# 暑期 2024
|
# 暑期 2024
|
||||||
|
|
||||||
**开源之夏 - 暑期 2024** 是**中国科学院软件研究所**发起的**开源软件供应链点亮计划**系列暑期活动,旨在鼓励高校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。活动联合各大开源社区,针对重要开源软件的开发与维护提供项目开发任务,并向全球高校学生开放报名。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
**开源之夏 - 暑期 2024** 是**中国科学院软件研究所**发起的**开源软件供应链点亮计划**系列暑期活动,旨在鼓励高校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。活动联合各大开源社区,针对重要开源软件的开发与维护提供项目开发任务,并向全球高校学生开放报名。关于具体的活动规划、报名方式,请查看该活动的 [官网](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 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
|
||||||
|
|
||||||
## NonePress 官网组件库更新与优化
|
## NonePress 官网组件库更新与优化
|
||||||
|
|
||||||
|
@@ -107,4 +107,4 @@ if __name__ == "__main__":
|
|||||||
python bot.py
|
python bot.py
|
||||||
```
|
```
|
||||||
|
|
||||||
如果你后续使用了 `nb-cli` ,你仍可以使用 `nb run` 命令来运行机器人,`nb-cli` 会自动检测入口文件 `bot.py` 是否存在并运行。
|
如果你后续使用了 `nb-cli` ,你仍可以使用 `nb run` 命令来运行机器人,`nb-cli` 会自动检测入口文件 `bot.py` 是否存在并运行。同时,你也可以使用 `nb run --reload` 来自动检测代码的更改并自动重新运行入口文件。
|
||||||
|
@@ -51,8 +51,8 @@ from nonebot.rule import to_me
|
|||||||
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
|
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
|
||||||
```
|
```
|
||||||
|
|
||||||
这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令,需要私聊或 `@bot` 时才会响应,优先级为 10 ,阻断事件传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。
|
这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令的响应规则,需要私聊或 `@bot` 时才会响应,优先级为 10(越小越优先),阻断事件向后续优先级传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。
|
||||||
|
|
||||||
:::tip 提示
|
:::tip 提示
|
||||||
需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶](../advanced/matcher.md)或编辑器的提示。
|
需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶 - 基本辅助函数](../advanced/matcher.md#基本辅助函数)或 [API 文档](../api/plugin/on.md#on)。
|
||||||
:::
|
:::
|
||||||
|
@@ -1,317 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
// Note: type annotations allow type checking and IDEs autocompletion
|
|
||||||
|
|
||||||
// color mode config
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["colorMode"]} */
|
|
||||||
const colorMode = {
|
|
||||||
defaultMode: "light",
|
|
||||||
respectPrefersColorScheme: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// navbar config
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["navbar"]} */
|
|
||||||
const navbar = {
|
|
||||||
title: "NoneBot",
|
|
||||||
logo: {
|
|
||||||
alt: "NoneBot",
|
|
||||||
src: "logo.png",
|
|
||||||
href: "/",
|
|
||||||
target: "_self",
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
},
|
|
||||||
hideOnScroll: false,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: "指南",
|
|
||||||
type: "docsMenu",
|
|
||||||
category: "tutorial",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "深入",
|
|
||||||
type: "docsMenu",
|
|
||||||
category: "appendices",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "进阶",
|
|
||||||
type: "docsMenu",
|
|
||||||
category: "advanced",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "API",
|
|
||||||
type: "doc",
|
|
||||||
docId: "api/index",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "更多",
|
|
||||||
type: "dropdown",
|
|
||||||
to: "/store/plugins",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: "最佳实践",
|
|
||||||
type: "doc",
|
|
||||||
docId: "best-practice/scheduler",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "开发者",
|
|
||||||
type: "doc",
|
|
||||||
docId: "developer/plugin-publishing",
|
|
||||||
},
|
|
||||||
{ label: "社区", type: "doc", docId: "community/contact" },
|
|
||||||
{ label: "开源之夏", type: "doc", docId: "ospp/2024" },
|
|
||||||
{ label: "商店", to: "/store/plugins" },
|
|
||||||
{ label: "更新日志", to: "/changelog" },
|
|
||||||
{ label: "论坛", href: "https://discussions.nonebot.dev" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// footer config
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["footer"]} */
|
|
||||||
const footer = {
|
|
||||||
style: "light",
|
|
||||||
logo: {
|
|
||||||
alt: "NoneBot",
|
|
||||||
src: "logo.png",
|
|
||||||
href: "/",
|
|
||||||
target: "_self",
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
},
|
|
||||||
copyright: `Copyright © ${new Date().getFullYear()} NoneBot. All rights reserved.`,
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
title: "Learn",
|
|
||||||
items: [
|
|
||||||
{ label: "Introduction", to: "/docs/" },
|
|
||||||
{ label: "QuickStart", to: "/docs/quick-start" },
|
|
||||||
{ label: "Changelog", to: "/changelog" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "NoneBot Team",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: "Homepage",
|
|
||||||
href: "https://nonebot.dev",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "NoneBot V1",
|
|
||||||
href: "https://v1.nonebot.dev",
|
|
||||||
},
|
|
||||||
{ label: "NoneBot CLI", href: "https://cli.nonebot.dev" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Related",
|
|
||||||
items: [
|
|
||||||
{ label: "OneBot", href: "https://onebot.dev/" },
|
|
||||||
{ label: "go-cqhttp", href: "https://docs.go-cqhttp.org/" },
|
|
||||||
{ label: "Mirai", href: "https://mirai.mamoe.net/" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// prism config
|
|
||||||
/** @type {import('prism-react-renderer').PrismTheme} */
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
// eslint-disable-next-line import/order
|
|
||||||
const lightCodeTheme = require("prism-react-renderer/themes/github");
|
|
||||||
/** @type {import('prism-react-renderer').PrismTheme} */
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
// eslint-disable-next-line import/order
|
|
||||||
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
|
|
||||||
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["prism"]} */
|
|
||||||
const prism = {
|
|
||||||
theme: lightCodeTheme,
|
|
||||||
darkTheme: darkCodeTheme,
|
|
||||||
additionalLanguages: ["docker", "ini"],
|
|
||||||
};
|
|
||||||
|
|
||||||
// algolia config
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["algolia"]} */
|
|
||||||
const algolia = {
|
|
||||||
appId: "X0X5UACHZQ",
|
|
||||||
apiKey: "ac03e1ac2bd0812e2ea38c0cc1ea38c5",
|
|
||||||
indexName: "nonebot",
|
|
||||||
contextualSearch: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// nonepress config
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["nonepress"]} */
|
|
||||||
const nonepress = {
|
|
||||||
tailwindConfig: require("./tailwind.config"),
|
|
||||||
navbar: {
|
|
||||||
docsVersionDropdown: {
|
|
||||||
dropdownItemsAfter: [
|
|
||||||
{
|
|
||||||
label: "1.x",
|
|
||||||
href: "https://v1.nonebot.dev/",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
socialLinks: [
|
|
||||||
{
|
|
||||||
icon: ["fab", "github"],
|
|
||||||
href: "https://github.com/nonebot/nonebot2",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
socialLinks: [
|
|
||||||
{
|
|
||||||
icon: ["fab", "github"],
|
|
||||||
href: "https://github.com/nonebot/nonebot2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: ["fab", "qq"],
|
|
||||||
href: "https://jq.qq.com/?_wv=1027&k=5OFifDh",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: ["fab", "telegram"],
|
|
||||||
href: "https://t.me/botuniverse",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: ["fab", "discord"],
|
|
||||||
href: "https://discord.gg/VKtE6Gdc4h",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// theme config
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig} */
|
|
||||||
const themeConfig = {
|
|
||||||
colorMode,
|
|
||||||
navbar,
|
|
||||||
footer,
|
|
||||||
prism,
|
|
||||||
algolia,
|
|
||||||
nonepress,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('@docusaurus/types').Config} */
|
|
||||||
const siteConfig = {
|
|
||||||
title: "NoneBot",
|
|
||||||
tagline: "跨平台 Python 异步机器人框架",
|
|
||||||
favicon: "icons/favicon.ico",
|
|
||||||
|
|
||||||
// Set the production url of your site here
|
|
||||||
url: "https://nonebot.dev",
|
|
||||||
// Set the /<baseUrl>/ pathname under which your site is served
|
|
||||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
|
||||||
baseUrl: process.env.BASE_URL || "/",
|
|
||||||
|
|
||||||
// GitHub pages deployment config.
|
|
||||||
// If you aren't using GitHub pages, you don't need these.
|
|
||||||
organizationName: "nonebot", // Usually your GitHub org/user name.
|
|
||||||
projectName: "nonebot2", // Usually your repo name.
|
|
||||||
|
|
||||||
onBrokenLinks: "throw",
|
|
||||||
onBrokenMarkdownLinks: "warn",
|
|
||||||
|
|
||||||
// Even if you don't use internalization, you can use this field to set useful
|
|
||||||
// metadata like html lang. For example, if your site is Chinese, you may want
|
|
||||||
// to replace "en" with "zh-Hans".
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: "zh-Hans",
|
|
||||||
locales: ["zh-Hans"],
|
|
||||||
},
|
|
||||||
|
|
||||||
headTags: [
|
|
||||||
// 百度搜索资源平台
|
|
||||||
// https://ziyuan.baidu.com/
|
|
||||||
{
|
|
||||||
tagName: "meta",
|
|
||||||
attributes: {
|
|
||||||
name: "baidu-site-verification",
|
|
||||||
content: "codeva-0GTZpDnDrW",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
scripts: [
|
|
||||||
// 百度统计
|
|
||||||
// https://tongji.baidu.com/
|
|
||||||
{
|
|
||||||
type: "text/javascript",
|
|
||||||
charset: "UTF-8",
|
|
||||||
src: "https://hm.baidu.com/hm.js?875efa50097818701ee681edd63eaac6",
|
|
||||||
async: true,
|
|
||||||
},
|
|
||||||
// 万维广告
|
|
||||||
// https://wwads.cn/
|
|
||||||
{
|
|
||||||
type: "text/javascript",
|
|
||||||
charset: "UTF-8",
|
|
||||||
src: "https://cdn.wwads.cn/js/makemoney.js",
|
|
||||||
async: true,
|
|
||||||
},
|
|
||||||
// uwu logo
|
|
||||||
{
|
|
||||||
type: "text/javascript",
|
|
||||||
charset: "UTF-8",
|
|
||||||
src: "/uwu.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
"@nullbot/docusaurus-preset-nonepress",
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').Options} */
|
|
||||||
({
|
|
||||||
docs: {
|
|
||||||
sidebarPath: require.resolve("./sidebars.js"),
|
|
||||||
// Please change this to your repo.
|
|
||||||
editUrl: "https://github.com/nonebot/nonebot2/edit/master/website/",
|
|
||||||
showLastUpdateAuthor: true,
|
|
||||||
showLastUpdateTime: true,
|
|
||||||
// exclude: [
|
|
||||||
// "**/_*.{js,jsx,ts,tsx,md,mdx}",
|
|
||||||
// "**/_*/**",
|
|
||||||
// "**/*.test.{js,jsx,ts,tsx}",
|
|
||||||
// "**/__tests__/**",
|
|
||||||
// ],
|
|
||||||
// async sidebarItemsGenerator({
|
|
||||||
// isCategoryIndex: defaultCategoryIndexMatcher,
|
|
||||||
// defaultSidebarItemsGenerator,
|
|
||||||
// ...args
|
|
||||||
// }) {
|
|
||||||
// return defaultSidebarItemsGenerator({
|
|
||||||
// ...args,
|
|
||||||
// isCategoryIndex(doc) {
|
|
||||||
// // disable category index convention for generated API docs
|
|
||||||
// if (
|
|
||||||
// doc.directories.length > 0 &&
|
|
||||||
// doc.directories.at(-1) === "api"
|
|
||||||
// ) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// return defaultCategoryIndexMatcher(doc);
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
// theme: {
|
|
||||||
// customCss: require.resolve("./src/css/custom.css"),
|
|
||||||
// },
|
|
||||||
sitemap: {
|
|
||||||
changefreq: "daily",
|
|
||||||
priority: 0.5,
|
|
||||||
},
|
|
||||||
gtag: {
|
|
||||||
trackingID: "G-MRS1GMZG0F",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
plugins: [require("./src/plugins/webpack-plugin.cjs")],
|
|
||||||
|
|
||||||
themeConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = siteConfig;
|
|
353
website/docusaurus.config.ts
Normal file
353
website/docusaurus.config.ts
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
import type { Config } from "@docusaurus/types";
|
||||||
|
import type { Options as ChangelogOptions } from "@nullbot/docusaurus-plugin-changelog";
|
||||||
|
import type * as Preset from "@nullbot/docusaurus-preset-nonepress";
|
||||||
|
import { themes } from "prism-react-renderer";
|
||||||
|
|
||||||
|
// By default, we use Docusaurus Faster
|
||||||
|
// DOCUSAURUS_SLOWER=true is useful for benchmarking faster against slower
|
||||||
|
// hyperfine --prepare 'yarn clear:website' --runs 3 'DOCUSAURUS_SLOWER=true yarn build:website:fast' 'yarn build:website:fast'
|
||||||
|
const isSlower = process.env.DOCUSAURUS_SLOWER === "true";
|
||||||
|
if (isSlower) {
|
||||||
|
console.log("🐢 Using slower Docusaurus build");
|
||||||
|
}
|
||||||
|
|
||||||
|
// color mode config
|
||||||
|
const colorMode: Preset.ThemeConfig["colorMode"] = {
|
||||||
|
defaultMode: "light",
|
||||||
|
respectPrefersColorScheme: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// navbar config
|
||||||
|
const navbar: Preset.ThemeConfig["navbar"] = {
|
||||||
|
title: "NoneBot",
|
||||||
|
logo: {
|
||||||
|
alt: "NoneBot",
|
||||||
|
src: "logo.png",
|
||||||
|
href: "/",
|
||||||
|
target: "_self",
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
},
|
||||||
|
hideOnScroll: false,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "指南",
|
||||||
|
type: "docsMenu",
|
||||||
|
category: "tutorial",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "深入",
|
||||||
|
type: "docsMenu",
|
||||||
|
category: "appendices",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "进阶",
|
||||||
|
type: "docsMenu",
|
||||||
|
category: "advanced",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "API",
|
||||||
|
type: "doc",
|
||||||
|
docId: "api/index",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "更多",
|
||||||
|
type: "dropdown",
|
||||||
|
to: "/store/plugins",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "最佳实践",
|
||||||
|
type: "doc",
|
||||||
|
docId: "best-practice/scheduler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "开发者",
|
||||||
|
type: "doc",
|
||||||
|
docId: "developer/plugin-publishing",
|
||||||
|
},
|
||||||
|
{ label: "社区", type: "doc", docId: "community/contact" },
|
||||||
|
{ label: "开源之夏", type: "doc", docId: "ospp/2024" },
|
||||||
|
{ label: "商店", to: "/store/plugins" },
|
||||||
|
{ label: "更新日志", to: "/changelog" },
|
||||||
|
{ label: "论坛", href: "https://discussions.nonebot.dev" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// footer config
|
||||||
|
const footer: Preset.ThemeConfig["footer"] = {
|
||||||
|
style: "light",
|
||||||
|
logo: {
|
||||||
|
alt: "NoneBot",
|
||||||
|
src: "logo.png",
|
||||||
|
href: "/",
|
||||||
|
target: "_self",
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
},
|
||||||
|
copyright: `Copyright © ${new Date().getFullYear()} NoneBot. All rights reserved.`,
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
title: "Learn",
|
||||||
|
items: [
|
||||||
|
{ label: "Introduction", to: "/docs/" },
|
||||||
|
{ label: "QuickStart", to: "/docs/quick-start" },
|
||||||
|
{ label: "Changelog", to: "/changelog" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "NoneBot Team",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Homepage",
|
||||||
|
href: "https://nonebot.dev",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "NoneBot V1",
|
||||||
|
href: "https://v1.nonebot.dev",
|
||||||
|
},
|
||||||
|
{ label: "NoneBot CLI", href: "https://cli.nonebot.dev" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Related",
|
||||||
|
items: [
|
||||||
|
{ label: "OneBot", href: "https://onebot.dev/" },
|
||||||
|
{ label: "go-cqhttp", href: "https://docs.go-cqhttp.org/" },
|
||||||
|
{ label: "Mirai", href: "https://mirai.mamoe.net/" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// prism config
|
||||||
|
const lightCodeTheme = themes.github;
|
||||||
|
const darkCodeTheme = themes.dracula;
|
||||||
|
|
||||||
|
const prism: Preset.ThemeConfig["prism"] = {
|
||||||
|
theme: lightCodeTheme,
|
||||||
|
darkTheme: darkCodeTheme,
|
||||||
|
additionalLanguages: ["docker", "ini"],
|
||||||
|
};
|
||||||
|
|
||||||
|
// algolia config
|
||||||
|
const algolia: Preset.ThemeConfig["algolia"] = {
|
||||||
|
appId: "X0X5UACHZQ",
|
||||||
|
apiKey: "ac03e1ac2bd0812e2ea38c0cc1ea38c5",
|
||||||
|
indexName: "nonebot",
|
||||||
|
contextualSearch: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// nonepress config
|
||||||
|
const nonepress: Preset.ThemeConfig["nonepress"] = {
|
||||||
|
tailwindConfig: require("./tailwind.config"),
|
||||||
|
navbar: {
|
||||||
|
docsVersionDropdown: {
|
||||||
|
dropdownItemsAfter: [
|
||||||
|
{
|
||||||
|
label: "1.x",
|
||||||
|
href: "https://v1.nonebot.dev/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
socialLinks: [
|
||||||
|
{
|
||||||
|
icon: ["fab", "github"],
|
||||||
|
href: "https://github.com/nonebot/nonebot2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
socialLinks: [
|
||||||
|
{
|
||||||
|
icon: ["fab", "github"],
|
||||||
|
href: "https://github.com/nonebot/nonebot2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ["fab", "qq"],
|
||||||
|
href: "https://jq.qq.com/?_wv=1027&k=5OFifDh",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ["fab", "telegram"],
|
||||||
|
href: "https://t.me/botuniverse",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ["fab", "discord"],
|
||||||
|
href: "https://discord.gg/VKtE6Gdc4h",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// theme config
|
||||||
|
const themeConfig: Preset.ThemeConfig = {
|
||||||
|
colorMode,
|
||||||
|
navbar,
|
||||||
|
footer,
|
||||||
|
prism,
|
||||||
|
algolia,
|
||||||
|
nonepress,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function createConfigAsync() {
|
||||||
|
return {
|
||||||
|
title: "NoneBot",
|
||||||
|
tagline: "跨平台 Python 异步机器人框架",
|
||||||
|
favicon: "icons/favicon.ico",
|
||||||
|
|
||||||
|
// Set the production url of your site here
|
||||||
|
url: "https://nonebot.dev",
|
||||||
|
// Set the /<baseUrl>/ pathname under which your site is served
|
||||||
|
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||||
|
baseUrl: process.env.BASE_URL || "/",
|
||||||
|
|
||||||
|
// GitHub pages deployment config.
|
||||||
|
// If you aren't using GitHub pages, you don't need these.
|
||||||
|
organizationName: "nonebot", // Usually your GitHub org/user name.
|
||||||
|
projectName: "nonebot2", // Usually your repo name.
|
||||||
|
|
||||||
|
onBrokenLinks: "throw",
|
||||||
|
onBrokenMarkdownLinks: "warn",
|
||||||
|
|
||||||
|
// Even if you don't use internalization, you can use this field to set useful
|
||||||
|
// metadata like html lang. For example, if your site is Chinese, you may want
|
||||||
|
// to replace "en" with "zh-Hans".
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: "zh-Hans",
|
||||||
|
locales: ["zh-Hans"],
|
||||||
|
},
|
||||||
|
|
||||||
|
headTags: [
|
||||||
|
// 百度搜索资源平台
|
||||||
|
// https://ziyuan.baidu.com/
|
||||||
|
{
|
||||||
|
tagName: "meta",
|
||||||
|
attributes: {
|
||||||
|
name: "baidu-site-verification",
|
||||||
|
content: "codeva-0GTZpDnDrW",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scripts: [
|
||||||
|
// 百度统计
|
||||||
|
// https://tongji.baidu.com/
|
||||||
|
{
|
||||||
|
type: "text/javascript",
|
||||||
|
charset: "UTF-8",
|
||||||
|
src: "https://hm.baidu.com/hm.js?875efa50097818701ee681edd63eaac6",
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
// 万维广告
|
||||||
|
// https://wwads.cn/
|
||||||
|
{
|
||||||
|
type: "text/javascript",
|
||||||
|
charset: "UTF-8",
|
||||||
|
src: "https://cdn.wwads.cn/js/makemoney.js",
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
// uwu logo
|
||||||
|
{
|
||||||
|
type: "text/javascript",
|
||||||
|
charset: "UTF-8",
|
||||||
|
src: "/uwu.js",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
"@nullbot/docusaurus-preset-nonepress",
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').Options} */
|
||||||
|
{
|
||||||
|
docs: {
|
||||||
|
sidebarPath: require.resolve("./sidebars.js"),
|
||||||
|
// Please change this to your repo.
|
||||||
|
editUrl: "https://github.com/nonebot/nonebot2/edit/master/website/",
|
||||||
|
showLastUpdateAuthor: true,
|
||||||
|
showLastUpdateTime: true,
|
||||||
|
// exclude: [
|
||||||
|
// "**/_*.{js,jsx,ts,tsx,md,mdx}",
|
||||||
|
// "**/_*/**",
|
||||||
|
// "**/*.test.{js,jsx,ts,tsx}",
|
||||||
|
// "**/__tests__/**",
|
||||||
|
// ],
|
||||||
|
// async sidebarItemsGenerator({
|
||||||
|
// isCategoryIndex: defaultCategoryIndexMatcher,
|
||||||
|
// defaultSidebarItemsGenerator,
|
||||||
|
// ...args
|
||||||
|
// }) {
|
||||||
|
// return defaultSidebarItemsGenerator({
|
||||||
|
// ...args,
|
||||||
|
// isCategoryIndex(doc) {
|
||||||
|
// // disable category index convention for generated API docs
|
||||||
|
// if (
|
||||||
|
// doc.directories.length > 0 &&
|
||||||
|
// doc.directories.at(-1) === "api"
|
||||||
|
// ) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return defaultCategoryIndexMatcher(doc);
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
// theme: {
|
||||||
|
// customCss: require.resolve("./src/css/custom.css"),
|
||||||
|
// },
|
||||||
|
sitemap: {
|
||||||
|
changefreq: "daily",
|
||||||
|
priority: 0.5,
|
||||||
|
},
|
||||||
|
gtag: {
|
||||||
|
trackingID: "G-MRS1GMZG0F",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
webpack: {
|
||||||
|
jsLoader: (isServer) => ({
|
||||||
|
loader: require.resolve("swc-loader"),
|
||||||
|
options: {
|
||||||
|
jsc: {
|
||||||
|
parser: {
|
||||||
|
syntax: "typescript",
|
||||||
|
tsx: true,
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
react: {
|
||||||
|
runtime: "automatic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
target: "es2017",
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
type: isServer ? "commonjs" : "es6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
require("./src/plugins/webpack-plugin.ts"),
|
||||||
|
[
|
||||||
|
"@nullbot/docusaurus-plugin-changelog",
|
||||||
|
{
|
||||||
|
changelogPath: "src/changelog/changelog.md",
|
||||||
|
changelogHeader: `description: Changelog
|
||||||
|
toc_max_heading_level: 2
|
||||||
|
sidebar_custom_props:
|
||||||
|
sidebar_id: changelog`,
|
||||||
|
} satisfies ChangelogOptions,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
markdown: {
|
||||||
|
mdx1Compat: {
|
||||||
|
headingIds: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
themeConfig,
|
||||||
|
} satisfies Config;
|
||||||
|
}
|
@@ -22,24 +22,27 @@
|
|||||||
"typecheck": "tsc"
|
"typecheck": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "^2.4.1",
|
"@docusaurus/core": "^3.5.2",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"@nullbot/docusaurus-preset-nonepress": "^2.1.2",
|
"@nullbot/docusaurus-plugin-changelog": "^3.0.0",
|
||||||
"clsx": "^1.2.1",
|
"@nullbot/docusaurus-preset-nonepress": "^3.0.0",
|
||||||
"copy-text-to-clipboard": "^3.0.1",
|
"@swc/core": "^1.7.26",
|
||||||
"prism-react-renderer": "^1.3.5",
|
"clsx": "^2.0.0",
|
||||||
|
"copy-text-to-clipboard": "^3.2.0",
|
||||||
|
"prism-react-renderer": "^2.3.0",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"react": "^17.0.1",
|
"react": "^18.0.0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^18.0.0",
|
||||||
"react-use-pagination": "^2.0.1"
|
"react-use-pagination": "^2.0.1",
|
||||||
|
"swc-loader": "^0.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "^2.4.1",
|
"@docusaurus/module-type-aliases": "^3.5.2",
|
||||||
"@tsconfig/docusaurus": "^1.0.5",
|
"@nullbot/docusaurus-tsconfig": "^3.0.0",
|
||||||
"@types/react-color": "^3.0.10",
|
"@types/react-color": "^3.0.10",
|
||||||
"asciinema-player": "^3.5.0",
|
"asciinema-player": "^3.5.0",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "~5.5.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@@ -54,6 +57,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.14"
|
"node": ">=18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,11 +8,15 @@
|
|||||||
|
|
||||||
Create as many sidebars as you want.
|
Create as many sidebars as you want.
|
||||||
*/
|
*/
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
// @ts-check
|
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
|
||||||
|
import { getChangelogItemsSync } from "@nullbot/docusaurus-plugin-changelog";
|
||||||
|
|
||||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
const changelogPath = path.join(__dirname, "src/changelog/changelog.md");
|
||||||
const sidebars = {
|
const changelogItems = getChangelogItemsSync(changelogPath, 10);
|
||||||
|
|
||||||
|
const sidebars: SidebarsConfig = {
|
||||||
tutorial: [
|
tutorial: [
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
@@ -133,6 +137,22 @@ const sidebars = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
changelog: [
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "更新日志",
|
||||||
|
collapsible: false,
|
||||||
|
items: changelogItems.map<{ type: "link"; label: string; href: string }>(
|
||||||
|
(chunk, index) => ({
|
||||||
|
type: "link",
|
||||||
|
label: chunk[0]!.title,
|
||||||
|
href: `/changelog/${
|
||||||
|
index > 0 ? encodeURIComponent(chunk[0]!.title) : ""
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = sidebars;
|
export default sidebars;
|
@@ -5,6 +5,170 @@ toc_max_heading_level: 2
|
|||||||
|
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
## v2.4.0
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: 跳过部分非必要的 task group 创建 [@yanyongyu](https://github.com/yanyongyu) ([#3095](https://github.com/nonebot/nonebot2/pull/3095))
|
||||||
|
- Feature: 迁移至结构化并发框架 AnyIO [@yanyongyu](https://github.com/yanyongyu) ([#3053](https://github.com/nonebot/nonebot2/pull/3053))
|
||||||
|
- Feature: 添加 websockets 驱动器 proxy 连接警告 [@shoucandanghehe](https://github.com/shoucandanghehe) ([#2916](https://github.com/nonebot/nonebot2/pull/2916))
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- Fix: 修复结构化并发子依赖取消缓存问题 [@yanyongyu](https://github.com/yanyongyu) ([#3084](https://github.com/nonebot/nonebot2/pull/3084))
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Docs: 新增 nonebug 新版启动需要的配置 [@yanyongyu](https://github.com/yanyongyu) ([#3087](https://github.com/nonebot/nonebot2/pull/3087))
|
||||||
|
- Docs: 修复侧边栏滚动 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3062](https://github.com/nonebot/nonebot2/pull/3062))
|
||||||
|
- Docs: 升级到 Docusaurus V3 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#2956](https://github.com/nonebot/nonebot2/pull/2956))
|
||||||
|
- Docs: 修改文档示例代码与部分表述 [@yixinNB](https://github.com/yixinNB) ([#2797](https://github.com/nonebot/nonebot2/pull/2797))
|
||||||
|
- Docs: 添加钩子函数 IgnoredException 用法 [@refparo](https://github.com/refparo) ([#2912](https://github.com/nonebot/nonebot2/pull/2912))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Plugin: 移除不再维护的插件 [@ssttkkl](https://github.com/ssttkkl) ([#3040](https://github.com/nonebot/nonebot2/pull/3040))
|
||||||
|
- Plugin: 删除不再维护的 simplemusic hikarisearch 插件 [@MeetWq](https://github.com/MeetWq) ([#2933](https://github.com/nonebot/nonebot2/pull/2933))
|
||||||
|
- Plugin: 删除插件 `nonebot-plugin-ntqq-restart` [@kanbereina](https://github.com/kanbereina) ([#2926](https://github.com/nonebot/nonebot2/pull/2926))
|
||||||
|
- Adapter: 移除社区版 mirai 适配器 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2909](https://github.com/nonebot/nonebot2/pull/2909))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: Comfyui绘图插件 [@noneflow](https://github.com/noneflow) ([#3081](https://github.com/nonebot/nonebot2/pull/3081))
|
||||||
|
- Plugin: 每日wife [@noneflow](https://github.com/noneflow) ([#3094](https://github.com/nonebot/nonebot2/pull/3094))
|
||||||
|
- Plugin: nonebot_plugin_impart [@noneflow](https://github.com/noneflow) ([#3079](https://github.com/nonebot/nonebot2/pull/3079))
|
||||||
|
- Plugin: Pix图库 [@noneflow](https://github.com/noneflow) ([#3083](https://github.com/nonebot/nonebot2/pull/3083))
|
||||||
|
- Plugin: nonebot_plugin_partner_join [@noneflow](https://github.com/noneflow) ([#3051](https://github.com/nonebot/nonebot2/pull/3051))
|
||||||
|
- Plugin: pong [@noneflow](https://github.com/noneflow) ([#3066](https://github.com/nonebot/nonebot2/pull/3066))
|
||||||
|
- Plugin: Bot的消息也是消息 [@noneflow](https://github.com/noneflow) ([#3064](https://github.com/nonebot/nonebot2/pull/3064))
|
||||||
|
- Plugin: BiliMusic Downloader [@noneflow](https://github.com/noneflow) ([#3046](https://github.com/nonebot/nonebot2/pull/3046))
|
||||||
|
- Plugin: 防撤回 [@noneflow](https://github.com/noneflow) ([#3055](https://github.com/nonebot/nonebot2/pull/3055))
|
||||||
|
- Plugin: nonebot_plugin_mai_arcade [@noneflow](https://github.com/noneflow) ([#3047](https://github.com/nonebot/nonebot2/pull/3047))
|
||||||
|
- Plugin: DDNet 成绩查询 [@noneflow](https://github.com/noneflow) ([#3031](https://github.com/nonebot/nonebot2/pull/3031))
|
||||||
|
- Plugin: 省流 [@noneflow](https://github.com/noneflow) ([#3052](https://github.com/nonebot/nonebot2/pull/3052))
|
||||||
|
- Plugin: FishSpeechTTS [@noneflow](https://github.com/noneflow) ([#3050](https://github.com/nonebot/nonebot2/pull/3050))
|
||||||
|
- Plugin: 语音点歌 [@noneflow](https://github.com/noneflow) ([#3037](https://github.com/nonebot/nonebot2/pull/3037))
|
||||||
|
- Plugin: Gotify [@noneflow](https://github.com/noneflow) ([#3043](https://github.com/nonebot/nonebot2/pull/3043))
|
||||||
|
- Plugin: 涩图插件 [@noneflow](https://github.com/noneflow) ([#3039](https://github.com/nonebot/nonebot2/pull/3039))
|
||||||
|
- Plugin: boom [@noneflow](https://github.com/noneflow) ([#3017](https://github.com/nonebot/nonebot2/pull/3017))
|
||||||
|
- Plugin: 恶魔轮盘赌 [@noneflow](https://github.com/noneflow) ([#3033](https://github.com/nonebot/nonebot2/pull/3033))
|
||||||
|
- Plugin: 机厅 [@noneflow](https://github.com/noneflow) ([#3029](https://github.com/nonebot/nonebot2/pull/3029))
|
||||||
|
- Plugin: PM帮助 [@noneflow](https://github.com/noneflow) ([#3023](https://github.com/nonebot/nonebot2/pull/3023))
|
||||||
|
- Plugin: NailongRemove [@noneflow](https://github.com/noneflow) ([#2972](https://github.com/nonebot/nonebot2/pull/2972))
|
||||||
|
- Plugin: 团购 [@noneflow](https://github.com/noneflow) ([#3027](https://github.com/nonebot/nonebot2/pull/3027))
|
||||||
|
- Plugin: 真寻日报 [@noneflow](https://github.com/noneflow) ([#3021](https://github.com/nonebot/nonebot2/pull/3021))
|
||||||
|
- Plugin: 运行状态 [@noneflow](https://github.com/noneflow) ([#3019](https://github.com/nonebot/nonebot2/pull/3019))
|
||||||
|
- Plugin: 西工大翱翔门户成绩监控 [@noneflow](https://github.com/noneflow) ([#3013](https://github.com/nonebot/nonebot2/pull/3013))
|
||||||
|
- Plugin: nb插件更新器 [@noneflow](https://github.com/noneflow) ([#3015](https://github.com/nonebot/nonebot2/pull/3015))
|
||||||
|
- Plugin: 涩涩保存器 [@noneflow](https://github.com/noneflow) ([#2988](https://github.com/nonebot/nonebot2/pull/2988))
|
||||||
|
- Plugin: nonebot_plugin_BFVsearch [@noneflow](https://github.com/noneflow) ([#3008](https://github.com/nonebot/nonebot2/pull/3008))
|
||||||
|
- Plugin: lingyi_chat [@noneflow](https://github.com/noneflow) ([#3006](https://github.com/nonebot/nonebot2/pull/3006))
|
||||||
|
- Plugin: ZXPM插件管理 [@noneflow](https://github.com/noneflow) ([#3003](https://github.com/nonebot/nonebot2/pull/3003))
|
||||||
|
- Plugin: MinecraftWatcher [@noneflow](https://github.com/noneflow) ([#3010](https://github.com/nonebot/nonebot2/pull/3010))
|
||||||
|
- Plugin: BF5_grouptools [@noneflow](https://github.com/noneflow) ([#3004](https://github.com/nonebot/nonebot2/pull/3004))
|
||||||
|
- Plugin: lolinfo [@noneflow](https://github.com/noneflow) ([#2997](https://github.com/nonebot/nonebot2/pull/2997))
|
||||||
|
- Plugin: osu! Match Monitor [@noneflow](https://github.com/noneflow) ([#2985](https://github.com/nonebot/nonebot2/pull/2985))
|
||||||
|
- Plugin: Marsho AI插件 [@noneflow](https://github.com/noneflow) ([#2993](https://github.com/nonebot/nonebot2/pull/2993))
|
||||||
|
- Plugin: nonechat [@noneflow](https://github.com/noneflow) ([#2990](https://github.com/nonebot/nonebot2/pull/2990))
|
||||||
|
- Plugin: nonebot_plugin_SimpleToWrite [@noneflow](https://github.com/noneflow) ([#2995](https://github.com/nonebot/nonebot2/pull/2995))
|
||||||
|
- Plugin: Beat Saber查分器 [@noneflow](https://github.com/noneflow) ([#2974](https://github.com/nonebot/nonebot2/pull/2974))
|
||||||
|
- Plugin: githubmodels [@noneflow](https://github.com/noneflow) ([#2945](https://github.com/nonebot/nonebot2/pull/2945))
|
||||||
|
- Plugin: 给我点颜色瞧瞧 [@noneflow](https://github.com/noneflow) ([#2984](https://github.com/nonebot/nonebot2/pull/2984))
|
||||||
|
- Plugin: pjsk-helper [@noneflow](https://github.com/noneflow) ([#2980](https://github.com/nonebot/nonebot2/pull/2980))
|
||||||
|
- Plugin: 趣味内容插件 [@noneflow](https://github.com/noneflow) ([#2981](https://github.com/nonebot/nonebot2/pull/2981))
|
||||||
|
- Plugin: 计算器:游戏 [@noneflow](https://github.com/noneflow) ([#2976](https://github.com/nonebot/nonebot2/pull/2976))
|
||||||
|
- Plugin: nonebot-plugin-yareminder [@noneflow](https://github.com/noneflow) ([#2964](https://github.com/nonebot/nonebot2/pull/2964))
|
||||||
|
- Plugin: 批量撤回 [@noneflow](https://github.com/noneflow) ([#2966](https://github.com/nonebot/nonebot2/pull/2966))
|
||||||
|
- Plugin: inspect [@noneflow](https://github.com/noneflow) ([#2971](https://github.com/nonebot/nonebot2/pull/2971))
|
||||||
|
- Plugin: 通用信息 [@noneflow](https://github.com/noneflow) ([#2969](https://github.com/nonebot/nonebot2/pull/2969))
|
||||||
|
- Plugin: SSE日志输出流 [@noneflow](https://github.com/noneflow) ([#2960](https://github.com/nonebot/nonebot2/pull/2960))
|
||||||
|
- Plugin: WITFF [@noneflow](https://github.com/noneflow) ([#2955](https://github.com/nonebot/nonebot2/pull/2955))
|
||||||
|
- Plugin: weather-rank [@noneflow](https://github.com/noneflow) ([#2949](https://github.com/nonebot/nonebot2/pull/2949))
|
||||||
|
- Plugin: 二维码生成器 [@noneflow](https://github.com/noneflow) ([#2942](https://github.com/nonebot/nonebot2/pull/2942))
|
||||||
|
- Plugin: 次元星辰 [@noneflow](https://github.com/noneflow) ([#2935](https://github.com/nonebot/nonebot2/pull/2935))
|
||||||
|
- Plugin: nonebot-plugin-tarina-lang-turbo [@noneflow](https://github.com/noneflow) ([#2938](https://github.com/nonebot/nonebot2/pull/2938))
|
||||||
|
- Plugin: 狼人杀 [@noneflow](https://github.com/noneflow) ([#2932](https://github.com/nonebot/nonebot2/pull/2932))
|
||||||
|
- Plugin: 阿瓦隆 [@noneflow](https://github.com/noneflow) ([#2915](https://github.com/nonebot/nonebot2/pull/2915))
|
||||||
|
- Plugin: 消音器 [@noneflow](https://github.com/noneflow) ([#2919](https://github.com/nonebot/nonebot2/pull/2919))
|
||||||
|
- Plugin: 悠悠 [@noneflow](https://github.com/noneflow) ([#2928](https://github.com/nonebot/nonebot2/pull/2928))
|
||||||
|
- Plugin: LLOneBot-Master [@noneflow](https://github.com/noneflow) ([#2925](https://github.com/nonebot/nonebot2/pull/2925))
|
||||||
|
- Plugin: 无情的发图姬 [@noneflow](https://github.com/noneflow) ([#2923](https://github.com/nonebot/nonebot2/pull/2923))
|
||||||
|
- Plugin: maimai DX 查分 [@noneflow](https://github.com/noneflow) ([#2921](https://github.com/nonebot/nonebot2/pull/2921))
|
||||||
|
- Plugin: Minecraft查服 [@noneflow](https://github.com/noneflow) ([#2882](https://github.com/nonebot/nonebot2/pull/2882))
|
||||||
|
- Plugin: lagrange [@noneflow](https://github.com/noneflow) ([#2898](https://github.com/nonebot/nonebot2/pull/2898))
|
||||||
|
- Plugin: nekro-agent [@noneflow](https://github.com/noneflow) ([#2896](https://github.com/nonebot/nonebot2/pull/2896))
|
||||||
|
- Plugin: nonebot_plugin_mute [@noneflow](https://github.com/noneflow) ([#2893](https://github.com/nonebot/nonebot2/pull/2893))
|
||||||
|
- Plugin: LiteyukiBot(plugin) [@noneflow](https://github.com/noneflow) ([#2905](https://github.com/nonebot/nonebot2/pull/2905))
|
||||||
|
- Plugin: 复读6 [@noneflow](https://github.com/noneflow) ([#2900](https://github.com/nonebot/nonebot2/pull/2900))
|
||||||
|
|
||||||
|
### 🍻 机器人发布
|
||||||
|
|
||||||
|
- Bot: CanrotBot [@noneflow](https://github.com/noneflow) ([#3086](https://github.com/nonebot/nonebot2/pull/3086))
|
||||||
|
- Bot: 小安提Bot [@noneflow](https://github.com/noneflow) ([#3061](https://github.com/nonebot/nonebot2/pull/3061))
|
||||||
|
|
||||||
|
## v2.3.3
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: 优化依赖注入在 pydantic v2 下的性能 [@yanyongyu](https://github.com/yanyongyu) ([#2870](https://github.com/nonebot/nonebot2/pull/2870))
|
||||||
|
- Feature: 添加遗漏的类型标注 [@yanyongyu](https://github.com/yanyongyu) ([#2856](https://github.com/nonebot/nonebot2/pull/2856))
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- Fix: 错误的类型标注和 annotated 处理 [@yanyongyu](https://github.com/yanyongyu) ([#2828](https://github.com/nonebot/nonebot2/pull/2828))
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Docs: 添加 Windows Powershell 设置环境变量方法 [@LeoQuote](https://github.com/LeoQuote) ([#2874](https://github.com/nonebot/nonebot2/pull/2874))
|
||||||
|
- Docs: 更新 localstore 插件文档 [@yanyongyu](https://github.com/yanyongyu) ([#2871](https://github.com/nonebot/nonebot2/pull/2871))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Plugin: 修改插件 system-command 信息 [@tkgs0](https://github.com/tkgs0) ([#2862](https://github.com/nonebot/nonebot2/pull/2862))
|
||||||
|
- Plugin: 修改 nonebot-plugin-fishing 插件作者 [@ALittleBot](https://github.com/ALittleBot) ([#2854](https://github.com/nonebot/nonebot2/pull/2854))
|
||||||
|
- Bot: 更新 Minecraft QQBot 信息 [@Lonely-Sails](https://github.com/Lonely-Sails) ([#2838](https://github.com/nonebot/nonebot2/pull/2838))
|
||||||
|
- Plugin: 移除 kanonbot 插件 [@SuperGuGuGu](https://github.com/SuperGuGuGu) ([#2819](https://github.com/nonebot/nonebot2/pull/2819))
|
||||||
|
- Plugin: 更新插件 sparkapi 信息 [@CCLMSY](https://github.com/CCLMSY) ([#2812](https://github.com/nonebot/nonebot2/pull/2812))
|
||||||
|
- Plugin: 修改插件 miragetank \& charpic 信息 [@1umine](https://github.com/1umine) ([#2807](https://github.com/nonebot/nonebot2/pull/2807))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: nonebot-plugin-wait-a-minute [@noneflow](https://github.com/noneflow) ([#2902](https://github.com/nonebot/nonebot2/pull/2902))
|
||||||
|
- Plugin: 你看我像 [@noneflow](https://github.com/noneflow) ([#2895](https://github.com/nonebot/nonebot2/pull/2895))
|
||||||
|
- Plugin: dify插件 [@noneflow](https://github.com/noneflow) ([#2889](https://github.com/nonebot/nonebot2/pull/2889))
|
||||||
|
- Plugin: mai2_pcount [@noneflow](https://github.com/noneflow) ([#2891](https://github.com/nonebot/nonebot2/pull/2891))
|
||||||
|
- Plugin: nonebot-plugin-ehentai-search [@noneflow](https://github.com/noneflow) ([#2885](https://github.com/nonebot/nonebot2/pull/2885))
|
||||||
|
- Plugin: pokepoke_miss [@noneflow](https://github.com/noneflow) ([#2883](https://github.com/nonebot/nonebot2/pull/2883))
|
||||||
|
- Plugin: 聊天截图伪造 [@noneflow](https://github.com/noneflow) ([#2880](https://github.com/nonebot/nonebot2/pull/2880))
|
||||||
|
- Plugin: ba-tools [@noneflow](https://github.com/noneflow) ([#2867](https://github.com/nonebot/nonebot2/pull/2867))
|
||||||
|
- Plugin: 精华消息管理 [@noneflow](https://github.com/noneflow) ([#2873](https://github.com/nonebot/nonebot2/pull/2873))
|
||||||
|
- Plugin: B站收藏夹监视器 [@noneflow](https://github.com/noneflow) ([#2869](https://github.com/nonebot/nonebot2/pull/2869))
|
||||||
|
- Plugin: Alist [@noneflow](https://github.com/noneflow) ([#2865](https://github.com/nonebot/nonebot2/pull/2865))
|
||||||
|
- Plugin: 🦌管签到 [@noneflow](https://github.com/noneflow) ([#2859](https://github.com/nonebot/nonebot2/pull/2859))
|
||||||
|
- Plugin: 漂流瓶 [@noneflow](https://github.com/noneflow) ([#2861](https://github.com/nonebot/nonebot2/pull/2861))
|
||||||
|
- Plugin: 奇怪的小功能 [@noneflow](https://github.com/noneflow) ([#2851](https://github.com/nonebot/nonebot2/pull/2851))
|
||||||
|
- Plugin: SunoAI音乐生成 [@noneflow](https://github.com/noneflow) ([#2853](https://github.com/nonebot/nonebot2/pull/2853))
|
||||||
|
- Plugin: 谁是卷王 [@noneflow](https://github.com/noneflow) ([#2849](https://github.com/nonebot/nonebot2/pull/2849))
|
||||||
|
- Plugin: GPT-SoVITS 语音合成 [@noneflow](https://github.com/noneflow) ([#2847](https://github.com/nonebot/nonebot2/pull/2847))
|
||||||
|
- Plugin: 基于清影的AI视频生成 [@noneflow](https://github.com/noneflow) ([#2843](https://github.com/nonebot/nonebot2/pull/2843))
|
||||||
|
- Plugin: 命令行 [@noneflow](https://github.com/noneflow) ([#2840](https://github.com/nonebot/nonebot2/pull/2840))
|
||||||
|
- Plugin: exe_code [@noneflow](https://github.com/noneflow) ([#2835](https://github.com/nonebot/nonebot2/pull/2835))
|
||||||
|
- Plugin: nonebot-plugin-autopush [@noneflow](https://github.com/noneflow) ([#2833](https://github.com/nonebot/nonebot2/pull/2833))
|
||||||
|
- Plugin: vv_helper [@noneflow](https://github.com/noneflow) ([#2825](https://github.com/nonebot/nonebot2/pull/2825))
|
||||||
|
- Plugin: nonebot_plugin_game_torrent [@noneflow](https://github.com/noneflow) ([#2827](https://github.com/nonebot/nonebot2/pull/2827))
|
||||||
|
- Plugin: 每日油价 [@noneflow](https://github.com/noneflow) ([#2822](https://github.com/nonebot/nonebot2/pull/2822))
|
||||||
|
- Plugin: wordle [@noneflow](https://github.com/noneflow) ([#2818](https://github.com/nonebot/nonebot2/pull/2818))
|
||||||
|
- Plugin: 再润 [@noneflow](https://github.com/noneflow) ([#2816](https://github.com/nonebot/nonebot2/pull/2816))
|
||||||
|
- Plugin: 漫展/展览查询 [@noneflow](https://github.com/noneflow) ([#2811](https://github.com/nonebot/nonebot2/pull/2811))
|
||||||
|
- Plugin: 鸣潮wiki [@noneflow](https://github.com/noneflow) ([#2804](https://github.com/nonebot/nonebot2/pull/2804))
|
||||||
|
- Plugin: cloudfare R2 客服端 [@noneflow](https://github.com/noneflow) ([#2806](https://github.com/nonebot/nonebot2/pull/2806))
|
||||||
|
- Plugin: AnyMate小助手 [@noneflow](https://github.com/noneflow) ([#2761](https://github.com/nonebot/nonebot2/pull/2761))
|
||||||
|
|
||||||
|
### 🍻 机器人发布
|
||||||
|
|
||||||
|
- Bot: Minecraft_QQBot [@noneflow](https://github.com/noneflow) ([#2837](https://github.com/nonebot/nonebot2/pull/2837))
|
||||||
|
- Bot: 星辰 Bot [@noneflow](https://github.com/noneflow) ([#2824](https://github.com/nonebot/nonebot2/pull/2824))
|
||||||
|
|
||||||
## v2.3.2
|
## v2.3.2
|
||||||
|
|
||||||
### 🐛 Bug 修复
|
### 🐛 Bug 修复
|
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { useDocsVersionCandidates } from "@docusaurus/plugin-content-docs/client";
|
||||||
import { PageMetadata } from "@docusaurus/theme-common";
|
import { PageMetadata } from "@docusaurus/theme-common";
|
||||||
import { useDocsVersionCandidates } from "@docusaurus/theme-common/internal";
|
|
||||||
import { useVersionedSidebar } from "@nullbot/docusaurus-plugin-getsidebar/client";
|
import { useVersionedSidebar } from "@nullbot/docusaurus-plugin-getsidebar/client";
|
||||||
import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts";
|
import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts";
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ function StorePage({ title, children }: Props): JSX.Element {
|
|||||||
)!;
|
)!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page hideTableOfContents reduceContentWidth={false}>
|
<Page hideTableOfContents reduceContentWidth={false} sidebarId={SIDEBAR_ID}>
|
||||||
<SidebarContentFiller items={sidebarItems} />
|
<SidebarContentFiller items={sidebarItems} />
|
||||||
<article className="prose max-w-full">
|
<article className="prose max-w-full">
|
||||||
<h1 className="store-title">{title}</h1>
|
<h1 className="store-title">{title}</h1>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user