mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 11:16:43 +00:00
Compare commits
480 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
033c90dd74 | ||
|
762b2e6ef1 | ||
|
3e3f504c1c | ||
|
8f8ce4b853 | ||
|
18b6151c91 | ||
|
0f552743df | ||
|
fdc9c6f056 | ||
|
16812e3621 | ||
|
17c3c09d86 | ||
|
f7fe9fba5c | ||
|
cc4d0b61f0 | ||
|
19d9014279 | ||
|
a63322633a | ||
|
5ddb9b295d | ||
|
2d4379fcfa | ||
|
227fb3b667 | ||
|
faba8aae4e | ||
|
852f033769 | ||
|
4975f4a0c8 | ||
|
93eb6cae93 | ||
|
dea43fb1ef | ||
|
dc83031589 | ||
|
ebb4ca4dba | ||
|
25fc7a7449 | ||
|
f4f72dc2b3 | ||
|
ac9ee830c6 | ||
|
73710aa311 | ||
|
b8c4898eff | ||
|
9189711c0a | ||
|
2d4c2e472b | ||
|
34427f0dd2 | ||
|
4f7d3965d4 | ||
|
64ce1a64d9 | ||
|
ca79f29c60 | ||
|
d5cd6427b9 | ||
|
aa00ccf3be | ||
|
3997c09c34 | ||
|
4a06576c5e | ||
|
67bca08ee2 | ||
|
867766b469 | ||
|
1175a4452e | ||
|
208533f5ca | ||
|
4a6428100b | ||
|
32bc2c314a | ||
|
ab8dea5a02 | ||
|
e06076aa3a | ||
|
36d90c0efd | ||
|
0fbfa20257 | ||
|
6c0d5f3e1d | ||
|
0b72c765a7 | ||
|
21815b380f | ||
|
9fed938de1 | ||
|
6df8d5b254 | ||
|
aedc541d03 | ||
|
bdf8dff08e | ||
|
081dc8352d | ||
|
6dad4d2a74 | ||
|
3528339751 | ||
|
efae3c8756 | ||
|
3aa1bc7b66 | ||
|
f6027bbcd9 | ||
|
1dec074232 | ||
|
76a455227d | ||
|
b33845b936 | ||
|
56f1927376 | ||
|
232b7134f0 | ||
|
980affd31b | ||
|
f2a35a7520 | ||
|
02c41eb97a | ||
|
06682ee36a | ||
|
97e3ee32e7 | ||
|
a4bc8fe544 | ||
|
e0d7e90f4a | ||
|
4404a6c74e | ||
|
ae8bf488d0 | ||
|
9f6e8a1833 | ||
|
af03c61f89 | ||
|
7a1e9adf33 | ||
|
eed42db645 | ||
|
7f8b5e9993 | ||
|
83552d6995 | ||
|
3bf393444d | ||
|
fd2ed08009 | ||
|
9b421548d6 | ||
|
f2d9a3ba6b | ||
|
abe90c0074 | ||
|
9a0cf5b9dc | ||
|
f9f82da58d | ||
|
a067d1b1b1 | ||
|
a97f0e6da9 | ||
|
58b18ada6f | ||
|
6f8c4692c8 | ||
|
ad4a04dae2 | ||
|
d62e59325b | ||
|
781f8a67df | ||
|
599bb377b7 | ||
|
97fd095666 | ||
|
3aec8e3acd | ||
|
1fdb7a45cf | ||
|
9f6c750236 | ||
|
4a52662ad8 | ||
|
0660ddba28 | ||
|
463eddb0f4 | ||
|
aa0c113e65 | ||
|
babd8b2093 | ||
|
23bcab5450 | ||
|
d766455d13 | ||
|
c9bea5d0ea | ||
|
8704ee42f1 | ||
|
bf366d8361 | ||
|
cf9729aac4 | ||
|
58475fe929 | ||
|
57c553f971 | ||
|
2b8aae4eee | ||
|
c5aa5d3deb | ||
|
3d8731f41d | ||
|
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 | ||
|
4d070f5b48 | ||
|
82138454bc | ||
|
d98fe53d56 | ||
|
278b9e92c2 | ||
|
45418ccfae | ||
|
2ad2922565 | ||
|
84ebcb4ce6 | ||
|
6a0caacfd6 | ||
|
a8f3940cbc | ||
|
15d3910462 | ||
|
edfd0eb887 | ||
|
fe63717848 | ||
|
63424bc3ac | ||
|
99b1d0ed96 | ||
|
90c7fd4747 | ||
|
c1a9758a18 | ||
|
17e7a0c029 | ||
|
df6a948c08 | ||
|
9f19eb7a96 | ||
|
2b68428526 | ||
|
d62c6561c2 | ||
|
fc3bb5ff1f | ||
|
76b1bbb443 | ||
|
7b724925ba | ||
|
62dc2574c7 | ||
|
ea40ae3a18 | ||
|
f94e7d9b5b | ||
|
c8ba973280 | ||
|
35e062c588 | ||
|
53724487d3 | ||
|
a3003b0ff6 | ||
|
96ecd415cd | ||
|
e8ef4735ea | ||
|
b78b08ed81 | ||
|
e11ea52276 | ||
|
819e7334b2 | ||
|
1ebafaa9a5 | ||
|
3554292d5f | ||
|
ec9ef9a760 | ||
|
74663c7c5e | ||
|
cbc99be031 | ||
|
81e9bdd7ec | ||
|
323038ecc6 | ||
|
7091beb809 | ||
|
010c48d30f | ||
|
a5b2dd38d5 | ||
|
fa5f295fe7 | ||
|
7f7b23bd2f | ||
|
0434e12b8a | ||
|
425d140161 | ||
|
64d8f7843a | ||
|
a0a6427540 | ||
|
31fe8e6582 | ||
|
38e42919b7 | ||
|
c769f95688 | ||
|
d642897a5b | ||
|
d7931f8ec2 | ||
|
8a0b989718 | ||
|
4fbbb646c3 | ||
|
75856e63f6 | ||
|
98213f50db | ||
|
5bce1db24e | ||
|
380ace5780 | ||
|
6e5b01a3d4 | ||
|
622e8e8af3 | ||
|
2bbb83d3f2 | ||
|
54756134d4 | ||
|
932b212e04 | ||
|
3b40e5b20c | ||
|
f594db207d | ||
|
70e23427e8 | ||
|
c1a303fd3d | ||
|
a62b9a5e1a | ||
|
36eece311a | ||
|
29ea5f5787 | ||
|
c00e3aacfc | ||
|
cf9f78528c | ||
|
68d4795de6 | ||
|
e689d7f7d2 | ||
|
a607f868c2 | ||
|
84ac1c4bad | ||
|
e11ff528e2 | ||
|
047f4d1878 | ||
|
0294c33baf | ||
|
11a8b6e40b | ||
|
cade86b62a | ||
|
df836ec1c6 | ||
|
12cc00a3d3 | ||
|
24aa81f0be | ||
|
339706a3a6 | ||
|
b43c9adb7a | ||
|
c2783039d4 | ||
|
c4706e4123 | ||
|
8a997540b3 | ||
|
045022b22a | ||
|
723fa4b3d8 | ||
|
41b59cff06 | ||
|
bed1b46527 | ||
|
ad695ca6e8 | ||
|
33e997708c | ||
|
56b6ee1d38 | ||
|
27b2cf52a5 | ||
|
b532130f6e | ||
|
d16b8594ad | ||
|
ad8442c6de | ||
|
4edf7e2c2c | ||
|
ea49318809 |
@@ -9,13 +9,11 @@
|
|||||||
"vscode": {
|
"vscode": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"python.analysis.diagnosticMode": "workspace",
|
"python.analysis.diagnosticMode": "workspace",
|
||||||
"python.analysis.typeCheckingMode": "basic",
|
|
||||||
"ruff.organizeImports": false,
|
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter",
|
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.ruff": true,
|
"source.fixAll.ruff": "explicit",
|
||||||
"source.organizeImports": true
|
"source.organizeImports": "explicit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
@@ -44,8 +42,6 @@
|
|||||||
"extensions": [
|
"extensions": [
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"ms-python.vscode-pylance",
|
"ms-python.vscode-pylance",
|
||||||
"ms-python.isort",
|
|
||||||
"ms-python.black-formatter",
|
|
||||||
"charliermarsh.ruff",
|
"charliermarsh.ruff",
|
||||||
"EditorConfig.EditorConfig",
|
"EditorConfig.EditorConfig",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
|
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"]
|
||||||
|
2
.github/ISSUE_TEMPLATE/adapter_publish.yml
vendored
2
.github/ISSUE_TEMPLATE/adapter_publish.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: 发布适配器
|
name: 发布适配器
|
||||||
title: "Adapter: {name}"
|
title: "Adapter: {name}"
|
||||||
description: 发布适配器到 NoneBot 官方商店
|
description: 发布适配器到 NoneBot 官方商店
|
||||||
labels: ["Adapter"]
|
labels: ["Adapter", "Publish"]
|
||||||
body:
|
body:
|
||||||
- type: input
|
- type: input
|
||||||
id: name
|
id: name
|
||||||
|
2
.github/ISSUE_TEMPLATE/bot_publish.yml
vendored
2
.github/ISSUE_TEMPLATE/bot_publish.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: 发布机器人
|
name: 发布机器人
|
||||||
title: "Bot: {name}"
|
title: "Bot: {name}"
|
||||||
description: 发布机器人到 NoneBot 官方商店
|
description: 发布机器人到 NoneBot 官方商店
|
||||||
labels: ["Bot"]
|
labels: ["Bot", "Publish"]
|
||||||
body:
|
body:
|
||||||
- type: input
|
- type: input
|
||||||
id: name
|
id: name
|
||||||
|
2
.github/ISSUE_TEMPLATE/plugin_publish.yml
vendored
2
.github/ISSUE_TEMPLATE/plugin_publish.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: 发布插件
|
name: 发布插件
|
||||||
title: "Plugin: {name}"
|
title: "Plugin: {name}"
|
||||||
description: 发布插件到 NoneBot 官方商店
|
description: 发布插件到 NoneBot 官方商店
|
||||||
labels: ["Plugin"]
|
labels: ["Plugin", "Publish"]
|
||||||
body:
|
body:
|
||||||
- type: input
|
- type: input
|
||||||
id: pypi
|
id: pypi
|
||||||
|
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:
|
||||||
|
- "*"
|
||||||
|
12
.github/workflows/codecov.yml
vendored
12
.github/workflows/codecov.yml
vendored
@@ -48,11 +48,21 @@ jobs:
|
|||||||
cd ./envs/${{ matrix.env }}
|
cd ./envs/${{ matrix.env }}
|
||||||
poetry run bash "../../scripts/run-tests.sh"
|
poetry run bash "../../scripts/run-tests.sh"
|
||||||
|
|
||||||
|
- name: Upload test results
|
||||||
|
uses: codecov/test-results-action@v1
|
||||||
|
with:
|
||||||
|
env_vars: OS,PYTHON_VERSION,PYDANTIC_VERSION
|
||||||
|
files: ./tests/junit.xml
|
||||||
|
flags: unittests
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
- name: Upload coverage report
|
- name: Upload coverage report
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
env_vars: OS,PYTHON_VERSION,PYDANTIC_VERSION
|
env_vars: OS,PYTHON_VERSION,PYDANTIC_VERSION
|
||||||
files: ./tests/coverage.xml
|
files: ./tests/coverage.xml
|
||||||
flags: unittests
|
flags: unittests
|
||||||
|
fail_ci_if_error: true
|
||||||
env:
|
env:
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
88
.github/workflows/noneflow.yml
vendored
88
.github/workflows/noneflow.yml
vendored
@@ -15,9 +15,9 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
noneflow:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: check
|
name: noneflow
|
||||||
# do not run on forked PRs, do not run on not related issues, do not run on pr comments
|
# do not run on forked PRs, do not run on not related issues, do not run on pr comments
|
||||||
if: |
|
if: |
|
||||||
!(
|
!(
|
||||||
@@ -36,70 +36,6 @@ jobs:
|
|||||||
github.event_name == 'issue_comment' && github.event.issue.pull_request
|
github.event_name == 'issue_comment' && github.event.issue.pull_request
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
steps:
|
|
||||||
- run: echo "Check passed"
|
|
||||||
reaction:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: reaction
|
|
||||||
needs: check
|
|
||||||
if: |
|
|
||||||
(
|
|
||||||
github.event_name == 'issue_comment' &&
|
|
||||||
github.event.action == 'created'
|
|
||||||
) ||
|
|
||||||
(
|
|
||||||
github.event_name == 'issues' &&
|
|
||||||
github.event.action == 'opened'
|
|
||||||
)
|
|
||||||
steps:
|
|
||||||
- name: Generate token
|
|
||||||
id: generate-token
|
|
||||||
uses: tibdex/github-app-token@v2
|
|
||||||
with:
|
|
||||||
app_id: ${{ secrets.APP_ID }}
|
|
||||||
private_key: ${{ secrets.APP_KEY }}
|
|
||||||
|
|
||||||
- name: Reaction on issue
|
|
||||||
if: github.event_name == 'issues'
|
|
||||||
run: |
|
|
||||||
gh api --method POST /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/reactions -f "content=rocket"
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
|
||||||
|
|
||||||
- name: Reaction on issue comment
|
|
||||||
if: github.event_name == 'issue_comment'
|
|
||||||
run: |
|
|
||||||
gh api --method POST /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions -f "content=rocket"
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
|
||||||
plugin_test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: nonebot2 plugin test
|
|
||||||
needs: check
|
|
||||||
permissions:
|
|
||||||
issues: read
|
|
||||||
outputs:
|
|
||||||
result: ${{ steps.plugin-test.outputs.RESULT }}
|
|
||||||
output: ${{ steps.plugin-test.outputs.OUTPUT }}
|
|
||||||
metadata: ${{ steps.plugin-test.outputs.METADATA }}
|
|
||||||
steps:
|
|
||||||
- name: Install Poetry
|
|
||||||
if: ${{ !startsWith(github.event_name, 'pull_request') }}
|
|
||||||
run: pipx install poetry
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.x"
|
|
||||||
|
|
||||||
- name: Test Plugin
|
|
||||||
id: plugin-test
|
|
||||||
run: |
|
|
||||||
curl -sSL https://github.com/nonebot/noneflow/releases/latest/download/plugin_test.py | python -
|
|
||||||
noneflow:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: noneflow
|
|
||||||
needs: plugin_test
|
|
||||||
steps:
|
steps:
|
||||||
- name: Generate token
|
- name: Generate token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
@@ -113,29 +49,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Cache pre-commit hooks
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: .cache/.pre-commit
|
|
||||||
key: noneflow-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
|
||||||
|
|
||||||
- name: NoneFlow
|
- name: NoneFlow
|
||||||
uses: docker://ghcr.io/nonebot/noneflow:latest
|
uses: docker://ghcr.io/nonebot/noneflow:latest
|
||||||
with:
|
with:
|
||||||
config: >
|
config: >
|
||||||
{
|
{
|
||||||
"base": "master",
|
"base": "master",
|
||||||
"plugin_path": "assets/plugins.json",
|
"plugin_path": "assets/plugins.json5",
|
||||||
"bot_path": "assets/bots.json",
|
"bot_path": "assets/bots.json5",
|
||||||
"adapter_path": "assets/adapters.json"
|
"adapter_path": "assets/adapters.json5",
|
||||||
|
"registry_repository": "nonebot/registry"
|
||||||
}
|
}
|
||||||
env:
|
env:
|
||||||
PLUGIN_TEST_RESULT: ${{ needs.plugin_test.outputs.result }}
|
|
||||||
PLUGIN_TEST_OUTPUT: ${{ needs.plugin_test.outputs.output }}
|
|
||||||
PLUGIN_TEST_METADATA: ${{ needs.plugin_test.outputs.metadata }}
|
|
||||||
APP_ID: ${{ secrets.APP_ID }}
|
APP_ID: ${{ secrets.APP_ID }}
|
||||||
PRIVATE_KEY: ${{ secrets.APP_KEY }}
|
PRIVATE_KEY: ${{ secrets.APP_KEY }}
|
||||||
PRE_COMMIT_HOME: /github/workspace/.cache/.pre-commit
|
|
||||||
|
|
||||||
- name: Fix permission
|
|
||||||
run: sudo chown -R $(whoami):$(id -ng) .cache/.pre-commit
|
|
||||||
|
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
|
||||||
|
104
.github/workflows/website-preview-cd.yml
vendored
Normal file
104
.github/workflows/website-preview-cd.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
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: |
|
||||||
|
PR_NUMBER=$(cat ./pr-number)
|
||||||
|
if ! [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "Invalid PR number: ${PR_NUMBER}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "PR_NUMBER=${PR_NUMBER}" >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Set Deploy Name
|
||||||
|
run: |
|
||||||
|
echo "DEPLOY_NAME=deploy-preview-${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 "${{ github.event.pull_request.number }}" > ./pr-number
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: website-preview
|
||||||
|
path: |
|
||||||
|
./website/build
|
||||||
|
./pr-number
|
||||||
|
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,13 @@ 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.4.2
|
rev: v0.8.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix]
|
||||||
stages: [commit]
|
stages: [pre-commit]
|
||||||
|
- id: ruff-format
|
||||||
- repo: https://github.com/pycqa/isort
|
stages: [pre-commit]
|
||||||
rev: 5.13.2
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
stages: [commit]
|
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
|
||||||
rev: 24.4.2
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
stages: [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>
|
||||||
|
@@ -123,15 +123,17 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
| Discord([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
| Discord([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
||||||
| DoDo([仓库](https://github.com/nonebot/adapter-dodo),[协议](https://open.imdodo.com/)) | ✅ | DoDo Bot 协议 |
|
| DoDo([仓库](https://github.com/nonebot/adapter-dodo),[协议](https://open.imdodo.com/)) | ✅ | DoDo Bot 协议 |
|
||||||
| Kritor([仓库](https://github.com/nonebot/adapter-kritor),[协议](https://github.com/KarinJS/kritor)) | ✅ | Kritor (OnebotX) 协议,QQ 机器人接口标准 |
|
| Kritor([仓库](https://github.com/nonebot/adapter-kritor),[协议](https://github.com/KarinJS/kritor)) | ✅ | Kritor (OnebotX) 协议,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)) | ↗️ | 由社区贡献 |
|
||||||
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
||||||
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa)) | ❌ | 米游社大别野 Bot 协议,官方已下线 |
|
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa)) | ❌ | 米游社大别野 Bot 协议,官方已下线 |
|
||||||
| Rocket.Chat([仓库](https://github.com/IUnlimit/nonebot-adapter-rocketchat),[协议](https://developer.rocket.chat/)) | ↗️ | Rocket.Chat Bot 协议,由社区贡献 |
|
| Rocket.Chat([仓库](https://github.com/IUnlimit/nonebot-adapter-rocketchat),[协议](https://developer.rocket.chat/)) | ↗️ | Rocket.Chat Bot 协议,由社区贡献 |
|
||||||
|
| Tailchat([仓库](https://github.com/eya46/nonebot-adapter-tailchat),[协议](https://tailchat.msgbyte.com/)) | ↗️ | Tailchat 开放平台 Bot 协议,由社区贡献 |
|
||||||
|
| Mail([仓库](https://github.com/mobyw/nonebot-adapter-mail)) | ↗️ | 邮件收发协议,由社区贡献 |
|
||||||
|
|
||||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"project_link": "nonebot-adapter-onebot",
|
"project_link": "nonebot-adapter-onebot",
|
||||||
"name": "OneBot V11",
|
"name": "OneBot V11",
|
||||||
"desc": "OneBot V11 协议",
|
"desc": "OneBot V11 协议",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "https://onebot.adapters.nonebot.dev/",
|
"homepage": "https://onebot.adapters.nonebot.dev/",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"project_link": "nonebot-adapter-ding",
|
"project_link": "nonebot-adapter-ding",
|
||||||
"name": "钉钉",
|
"name": "钉钉",
|
||||||
"desc": "钉钉协议",
|
"desc": "钉钉协议",
|
||||||
"author": "Artin",
|
"author_id": 1184028,
|
||||||
"homepage": "https://github.com/nonebot/adapter-ding",
|
"homepage": "https://github.com/nonebot/adapter-ding",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"project_link": "nonebot-adapter-feishu",
|
"project_link": "nonebot-adapter-feishu",
|
||||||
"name": "飞书",
|
"name": "飞书",
|
||||||
"desc": "飞书协议",
|
"desc": "飞书协议",
|
||||||
"author": "StarHeartHunt",
|
"author_id": 14922941,
|
||||||
"homepage": "https://github.com/nonebot/adapter-feishu",
|
"homepage": "https://github.com/nonebot/adapter-feishu",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"project_link": "nonebot-adapter-telegram",
|
"project_link": "nonebot-adapter-telegram",
|
||||||
"name": "Telegram",
|
"name": "Telegram",
|
||||||
"desc": "Telegram 协议",
|
"desc": "Telegram 协议",
|
||||||
"author": "j1g5awi",
|
"author_id": 50312681,
|
||||||
"homepage": "https://github.com/nonebot/adapter-telegram",
|
"homepage": "https://github.com/nonebot/adapter-telegram",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"project_link": "nonebot-adapter-qq",
|
"project_link": "nonebot-adapter-qq",
|
||||||
"name": "QQ",
|
"name": "QQ",
|
||||||
"desc": "QQ 官方机器人",
|
"desc": "QQ 官方机器人",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "https://github.com/nonebot/adapter-qq",
|
"homepage": "https://github.com/nonebot/adapter-qq",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -54,27 +54,27 @@
|
|||||||
"project_link": "nonebot-adapter-kaiheila",
|
"project_link": "nonebot-adapter-kaiheila",
|
||||||
"name": "开黑啦",
|
"name": "开黑啦",
|
||||||
"desc": "开黑啦协议适配",
|
"desc": "开黑啦协议适配",
|
||||||
"author": "Tian-que",
|
"author_id": 37477320,
|
||||||
"homepage": "https://github.com/Tian-que/nonebot-adapter-kaiheila",
|
"homepage": "https://github.com/Tian-que/nonebot-adapter-kaiheila",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot.adapters.mirai2",
|
"module_name": "nonebot.adapters.mirai",
|
||||||
"project_link": "nonebot_adapter_mirai2",
|
"project_link": "nonebot-adapter-mirai",
|
||||||
"name": "mirai2",
|
"name": "Mirai",
|
||||||
"desc": "为 nonebot2 添加 mirai_api_http2.x的兼容适配器",
|
"desc": "mirai-api-http v2 协议适配",
|
||||||
"author": "ieew",
|
"author_id": 42648639,
|
||||||
"homepage": "https://github.com/ieew/nonebot_adapter_mirai2",
|
"homepage": "https://github.com/nonebot/adapter-mirai",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot.adapters.onebot.v12",
|
"module_name": "nonebot.adapters.onebot.v12",
|
||||||
"project_link": "nonebot-adapter-onebot",
|
"project_link": "nonebot-adapter-onebot",
|
||||||
"name": "OneBot V12",
|
"name": "OneBot V12",
|
||||||
"desc": "OneBot V12 协议",
|
"desc": "OneBot V12 协议",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "https://onebot.adapters.nonebot.dev/",
|
"homepage": "https://onebot.adapters.nonebot.dev/",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"project_link": "nonebot-adapter-console",
|
"project_link": "nonebot-adapter-console",
|
||||||
"name": "Console",
|
"name": "Console",
|
||||||
"desc": "基于终端的交互式适配器",
|
"desc": "基于终端的交互式适配器",
|
||||||
"author": "Melodyknit",
|
"author_id": 50488999,
|
||||||
"homepage": "https://github.com/nonebot/adapter-console",
|
"homepage": "https://github.com/nonebot/adapter-console",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
"project_link": "nonebot-adapter-github",
|
"project_link": "nonebot-adapter-github",
|
||||||
"name": "GitHub",
|
"name": "GitHub",
|
||||||
"desc": "GitHub APP & OAuth APP integration",
|
"desc": "GitHub APP & OAuth APP integration",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "https://github.com/nonebot/adapter-github",
|
"homepage": "https://github.com/nonebot/adapter-github",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
"project_link": "nonebot-adapter-ntchat",
|
"project_link": "nonebot-adapter-ntchat",
|
||||||
"name": "Ntchat",
|
"name": "Ntchat",
|
||||||
"desc": "pc hook的微信客户端适配",
|
"desc": "pc hook的微信客户端适配",
|
||||||
"author": "JustUndertaker",
|
"author_id": 37363867,
|
||||||
"homepage": "https://github.com/JustUndertaker/adapter-ntchat",
|
"homepage": "https://github.com/JustUndertaker/adapter-ntchat",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"project_link": "nonebot-adapter-minecraft",
|
"project_link": "nonebot-adapter-minecraft",
|
||||||
"name": "Minecraft",
|
"name": "Minecraft",
|
||||||
"desc": "MineCraft通信适配,支持Rcon",
|
"desc": "MineCraft通信适配,支持Rcon",
|
||||||
"author": "17TheWord",
|
"author_id": 54731914,
|
||||||
"homepage": "https://github.com/17TheWord/nonebot-adapter-minecraft",
|
"homepage": "https://github.com/17TheWord/nonebot-adapter-minecraft",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
"project_link": "nonebot-adapter-bilibili",
|
"project_link": "nonebot-adapter-bilibili",
|
||||||
"name": "BilibiliLive",
|
"name": "BilibiliLive",
|
||||||
"desc": "b站直播间ws协议",
|
"desc": "b站直播间ws协议",
|
||||||
"author": "wwweww",
|
"author_id": 39620657,
|
||||||
"homepage": "https://github.com/wwweww/adapter-bilibili",
|
"homepage": "https://github.com/wwweww/adapter-bilibili",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
"project_link": "nonebot-adapter-walleq",
|
"project_link": "nonebot-adapter-walleq",
|
||||||
"name": "Walle-Q",
|
"name": "Walle-Q",
|
||||||
"desc": "内置 QQ 协议实现",
|
"desc": "内置 QQ 协议实现",
|
||||||
"author": "abrahum",
|
"author_id": 18395948,
|
||||||
"homepage": "https://github.com/onebot-walle/nonebot_adapter_walleq",
|
"homepage": "https://github.com/onebot-walle/nonebot_adapter_walleq",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
"project_link": "nonebot-adapter-villa",
|
"project_link": "nonebot-adapter-villa",
|
||||||
"name": "大别野",
|
"name": "大别野",
|
||||||
"desc": "米游社大别野官方Bot适配",
|
"desc": "米游社大别野官方Bot适配",
|
||||||
"author": "CMHopeSunshine",
|
"author_id": 63870437,
|
||||||
"homepage": "https://github.com/CMHopeSunshine/nonebot-adapter-villa",
|
"homepage": "https://github.com/CMHopeSunshine/nonebot-adapter-villa",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
"project_link": "nonebot-adapter-red",
|
"project_link": "nonebot-adapter-red",
|
||||||
"name": "RedProtocol",
|
"name": "RedProtocol",
|
||||||
"desc": "QQNT RedProtocol 适配",
|
"desc": "QQNT RedProtocol 适配",
|
||||||
"author": "zhaomaoniu",
|
"author_id": 55650833,
|
||||||
"homepage": "https://github.com/nonebot/adapter-red",
|
"homepage": "https://github.com/nonebot/adapter-red",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
"project_link": "nonebot-adapter-discord",
|
"project_link": "nonebot-adapter-discord",
|
||||||
"name": "Discord",
|
"name": "Discord",
|
||||||
"desc": "Discord 官方 Bot 协议适配",
|
"desc": "Discord 官方 Bot 协议适配",
|
||||||
"author": "CMHopeSunshine",
|
"author_id": 63870437,
|
||||||
"homepage": "https://github.com/nonebot/adapter-discord",
|
"homepage": "https://github.com/nonebot/adapter-discord",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
"project_link": "nonebot-adapter-satori",
|
"project_link": "nonebot-adapter-satori",
|
||||||
"name": "Satori",
|
"name": "Satori",
|
||||||
"desc": "Satori 协议适配器",
|
"desc": "Satori 协议适配器",
|
||||||
"author": "RF-Tar-Railt",
|
"author_id": 42648639,
|
||||||
"homepage": "https://github.com/nonebot/adapter-satori",
|
"homepage": "https://github.com/nonebot/adapter-satori",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
"project_link": "nonebot-adapter-dodo",
|
"project_link": "nonebot-adapter-dodo",
|
||||||
"name": "DoDo",
|
"name": "DoDo",
|
||||||
"desc": "DoDo Bot 协议适配器",
|
"desc": "DoDo Bot 协议适配器",
|
||||||
"author": "CMHopeSunshine",
|
"author_id": 63870437,
|
||||||
"homepage": "https://github.com/nonebot/adapter-dodo",
|
"homepage": "https://github.com/nonebot/adapter-dodo",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
"project_link": "nonebot-adapter-rocketchat",
|
"project_link": "nonebot-adapter-rocketchat",
|
||||||
"name": "RocketChat",
|
"name": "RocketChat",
|
||||||
"desc": "RocketChat adapter for nonebot2",
|
"desc": "RocketChat adapter for nonebot2",
|
||||||
"author": "IllTamer",
|
"author_id": 78360471,
|
||||||
"homepage": "https://github.com/IUnlimit/nonebot-adapter-rocketchat",
|
"homepage": "https://github.com/IUnlimit/nonebot-adapter-rocketchat",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -229,7 +229,7 @@
|
|||||||
"project_link": "nonebot-adapter-kritor",
|
"project_link": "nonebot-adapter-kritor",
|
||||||
"name": "Kritor",
|
"name": "Kritor",
|
||||||
"desc": "Kritor 协议适配",
|
"desc": "Kritor 协议适配",
|
||||||
"author": "RF-Tar-Railt",
|
"author_id": 42648639,
|
||||||
"homepage": "https://github.com/nonebot/adapter-kritor",
|
"homepage": "https://github.com/nonebot/adapter-kritor",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -238,5 +238,25 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_adapter_tailchat",
|
||||||
|
"project_link": "nonebot-adapter-tailchat",
|
||||||
|
"name": "Tailchat",
|
||||||
|
"desc": "Tailchat 适配器",
|
||||||
|
"author_id": 61458340,
|
||||||
|
"homepage": "https://github.com/eya46/nonebot-adapter-tailchat",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.mail",
|
||||||
|
"project_link": "nonebot-adapter-mail",
|
||||||
|
"name": "Mail",
|
||||||
|
"desc": "邮件收发协议",
|
||||||
|
"author_id": 44370805,
|
||||||
|
"homepage": "https://github.com/mobyw/nonebot-adapter-mail",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
]
|
]
|
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"name": "HarukaBot",
|
"name": "HarukaBot",
|
||||||
"desc": "将B站UP主的动态和直播信息推送至QQ",
|
"desc": "将B站UP主的动态和直播信息推送至QQ",
|
||||||
"author": "SK-415",
|
"author_id": 36433929,
|
||||||
"homepage": "https://github.com/SK-415/HarukaBot",
|
"homepage": "https://github.com/SK-415/HarukaBot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Omega Miya",
|
"name": "Omega Miya",
|
||||||
"desc": "B站推送Pixiv搜图识番求签抽卡表情包还有其他杂七杂八的功能",
|
"desc": "B站推送Pixiv搜图识番求签抽卡表情包还有其他杂七杂八的功能",
|
||||||
"author": "Ailitonia",
|
"author_id": 41713304,
|
||||||
"homepage": "https://github.com/Ailitonia/omega-miya",
|
"homepage": "https://github.com/Ailitonia/omega-miya",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Github Bot",
|
"name": "Github Bot",
|
||||||
"desc": "在QQ获取/处理Github repo/pr/issue",
|
"desc": "在QQ获取/处理Github repo/pr/issue",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "https://github.com/cscs181/QQ-GitHub-Bot",
|
"homepage": "https://github.com/cscs181/QQ-GitHub-Bot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{
|
{
|
||||||
"name": "YanXiBot",
|
"name": "YanXiBot",
|
||||||
"desc": "动漫资源查找与娱乐机器人",
|
"desc": "动漫资源查找与娱乐机器人",
|
||||||
"author": "Melodyknit",
|
"author_id": 50488999,
|
||||||
"homepage": "https://github.com/Melodyknit/YanXiBot",
|
"homepage": "https://github.com/Melodyknit/YanXiBot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
{
|
{
|
||||||
"name": "绪山真寻bot",
|
"name": "绪山真寻bot",
|
||||||
"desc": "含有不少的娱乐功能同时稍稍有一些实用的功能 :P",
|
"desc": "含有不少的娱乐功能同时稍稍有一些实用的功能 :P",
|
||||||
"author": "HibiKier",
|
"author_id": 45528451,
|
||||||
"homepage": "https://github.com/HibiKier/zhenxun_bot",
|
"homepage": "https://github.com/HibiKier/zhenxun_bot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ATRI",
|
"name": "ATRI",
|
||||||
"desc": "高性能文爱萝卜子,糅杂了各类有趣小功能",
|
"desc": "高性能文爱萝卜子,糅杂了各类有趣小功能",
|
||||||
"author": "Kyomotoi",
|
"author_id": 37587870,
|
||||||
"homepage": "https://github.com/Kyomotoi/ATRI",
|
"homepage": "https://github.com/Kyomotoi/ATRI",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
{
|
{
|
||||||
"name": "dumbot傻瓜机器人",
|
"name": "dumbot傻瓜机器人",
|
||||||
"desc": "猜一猜游戏、新闻一览、英文每日一词一短语等等,含一键启动及docker容器部署就绪",
|
"desc": "猜一猜游戏、新闻一览、英文每日一词一短语等等,含一键启动及docker容器部署就绪",
|
||||||
"author": "ffreemt",
|
"author_id": 52522252,
|
||||||
"homepage": "https://github.com/ffreemt/koyeb-nb2",
|
"homepage": "https://github.com/ffreemt/koyeb-nb2",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
{
|
{
|
||||||
"name": "DicePP",
|
"name": "DicePP",
|
||||||
"desc": "TRPG骰娘, 带先攻, 查询等功能, 主要面向DND5E. 面对骰主推出的船新版本, 内置Windows/Linux详细部署指南以及方便的自定义骰娘方法, 从回复文本到查询资料库都可轻松配置~",
|
"desc": "TRPG骰娘, 带先攻, 查询等功能, 主要面向DND5E. 面对骰主推出的船新版本, 内置Windows/Linux详细部署指南以及方便的自定义骰娘方法, 从回复文本到查询资料库都可轻松配置~",
|
||||||
"author": "pear-studio",
|
"author_id": 88259371,
|
||||||
"homepage": "https://github.com/pear-studio/nonebot-dicepp",
|
"homepage": "https://github.com/pear-studio/nonebot-dicepp",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
{
|
{
|
||||||
"name": "SetuBot",
|
"name": "SetuBot",
|
||||||
"desc": "每个群配置文件独立,可以控制频率,socks http代理,R18开关,支持多tag,自建API lolicon Pixiv热度榜",
|
"desc": "每个群配置文件独立,可以控制频率,socks http代理,R18开关,支持多tag,自建API lolicon Pixiv热度榜",
|
||||||
"author": "yuban10703",
|
"author_id": 39484884,
|
||||||
"homepage": "https://github.com/yuban10703/setu-nonebot2",
|
"homepage": "https://github.com/yuban10703/setu-nonebot2",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
{
|
{
|
||||||
"name": "剑网三bot",
|
"name": "剑网三bot",
|
||||||
"desc": "网络游戏《剑侠情缘三》的群聊机器人,数据使用:www.jx3api.com",
|
"desc": "网络游戏《剑侠情缘三》的群聊机器人,数据使用:www.jx3api.com",
|
||||||
"author": "JustUndertaker",
|
"author_id": 37363867,
|
||||||
"homepage": "https://github.com/JustUndertaker/mini_jx3_bot",
|
"homepage": "https://github.com/JustUndertaker/mini_jx3_bot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
{
|
{
|
||||||
"name": "PixivBot",
|
"name": "PixivBot",
|
||||||
"desc": "顾名思义是Pixiv的bot(随机推荐插画、随机指定关键词插画、随机书签、查看排行榜、查看指定id插画)",
|
"desc": "顾名思义是Pixiv的bot(随机推荐插画、随机指定关键词插画、随机书签、查看排行榜、查看指定id插画)",
|
||||||
"author": "ssttkkl",
|
"author_id": 17331698,
|
||||||
"homepage": "https://github.com/ssttkkl/PixivBot",
|
"homepage": "https://github.com/ssttkkl/PixivBot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
{
|
{
|
||||||
"name": "SeaBot_QQ",
|
"name": "SeaBot_QQ",
|
||||||
"desc": "一个能够获取新闻资讯并推送至QQ的群聊机器人。",
|
"desc": "一个能够获取新闻资讯并推送至QQ的群聊机器人。",
|
||||||
"author": "B1ue1nWh1te",
|
"author_id": 31682561,
|
||||||
"homepage": "https://github.com/B1ue1nWh1te/SeaBot_QQ",
|
"homepage": "https://github.com/B1ue1nWh1te/SeaBot_QQ",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
{
|
{
|
||||||
"name": "琪露诺Bot",
|
"name": "琪露诺Bot",
|
||||||
"desc": "用QQ机器人控制Minecraft服务器!服务器状态查询/服务器白名单/插件列表/玩家查询/转发服务器消息/执行指令... 其他实用娱乐功能,三步即可成功部署的QQ bot",
|
"desc": "用QQ机器人控制Minecraft服务器!服务器状态查询/服务器白名单/插件列表/玩家查询/转发服务器消息/执行指令... 其他实用娱乐功能,三步即可成功部署的QQ bot",
|
||||||
"author": "summerkirakira",
|
"author_id": 56951617,
|
||||||
"homepage": "https://github.com/summerkirakira/CirnoBot",
|
"homepage": "https://github.com/summerkirakira/CirnoBot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Inkar Suki",
|
"name": "Inkar Suki",
|
||||||
"desc": "一个十分方便的Bot,支持包括Webhook、群管、剑网3等一系列功能,持续更新中……",
|
"desc": "一个十分方便的Bot,支持包括Webhook、群管、剑网3等一系列功能,持续更新中……",
|
||||||
"author": "HornCopper",
|
"author_id": 68726147,
|
||||||
"homepage": "https://github.com/HornCopper/Inkar-Suki",
|
"homepage": "https://github.com/HornCopper/Inkar-Suki",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
{
|
{
|
||||||
"name": "屑岛风Bot",
|
"name": "屑岛风Bot",
|
||||||
"desc": "自家用屑Bot",
|
"desc": "自家用屑Bot",
|
||||||
"author": "kexue-z",
|
"author_id": 71873002,
|
||||||
"homepage": "https://github.com/kexue-z/Dao-bot",
|
"homepage": "https://github.com/kexue-z/Dao-bot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
{
|
{
|
||||||
"name": "LiteyukiBot-轻雪机器人",
|
"name": "LiteyukiBot-轻雪机器人",
|
||||||
"desc": "一个有各种琐事功能的bot,有AI接口,能陪聊",
|
"desc": "一个有各种琐事功能的bot,有AI接口,能陪聊",
|
||||||
"author": "snowyfirefly",
|
"author_id": 79104275,
|
||||||
"homepage": "https://github.com/snowyfirefly/Liteyuki",
|
"homepage": "https://github.com/snowyfirefly/Liteyuki",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
{
|
{
|
||||||
"name": "nya_bot",
|
"name": "nya_bot",
|
||||||
"desc": "喵服——战魂铭人联机服务器兼机器人",
|
"desc": "喵服——战魂铭人联机服务器兼机器人",
|
||||||
"author": "nikissXI",
|
"author_id": 31379266,
|
||||||
"homepage": "https://github.com/nikissXI/nya_bot",
|
"homepage": "https://github.com/nikissXI/nya_bot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
{
|
{
|
||||||
"name": "真宵Bot",
|
"name": "真宵Bot",
|
||||||
"desc": "专注群聊的QQ机器人",
|
"desc": "专注群聊的QQ机器人",
|
||||||
"author": "Shine-Light",
|
"author_id": 71173418,
|
||||||
"homepage": "https://github.com/Shine-Light/Nonebot_Bot_MayaFey",
|
"homepage": "https://github.com/Shine-Light/Nonebot_Bot_MayaFey",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
{
|
{
|
||||||
"name": "SkadiBot",
|
"name": "SkadiBot",
|
||||||
"desc": "明日方舟主题机器人—斯卡蒂",
|
"desc": "明日方舟主题机器人—斯卡蒂",
|
||||||
"author": "yuyuziYYZ",
|
"author_id": 101615359,
|
||||||
"homepage": "https://github.com/yuyuziYYZ/skadi_bot",
|
"homepage": "https://github.com/yuyuziYYZ/skadi_bot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
{
|
{
|
||||||
"name": "小白机器人",
|
"name": "小白机器人",
|
||||||
"desc": "一个高度依赖数据库的群管理机器人",
|
"desc": "一个高度依赖数据库的群管理机器人",
|
||||||
"author": "SDIJF1521",
|
"author_id": 69745333,
|
||||||
"homepage": "https://github.com/SDIJF1521/qqai",
|
"homepage": "https://github.com/SDIJF1521/qqai",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -234,7 +234,7 @@
|
|||||||
{
|
{
|
||||||
"name": "LittlePaimon",
|
"name": "LittlePaimon",
|
||||||
"desc": "小派蒙,多功能原神机器人。",
|
"desc": "小派蒙,多功能原神机器人。",
|
||||||
"author": "CMHopeSunshine",
|
"author_id": 63870437,
|
||||||
"homepage": "https://github.com/CMHopeSunshine/LittlePaimon",
|
"homepage": "https://github.com/CMHopeSunshine/LittlePaimon",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -247,7 +247,7 @@
|
|||||||
{
|
{
|
||||||
"name": "IdhagnBot",
|
"name": "IdhagnBot",
|
||||||
"desc": "🐱🤖 一个以娱乐功能为主的缝合怪(划掉)QQ机器人,包含一定Furry要素但是不会卖萌(就是逊啦!)",
|
"desc": "🐱🤖 一个以娱乐功能为主的缝合怪(划掉)QQ机器人,包含一定Furry要素但是不会卖萌(就是逊啦!)",
|
||||||
"author": "su226",
|
"author_id": 17371317,
|
||||||
"homepage": "https://github.com/su226/IdhagnBot",
|
"homepage": "https://github.com/su226/IdhagnBot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -255,7 +255,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hsbot",
|
"name": "hsbot",
|
||||||
"desc": "服务于《炉石传说》玩家的机器人,上线至今已有加入十余个个炉石相关群聊,上千名用户使用,响应请求数万次。 数据使用:HSreplay, Fbigame, Hearthstone API",
|
"desc": "服务于《炉石传说》玩家的机器人,上线至今已有加入十余个个炉石相关群聊,上千名用户使用,响应请求数万次。 数据使用:HSreplay, Fbigame, Hearthstone API",
|
||||||
"author": "gzy02",
|
"author_id": 67055520,
|
||||||
"homepage": "https://github.com/gzy02/hsbot",
|
"homepage": "https://github.com/gzy02/hsbot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -268,7 +268,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Bread Dog Bot",
|
"name": "Bread Dog Bot",
|
||||||
"desc": "Terraria TShock QQ 机器人",
|
"desc": "Terraria TShock QQ 机器人",
|
||||||
"author": "Qianyiovo",
|
"author_id": 160252668,
|
||||||
"homepage": "https://github.com/Qianyiovo/bread_dog_bot",
|
"homepage": "https://github.com/Qianyiovo/bread_dog_bot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
{
|
{
|
||||||
"name": "RanBot",
|
"name": "RanBot",
|
||||||
"desc": "不@会很安静的Bot",
|
"desc": "不@会很安静的Bot",
|
||||||
"author": "IAXRetailer",
|
"author_id": 88923783,
|
||||||
"homepage": "https://github.com/Hecatia-Hell-Workshop/RanBot",
|
"homepage": "https://github.com/Hecatia-Hell-Workshop/RanBot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -297,7 +297,7 @@
|
|||||||
{
|
{
|
||||||
"name": "辞辞(cici)Bot",
|
"name": "辞辞(cici)Bot",
|
||||||
"desc": "一个集成娱乐和群管为一体的机器人",
|
"desc": "一个集成娱乐和群管为一体的机器人",
|
||||||
"author": "mengxinyuan638",
|
"author_id": 90902259,
|
||||||
"homepage": "https://github.com/mengxinyuan638/cici-bot",
|
"homepage": "https://github.com/mengxinyuan638/cici-bot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -318,7 +318,7 @@
|
|||||||
{
|
{
|
||||||
"name": "SuzunoBot",
|
"name": "SuzunoBot",
|
||||||
"desc": "多功能音游bot,主要服务maimaiDX、Arcaea",
|
"desc": "多功能音游bot,主要服务maimaiDX、Arcaea",
|
||||||
"author": "Rinfair-CSP-A016",
|
"author_id": 29980586,
|
||||||
"homepage": "https://github.com/Rinfair-CSP-A016/SuzunoBot-AGLAS",
|
"homepage": "https://github.com/Rinfair-CSP-A016/SuzunoBot-AGLAS",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -339,7 +339,7 @@
|
|||||||
{
|
{
|
||||||
"name": "青岚",
|
"name": "青岚",
|
||||||
"desc": "基于NoneBot的与Minecraft Server互通消息的机器人",
|
"desc": "基于NoneBot的与Minecraft Server互通消息的机器人",
|
||||||
"author": "17TheWord",
|
"author_id": 54731914,
|
||||||
"homepage": "https://github.com/17TheWord/qinglan_bot",
|
"homepage": "https://github.com/17TheWord/qinglan_bot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -352,7 +352,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ChensQBOTv2",
|
"name": "ChensQBOTv2",
|
||||||
"desc": "多功能QQ群机器人,权限管理/联ban/社工等等等等,以及拥有一个强大的开发者",
|
"desc": "多功能QQ群机器人,权限管理/联ban/社工等等等等,以及拥有一个强大的开发者",
|
||||||
"author": "cnchens",
|
"author_id": 116929900,
|
||||||
"homepage": "https://github.com/cnchens/ChensQBOTv2",
|
"homepage": "https://github.com/cnchens/ChensQBOTv2",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
{
|
{
|
||||||
"name": "koishi",
|
"name": "koishi",
|
||||||
"desc": "支持爬取 codeforces, atcoder, 牛客上程序设计赛事的 bot。",
|
"desc": "支持爬取 codeforces, atcoder, 牛客上程序设计赛事的 bot。",
|
||||||
"author": "CupidsBow",
|
"author_id": 71639222,
|
||||||
"homepage": "https://github.com/CupidsBow/koishi",
|
"homepage": "https://github.com/CupidsBow/koishi",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -381,7 +381,7 @@
|
|||||||
{
|
{
|
||||||
"name": "脑积水",
|
"name": "脑积水",
|
||||||
"desc": "一个超级缝合怪...",
|
"desc": "一个超级缝合怪...",
|
||||||
"author": "zhulinyv",
|
"author_id": 66541860,
|
||||||
"homepage": "https://github.com/zhulinyv/NJS",
|
"homepage": "https://github.com/zhulinyv/NJS",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -394,7 +394,7 @@
|
|||||||
{
|
{
|
||||||
"name": "LOVE酱",
|
"name": "LOVE酱",
|
||||||
"desc": "为铁锈战争游戏群服务的虚拟少女,内置了爬取铁锈房间列表功能,以及游戏内单位查询功能,并制作了教学系统以及铁锈相关游戏群的收集功能。",
|
"desc": "为铁锈战争游戏群服务的虚拟少女,内置了爬取铁锈房间列表功能,以及游戏内单位查询功能,并制作了教学系统以及铁锈相关游戏群的收集功能。",
|
||||||
"author": "allureluoli",
|
"author_id": 106828088,
|
||||||
"homepage": "https://github.com/allureluoli/LOVE-",
|
"homepage": "https://github.com/allureluoli/LOVE-",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -411,7 +411,7 @@
|
|||||||
{
|
{
|
||||||
"name": "fubot",
|
"name": "fubot",
|
||||||
"desc": "基于nonebot与go-cqhttp的QQ娱乐bot,提供群日常娱乐功能与舞萌DX游戏相关的信息查询功能。",
|
"desc": "基于nonebot与go-cqhttp的QQ娱乐bot,提供群日常娱乐功能与舞萌DX游戏相关的信息查询功能。",
|
||||||
"author": "HCskia",
|
"author_id": 54059896,
|
||||||
"homepage": "https://github.com/HCskia/fu-Bot",
|
"homepage": "https://github.com/HCskia/fu-Bot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -424,7 +424,7 @@
|
|||||||
{
|
{
|
||||||
"name": "桃桃酱",
|
"name": "桃桃酱",
|
||||||
"desc": "一个会拆家的高性能缝合萝卜子",
|
"desc": "一个会拆家的高性能缝合萝卜子",
|
||||||
"author": "tkgs0",
|
"author_id": 107618388,
|
||||||
"homepage": "https://github.com/tkgs0/Momoko",
|
"homepage": "https://github.com/tkgs0/Momoko",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -432,7 +432,7 @@
|
|||||||
{
|
{
|
||||||
"name": "CoolQBot",
|
"name": "CoolQBot",
|
||||||
"desc": "基于 NoneBot2 的聊天机器人",
|
"desc": "基于 NoneBot2 的聊天机器人",
|
||||||
"author": "he0119",
|
"author_id": 5219550,
|
||||||
"homepage": "https://github.com/he0119/CoolQBot",
|
"homepage": "https://github.com/he0119/CoolQBot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
{
|
{
|
||||||
"name": "XDbot2",
|
"name": "XDbot2",
|
||||||
"desc": "简单的QQ功能型机器人",
|
"desc": "简单的QQ功能型机器人",
|
||||||
"author": "This-is-XiaoDeng",
|
"author_id": 104149371,
|
||||||
"homepage": "https://github.com/ITCraftDevelopmentTeam/XDbot2",
|
"homepage": "https://github.com/ITCraftDevelopmentTeam/XDbot2",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -448,7 +448,7 @@
|
|||||||
{
|
{
|
||||||
"name": "March7th",
|
"name": "March7th",
|
||||||
"desc": "三月七 - 崩坏:星穹铁道机器人",
|
"desc": "三月七 - 崩坏:星穹铁道机器人",
|
||||||
"author": "mobyw",
|
"author_id": 44370805,
|
||||||
"homepage": "https://github.com/Mar-7th/March7th",
|
"homepage": "https://github.com/Mar-7th/March7th",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -465,7 +465,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ay机器人",
|
"name": "ay机器人",
|
||||||
"desc": "codeforces和洛谷卷王监视、股票监控、ai聊天",
|
"desc": "codeforces和洛谷卷王监视、股票监控、ai聊天",
|
||||||
"author": "863109569",
|
"author_id": 77315378,
|
||||||
"homepage": "https://github.com/863109569/qqbot",
|
"homepage": "https://github.com/863109569/qqbot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -486,7 +486,7 @@
|
|||||||
{
|
{
|
||||||
"name": "狐尾",
|
"name": "狐尾",
|
||||||
"desc": "一个整合了兽云祭api的机器人,支持账号令牌操作,以及上传兽图",
|
"desc": "一个整合了兽云祭api的机器人,支持账号令牌操作,以及上传兽图",
|
||||||
"author": "bingqiu456",
|
"author_id": 99388013,
|
||||||
"homepage": "https://github.com/bingqiu456/shouyun",
|
"homepage": "https://github.com/bingqiu456/shouyun",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -499,7 +499,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ReimeiBot-黎明机器人",
|
"name": "ReimeiBot-黎明机器人",
|
||||||
"desc": "流星飞逝,黎明终将到来。",
|
"desc": "流星飞逝,黎明终将到来。",
|
||||||
"author": "ThirdBlood",
|
"author_id": 65395090,
|
||||||
"homepage": "https://github.com/3rdBit/ReimeiBot",
|
"homepage": "https://github.com/3rdBit/ReimeiBot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -507,7 +507,7 @@
|
|||||||
{
|
{
|
||||||
"name": "web_bot",
|
"name": "web_bot",
|
||||||
"desc": "把机器人搬到网络上",
|
"desc": "把机器人搬到网络上",
|
||||||
"author": "wsdtl",
|
"author_id": 63489103,
|
||||||
"homepage": "https://github.com/wsdtl/web_bot",
|
"homepage": "https://github.com/wsdtl/web_bot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -520,7 +520,7 @@
|
|||||||
{
|
{
|
||||||
"name": "林汐",
|
"name": "林汐",
|
||||||
"desc": "多平台功能型Bot",
|
"desc": "多平台功能型Bot",
|
||||||
"author": "mute23-code",
|
"author_id": 110453675,
|
||||||
"homepage": "https://github.com/netsora/SoraBot",
|
"homepage": "https://github.com/netsora/SoraBot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -537,7 +537,7 @@
|
|||||||
{
|
{
|
||||||
"name": "米缸",
|
"name": "米缸",
|
||||||
"desc": "基于nonebot2的米缸Bot",
|
"desc": "基于nonebot2的米缸Bot",
|
||||||
"author": "LambdaYH",
|
"author_id": 13503375,
|
||||||
"homepage": "https://github.com/LambdaYH/MigangBot",
|
"homepage": "https://github.com/LambdaYH/MigangBot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -545,7 +545,7 @@
|
|||||||
{
|
{
|
||||||
"name": "不正经的妹妹",
|
"name": "不正经的妹妹",
|
||||||
"desc": "一款功能丰富、简单易用、自定义性强、扩展性强的可爱的QQ娱乐机器人",
|
"desc": "一款功能丰富、简单易用、自定义性强、扩展性强的可爱的QQ娱乐机器人",
|
||||||
"author": "itsevin",
|
"author_id": 104713034,
|
||||||
"homepage": "https://github.com/itsevin/sister_bot",
|
"homepage": "https://github.com/itsevin/sister_bot",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -553,7 +553,7 @@
|
|||||||
{
|
{
|
||||||
"name": "星见Kirami",
|
"name": "星见Kirami",
|
||||||
"desc": "🌟 读作 Kirami,写作星见,简明轻快的聊天机器人应用。",
|
"desc": "🌟 读作 Kirami,写作星见,简明轻快的聊天机器人应用。",
|
||||||
"author": "A-kirami",
|
"author_id": 66513481,
|
||||||
"homepage": "https://kiramibot.dev/",
|
"homepage": "https://kiramibot.dev/",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -561,7 +561,7 @@
|
|||||||
{
|
{
|
||||||
"name": "OCNbot",
|
"name": "OCNbot",
|
||||||
"desc": "OI Contest Notifier bot,一个可以推送洛谷、cf、atcoder、牛客比赛通知的bot",
|
"desc": "OI Contest Notifier bot,一个可以推送洛谷、cf、atcoder、牛客比赛通知的bot",
|
||||||
"author": "ACnoway",
|
"author_id": 91535478,
|
||||||
"homepage": "https://github.com/ACnoway/OCNbot",
|
"homepage": "https://github.com/ACnoway/OCNbot",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -578,7 +578,7 @@
|
|||||||
{
|
{
|
||||||
"name": "妃爱",
|
"name": "妃爱",
|
||||||
"desc": "超可爱的妃爱QQ群聊机器人",
|
"desc": "超可爱的妃爱QQ群聊机器人",
|
||||||
"author": "jiangyuxiaoxiao",
|
"author_id": 52267304,
|
||||||
"homepage": "https://github.com/jiangyuxiaoxiao/Hiyori",
|
"homepage": "https://github.com/jiangyuxiaoxiao/Hiyori",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -586,7 +586,7 @@
|
|||||||
{
|
{
|
||||||
"name": "芙芙",
|
"name": "芙芙",
|
||||||
"desc": "供 Mooncell Wiki 协作使用的跨平台机器人",
|
"desc": "供 Mooncell Wiki 协作使用的跨平台机器人",
|
||||||
"author": "StarHeartHunt",
|
"author_id": 14922941,
|
||||||
"homepage": "https://github.com/MooncellWiki/BotFooChan",
|
"homepage": "https://github.com/MooncellWiki/BotFooChan",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -594,7 +594,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Sakiko",
|
"name": "Sakiko",
|
||||||
"desc": "基于 LiteLoaderBDS 的 Minecraft 基岩版 Bot",
|
"desc": "基于 LiteLoaderBDS 的 Minecraft 基岩版 Bot",
|
||||||
"author": "zhaomaoniu",
|
"author_id": 55650833,
|
||||||
"homepage": "https://github.com/zhaomaoniu/Sakiko",
|
"homepage": "https://github.com/zhaomaoniu/Sakiko",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -607,5 +607,60 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"name": "Minecraft_QQBot",
|
||||||
|
"desc": "基于 NoneBot2 的 Minecraft 群服互联 QQ 机器人,支持多服务器多种方式连接。",
|
||||||
|
"author_id": 90964775,
|
||||||
|
"homepage": "https://github.com/Minecraft-QQBot/BotServer",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "Minecraft",
|
||||||
|
"color": "#ea5252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "娱乐",
|
||||||
|
"color": "#37a7e7"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "小安提Bot",
|
||||||
|
"desc": "服务于音游 舞萌DX 的多功能Bot",
|
||||||
|
"author_id": 186144551,
|
||||||
|
"homepage": "https://github.com/Ant1816/Ant1Bot",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "maimaiDX",
|
||||||
|
"color": "#52ea9a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "音游",
|
||||||
|
"color": "#f74b18"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CanrotBot",
|
||||||
|
"desc": "有很多实用功能的bot,也有很多没什么用的娱乐功能;接入了大模型,并且有一部分功能可以被大模型调用。主打一个全都有(",
|
||||||
|
"author_id": 18070676,
|
||||||
|
"homepage": "https://github.com/wangyw15/CanrotBot",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mio澪",
|
||||||
|
"desc": "超可爱多功能Qbot",
|
||||||
|
"author_id": 50508678,
|
||||||
|
"homepage": "https://github.com/EienSakura/mio",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "娱乐",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
]
|
]
|
@@ -4,7 +4,7 @@
|
|||||||
"project_link": "",
|
"project_link": "",
|
||||||
"name": "None",
|
"name": "None",
|
||||||
"desc": "None 驱动器",
|
"desc": "None 驱动器",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "/docs/advanced/driver",
|
"homepage": "/docs/advanced/driver",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"project_link": "nonebot2[fastapi]",
|
"project_link": "nonebot2[fastapi]",
|
||||||
"name": "FastAPI",
|
"name": "FastAPI",
|
||||||
"desc": "FastAPI 驱动器",
|
"desc": "FastAPI 驱动器",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "/docs/advanced/driver",
|
"homepage": "/docs/advanced/driver",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"project_link": "nonebot2[quart]",
|
"project_link": "nonebot2[quart]",
|
||||||
"name": "Quart",
|
"name": "Quart",
|
||||||
"desc": "Quart 驱动器",
|
"desc": "Quart 驱动器",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "/docs/advanced/driver",
|
"homepage": "/docs/advanced/driver",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"project_link": "nonebot2[httpx]",
|
"project_link": "nonebot2[httpx]",
|
||||||
"name": "HTTPX",
|
"name": "HTTPX",
|
||||||
"desc": "HTTPX 驱动器",
|
"desc": "HTTPX 驱动器",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "/docs/advanced/driver",
|
"homepage": "/docs/advanced/driver",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"project_link": "nonebot2[websockets]",
|
"project_link": "nonebot2[websockets]",
|
||||||
"name": "websockets",
|
"name": "websockets",
|
||||||
"desc": "websockets 驱动器",
|
"desc": "websockets 驱动器",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "/docs/advanced/driver",
|
"homepage": "/docs/advanced/driver",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
@@ -54,9 +54,9 @@
|
|||||||
"project_link": "nonebot2[aiohttp]",
|
"project_link": "nonebot2[aiohttp]",
|
||||||
"name": "AIOHTTP",
|
"name": "AIOHTTP",
|
||||||
"desc": "AIOHTTP 驱动器",
|
"desc": "AIOHTTP 驱动器",
|
||||||
"author": "yanyongyu",
|
"author_id": 42488585,
|
||||||
"homepage": "/docs/advanced/driver",
|
"homepage": "/docs/advanced/driver",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
}
|
},
|
||||||
]
|
]
|
File diff suppressed because it is too large
Load Diff
2622
envs/pydantic-v1/poetry.lock
generated
2622
envs/pydantic-v1/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
2741
envs/pydantic-v2/poetry.lock
generated
2741
envs/pydantic-v2/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
1496
envs/test/poetry.lock
generated
1496
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 = "^6.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,22 +39,24 @@
|
|||||||
- `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 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
from typing import Any, Union, TypeVar, Optional, overload
|
import os
|
||||||
|
from typing import Any, Optional, TypeVar, Union, overload
|
||||||
|
|
||||||
import loguru
|
import loguru
|
||||||
|
|
||||||
|
from nonebot.adapters import Adapter, Bot
|
||||||
from nonebot.compat import model_dump
|
from nonebot.compat import model_dump
|
||||||
|
from nonebot.config import DOTENV_TYPE, Config, Env
|
||||||
|
from nonebot.drivers import ASGIMixin, Driver, combine_driver
|
||||||
from nonebot.log import logger as logger
|
from nonebot.log import logger as logger
|
||||||
from nonebot.adapters import Bot, Adapter
|
|
||||||
from nonebot.config import DOTENV_TYPE, Env, Config
|
|
||||||
from nonebot.utils import escape_tag, resolve_dot_notation
|
from nonebot.utils import escape_tag, resolve_dot_notation
|
||||||
from nonebot.drivers import Driver, ASGIMixin, combine_driver
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__version__ = version("nonebot2")
|
__version__ = version("nonebot2")
|
||||||
@@ -335,31 +337,31 @@ def run(*args: Any, **kwargs: Any) -> None:
|
|||||||
get_driver().run(*args, **kwargs)
|
get_driver().run(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
from nonebot.plugin import on as on
|
|
||||||
from nonebot.plugin import on_type as on_type
|
|
||||||
from nonebot.plugin import require as require
|
|
||||||
from nonebot.plugin import on_regex as on_regex
|
|
||||||
from nonebot.plugin import on_notice as on_notice
|
|
||||||
from nonebot.plugin import get_plugin as get_plugin
|
|
||||||
from nonebot.plugin import on_command as on_command
|
|
||||||
from nonebot.plugin import on_keyword as on_keyword
|
|
||||||
from nonebot.plugin import on_message as on_message
|
|
||||||
from nonebot.plugin import on_request as on_request
|
|
||||||
from nonebot.plugin import load_plugin as load_plugin
|
|
||||||
from nonebot.plugin import on_endswith as on_endswith
|
|
||||||
from nonebot.plugin import CommandGroup as CommandGroup
|
from nonebot.plugin import CommandGroup as CommandGroup
|
||||||
from nonebot.plugin import MatcherGroup as MatcherGroup
|
from nonebot.plugin import MatcherGroup as MatcherGroup
|
||||||
from nonebot.plugin import load_plugins as load_plugins
|
from nonebot.plugin import get_available_plugin_names as get_available_plugin_names
|
||||||
from nonebot.plugin import on_fullmatch as on_fullmatch
|
|
||||||
from nonebot.plugin import on_metaevent as on_metaevent
|
|
||||||
from nonebot.plugin import on_startswith as on_startswith
|
|
||||||
from nonebot.plugin import load_from_json as load_from_json
|
|
||||||
from nonebot.plugin import load_from_toml as load_from_toml
|
|
||||||
from nonebot.plugin import load_all_plugins as load_all_plugins
|
|
||||||
from nonebot.plugin import on_shell_command as on_shell_command
|
|
||||||
from nonebot.plugin import get_plugin_config as get_plugin_config
|
|
||||||
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
|
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
|
||||||
|
from nonebot.plugin import get_plugin as get_plugin
|
||||||
|
from nonebot.plugin import get_plugin_by_module_name as get_plugin_by_module_name
|
||||||
|
from nonebot.plugin import get_plugin_config as get_plugin_config
|
||||||
|
from nonebot.plugin import load_all_plugins as load_all_plugins
|
||||||
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
|
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
|
||||||
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
|
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
|
||||||
from nonebot.plugin import get_plugin_by_module_name as get_plugin_by_module_name
|
from nonebot.plugin import load_from_json as load_from_json
|
||||||
from nonebot.plugin import get_available_plugin_names as get_available_plugin_names
|
from nonebot.plugin import load_from_toml as load_from_toml
|
||||||
|
from nonebot.plugin import load_plugin as load_plugin
|
||||||
|
from nonebot.plugin import load_plugins as load_plugins
|
||||||
|
from nonebot.plugin import on as on
|
||||||
|
from nonebot.plugin import on_command as on_command
|
||||||
|
from nonebot.plugin import on_endswith as on_endswith
|
||||||
|
from nonebot.plugin import on_fullmatch as on_fullmatch
|
||||||
|
from nonebot.plugin import on_keyword as on_keyword
|
||||||
|
from nonebot.plugin import on_message as on_message
|
||||||
|
from nonebot.plugin import on_metaevent as on_metaevent
|
||||||
|
from nonebot.plugin import on_notice as on_notice
|
||||||
|
from nonebot.plugin import on_regex as on_regex
|
||||||
|
from nonebot.plugin import on_request as on_request
|
||||||
|
from nonebot.plugin import on_shell_command as on_shell_command
|
||||||
|
from nonebot.plugin import on_startswith as on_startswith
|
||||||
|
from nonebot.plugin import on_type as on_type
|
||||||
|
from nonebot.plugin import require as require
|
||||||
|
@@ -3,13 +3,15 @@
|
|||||||
使用 {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 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from nonebot.internal.adapter import Adapter as Adapter
|
||||||
from nonebot.internal.adapter import Bot as Bot
|
from nonebot.internal.adapter import Bot as Bot
|
||||||
from nonebot.internal.adapter import Event as Event
|
from nonebot.internal.adapter import Event as Event
|
||||||
from nonebot.internal.adapter import Adapter as Adapter
|
|
||||||
from nonebot.internal.adapter import Message as Message
|
from nonebot.internal.adapter import Message as Message
|
||||||
from nonebot.internal.adapter import MessageSegment as MessageSegment
|
from nonebot.internal.adapter import MessageSegment as MessageSegment
|
||||||
from nonebot.internal.adapter import MessageTemplate as MessageTemplate
|
from nonebot.internal.adapter import MessageTemplate as MessageTemplate
|
||||||
|
@@ -3,23 +3,28 @@
|
|||||||
为兼容 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 dataclasses import dataclass, is_dataclass
|
from dataclasses import dataclass, is_dataclass
|
||||||
from typing_extensions import Self, get_args, get_origin, is_typeddict
|
from functools import cached_property
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
|
Annotated,
|
||||||
Any,
|
Any,
|
||||||
Union,
|
|
||||||
TypeVar,
|
|
||||||
Callable,
|
Callable,
|
||||||
|
Generic,
|
||||||
Optional,
|
Optional,
|
||||||
Protocol,
|
Protocol,
|
||||||
Annotated,
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
overload,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import Self, get_args, get_origin, is_typeddict
|
||||||
|
|
||||||
from pydantic import VERSION, BaseModel
|
from pydantic import VERSION, BaseModel
|
||||||
|
|
||||||
@@ -39,21 +44,21 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"Required",
|
|
||||||
"PydanticUndefined",
|
|
||||||
"PydanticUndefinedType",
|
|
||||||
"ConfigDict",
|
|
||||||
"DEFAULT_CONFIG",
|
"DEFAULT_CONFIG",
|
||||||
|
"ConfigDict",
|
||||||
"FieldInfo",
|
"FieldInfo",
|
||||||
"ModelField",
|
"ModelField",
|
||||||
|
"PydanticUndefined",
|
||||||
|
"PydanticUndefinedType",
|
||||||
|
"Required",
|
||||||
|
"TypeAdapter",
|
||||||
|
"custom_validation",
|
||||||
"extract_field_info",
|
"extract_field_info",
|
||||||
"model_field_validate",
|
|
||||||
"model_fields",
|
|
||||||
"model_config",
|
"model_config",
|
||||||
"model_dump",
|
"model_dump",
|
||||||
"type_validate_python",
|
"model_fields",
|
||||||
"type_validate_json",
|
"type_validate_json",
|
||||||
"custom_validation",
|
"type_validate_python",
|
||||||
)
|
)
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
@@ -63,10 +68,11 @@ __autodoc__ = {
|
|||||||
|
|
||||||
|
|
||||||
if PYDANTIC_V2: # pragma: pydantic-v2
|
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||||
from pydantic_core import CoreSchema, core_schema
|
from pydantic import GetCoreSchemaHandler
|
||||||
|
from pydantic import TypeAdapter as TypeAdapter
|
||||||
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
|
||||||
|
from pydantic_core import CoreSchema, core_schema
|
||||||
|
|
||||||
Required = Ellipsis
|
Required = Ellipsis
|
||||||
"""Alias of Ellipsis for compatibility with pydantic v1"""
|
"""Alias of Ellipsis for compatibility with pydantic v1"""
|
||||||
@@ -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."""
|
||||||
|
|
||||||
@@ -238,9 +253,8 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
|||||||
return class_
|
return class_
|
||||||
|
|
||||||
else: # pragma: pydantic-v1
|
else: # pragma: pydantic-v1
|
||||||
from pydantic import Extra
|
|
||||||
from pydantic import parse_obj_as, parse_raw_as
|
|
||||||
from pydantic import BaseConfig as PydanticConfig
|
from pydantic import BaseConfig as PydanticConfig
|
||||||
|
from pydantic import Extra, parse_obj_as, parse_raw_as
|
||||||
from pydantic.fields import FieldInfo as BaseFieldInfo
|
from pydantic.fields import FieldInfo as BaseFieldInfo
|
||||||
from pydantic.fields import ModelField as BaseModelField
|
from pydantic.fields import ModelField as BaseModelField
|
||||||
from pydantic.schema import get_annotation_from_field_info
|
from pydantic.schema import get_annotation_from_field_info
|
||||||
@@ -305,6 +319,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 +367,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,27 +7,26 @@ 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 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import abc
|
import abc
|
||||||
import json
|
from collections.abc import Mapping
|
||||||
from pathlib import Path
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
from collections.abc import Mapping
|
import json
|
||||||
from typing import TYPE_CHECKING, Any, Union, Optional
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||||
from typing_extensions import TypeAlias, get_args, get_origin
|
from typing_extensions import TypeAlias, get_args, get_origin
|
||||||
|
|
||||||
from dotenv import dotenv_values
|
from dotenv import dotenv_values
|
||||||
from pydantic import Field, BaseModel
|
from pydantic import BaseModel, Field
|
||||||
from pydantic.networks import IPvAnyAddress
|
from pydantic.networks import IPvAnyAddress
|
||||||
|
|
||||||
from nonebot.log import logger
|
|
||||||
from nonebot.typing import origin_is_union
|
|
||||||
from nonebot.utils import deep_update, type_is_complex, lenient_issubclass
|
|
||||||
from nonebot.compat import (
|
from nonebot.compat import (
|
||||||
PYDANTIC_V2,
|
PYDANTIC_V2,
|
||||||
ConfigDict,
|
ConfigDict,
|
||||||
@@ -37,6 +36,9 @@ from nonebot.compat import (
|
|||||||
model_config,
|
model_config,
|
||||||
model_fields,
|
model_fields,
|
||||||
)
|
)
|
||||||
|
from nonebot.log import logger
|
||||||
|
from nonebot.typing import origin_is_union
|
||||||
|
from nonebot.utils import deep_update, lenient_issubclass, type_is_complex
|
||||||
|
|
||||||
DOTENV_TYPE: TypeAlias = Union[
|
DOTENV_TYPE: TypeAlias = Union[
|
||||||
Path, str, list[Union[Path, str]], tuple[Union[Path, str], ...]
|
Path, str, list[Union[Path, str]], tuple[Union[Path, str], ...]
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 9
|
sidebar_position: 9
|
||||||
description: nonebot.consts 模块
|
description: nonebot.consts 模块
|
||||||
"""
|
"""
|
||||||
@@ -20,6 +22,10 @@ REJECT_TARGET: Literal["_current_target"] = "_current_target"
|
|||||||
"""当前 `reject` 目标存储 key"""
|
"""当前 `reject` 目标存储 key"""
|
||||||
REJECT_CACHE_TARGET: Literal["_next_target"] = "_next_target"
|
REJECT_CACHE_TARGET: Literal["_next_target"] = "_next_target"
|
||||||
"""下一个 `reject` 目标存储 key"""
|
"""下一个 `reject` 目标存储 key"""
|
||||||
|
PAUSE_PROMPT_RESULT_KEY: Literal["_pause_result"] = "_pause_result"
|
||||||
|
"""`pause` prompt 发送结果存储 key"""
|
||||||
|
REJECT_PROMPT_RESULT_KEY: Literal["_reject_{key}_result"] = "_reject_{key}_result"
|
||||||
|
"""`reject` prompt 发送结果存储 key"""
|
||||||
|
|
||||||
# used by Rule
|
# used by Rule
|
||||||
PREFIX_KEY: Literal["_prefix"] = "_prefix"
|
PREFIX_KEY: Literal["_prefix"] = "_prefix"
|
||||||
|
@@ -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
|
from collections.abc import Awaitable, Iterable
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from functools import partial
|
||||||
import inspect
|
import inspect
|
||||||
from dataclasses import field, dataclass
|
from typing import Any, Callable, Generic, Optional, TypeVar, cast
|
||||||
from collections.abc import Iterable, Awaitable
|
|
||||||
from typing import Any, Generic, TypeVar, Callable, Optional, cast
|
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
|
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
|
||||||
|
from nonebot.exception import SkippedException
|
||||||
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.utils import (
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
flatten_exception_group,
|
||||||
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
|
is_coroutine_callable,
|
||||||
|
run_coro_with_shield,
|
||||||
|
run_sync,
|
||||||
|
)
|
||||||
|
|
||||||
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,29 +3,31 @@
|
|||||||
各驱动请继承以下基类。
|
各驱动请继承以下基类。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.drivers 模块
|
description: nonebot.drivers 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from nonebot.internal.driver import URL as URL
|
from nonebot.internal.driver import URL as URL
|
||||||
from nonebot.internal.driver import Mixin as Mixin
|
from nonebot.internal.driver import ASGIMixin as ASGIMixin
|
||||||
from nonebot.internal.driver import Driver as Driver
|
|
||||||
from nonebot.internal.driver import Cookies as Cookies
|
from nonebot.internal.driver import Cookies as Cookies
|
||||||
|
from nonebot.internal.driver import Driver as Driver
|
||||||
|
from nonebot.internal.driver import ForwardDriver as ForwardDriver
|
||||||
|
from nonebot.internal.driver import ForwardMixin as ForwardMixin
|
||||||
|
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
|
||||||
|
from nonebot.internal.driver import HTTPClientSession as HTTPClientSession
|
||||||
|
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
|
||||||
|
from nonebot.internal.driver import HTTPVersion as HTTPVersion
|
||||||
|
from nonebot.internal.driver import Mixin as Mixin
|
||||||
from nonebot.internal.driver import Request as Request
|
from nonebot.internal.driver import Request as Request
|
||||||
from nonebot.internal.driver import Response as Response
|
from nonebot.internal.driver import Response as Response
|
||||||
from nonebot.internal.driver import ASGIMixin as ASGIMixin
|
|
||||||
from nonebot.internal.driver import WebSocket as WebSocket
|
|
||||||
from nonebot.internal.driver import HTTPVersion as HTTPVersion
|
|
||||||
from nonebot.internal.driver import ForwardMixin as ForwardMixin
|
|
||||||
from nonebot.internal.driver import ReverseMixin as ReverseMixin
|
|
||||||
from nonebot.internal.driver import ForwardDriver as ForwardDriver
|
|
||||||
from nonebot.internal.driver import ReverseDriver as ReverseDriver
|
from nonebot.internal.driver import ReverseDriver as ReverseDriver
|
||||||
from nonebot.internal.driver import combine_driver as combine_driver
|
from nonebot.internal.driver import ReverseMixin as ReverseMixin
|
||||||
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
|
from nonebot.internal.driver import WebSocket as WebSocket
|
||||||
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
|
|
||||||
from nonebot.internal.driver import HTTPClientSession as HTTPClientSession
|
|
||||||
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
|
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
|
||||||
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
||||||
|
from nonebot.internal.driver import combine_driver as combine_driver
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"URL": True,
|
"URL": True,
|
||||||
|
@@ -11,29 +11,33 @@ pip install nonebot2[aiohttp]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.drivers.aiohttp 模块
|
description: nonebot.drivers.aiohttp 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing_extensions import override
|
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import TYPE_CHECKING, Union, Optional
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from multidict import CIMultiDict
|
from multidict import CIMultiDict
|
||||||
|
|
||||||
from nonebot.exception import WebSocketClosed
|
|
||||||
from nonebot.drivers import URL, Request, Response
|
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
|
||||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
|
||||||
from nonebot.internal.driver import Cookies, QueryTypes, CookieTypes, HeaderTypes
|
|
||||||
from nonebot.drivers import (
|
from nonebot.drivers import (
|
||||||
HTTPVersion,
|
URL,
|
||||||
HTTPClientMixin,
|
HTTPClientMixin,
|
||||||
HTTPClientSession,
|
HTTPClientSession,
|
||||||
|
HTTPVersion,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
WebSocketClientMixin,
|
WebSocketClientMixin,
|
||||||
combine_driver,
|
combine_driver,
|
||||||
)
|
)
|
||||||
|
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||||
|
from nonebot.drivers.none import Driver as NoneDriver
|
||||||
|
from nonebot.exception import WebSocketClosed
|
||||||
|
from nonebot.internal.driver import Cookies, CookieTypes, HeaderTypes, QueryTypes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -86,9 +90,7 @@ class Session(HTTPClientSession):
|
|||||||
@override
|
@override
|
||||||
async def request(self, setup: Request) -> Response:
|
async def request(self, setup: Request) -> Response:
|
||||||
if self._params:
|
if self._params:
|
||||||
params = self._params.copy()
|
url = setup.url.with_query({**self._params, **setup.url.query})
|
||||||
params.update(setup.url.query)
|
|
||||||
url = setup.url.with_query(params)
|
|
||||||
else:
|
else:
|
||||||
url = setup.url
|
url = setup.url
|
||||||
|
|
||||||
@@ -168,11 +170,13 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
|||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
|
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
|
||||||
|
|
||||||
|
timeout = aiohttp.ClientWSTimeout(ws_close=setup.timeout or 10.0) # type: ignore
|
||||||
|
|
||||||
async with aiohttp.ClientSession(version=version, trust_env=True) as session:
|
async with aiohttp.ClientSession(version=version, trust_env=True) as session:
|
||||||
async with session.ws_connect(
|
async with session.ws_connect(
|
||||||
setup.url,
|
setup.url,
|
||||||
method=setup.method,
|
method=setup.method,
|
||||||
timeout=setup.timeout or 10,
|
timeout=timeout,
|
||||||
headers=setup.headers,
|
headers=setup.headers,
|
||||||
proxy=setup.proxy,
|
proxy=setup.proxy,
|
||||||
) as ws:
|
) as ws:
|
||||||
|
@@ -11,34 +11,35 @@ pip install nonebot2[fastapi]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.drivers.fastapi 模块
|
description: nonebot.drivers.fastapi 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import contextlib
|
import contextlib
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
import logging
|
||||||
|
from typing import Any, Optional, Union
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from typing import Any, Union, Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.config import Env
|
from nonebot.compat import model_dump, type_validate_python
|
||||||
from nonebot.drivers import ASGIMixin
|
|
||||||
from nonebot.exception import WebSocketClosed
|
|
||||||
from nonebot.internal.driver import FileTypes
|
|
||||||
from nonebot.drivers import Driver as BaseDriver
|
|
||||||
from nonebot.config import Config as NoneBotConfig
|
from nonebot.config import Config as NoneBotConfig
|
||||||
|
from nonebot.config import Env
|
||||||
|
from nonebot.drivers import ASGIMixin, HTTPServerSetup, WebSocketServerSetup
|
||||||
|
from nonebot.drivers import Driver as BaseDriver
|
||||||
from nonebot.drivers import Request as BaseRequest
|
from nonebot.drivers import Request as BaseRequest
|
||||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||||
from nonebot.compat import model_dump, type_validate_python
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
from nonebot.internal.driver import FileTypes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uvicorn
|
|
||||||
from fastapi.responses import Response
|
|
||||||
from fastapi import FastAPI, Request, UploadFile, status
|
from fastapi import FastAPI, Request, UploadFile, status
|
||||||
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
from fastapi.responses import Response
|
||||||
|
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
|
||||||
|
import uvicorn
|
||||||
except ModuleNotFoundError as e: # pragma: no cover
|
except ModuleNotFoundError as e: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install FastAPI first to use this driver. "
|
"Please install FastAPI first to use this driver. "
|
||||||
|
@@ -11,26 +11,28 @@ pip install nonebot2[httpx]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.drivers.httpx 模块
|
description: nonebot.drivers.httpx 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from typing import TYPE_CHECKING, Union, Optional
|
|
||||||
|
|
||||||
from multidict import CIMultiDict
|
from multidict import CIMultiDict
|
||||||
|
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
|
||||||
from nonebot.internal.driver import Cookies, QueryTypes, CookieTypes, HeaderTypes
|
|
||||||
from nonebot.drivers import (
|
from nonebot.drivers import (
|
||||||
URL,
|
URL,
|
||||||
Request,
|
|
||||||
Response,
|
|
||||||
HTTPVersion,
|
|
||||||
HTTPClientMixin,
|
HTTPClientMixin,
|
||||||
HTTPClientSession,
|
HTTPClientSession,
|
||||||
|
HTTPVersion,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
combine_driver,
|
combine_driver,
|
||||||
)
|
)
|
||||||
|
from nonebot.drivers.none import Driver as NoneDriver
|
||||||
|
from nonebot.internal.driver import Cookies, CookieTypes, HeaderTypes, QueryTypes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import httpx
|
import httpx
|
||||||
@@ -80,6 +82,8 @@ class Session(HTTPClientSession):
|
|||||||
data=setup.data,
|
data=setup.data,
|
||||||
files=setup.files,
|
files=setup.files,
|
||||||
json=setup.json,
|
json=setup.json,
|
||||||
|
# ensure the params priority
|
||||||
|
params=setup.url.raw_query_string,
|
||||||
headers=tuple(setup.headers.items()),
|
headers=tuple(setup.headers.items()),
|
||||||
cookies=setup.cookies.jar,
|
cookies=setup.cookies.jar,
|
||||||
timeout=setup.timeout,
|
timeout=setup.timeout,
|
||||||
@@ -100,7 +104,7 @@ class Session(HTTPClientSession):
|
|||||||
headers=self._headers,
|
headers=self._headers,
|
||||||
cookies=self._cookies.jar,
|
cookies=self._cookies.jar,
|
||||||
http2=self._version == HTTPVersion.H2,
|
http2=self._version == HTTPVersion.H2,
|
||||||
proxies=self._proxy,
|
proxy=self._proxy,
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
)
|
)
|
||||||
await self._client.__aenter__()
|
await self._client.__aenter__()
|
||||||
|
@@ -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
|
||||||
|
|
||||||
from nonebot.log import logger
|
import anyio
|
||||||
|
from anyio.abc import TaskGroup
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
|
from nonebot.config import Config, Env
|
||||||
from nonebot.consts import WINDOWS
|
from nonebot.consts import WINDOWS
|
||||||
from nonebot.config import Env, Config
|
|
||||||
from nonebot.drivers import Driver as BaseDriver
|
from nonebot.drivers import Driver as BaseDriver
|
||||||
|
from nonebot.log import logger
|
||||||
|
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,36 +11,37 @@ pip install nonebot2[quart]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.drivers.quart 模块
|
description: nonebot.drivers.quart 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from typing import Any, Optional, Union, cast
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from typing import Any, Union, Optional, cast
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.config import Env
|
from nonebot.compat import model_dump, type_validate_python
|
||||||
from nonebot.drivers import ASGIMixin
|
|
||||||
from nonebot.exception import WebSocketClosed
|
|
||||||
from nonebot.internal.driver import FileTypes
|
|
||||||
from nonebot.drivers import Driver as BaseDriver
|
|
||||||
from nonebot.config import Config as NoneBotConfig
|
from nonebot.config import Config as NoneBotConfig
|
||||||
|
from nonebot.config import Env
|
||||||
|
from nonebot.drivers import ASGIMixin, HTTPServerSetup, WebSocketServerSetup
|
||||||
|
from nonebot.drivers import Driver as BaseDriver
|
||||||
from nonebot.drivers import Request as BaseRequest
|
from nonebot.drivers import Request as BaseRequest
|
||||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||||
from nonebot.compat import model_dump, type_validate_python
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
from nonebot.internal.driver import FileTypes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uvicorn
|
from quart import Quart, Request, Response
|
||||||
|
from quart import Websocket as QuartWebSocket
|
||||||
from quart import request as _request
|
from quart import request as _request
|
||||||
from quart.ctx import WebsocketContext
|
from quart.ctx import WebsocketContext
|
||||||
from quart.globals import websocket_ctx
|
|
||||||
from quart import Quart, Request, Response
|
|
||||||
from quart.datastructures import FileStorage
|
from quart.datastructures import FileStorage
|
||||||
from quart import Websocket as QuartWebSocket
|
from quart.globals import websocket_ctx
|
||||||
|
import uvicorn
|
||||||
except ModuleNotFoundError as e: # pragma: no cover
|
except ModuleNotFoundError as e: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install Quart first to use this driver. "
|
"Please install Quart first to use this driver. "
|
||||||
|
@@ -11,23 +11,24 @@ pip install nonebot2[websockets]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 4
|
sidebar_position: 4
|
||||||
description: nonebot.drivers.websockets 模块
|
description: nonebot.drivers.websockets 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
from collections.abc import AsyncGenerator, Coroutine
|
||||||
from functools import wraps
|
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
from functools import wraps
|
||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union
|
||||||
from typing_extensions import ParamSpec, override
|
from typing_extensions import ParamSpec, override
|
||||||
from collections.abc import Coroutine, AsyncGenerator
|
|
||||||
from typing import TYPE_CHECKING, Any, Union, TypeVar, Callable
|
|
||||||
|
|
||||||
from nonebot.drivers import Request
|
from nonebot.drivers import Request, WebSocketClientMixin, combine_driver
|
||||||
from nonebot.log import LoguruHandler
|
|
||||||
from nonebot.exception import WebSocketClosed
|
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
|
||||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||||
from nonebot.drivers import WebSocketClientMixin, combine_driver
|
from nonebot.drivers.none import Driver as NoneDriver
|
||||||
|
from nonebot.exception import WebSocketClosed
|
||||||
|
from nonebot.log import LoguruHandler
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from websockets.exceptions import ConnectionClosed
|
from websockets.exceptions import ConnectionClosed
|
||||||
@@ -46,7 +47,7 @@ logger.addHandler(LoguruHandler())
|
|||||||
|
|
||||||
|
|
||||||
def catch_closed(
|
def catch_closed(
|
||||||
func: Callable[P, Coroutine[Any, Any, T]]
|
func: Callable[P, Coroutine[Any, Any, T]],
|
||||||
) -> Callable[P, Coroutine[Any, Any, T]]:
|
) -> Callable[P, Coroutine[Any, Any, T]]:
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
|
async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||||
@@ -69,6 +70,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,6 +1,6 @@
|
|||||||
|
from .adapter import Adapter as Adapter
|
||||||
from .bot import Bot as Bot
|
from .bot import Bot as Bot
|
||||||
from .event import Event as Event
|
from .event import Event as Event
|
||||||
from .adapter import Adapter as Adapter
|
|
||||||
from .message import Message as Message
|
from .message import Message as Message
|
||||||
from .message import MessageSegment as MessageSegment
|
from .message import MessageSegment as MessageSegment
|
||||||
from .template import MessageTemplate as MessageTemplate
|
from .template import MessageTemplate as MessageTemplate
|
||||||
|
@@ -1,21 +1,21 @@
|
|||||||
import abc
|
import abc
|
||||||
from typing import Any
|
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from nonebot.config import Config
|
from nonebot.config import Config
|
||||||
from nonebot.internal.driver._lifespan import LIFESPAN_FUNC
|
|
||||||
from nonebot.internal.driver import (
|
from nonebot.internal.driver import (
|
||||||
Driver,
|
|
||||||
Request,
|
|
||||||
Response,
|
|
||||||
ASGIMixin,
|
ASGIMixin,
|
||||||
WebSocket,
|
Driver,
|
||||||
HTTPClientMixin,
|
HTTPClientMixin,
|
||||||
HTTPServerSetup,
|
HTTPServerSetup,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
WebSocket,
|
||||||
WebSocketClientMixin,
|
WebSocketClientMixin,
|
||||||
WebSocketServerSetup,
|
WebSocketServerSetup,
|
||||||
)
|
)
|
||||||
|
from nonebot.internal.driver._lifespan import LIFESPAN_FUNC
|
||||||
|
|
||||||
from .bot import Bot
|
from .bot import Bot
|
||||||
|
|
||||||
|
@@ -1,16 +1,19 @@
|
|||||||
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, ClassVar, Optional, Protocol, Union
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
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.log import logger
|
||||||
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
|
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
|
||||||
|
from nonebot.utils import flatten_exception_group
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .event import Event
|
|
||||||
from .adapter import Adapter
|
from .adapter import Adapter
|
||||||
|
from .event import Event
|
||||||
from .message import Message, MessageSegment
|
from .message import Message, MessageSegment
|
||||||
|
|
||||||
class _ApiCall(Protocol):
|
class _ApiCall(Protocol):
|
||||||
@@ -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
|
||||||
|
@@ -3,8 +3,8 @@ from typing import Any, TypeVar
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.utils import DataclassEncoder
|
|
||||||
from nonebot.compat import PYDANTIC_V2, ConfigDict
|
from nonebot.compat import PYDANTIC_V2, ConfigDict
|
||||||
|
from nonebot.utils import DataclassEncoder
|
||||||
|
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
import abc
|
import abc
|
||||||
from copy import deepcopy
|
|
||||||
from typing_extensions import Self
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from dataclasses import field, asdict, dataclass
|
from copy import deepcopy
|
||||||
|
from dataclasses import asdict, dataclass, field
|
||||||
from typing import ( # noqa: UP035
|
from typing import ( # noqa: UP035
|
||||||
Any,
|
Any,
|
||||||
Type,
|
|
||||||
Union,
|
|
||||||
Generic,
|
Generic,
|
||||||
TypeVar,
|
|
||||||
Optional,
|
Optional,
|
||||||
SupportsIndex,
|
SupportsIndex,
|
||||||
|
Type,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from nonebot.compat import custom_validation, type_validate_python
|
from nonebot.compat import custom_validation, type_validate_python
|
||||||
|
|
||||||
@@ -329,8 +329,9 @@ class Message(list[TMS], abc.ABC):
|
|||||||
return self[type_]
|
return self[type_]
|
||||||
|
|
||||||
iterator, filtered = (
|
iterator, filtered = (
|
||||||
seg for seg in self if seg.type == type_
|
(seg for seg in self if seg.type == type_),
|
||||||
), self.__class__()
|
self.__class__(),
|
||||||
|
)
|
||||||
for _ in range(count):
|
for _ in range(count):
|
||||||
seg = next(iterator, None)
|
seg = next(iterator, None)
|
||||||
if seg is None:
|
if seg is None:
|
||||||
|
@@ -1,20 +1,19 @@
|
|||||||
|
from _string import formatter_field_name_split # type: ignore
|
||||||
|
from collections.abc import Mapping, Sequence
|
||||||
import functools
|
import functools
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
from typing_extensions import TypeAlias
|
|
||||||
from collections.abc import Mapping, Sequence
|
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Union,
|
|
||||||
Generic,
|
|
||||||
TypeVar,
|
|
||||||
Callable,
|
Callable,
|
||||||
|
Generic,
|
||||||
Optional,
|
Optional,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
cast,
|
cast,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
from _string import formatter_field_name_split # type: ignore
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .message import Message, MessageSegment
|
from .message import Message, MessageSegment
|
||||||
|
@@ -1,31 +1,31 @@
|
|||||||
from .model import URL as URL
|
|
||||||
from .model import RawURL as RawURL
|
|
||||||
from .abstract import Mixin as Mixin
|
|
||||||
from .model import Cookies as Cookies
|
|
||||||
from .model import Request as Request
|
|
||||||
from .abstract import Driver as Driver
|
|
||||||
from .model import FileType as FileType
|
|
||||||
from .model import Response as Response
|
|
||||||
from .model import DataTypes as DataTypes
|
|
||||||
from .model import FileTypes as FileTypes
|
|
||||||
from .model import WebSocket as WebSocket
|
|
||||||
from .model import FilesTypes as FilesTypes
|
|
||||||
from .model import QueryTypes as QueryTypes
|
|
||||||
from .abstract import ASGIMixin as ASGIMixin
|
from .abstract import ASGIMixin as ASGIMixin
|
||||||
from .model import CookieTypes as CookieTypes
|
from .abstract import Driver as Driver
|
||||||
from .model import FileContent as FileContent
|
|
||||||
from .model import HTTPVersion as HTTPVersion
|
|
||||||
from .model import HeaderTypes as HeaderTypes
|
|
||||||
from .model import SimpleQuery as SimpleQuery
|
|
||||||
from .model import ContentTypes as ContentTypes
|
|
||||||
from .model import QueryVariable as QueryVariable
|
|
||||||
from .abstract import ForwardMixin as ForwardMixin
|
|
||||||
from .abstract import ReverseMixin as ReverseMixin
|
|
||||||
from .abstract import ForwardDriver as ForwardDriver
|
from .abstract import ForwardDriver as ForwardDriver
|
||||||
from .abstract import ReverseDriver as ReverseDriver
|
from .abstract import ForwardMixin as ForwardMixin
|
||||||
from .combine import combine_driver as combine_driver
|
|
||||||
from .model import HTTPServerSetup as HTTPServerSetup
|
|
||||||
from .abstract import HTTPClientMixin as HTTPClientMixin
|
from .abstract import HTTPClientMixin as HTTPClientMixin
|
||||||
from .abstract import HTTPClientSession as HTTPClientSession
|
from .abstract import HTTPClientSession as HTTPClientSession
|
||||||
from .model import WebSocketServerSetup as WebSocketServerSetup
|
from .abstract import Mixin as Mixin
|
||||||
|
from .abstract import ReverseDriver as ReverseDriver
|
||||||
|
from .abstract import ReverseMixin as ReverseMixin
|
||||||
from .abstract import WebSocketClientMixin as WebSocketClientMixin
|
from .abstract import WebSocketClientMixin as WebSocketClientMixin
|
||||||
|
from .combine import combine_driver as combine_driver
|
||||||
|
from .model import URL as URL
|
||||||
|
from .model import ContentTypes as ContentTypes
|
||||||
|
from .model import Cookies as Cookies
|
||||||
|
from .model import CookieTypes as CookieTypes
|
||||||
|
from .model import DataTypes as DataTypes
|
||||||
|
from .model import FileContent as FileContent
|
||||||
|
from .model import FilesTypes as FilesTypes
|
||||||
|
from .model import FileType as FileType
|
||||||
|
from .model import FileTypes as FileTypes
|
||||||
|
from .model import HeaderTypes as HeaderTypes
|
||||||
|
from .model import HTTPServerSetup as HTTPServerSetup
|
||||||
|
from .model import HTTPVersion as HTTPVersion
|
||||||
|
from .model import QueryTypes as QueryTypes
|
||||||
|
from .model import QueryVariable as QueryVariable
|
||||||
|
from .model import RawURL as RawURL
|
||||||
|
from .model import Request as Request
|
||||||
|
from .model import Response as Response
|
||||||
|
from .model import SimpleQuery as SimpleQuery
|
||||||
|
from .model import WebSocket as WebSocket
|
||||||
|
from .model import WebSocketServerSetup as WebSocketServerSetup
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
from collections.abc import Awaitable
|
from collections.abc import Awaitable, Iterable
|
||||||
|
from types import TracebackType
|
||||||
|
from typing import Any, Callable, Optional, Union, cast
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import TypeAlias
|
||||||
from typing import Any, Union, Callable, cast
|
|
||||||
|
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
import anyio
|
||||||
|
from anyio.abc import TaskGroup
|
||||||
|
from exceptiongroup import suppress
|
||||||
|
|
||||||
|
from nonebot.utils import is_coroutine_callable, run_sync
|
||||||
|
|
||||||
SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any]
|
SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any]
|
||||||
ASYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Awaitable[Any]]
|
ASYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Awaitable[Any]]
|
||||||
@@ -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,38 +1,41 @@
|
|||||||
import abc
|
import abc
|
||||||
import asyncio
|
|
||||||
from types import TracebackType
|
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
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 types import TracebackType
|
||||||
|
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
|
||||||
|
from typing_extensions import Self, TypeAlias
|
||||||
|
|
||||||
from nonebot.log import logger
|
from anyio import CancelScope, create_task_group
|
||||||
from nonebot.config import Env, Config
|
from anyio.abc import TaskGroup
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
|
from nonebot.config import Config, Env
|
||||||
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, DefaultParam, DependParam
|
||||||
from nonebot.internal.params import BotParam, DependParam, DefaultParam
|
from nonebot.log import logger
|
||||||
from nonebot.typing import (
|
from nonebot.typing import (
|
||||||
T_DependencyCache,
|
|
||||||
T_BotConnectionHook,
|
T_BotConnectionHook,
|
||||||
T_BotDisconnectionHook,
|
T_BotDisconnectionHook,
|
||||||
|
T_DependencyCache,
|
||||||
)
|
)
|
||||||
|
from nonebot.utils import escape_tag, flatten_exception_group, run_coro_with_catch
|
||||||
|
|
||||||
from ._lifespan import LIFESPAN_FUNC, Lifespan
|
from ._lifespan import LIFESPAN_FUNC, Lifespan
|
||||||
from .model import (
|
from .model import (
|
||||||
|
CookieTypes,
|
||||||
|
HeaderTypes,
|
||||||
|
HTTPServerSetup,
|
||||||
|
HTTPVersion,
|
||||||
|
QueryTypes,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
WebSocket,
|
WebSocket,
|
||||||
QueryTypes,
|
|
||||||
CookieTypes,
|
|
||||||
HeaderTypes,
|
|
||||||
HTTPVersion,
|
|
||||||
HTTPServerSetup,
|
|
||||||
WebSocketServerSetup,
|
WebSocketServerSetup,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from nonebot.internal.adapter import Bot, Adapter
|
from nonebot.internal.adapter import Adapter, Bot
|
||||||
|
|
||||||
|
|
||||||
BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
|
BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
|
||||||
@@ -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:
|
||||||
"""注册一个协议适配器
|
"""注册一个协议适配器
|
||||||
|
|
||||||
@@ -108,12 +114,10 @@ class Driver(abc.ABC):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
"""启动驱动框架"""
|
"""启动驱动框架"""
|
||||||
logger.opt(colors=True).debug(
|
logger.opt(colors=True).success(
|
||||||
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):
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
from typing import TYPE_CHECKING, Union, TypeVar, overload
|
from typing import TYPE_CHECKING, TypeVar, Union, overload
|
||||||
|
|
||||||
from .abstract import Mixin, Driver
|
from .abstract import Driver, Mixin
|
||||||
|
|
||||||
D = TypeVar("D", bound="Driver")
|
D = TypeVar("D", bound="Driver")
|
||||||
|
|
||||||
@@ -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"]: ...
|
||||||
|
|
||||||
|
|
||||||
@@ -39,6 +39,4 @@ def combine_driver(
|
|||||||
+ "+".join(x.type.__get__(self) for x in mixins) # type: ignore
|
+ "+".join(x.type.__get__(self) for x in mixins) # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
return type(
|
return type("CombinedDriver", (*mixins, driver), {"type": property(type_)}) # type: ignore
|
||||||
"CombinedDriver", (*mixins, driver), {"type": property(type_)}
|
|
||||||
) # type: ignore
|
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import abc
|
import abc
|
||||||
import urllib.request
|
from collections.abc import Awaitable, Iterator, Mapping, MutableMapping
|
||||||
from enum import Enum
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing_extensions import TypeAlias
|
from enum import Enum
|
||||||
from http.cookiejar import Cookie, CookieJar
|
from http.cookiejar import Cookie, CookieJar
|
||||||
from typing import IO, Any, Union, Callable, Optional
|
from typing import IO, Any, Callable, Optional, Union
|
||||||
from collections.abc import Mapping, Iterator, Awaitable, MutableMapping
|
from typing_extensions import TypeAlias
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
from yarl import URL as URL
|
|
||||||
from multidict import CIMultiDict
|
from multidict import CIMultiDict
|
||||||
|
from yarl import URL as URL
|
||||||
|
|
||||||
RawURL: TypeAlias = tuple[bytes, bytes, Optional[int], bytes]
|
RawURL: TypeAlias = tuple[bytes, bytes, Optional[int], bytes]
|
||||||
|
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
from .manager import MatcherManager as MatcherManager
|
from .manager import MatcherManager as MatcherManager
|
||||||
from .provider import MatcherProvider as MatcherProvider
|
|
||||||
from .provider import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
|
from .provider import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
|
||||||
|
from .provider import MatcherProvider as MatcherProvider
|
||||||
|
|
||||||
matchers = MatcherManager()
|
matchers = MatcherManager()
|
||||||
|
|
||||||
from .matcher import Matcher as Matcher
|
from .matcher import Matcher as Matcher
|
||||||
from .matcher import current_bot as current_bot
|
|
||||||
from .matcher import MatcherSource as MatcherSource
|
from .matcher import MatcherSource as MatcherSource
|
||||||
|
from .matcher import current_bot as current_bot
|
||||||
from .matcher import current_event as current_event
|
from .matcher import current_event as current_event
|
||||||
from .matcher import current_handler as current_handler
|
from .matcher import current_handler as current_handler
|
||||||
from .matcher import current_matcher as current_matcher
|
from .matcher import current_matcher as current_matcher
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
from typing import TYPE_CHECKING, Union, TypeVar, Optional, overload
|
from collections.abc import ItemsView, Iterator, KeysView, MutableMapping, ValuesView
|
||||||
from collections.abc import Iterator, KeysView, ItemsView, ValuesView, MutableMapping
|
from typing import TYPE_CHECKING, Optional, TypeVar, Union, overload
|
||||||
|
|
||||||
from .provider import DEFAULT_PROVIDER_CLASS, MatcherProvider
|
from .provider import DEFAULT_PROVIDER_CLASS, MatcherProvider
|
||||||
|
|
||||||
@@ -74,9 +74,9 @@ class MatcherManager(MutableMapping[int, list[type["Matcher"]]]):
|
|||||||
self.provider.clear()
|
self.provider.clear()
|
||||||
|
|
||||||
def update( # pyright: ignore[reportIncompatibleMethodOverride]
|
def update( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||||
self, __m: MutableMapping[int, list[type["Matcher"]]]
|
self, m: MutableMapping[int, list[type["Matcher"]]], /
|
||||||
) -> None:
|
) -> None:
|
||||||
self.provider.update(__m)
|
self.provider.update(m)
|
||||||
|
|
||||||
def setdefault(
|
def setdefault(
|
||||||
self, key: int, default: list[type["Matcher"]]
|
self, key: int, default: list[type["Matcher"]]
|
||||||
|
@@ -1,32 +1,46 @@
|
|||||||
import sys
|
|
||||||
import inspect
|
|
||||||
import warnings
|
|
||||||
from pathlib import Path
|
|
||||||
from types import ModuleType
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from contextvars import ContextVar
|
|
||||||
from typing_extensions import Self
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from contextlib import AsyncExitStack, contextmanager
|
from contextlib import AsyncExitStack, contextmanager
|
||||||
|
from contextvars import ContextVar
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import inspect
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
from types import ModuleType
|
||||||
from typing import ( # noqa: UP035
|
from typing import ( # noqa: UP035
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Type,
|
|
||||||
Union,
|
|
||||||
TypeVar,
|
|
||||||
Callable,
|
Callable,
|
||||||
ClassVar,
|
ClassVar,
|
||||||
NoReturn,
|
NoReturn,
|
||||||
Optional,
|
Optional,
|
||||||
|
Type,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import Self
|
||||||
|
import warnings
|
||||||
|
|
||||||
from nonebot.log import logger
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
from nonebot.internal.rule import Rule
|
|
||||||
from nonebot.utils import classproperty
|
from nonebot.consts import (
|
||||||
from nonebot.dependencies import Param, Dependent
|
ARG_KEY,
|
||||||
from nonebot.internal.permission import User, Permission
|
LAST_RECEIVE_KEY,
|
||||||
|
PAUSE_PROMPT_RESULT_KEY,
|
||||||
|
RECEIVE_KEY,
|
||||||
|
REJECT_CACHE_TARGET,
|
||||||
|
REJECT_PROMPT_RESULT_KEY,
|
||||||
|
REJECT_TARGET,
|
||||||
|
)
|
||||||
|
from nonebot.dependencies import Dependent, Param
|
||||||
|
from nonebot.exception import (
|
||||||
|
FinishedException,
|
||||||
|
PausedException,
|
||||||
|
RejectedException,
|
||||||
|
SkippedException,
|
||||||
|
StopPropagation,
|
||||||
|
)
|
||||||
from nonebot.internal.adapter import (
|
from nonebot.internal.adapter import (
|
||||||
Bot,
|
Bot,
|
||||||
Event,
|
Event,
|
||||||
@@ -34,37 +48,27 @@ from nonebot.internal.adapter import (
|
|||||||
MessageSegment,
|
MessageSegment,
|
||||||
MessageTemplate,
|
MessageTemplate,
|
||||||
)
|
)
|
||||||
from nonebot.typing import (
|
|
||||||
T_State,
|
|
||||||
T_Handler,
|
|
||||||
T_TypeUpdater,
|
|
||||||
T_DependencyCache,
|
|
||||||
T_PermissionUpdater,
|
|
||||||
)
|
|
||||||
from nonebot.consts import (
|
|
||||||
ARG_KEY,
|
|
||||||
RECEIVE_KEY,
|
|
||||||
REJECT_TARGET,
|
|
||||||
LAST_RECEIVE_KEY,
|
|
||||||
REJECT_CACHE_TARGET,
|
|
||||||
)
|
|
||||||
from nonebot.exception import (
|
|
||||||
PausedException,
|
|
||||||
StopPropagation,
|
|
||||||
SkippedException,
|
|
||||||
FinishedException,
|
|
||||||
RejectedException,
|
|
||||||
)
|
|
||||||
from nonebot.internal.params import (
|
from nonebot.internal.params import (
|
||||||
Depends,
|
|
||||||
ArgParam,
|
ArgParam,
|
||||||
BotParam,
|
BotParam,
|
||||||
EventParam,
|
|
||||||
StateParam,
|
|
||||||
DependParam,
|
|
||||||
DefaultParam,
|
DefaultParam,
|
||||||
|
DependParam,
|
||||||
|
Depends,
|
||||||
|
EventParam,
|
||||||
MatcherParam,
|
MatcherParam,
|
||||||
|
StateParam,
|
||||||
)
|
)
|
||||||
|
from nonebot.internal.permission import Permission, User
|
||||||
|
from nonebot.internal.rule import Rule
|
||||||
|
from nonebot.log import logger
|
||||||
|
from nonebot.typing import (
|
||||||
|
T_DependencyCache,
|
||||||
|
T_Handler,
|
||||||
|
T_PermissionUpdater,
|
||||||
|
T_State,
|
||||||
|
T_TypeUpdater,
|
||||||
|
)
|
||||||
|
from nonebot.utils import classproperty, flatten_exception_group
|
||||||
|
|
||||||
from . import matchers
|
from . import matchers
|
||||||
|
|
||||||
@@ -76,7 +80,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
|
||||||
@@ -558,8 +562,8 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
"""
|
"""
|
||||||
bot = current_bot.get()
|
bot = current_bot.get()
|
||||||
event = current_event.get()
|
event = current_event.get()
|
||||||
state = current_matcher.get().state
|
|
||||||
if isinstance(message, MessageTemplate):
|
if isinstance(message, MessageTemplate):
|
||||||
|
state = current_matcher.get().state
|
||||||
_message = message.format(**state)
|
_message = message.format(**state)
|
||||||
else:
|
else:
|
||||||
_message = message
|
_message = message
|
||||||
@@ -595,8 +599,15 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
||||||
请参考对应 adapter 的 bot 对象 api
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
matcher = current_matcher.get()
|
||||||
|
except Exception:
|
||||||
|
matcher = None
|
||||||
|
|
||||||
if prompt is not None:
|
if prompt is not None:
|
||||||
await cls.send(prompt, **kwargs)
|
result = await cls.send(prompt, **kwargs)
|
||||||
|
if matcher is not None:
|
||||||
|
matcher.state[PAUSE_PROMPT_RESULT_KEY] = result
|
||||||
raise PausedException
|
raise PausedException
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -613,8 +624,19 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
||||||
请参考对应 adapter 的 bot 对象 api
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
matcher = current_matcher.get()
|
||||||
|
key = matcher.get_target()
|
||||||
|
except Exception:
|
||||||
|
matcher = None
|
||||||
|
key = None
|
||||||
|
|
||||||
|
key = REJECT_PROMPT_RESULT_KEY.format(key=key) if key is not None else None
|
||||||
|
|
||||||
if prompt is not None:
|
if prompt is not None:
|
||||||
await cls.send(prompt, **kwargs)
|
result = await cls.send(prompt, **kwargs)
|
||||||
|
if key is not None and matcher:
|
||||||
|
matcher.state[key] = result
|
||||||
raise RejectedException
|
raise RejectedException
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -634,9 +656,12 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
请参考对应 adapter 的 bot 对象 api
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
matcher = current_matcher.get()
|
matcher = current_matcher.get()
|
||||||
matcher.set_target(ARG_KEY.format(key=key))
|
arg_key = ARG_KEY.format(key=key)
|
||||||
|
matcher.set_target(arg_key)
|
||||||
|
|
||||||
if prompt is not None:
|
if prompt is not None:
|
||||||
await cls.send(prompt, **kwargs)
|
result = await cls.send(prompt, **kwargs)
|
||||||
|
matcher.state[REJECT_PROMPT_RESULT_KEY.format(key=arg_key)] = result
|
||||||
raise RejectedException
|
raise RejectedException
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -656,9 +681,12 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
请参考对应 adapter 的 bot 对象 api
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
matcher = current_matcher.get()
|
matcher = current_matcher.get()
|
||||||
matcher.set_target(RECEIVE_KEY.format(id=id))
|
receive_key = RECEIVE_KEY.format(id=id)
|
||||||
|
matcher.set_target(receive_key)
|
||||||
|
|
||||||
if prompt is not None:
|
if prompt is not None:
|
||||||
await cls.send(prompt, **kwargs)
|
result = await cls.send(prompt, **kwargs)
|
||||||
|
matcher.state[REJECT_PROMPT_RESULT_KEY.format(key=receive_key)] = result
|
||||||
raise RejectedException
|
raise RejectedException
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -812,28 +840,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 +880,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 +948,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 +968,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,7 +1,7 @@
|
|||||||
import abc
|
import abc
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Mapping, MutableMapping
|
from collections.abc import Mapping, MutableMapping
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .matcher import Matcher
|
from .matcher import Matcher
|
||||||
|
@@ -1,37 +1,47 @@
|
|||||||
import asyncio
|
from contextlib import AsyncExitStack, asynccontextmanager, contextmanager
|
||||||
|
from enum import Enum
|
||||||
import inspect
|
import inspect
|
||||||
from typing_extensions import Self, get_args, override, get_origin
|
|
||||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
|
||||||
Union,
|
|
||||||
Literal,
|
|
||||||
Callable,
|
|
||||||
Optional,
|
|
||||||
Annotated,
|
Annotated,
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Literal,
|
||||||
|
Optional,
|
||||||
|
Union,
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import Self, get_args, get_origin, override
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
||||||
|
|
||||||
from nonebot.dependencies import Param, Dependent
|
|
||||||
from nonebot.dependencies.utils import check_field_type
|
|
||||||
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
|
||||||
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
|
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
|
||||||
|
from nonebot.consts import ARG_KEY, REJECT_PROMPT_RESULT_KEY
|
||||||
|
from nonebot.dependencies import Dependent, Param
|
||||||
|
from nonebot.dependencies.utils import check_field_type
|
||||||
|
from nonebot.exception import SkippedException
|
||||||
|
from nonebot.typing import (
|
||||||
|
_STATE_FLAG,
|
||||||
|
T_DependencyCache,
|
||||||
|
T_Handler,
|
||||||
|
T_State,
|
||||||
|
origin_is_annotated,
|
||||||
|
)
|
||||||
from nonebot.utils import (
|
from nonebot.utils import (
|
||||||
|
generic_check_issubclass,
|
||||||
get_name,
|
get_name,
|
||||||
run_sync,
|
|
||||||
is_gen_callable,
|
|
||||||
run_sync_ctx_manager,
|
|
||||||
is_async_gen_callable,
|
is_async_gen_callable,
|
||||||
is_coroutine_callable,
|
is_coroutine_callable,
|
||||||
generic_check_issubclass,
|
is_gen_callable,
|
||||||
|
run_sync,
|
||||||
|
run_sync_ctx_manager,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from nonebot.adapters import Bot, Event, Message
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.adapters import Bot, Event
|
|
||||||
|
|
||||||
|
|
||||||
class DependsInner:
|
class DependsInner:
|
||||||
@@ -87,6 +97,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):
|
||||||
"""子依赖注入参数。
|
"""子依赖注入参数。
|
||||||
|
|
||||||
@@ -96,7 +178,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
|
||||||
@@ -108,7 +190,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:
|
||||||
@@ -184,21 +266,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"
|
||||||
@@ -206,17 +298,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:
|
||||||
@@ -349,7 +452,9 @@ class StateParam(Param):
|
|||||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||||
) -> Optional[Self]:
|
) -> Optional[Self]:
|
||||||
# param type is T_State
|
# param type is T_State
|
||||||
if param.annotation is T_State:
|
if origin_is_annotated(
|
||||||
|
get_origin(param.annotation)
|
||||||
|
) and _STATE_FLAG in get_args(param.annotation):
|
||||||
return cls()
|
return cls()
|
||||||
# legacy: param is named "state" and has no type annotation
|
# legacy: param is named "state" and has no type annotation
|
||||||
elif param.annotation == param.empty and param.name == "state":
|
elif param.annotation == param.empty and param.name == "state":
|
||||||
@@ -418,10 +523,10 @@ class MatcherParam(Param):
|
|||||||
|
|
||||||
class ArgInner:
|
class ArgInner:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, key: Optional[str], type: Literal["message", "str", "plaintext"]
|
self, key: Optional[str], type: Literal["message", "str", "plaintext", "prompt"]
|
||||||
) -> None:
|
) -> None:
|
||||||
self.key: Optional[str] = key
|
self.key: Optional[str] = key
|
||||||
self.type: Literal["message", "str", "plaintext"] = type
|
self.type: Literal["message", "str", "plaintext", "prompt"] = type
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ArgInner(key={self.key!r}, type={self.type!r})"
|
return f"ArgInner(key={self.key!r}, type={self.type!r})"
|
||||||
@@ -442,6 +547,11 @@ def ArgPlainText(key: Optional[str] = None) -> str:
|
|||||||
return ArgInner(key, "plaintext") # type: ignore
|
return ArgInner(key, "plaintext") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def ArgPromptResult(key: Optional[str] = None) -> Any:
|
||||||
|
"""`arg` prompt 发送结果"""
|
||||||
|
return ArgInner(key, "prompt")
|
||||||
|
|
||||||
|
|
||||||
class ArgParam(Param):
|
class ArgParam(Param):
|
||||||
"""Arg 注入参数
|
"""Arg 注入参数
|
||||||
|
|
||||||
@@ -455,7 +565,7 @@ class ArgParam(Param):
|
|||||||
self,
|
self,
|
||||||
*args,
|
*args,
|
||||||
key: str,
|
key: str,
|
||||||
type: Literal["message", "str", "plaintext"],
|
type: Literal["message", "str", "plaintext", "prompt"],
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -480,15 +590,32 @@ class ArgParam(Param):
|
|||||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||||
self, matcher: "Matcher", **kwargs: Any
|
self, matcher: "Matcher", **kwargs: Any
|
||||||
) -> Any:
|
) -> Any:
|
||||||
message = matcher.get_arg(self.key)
|
|
||||||
if message is None:
|
|
||||||
return message
|
|
||||||
if self.type == "message":
|
if self.type == "message":
|
||||||
return message
|
return self._solve_message(matcher)
|
||||||
elif self.type == "str":
|
elif self.type == "str":
|
||||||
return str(message)
|
return self._solve_str(matcher)
|
||||||
|
elif self.type == "plaintext":
|
||||||
|
return self._solve_plaintext(matcher)
|
||||||
|
elif self.type == "prompt":
|
||||||
|
return self._solve_prompt(matcher)
|
||||||
else:
|
else:
|
||||||
return message.extract_plain_text()
|
raise ValueError(f"Unknown Arg type: {self.type}")
|
||||||
|
|
||||||
|
def _solve_message(self, matcher: "Matcher") -> Optional["Message"]:
|
||||||
|
return matcher.get_arg(self.key)
|
||||||
|
|
||||||
|
def _solve_str(self, matcher: "Matcher") -> Optional[str]:
|
||||||
|
message = matcher.get_arg(self.key)
|
||||||
|
return str(message) if message is not None else None
|
||||||
|
|
||||||
|
def _solve_plaintext(self, matcher: "Matcher") -> Optional[str]:
|
||||||
|
message = matcher.get_arg(self.key)
|
||||||
|
return message.extract_plain_text() if message is not None else None
|
||||||
|
|
||||||
|
def _solve_prompt(self, matcher: "Matcher") -> Optional[Any]:
|
||||||
|
return matcher.state.get(
|
||||||
|
REJECT_PROMPT_RESULT_KEY.format(key=ARG_KEY.format(key=self.key))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExceptionParam(Param):
|
class ExceptionParam(Param):
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
import asyncio
|
|
||||||
from typing_extensions import Self
|
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import Union, ClassVar, NoReturn, Optional
|
from typing import ClassVar, NoReturn, Optional, Union
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.utils import run_coro_with_catch
|
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
from nonebot.typing import T_DependencyCache, T_PermissionChecker
|
from nonebot.typing import T_DependencyCache, T_PermissionChecker
|
||||||
|
from nonebot.utils import run_coro_with_catch
|
||||||
|
|
||||||
from .adapter import Bot, Event
|
from .adapter import Bot, Event
|
||||||
from .params import Param, BotParam, EventParam, DependParam, DefaultParam
|
from .params import BotParam, DefaultParam, DependParam, EventParam, Param
|
||||||
|
|
||||||
|
|
||||||
class Permission:
|
class Permission:
|
||||||
@@ -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.")
|
||||||
@@ -119,7 +124,7 @@ class User:
|
|||||||
perm: 需同时满足的权限
|
perm: 需同时满足的权限
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("users", "perm")
|
__slots__ = ("perm", "users")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, users: tuple[str, ...], perm: Optional[Permission] = None
|
self, users: tuple[str, ...], perm: Optional[Permission] = None
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
import asyncio
|
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import Union, ClassVar, NoReturn, Optional
|
from typing import ClassVar, NoReturn, Optional, Union
|
||||||
|
|
||||||
|
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_DependencyCache, T_RuleChecker, T_State
|
||||||
|
|
||||||
from .adapter import Bot, Event
|
from .adapter import Bot, Event
|
||||||
from .params import Param, BotParam, EventParam, StateParam, DependParam, DefaultParam
|
from .params import BotParam, DefaultParam, DependParam, EventParam, Param, StateParam
|
||||||
|
|
||||||
|
|
||||||
class Rule:
|
class Rule:
|
||||||
@@ -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,13 +8,15 @@ 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 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import loguru
|
import loguru
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.matcher 模块
|
description: nonebot.matcher 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from nonebot.internal.matcher import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
|
||||||
from nonebot.internal.matcher import Matcher as Matcher
|
from nonebot.internal.matcher import Matcher as Matcher
|
||||||
from nonebot.internal.matcher import matchers as matchers
|
|
||||||
from nonebot.internal.matcher import current_bot as current_bot
|
|
||||||
from nonebot.internal.matcher import MatcherSource as MatcherSource
|
|
||||||
from nonebot.internal.matcher import current_event as current_event
|
|
||||||
from nonebot.internal.matcher import MatcherManager as MatcherManager
|
from nonebot.internal.matcher import MatcherManager as MatcherManager
|
||||||
from nonebot.internal.matcher import MatcherProvider as MatcherProvider
|
from nonebot.internal.matcher import MatcherProvider as MatcherProvider
|
||||||
|
from nonebot.internal.matcher import MatcherSource as MatcherSource
|
||||||
|
from nonebot.internal.matcher import current_bot as current_bot
|
||||||
|
from nonebot.internal.matcher import current_event as current_event
|
||||||
from nonebot.internal.matcher import current_handler as current_handler
|
from nonebot.internal.matcher import current_handler as current_handler
|
||||||
from nonebot.internal.matcher import current_matcher as current_matcher
|
from nonebot.internal.matcher import current_matcher as current_matcher
|
||||||
from nonebot.internal.matcher import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
|
from nonebot.internal.matcher import matchers as matchers
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"Matcher": True,
|
"Matcher": True,
|
||||||
|
@@ -3,44 +3,53 @@
|
|||||||
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 contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from datetime import datetime
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, Optional
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
|
|
||||||
from nonebot.log import logger
|
|
||||||
from nonebot.rule import TrieRule
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
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,
|
|
||||||
StopPropagation,
|
|
||||||
IgnoredException,
|
IgnoredException,
|
||||||
|
NoLogException,
|
||||||
SkippedException,
|
SkippedException,
|
||||||
)
|
StopPropagation,
|
||||||
from nonebot.typing import (
|
|
||||||
T_State,
|
|
||||||
T_DependencyCache,
|
|
||||||
T_RunPreProcessor,
|
|
||||||
T_RunPostProcessor,
|
|
||||||
T_EventPreProcessor,
|
|
||||||
T_EventPostProcessor,
|
|
||||||
)
|
)
|
||||||
from nonebot.internal.params import (
|
from nonebot.internal.params import (
|
||||||
ArgParam,
|
ArgParam,
|
||||||
BotParam,
|
BotParam,
|
||||||
EventParam,
|
|
||||||
StateParam,
|
|
||||||
DependParam,
|
|
||||||
DefaultParam,
|
DefaultParam,
|
||||||
MatcherParam,
|
DependParam,
|
||||||
|
EventParam,
|
||||||
ExceptionParam,
|
ExceptionParam,
|
||||||
|
MatcherParam,
|
||||||
|
StateParam,
|
||||||
|
)
|
||||||
|
from nonebot.log import logger
|
||||||
|
from nonebot.matcher import Matcher, matchers
|
||||||
|
from nonebot.rule import TrieRule
|
||||||
|
from nonebot.typing import (
|
||||||
|
T_DependencyCache,
|
||||||
|
T_EventPostProcessor,
|
||||||
|
T_EventPreProcessor,
|
||||||
|
T_RunPostProcessor,
|
||||||
|
T_RunPreProcessor,
|
||||||
|
T_State,
|
||||||
|
)
|
||||||
|
from nonebot.utils import (
|
||||||
|
escape_tag,
|
||||||
|
flatten_exception_group,
|
||||||
|
run_coro_with_catch,
|
||||||
|
run_coro_with_shield,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -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,43 +1,49 @@
|
|||||||
"""本模块定义了依赖注入的各类参数。
|
"""本模块定义了依赖注入的各类参数。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 4
|
sidebar_position: 4
|
||||||
description: nonebot.params 模块
|
description: nonebot.params 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from re import Match
|
from re import Match
|
||||||
from typing import Any, Union, Literal, Callable, Optional, overload
|
from typing import Any, Callable, Literal, Optional, Union, overload
|
||||||
|
|
||||||
from nonebot.typing import T_State
|
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot.internal.params import Arg as Arg
|
|
||||||
from nonebot.internal.params import ArgStr as ArgStr
|
|
||||||
from nonebot.internal.params import Depends as Depends
|
|
||||||
from nonebot.internal.params import ArgParam as ArgParam
|
|
||||||
from nonebot.internal.params import BotParam as BotParam
|
|
||||||
from nonebot.adapters import Event, Message, MessageSegment
|
from nonebot.adapters import Event, Message, MessageSegment
|
||||||
from nonebot.internal.params import EventParam as EventParam
|
|
||||||
from nonebot.internal.params import StateParam as StateParam
|
|
||||||
from nonebot.internal.params import DependParam as DependParam
|
|
||||||
from nonebot.internal.params import ArgPlainText as ArgPlainText
|
|
||||||
from nonebot.internal.params import DefaultParam as DefaultParam
|
|
||||||
from nonebot.internal.params import MatcherParam as MatcherParam
|
|
||||||
from nonebot.internal.params import ExceptionParam as ExceptionParam
|
|
||||||
from nonebot.consts import (
|
from nonebot.consts import (
|
||||||
|
CMD_ARG_KEY,
|
||||||
CMD_KEY,
|
CMD_KEY,
|
||||||
|
CMD_START_KEY,
|
||||||
|
CMD_WHITESPACE_KEY,
|
||||||
|
ENDSWITH_KEY,
|
||||||
|
FULLMATCH_KEY,
|
||||||
|
KEYWORD_KEY,
|
||||||
|
PAUSE_PROMPT_RESULT_KEY,
|
||||||
PREFIX_KEY,
|
PREFIX_KEY,
|
||||||
|
RAW_CMD_KEY,
|
||||||
|
RECEIVE_KEY,
|
||||||
|
REGEX_MATCHED,
|
||||||
|
REJECT_PROMPT_RESULT_KEY,
|
||||||
SHELL_ARGS,
|
SHELL_ARGS,
|
||||||
SHELL_ARGV,
|
SHELL_ARGV,
|
||||||
CMD_ARG_KEY,
|
|
||||||
KEYWORD_KEY,
|
|
||||||
RAW_CMD_KEY,
|
|
||||||
ENDSWITH_KEY,
|
|
||||||
CMD_START_KEY,
|
|
||||||
FULLMATCH_KEY,
|
|
||||||
REGEX_MATCHED,
|
|
||||||
STARTSWITH_KEY,
|
STARTSWITH_KEY,
|
||||||
CMD_WHITESPACE_KEY,
|
|
||||||
)
|
)
|
||||||
|
from nonebot.internal.params import Arg as Arg
|
||||||
|
from nonebot.internal.params import ArgParam as ArgParam
|
||||||
|
from nonebot.internal.params import ArgPlainText as ArgPlainText
|
||||||
|
from nonebot.internal.params import ArgPromptResult as ArgPromptResult
|
||||||
|
from nonebot.internal.params import ArgStr as ArgStr
|
||||||
|
from nonebot.internal.params import BotParam as BotParam
|
||||||
|
from nonebot.internal.params import DefaultParam as DefaultParam
|
||||||
|
from nonebot.internal.params import DependParam as DependParam
|
||||||
|
from nonebot.internal.params import Depends as Depends
|
||||||
|
from nonebot.internal.params import EventParam as EventParam
|
||||||
|
from nonebot.internal.params import ExceptionParam as ExceptionParam
|
||||||
|
from nonebot.internal.params import MatcherParam as MatcherParam
|
||||||
|
from nonebot.internal.params import StateParam as StateParam
|
||||||
|
from nonebot.matcher import Matcher
|
||||||
|
from nonebot.typing import T_State
|
||||||
|
|
||||||
|
|
||||||
async def _event_type(event: Event) -> str:
|
async def _event_type(event: Event) -> str:
|
||||||
@@ -149,7 +155,7 @@ def RegexMatched() -> Match[str]:
|
|||||||
|
|
||||||
|
|
||||||
def _regex_str(
|
def _regex_str(
|
||||||
groups: tuple[Union[str, int], ...]
|
groups: tuple[Union[str, int], ...],
|
||||||
) -> Callable[[T_State], Union[str, tuple[Union[str, Any], ...], Any]]:
|
) -> Callable[[T_State], Union[str, tuple[Union[str, Any], ...], Any]]:
|
||||||
def _regex_str_dependency(
|
def _regex_str_dependency(
|
||||||
state: T_State,
|
state: T_State,
|
||||||
@@ -160,16 +166,16 @@ def _regex_str(
|
|||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def RegexStr(__group: Literal[0] = 0) -> str: ...
|
def RegexStr(group: Literal[0] = 0, /) -> str: ...
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def RegexStr(__group: Union[str, int]) -> Union[str, Any]: ...
|
def RegexStr(group: Union[str, int], /) -> Union[str, Any]: ...
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def RegexStr(
|
def RegexStr(
|
||||||
__group1: Union[str, int], __group2: Union[str, int], *groups: Union[str, int]
|
group1: Union[str, int], group2: Union[str, int], /, *groups: Union[str, int]
|
||||||
) -> tuple[Union[str, Any], ...]: ...
|
) -> tuple[Union[str, Any], ...]: ...
|
||||||
|
|
||||||
|
|
||||||
@@ -250,6 +256,26 @@ def LastReceived(default: Any = None) -> Any:
|
|||||||
return Depends(_last_received, use_cache=False)
|
return Depends(_last_received, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
|
def ReceivePromptResult(id: Optional[str] = None) -> Any:
|
||||||
|
"""`receive` prompt 发送结果"""
|
||||||
|
|
||||||
|
def _receive_prompt_result(matcher: "Matcher") -> Any:
|
||||||
|
return matcher.state.get(
|
||||||
|
REJECT_PROMPT_RESULT_KEY.format(key=RECEIVE_KEY.format(id=id))
|
||||||
|
)
|
||||||
|
|
||||||
|
return Depends(_receive_prompt_result, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
|
def PausePromptResult() -> Any:
|
||||||
|
"""`pause` prompt 发送结果"""
|
||||||
|
|
||||||
|
def _pause_prompt_result(matcher: "Matcher") -> Any:
|
||||||
|
return matcher.state.get(PAUSE_PROMPT_RESULT_KEY)
|
||||||
|
|
||||||
|
return Depends(_pause_prompt_result, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"Arg": True,
|
"Arg": True,
|
||||||
"ArgStr": True,
|
"ArgStr": True,
|
||||||
@@ -263,4 +289,5 @@ __autodoc__ = {
|
|||||||
"DefaultParam": True,
|
"DefaultParam": True,
|
||||||
"MatcherParam": True,
|
"MatcherParam": True,
|
||||||
"ExceptionParam": True,
|
"ExceptionParam": True,
|
||||||
|
"ArgPromptResult": True,
|
||||||
}
|
}
|
||||||
|
@@ -5,15 +5,17 @@
|
|||||||
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 6
|
sidebar_position: 6
|
||||||
description: nonebot.permission 模块
|
description: nonebot.permission 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from nonebot.params import EventType
|
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot.adapters import Bot, Event
|
||||||
from nonebot.internal.permission import USER as USER
|
from nonebot.internal.permission import USER as USER
|
||||||
from nonebot.internal.permission import User as User
|
|
||||||
from nonebot.internal.permission import Permission as Permission
|
from nonebot.internal.permission import Permission as Permission
|
||||||
|
from nonebot.internal.permission import User as User
|
||||||
|
from nonebot.params import EventType
|
||||||
|
|
||||||
|
|
||||||
class Message:
|
class Message:
|
||||||
|
@@ -32,14 +32,16 @@
|
|||||||
- `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 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from contextvars import ContextVar
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from contextvars import ContextVar
|
from typing import Optional, TypeVar
|
||||||
from typing import TypeVar, Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -173,30 +175,30 @@ def get_plugin_config(config: type[C]) -> C:
|
|||||||
return type_validate_python(config, model_dump(get_driver().config))
|
return type_validate_python(config, model_dump(get_driver().config))
|
||||||
|
|
||||||
|
|
||||||
from .on import on as on
|
from .load import inherit_supported_adapters as inherit_supported_adapters
|
||||||
from .manager import PluginManager
|
|
||||||
from .on import on_type as on_type
|
|
||||||
from .model import Plugin as Plugin
|
|
||||||
from .load import require as require
|
|
||||||
from .on import on_regex as on_regex
|
|
||||||
from .on import on_notice as on_notice
|
|
||||||
from .on import on_command as on_command
|
|
||||||
from .on import on_keyword as on_keyword
|
|
||||||
from .on import on_message as on_message
|
|
||||||
from .on import on_request as on_request
|
|
||||||
from .on import on_endswith as on_endswith
|
|
||||||
from .load import load_plugin as load_plugin
|
|
||||||
from .on import CommandGroup as CommandGroup
|
|
||||||
from .on import MatcherGroup as MatcherGroup
|
|
||||||
from .on import on_fullmatch as on_fullmatch
|
|
||||||
from .on import on_metaevent as on_metaevent
|
|
||||||
from .load import load_plugins as load_plugins
|
|
||||||
from .on import on_startswith as on_startswith
|
|
||||||
from .load import load_from_json as load_from_json
|
|
||||||
from .load import load_from_toml as load_from_toml
|
|
||||||
from .model import PluginMetadata as PluginMetadata
|
|
||||||
from .on import on_shell_command as on_shell_command
|
|
||||||
from .load import load_all_plugins as load_all_plugins
|
from .load import load_all_plugins as load_all_plugins
|
||||||
from .load import load_builtin_plugin as load_builtin_plugin
|
from .load import load_builtin_plugin as load_builtin_plugin
|
||||||
from .load import load_builtin_plugins as load_builtin_plugins
|
from .load import load_builtin_plugins as load_builtin_plugins
|
||||||
from .load import inherit_supported_adapters as inherit_supported_adapters
|
from .load import load_from_json as load_from_json
|
||||||
|
from .load import load_from_toml as load_from_toml
|
||||||
|
from .load import load_plugin as load_plugin
|
||||||
|
from .load import load_plugins as load_plugins
|
||||||
|
from .load import require as require
|
||||||
|
from .manager import PluginManager
|
||||||
|
from .model import Plugin as Plugin
|
||||||
|
from .model import PluginMetadata as PluginMetadata
|
||||||
|
from .on import CommandGroup as CommandGroup
|
||||||
|
from .on import MatcherGroup as MatcherGroup
|
||||||
|
from .on import on as on
|
||||||
|
from .on import on_command as on_command
|
||||||
|
from .on import on_endswith as on_endswith
|
||||||
|
from .on import on_fullmatch as on_fullmatch
|
||||||
|
from .on import on_keyword as on_keyword
|
||||||
|
from .on import on_message as on_message
|
||||||
|
from .on import on_metaevent as on_metaevent
|
||||||
|
from .on import on_notice as on_notice
|
||||||
|
from .on import on_regex as on_regex
|
||||||
|
from .on import on_request as on_request
|
||||||
|
from .on import on_shell_command as on_shell_command
|
||||||
|
from .on import on_startswith as on_startswith
|
||||||
|
from .on import on_type as on_type
|
||||||
|
@@ -1,26 +1,28 @@
|
|||||||
"""本模块定义插件加载接口。
|
"""本模块定义插件加载接口。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.plugin.load 模块
|
description: nonebot.plugin.load 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from collections.abc import Iterable
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Union, Optional
|
from typing import Optional, Union
|
||||||
from collections.abc import Iterable
|
|
||||||
|
|
||||||
from nonebot.utils import path_to_module_name
|
from nonebot.utils import path_to_module_name
|
||||||
|
|
||||||
from .model import Plugin
|
from . import _managers, _module_name_to_plugin_id, get_plugin
|
||||||
from .manager import PluginManager
|
from .manager import PluginManager
|
||||||
from . import _managers, get_plugin, _module_name_to_plugin_id
|
from .model import Plugin
|
||||||
|
|
||||||
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,32 +3,34 @@
|
|||||||
参考: [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 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import pkgutil
|
|
||||||
import importlib
|
|
||||||
from pathlib import Path
|
|
||||||
from itertools import chain
|
|
||||||
from typing import Optional
|
|
||||||
from types import ModuleType
|
|
||||||
from importlib.abc import MetaPathFinder
|
|
||||||
from collections.abc import Iterable, Sequence
|
from collections.abc import Iterable, Sequence
|
||||||
|
import importlib
|
||||||
|
from importlib.abc import MetaPathFinder
|
||||||
from importlib.machinery import PathFinder, SourceFileLoader
|
from importlib.machinery import PathFinder, SourceFileLoader
|
||||||
|
from itertools import chain
|
||||||
|
from pathlib import Path
|
||||||
|
import pkgutil
|
||||||
|
import sys
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.utils import escape_tag, path_to_module_name
|
from nonebot.utils import escape_tag, path_to_module_name
|
||||||
|
|
||||||
from .model import Plugin, PluginMetadata
|
|
||||||
from . import (
|
from . import (
|
||||||
|
_current_plugin,
|
||||||
_managers,
|
_managers,
|
||||||
|
_module_name_to_plugin_id,
|
||||||
_new_plugin,
|
_new_plugin,
|
||||||
_revert_plugin,
|
_revert_plugin,
|
||||||
_current_plugin,
|
|
||||||
_module_name_to_plugin_id,
|
|
||||||
)
|
)
|
||||||
|
from .model import Plugin, PluginMetadata
|
||||||
|
|
||||||
|
|
||||||
class PluginManager:
|
class PluginManager:
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
"""本模块定义插件相关信息。
|
"""本模块定义插件相关信息。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.plugin.model 模块
|
description: nonebot.plugin.model 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from dataclasses import field, dataclass
|
from typing import TYPE_CHECKING, Any, Optional, Type # noqa: UP035
|
||||||
from typing import TYPE_CHECKING, Any, Type, Optional # noqa: UP035
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
@@ -1,38 +1,40 @@
|
|||||||
"""本模块定义事件响应器便携定义函数。
|
"""本模块定义事件响应器便携定义函数。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.plugin.on 模块
|
description: nonebot.plugin.on 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
|
||||||
import inspect
|
|
||||||
import warnings
|
|
||||||
from types import ModuleType
|
|
||||||
from typing import Any, Union, Optional
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
import inspect
|
||||||
|
import re
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Any, Optional, Union
|
||||||
|
import warnings
|
||||||
|
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
from nonebot.permission import Permission
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.matcher import Matcher, MatcherSource
|
from nonebot.matcher import Matcher, MatcherSource
|
||||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
from nonebot.permission import Permission
|
||||||
from nonebot.rule import (
|
from nonebot.rule import (
|
||||||
Rule,
|
|
||||||
ArgumentParser,
|
ArgumentParser,
|
||||||
regex,
|
Rule,
|
||||||
command,
|
command,
|
||||||
is_type,
|
|
||||||
keyword,
|
|
||||||
endswith,
|
endswith,
|
||||||
fullmatch,
|
fullmatch,
|
||||||
startswith,
|
is_type,
|
||||||
|
keyword,
|
||||||
|
regex,
|
||||||
shell_command,
|
shell_command,
|
||||||
|
startswith,
|
||||||
)
|
)
|
||||||
|
from nonebot.typing import T_Handler, T_PermissionChecker, T_RuleChecker, T_State
|
||||||
|
|
||||||
from .model import Plugin
|
|
||||||
from .manager import _current_plugin
|
|
||||||
from . import get_plugin_by_module_name
|
from . import get_plugin_by_module_name
|
||||||
|
from .manager import _current_plugin
|
||||||
|
from .model import Plugin
|
||||||
|
|
||||||
|
|
||||||
def store_matcher(matcher: type[Matcher]) -> None:
|
def store_matcher(matcher: type[Matcher]) -> None:
|
||||||
@@ -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,
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import re
|
|
||||||
from typing import Any
|
|
||||||
from types import ModuleType
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
import re
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
from nonebot.permission import Permission
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.rule import Rule, ArgumentParser
|
|
||||||
from nonebot.matcher import Matcher, MatcherSource
|
from nonebot.matcher import Matcher, MatcherSource
|
||||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
from nonebot.permission import Permission
|
||||||
|
from nonebot.rule import ArgumentParser, Rule
|
||||||
|
from nonebot.typing import T_Handler, T_PermissionChecker, T_RuleChecker, T_State
|
||||||
|
|
||||||
from .model import Plugin
|
from .model import Plugin
|
||||||
|
|
||||||
@@ -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 = ...,
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
from nonebot import on_command
|
from nonebot import on_command
|
||||||
from nonebot.rule import to_me
|
|
||||||
from nonebot.adapters import Message
|
from nonebot.adapters import Message
|
||||||
from nonebot.params import CommandArg
|
from nonebot.params import CommandArg
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
|
from nonebot.rule import to_me
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="echo",
|
name="echo",
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
|
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
|
from nonebot.message import IgnoredException, event_preprocessor
|
||||||
from nonebot.params import Depends
|
from nonebot.params import Depends
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot.message import IgnoredException, event_preprocessor
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="唯一会话",
|
name="唯一会话",
|
||||||
|
@@ -5,28 +5,29 @@
|
|||||||
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.rule 模块
|
description: nonebot.rule 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from argparse import Action, ArgumentError
|
||||||
|
from argparse import ArgumentParser as ArgParser
|
||||||
|
from argparse import Namespace as Namespace
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from contextvars import ContextVar
|
||||||
|
from gettext import gettext
|
||||||
|
from itertools import chain, product
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
from argparse import Action
|
|
||||||
from gettext import gettext
|
|
||||||
from argparse import ArgumentError
|
|
||||||
from contextvars import ContextVar
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from itertools import chain, product
|
|
||||||
from argparse import Namespace as Namespace
|
|
||||||
from argparse import ArgumentParser as ArgParser
|
|
||||||
from typing import (
|
from typing import (
|
||||||
IO,
|
IO,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Union,
|
NamedTuple,
|
||||||
TypeVar,
|
|
||||||
Optional,
|
Optional,
|
||||||
TypedDict,
|
TypedDict,
|
||||||
NamedTuple,
|
TypeVar,
|
||||||
|
Union,
|
||||||
cast,
|
cast,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
@@ -34,27 +35,27 @@ from typing import (
|
|||||||
from pygtrie import CharTrie
|
from pygtrie import CharTrie
|
||||||
|
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
from nonebot.log import logger
|
|
||||||
from nonebot.typing import T_State
|
|
||||||
from nonebot.exception import ParserExit
|
|
||||||
from nonebot.internal.rule import Rule as Rule
|
|
||||||
from nonebot.adapters import Bot, Event, Message, MessageSegment
|
from nonebot.adapters import Bot, Event, Message, MessageSegment
|
||||||
from nonebot.params import Command, EventToMe, CommandArg, CommandWhitespace
|
|
||||||
from nonebot.consts import (
|
from nonebot.consts import (
|
||||||
|
CMD_ARG_KEY,
|
||||||
CMD_KEY,
|
CMD_KEY,
|
||||||
|
CMD_START_KEY,
|
||||||
|
CMD_WHITESPACE_KEY,
|
||||||
|
ENDSWITH_KEY,
|
||||||
|
FULLMATCH_KEY,
|
||||||
|
KEYWORD_KEY,
|
||||||
PREFIX_KEY,
|
PREFIX_KEY,
|
||||||
|
RAW_CMD_KEY,
|
||||||
|
REGEX_MATCHED,
|
||||||
SHELL_ARGS,
|
SHELL_ARGS,
|
||||||
SHELL_ARGV,
|
SHELL_ARGV,
|
||||||
CMD_ARG_KEY,
|
|
||||||
KEYWORD_KEY,
|
|
||||||
RAW_CMD_KEY,
|
|
||||||
ENDSWITH_KEY,
|
|
||||||
CMD_START_KEY,
|
|
||||||
FULLMATCH_KEY,
|
|
||||||
REGEX_MATCHED,
|
|
||||||
STARTSWITH_KEY,
|
STARTSWITH_KEY,
|
||||||
CMD_WHITESPACE_KEY,
|
|
||||||
)
|
)
|
||||||
|
from nonebot.exception import ParserExit
|
||||||
|
from nonebot.internal.rule import Rule as Rule
|
||||||
|
from nonebot.log import logger
|
||||||
|
from nonebot.params import Command, CommandArg, CommandWhitespace, EventToMe
|
||||||
|
from nonebot.typing import T_State
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
@@ -144,7 +145,7 @@ class StartswithRule:
|
|||||||
ignorecase: 是否忽略大小写
|
ignorecase: 是否忽略大小写
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("msg", "ignorecase")
|
__slots__ = ("ignorecase", "msg")
|
||||||
|
|
||||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
@@ -199,7 +200,7 @@ class EndswithRule:
|
|||||||
ignorecase: 是否忽略大小写
|
ignorecase: 是否忽略大小写
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("msg", "ignorecase")
|
__slots__ = ("ignorecase", "msg")
|
||||||
|
|
||||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
@@ -254,7 +255,7 @@ class FullmatchRule:
|
|||||||
ignorecase: 是否忽略大小写
|
ignorecase: 是否忽略大小写
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("msg", "ignorecase")
|
__slots__ = ("ignorecase", "msg")
|
||||||
|
|
||||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||||
self.msg = tuple(map(str.casefold, msg) if ignorecase else msg)
|
self.msg = tuple(map(str.casefold, msg) if ignorecase else msg)
|
||||||
@@ -653,7 +654,7 @@ class RegexRule:
|
|||||||
flags: 正则表达式标记
|
flags: 正则表达式标记
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("regex", "flags")
|
__slots__ = ("flags", "regex")
|
||||||
|
|
||||||
def __init__(self, regex: str, flags: int = 0):
|
def __init__(self, regex: str, flags: int = 0):
|
||||||
self.regex = regex
|
self.regex = regex
|
||||||
|
@@ -6,23 +6,23 @@
|
|||||||
[`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 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import warnings
|
|
||||||
import contextlib
|
|
||||||
import typing as t
|
import typing as t
|
||||||
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
|
import typing_extensions as t_ext
|
||||||
|
from typing_extensions import ParamSpec, TypeAlias, get_args, get_origin, override
|
||||||
|
import warnings
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from asyncio import Task
|
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
|
from nonebot.internal.params import DependencyCache
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
@@ -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]}
|
||||||
@@ -104,11 +102,23 @@ def is_none_type(type_: type[t.Any]) -> bool:
|
|||||||
def evaluate_forwardref(
|
def evaluate_forwardref(
|
||||||
ref: t.ForwardRef, globalns: dict[str, t.Any], localns: dict[str, t.Any]
|
ref: t.ForwardRef, globalns: dict[str, t.Any], localns: dict[str, t.Any]
|
||||||
) -> t.Any:
|
) -> t.Any:
|
||||||
return ref._evaluate(globalns, localns, frozenset())
|
# Python 3.13/3.12.4+ made `recursive_guard` a kwarg,
|
||||||
|
# so name it explicitly to avoid:
|
||||||
|
# TypeError: ForwardRef._evaluate()
|
||||||
|
# missing 1 required keyword-only argument: 'recursive_guard'
|
||||||
|
return ref._evaluate(globalns, localns, recursive_guard=frozenset())
|
||||||
|
|
||||||
|
|
||||||
# state
|
# state
|
||||||
T_State: TypeAlias = dict[t.Any, t.Any]
|
# use annotated flag to avoid ForwardRef recreate generic type (py >= 3.11)
|
||||||
|
class StateFlag:
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "StateFlag()"
|
||||||
|
|
||||||
|
|
||||||
|
_STATE_FLAG = StateFlag()
|
||||||
|
|
||||||
|
T_State: TypeAlias = t.Annotated[dict[t.Any, t.Any], _STATE_FLAG]
|
||||||
"""事件处理状态 State 类型"""
|
"""事件处理状态 State 类型"""
|
||||||
|
|
||||||
_DependentCallable: TypeAlias = t.Union[
|
_DependentCallable: TypeAlias = t.Union[
|
||||||
@@ -247,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,35 +1,38 @@
|
|||||||
"""本模块包含了 NoneBot 的一些工具函数
|
"""本模块包含了 NoneBot 的一些工具函数
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
sidebar_position: 8
|
sidebar_position: 8
|
||||||
description: nonebot.utils 模块
|
description: nonebot.utils 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import asyncio
|
|
||||||
import inspect
|
|
||||||
import importlib
|
|
||||||
import contextlib
|
|
||||||
import dataclasses
|
|
||||||
from pathlib import Path
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from contextvars import copy_context
|
from collections.abc import AsyncGenerator, Coroutine, Generator, Mapping, Sequence
|
||||||
from functools import wraps, partial
|
import contextlib
|
||||||
from contextlib import AbstractContextManager, asynccontextmanager
|
from contextlib import AbstractContextManager, asynccontextmanager
|
||||||
from typing_extensions import ParamSpec, get_args, override, get_origin
|
import dataclasses
|
||||||
from collections.abc import Mapping, Sequence, Coroutine, AsyncGenerator
|
from functools import partial, wraps
|
||||||
from typing import Any, Union, Generic, TypeVar, Callable, Optional, overload
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
from typing import Any, Callable, Generic, Optional, TypeVar, Union, overload
|
||||||
|
from typing_extensions import ParamSpec, get_args, get_origin, override
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
import anyio.to_thread
|
||||||
|
from exceptiongroup import BaseExceptionGroup, catch
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.typing import (
|
from nonebot.typing import (
|
||||||
is_none_type,
|
|
||||||
type_has_args,
|
|
||||||
origin_is_union,
|
|
||||||
origin_is_literal,
|
|
||||||
all_literal_values,
|
all_literal_values,
|
||||||
|
is_none_type,
|
||||||
|
origin_is_literal,
|
||||||
|
origin_is_union,
|
||||||
|
type_has_args,
|
||||||
)
|
)
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
@@ -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:
|
||||||
|
2907
poetry.lock
generated
2907
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.0"
|
version = "2.4.1"
|
||||||
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,33 +22,33 @@ 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"
|
||||||
pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1"
|
|
||||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
tomli = { version = "^2.0.1", python = "<3.11" }
|
||||||
|
pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1,!=2.10.0,!=2.10.1"
|
||||||
|
|
||||||
websockets = { version = ">=10.0", optional = true }
|
websockets = { version = ">=10.0", optional = true }
|
||||||
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
||||||
fastapi = { version = ">=0.93.0,<1.0.0", optional = true }
|
fastapi = { version = ">=0.93.0,<1.0.0", optional = true }
|
||||||
aiohttp = { version = "^3.9.0b0", extras = ["speedups"], optional = true }
|
aiohttp = { version = "^3.11.0", extras = ["speedups"], optional = true }
|
||||||
httpx = { version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true }
|
httpx = { version = ">=0.26.0,<1.0.0", extras = ["http2"], optional = true }
|
||||||
uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
||||||
"standard",
|
"standard",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "^0.4.0"
|
ruff = "^0.8.0"
|
||||||
isort = "^5.10.1"
|
|
||||||
black = "^24.0.0"
|
|
||||||
nonemoji = "^0.1.2"
|
nonemoji = "^0.1.2"
|
||||||
pre-commit = "^3.0.0"
|
pre-commit = "^4.0.0"
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
nonebot-test = { path = "./envs/test/", develop = false }
|
nonebot-test = { path = "./envs/test/", develop = false }
|
||||||
@@ -65,35 +65,22 @@ 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"]
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 88
|
|
||||||
target-version = ["py39", "py310", "py311", "py312"]
|
|
||||||
include = '\.pyi?$'
|
|
||||||
extend-exclude = '''
|
|
||||||
'''
|
|
||||||
|
|
||||||
[tool.isort]
|
|
||||||
profile = "black"
|
|
||||||
line_length = 88
|
|
||||||
length_sort = true
|
|
||||||
skip_gitignore = true
|
|
||||||
force_sort_within_sections = true
|
|
||||||
src_paths = ["nonebot", "tests"]
|
|
||||||
extra_standard_library = ["typing_extensions"]
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = "py39"
|
target-version = "py39"
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
line-ending = "lf"
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = [
|
select = [
|
||||||
"F", # Pyflakes
|
"F", # Pyflakes
|
||||||
"W", # pycodestyle warnings
|
"W", # pycodestyle warnings
|
||||||
"E", # pycodestyle errors
|
"E", # pycodestyle errors
|
||||||
|
"I", # isort
|
||||||
"UP", # pyupgrade
|
"UP", # pyupgrade
|
||||||
"ASYNC", # flake8-async
|
"ASYNC", # flake8-async
|
||||||
"C4", # flake8-comprehensions
|
"C4", # flake8-comprehensions
|
||||||
@@ -102,6 +89,7 @@ select = [
|
|||||||
"PYI", # flake8-pyi
|
"PYI", # flake8-pyi
|
||||||
"PT", # flake8-pytest-style
|
"PT", # flake8-pytest-style
|
||||||
"Q", # flake8-quotes
|
"Q", # flake8-quotes
|
||||||
|
"TID", # flake8-tidy-imports
|
||||||
"RUF", # Ruff-specific rules
|
"RUF", # Ruff-specific rules
|
||||||
]
|
]
|
||||||
ignore = [
|
ignore = [
|
||||||
@@ -112,10 +100,19 @@ ignore = [
|
|||||||
"RUF003", # ambiguous-unicode-character-comment
|
"RUF003", # ambiguous-unicode-character-comment
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
force-sort-within-sections = true
|
||||||
|
known-first-party = ["nonebot", "tests/*"]
|
||||||
|
extra-standard-library = ["typing_extensions"]
|
||||||
|
|
||||||
[tool.ruff.lint.flake8-pytest-style]
|
[tool.ruff.lint.flake8-pytest-style]
|
||||||
fixture-parentheses = false
|
fixture-parentheses = false
|
||||||
mark-parentheses = false
|
mark-parentheses = false
|
||||||
|
|
||||||
|
[tool.ruff.lint.pyupgrade]
|
||||||
|
keep-runtime-typing = true
|
||||||
|
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
pythonVersion = "3.9"
|
pythonVersion = "3.9"
|
||||||
pythonPlatform = "All"
|
pythonPlatform = "All"
|
||||||
|
@@ -4,4 +4,4 @@
|
|||||||
cd "$(dirname "$0")/../tests"
|
cd "$(dirname "$0")/../tests"
|
||||||
|
|
||||||
# Run the tests
|
# Run the tests
|
||||||
pytest -n auto --cov-append --cov-report xml $@
|
pytest -n auto --cov-append --cov-report xml --junitxml=./junit.xml $@
|
||||||
|
@@ -1,18 +1,20 @@
|
|||||||
import os
|
|
||||||
import threading
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
|
from functools import wraps
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import threading
|
||||||
|
from typing import TYPE_CHECKING, Callable, TypeVar
|
||||||
|
from typing_extensions import ParamSpec
|
||||||
|
|
||||||
import pytest
|
|
||||||
from nonebug import NONEBOT_INIT_KWARGS
|
from nonebug import NONEBOT_INIT_KWARGS
|
||||||
|
import pytest
|
||||||
from werkzeug.serving import BaseWSGIServer, make_server
|
from werkzeug.serving import BaseWSGIServer, make_server
|
||||||
|
|
||||||
import nonebot
|
|
||||||
from nonebot.config import Env
|
|
||||||
from fake_server import request_handler
|
from fake_server import request_handler
|
||||||
from nonebot.drivers import URL, Driver
|
import nonebot
|
||||||
from nonebot import _resolve_combine_expr
|
from nonebot import _resolve_combine_expr
|
||||||
|
from nonebot.config import Env
|
||||||
|
from nonebot.drivers import URL, Driver
|
||||||
|
|
||||||
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
|
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
|
||||||
os.environ["CONFIG_OVERRIDE"] = "new"
|
os.environ["CONFIG_OVERRIDE"] = "new"
|
||||||
@@ -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,15 +1,20 @@
|
|||||||
import json
|
|
||||||
import base64
|
import base64
|
||||||
|
import json
|
||||||
import socket
|
import socket
|
||||||
from typing import Union, TypeVar
|
from typing import TypeVar, Union
|
||||||
|
|
||||||
from wsproto.events import Ping
|
|
||||||
from werkzeug import Request, Response
|
from werkzeug import Request, Response
|
||||||
from werkzeug.datastructures import MultiDict
|
from werkzeug.datastructures import MultiDict
|
||||||
from wsproto.frame_protocol import CloseReason
|
from wsproto import ConnectionType, WSConnection
|
||||||
|
from wsproto.events import (
|
||||||
|
AcceptConnection,
|
||||||
|
BytesMessage,
|
||||||
|
CloseConnection,
|
||||||
|
Ping,
|
||||||
|
TextMessage,
|
||||||
|
)
|
||||||
from wsproto.events import Request as WSRequest
|
from wsproto.events import Request as WSRequest
|
||||||
from wsproto import WSConnection, ConnectionType
|
from wsproto.frame_protocol import CloseReason
|
||||||
from wsproto.events import TextMessage, BytesMessage, CloseConnection, AcceptConnection
|
|
||||||
|
|
||||||
K = TypeVar("K")
|
K = TypeVar("K")
|
||||||
V = TypeVar("V")
|
V = TypeVar("V")
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from nonebot import on_message
|
from nonebot import on_message
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot.adapters import Event, Message
|
from nonebot.adapters import Event, Message
|
||||||
from nonebot.params import ArgStr, Received, EventMessage, LastReceived
|
from nonebot.matcher import Matcher
|
||||||
|
from nonebot.params import ArgStr, EventMessage, LastReceived, Received
|
||||||
|
|
||||||
test_handle = on_message()
|
test_handle = on_message()
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated, Any
|
||||||
|
|
||||||
from nonebot.adapters import Message
|
from nonebot.adapters import Message
|
||||||
from nonebot.params import Arg, ArgStr, ArgPlainText
|
from nonebot.params import Arg, ArgPlainText, ArgPromptResult, ArgStr
|
||||||
|
|
||||||
|
|
||||||
async def arg(key: Message = Arg()) -> Message:
|
async def arg(key: Message = Arg()) -> Message:
|
||||||
@@ -28,12 +28,16 @@ async def annotated_arg_plain_text(key: Annotated[str, ArgPlainText()]) -> str:
|
|||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
async def annotated_arg_prompt_result(key: Annotated[Any, ArgPromptResult()]) -> Any:
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
# test dependency priority
|
# test dependency priority
|
||||||
async def annotated_prior_arg(key: Annotated[str, ArgStr("foo")] = ArgPlainText()):
|
async def annotated_prior_arg(key: Annotated[str, ArgStr("foo")] = ArgPlainText()):
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
async def annotated_multi_arg(
|
async def annotated_multi_arg(
|
||||||
key: Annotated[Annotated[str, ArgStr("foo")], ArgPlainText()]
|
key: Annotated[Annotated[str, ArgStr("foo")], ArgPlainText()],
|
||||||
):
|
):
|
||||||
return key
|
return key
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from typing import Union, TypeVar
|
from typing import TypeVar, Union
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
|
|
||||||
@@ -7,6 +7,10 @@ async def get_bot(b: Bot) -> Bot:
|
|||||||
return b
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
async def postpone_bot(b: "Bot") -> Bot:
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
async def legacy_bot(bot):
|
async def legacy_bot(bot):
|
||||||
return bot
|
return bot
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
from typing import Annotated
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import anyio
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from nonebot import on_message
|
from nonebot import on_message
|
||||||
@@ -73,13 +74,13 @@ async def annotated_class_depend(c: Annotated[ClassDependency, Depends()]):
|
|||||||
|
|
||||||
# test dependency priority
|
# test dependency priority
|
||||||
async def annotated_prior_depend(
|
async def annotated_prior_depend(
|
||||||
x: Annotated[int, Depends(lambda: 2)] = Depends(dependency)
|
x: Annotated[int, Depends(lambda: 2)] = Depends(dependency),
|
||||||
):
|
):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
async def annotated_multi_depend(
|
async def annotated_multi_depend(
|
||||||
x: Annotated[Annotated[int, Depends(lambda: 2)], Depends(dependency)]
|
x: Annotated[Annotated[int, Depends(lambda: 2)], Depends(dependency)],
|
||||||
):
|
):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
@@ -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
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
from typing import Union, TypeVar
|
from typing import TypeVar, Union
|
||||||
|
|
||||||
from nonebot.adapters import Event, Message
|
from nonebot.adapters import Event, Message
|
||||||
from nonebot.params import EventToMe, EventType, EventMessage, EventPlainText
|
from nonebot.params import EventMessage, EventPlainText, EventToMe, EventType
|
||||||
|
|
||||||
|
|
||||||
async def event(e: Event) -> Event:
|
async def event(e: Event) -> Event:
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
async def postpone_event(e: "Event") -> Event:
|
||||||
|
return e
|
||||||
|
|
||||||
|
|
||||||
async def legacy_event(event):
|
async def legacy_event(event):
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
@@ -1,14 +1,23 @@
|
|||||||
from typing import Union, TypeVar
|
from typing import Any, TypeVar, Union
|
||||||
|
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.params import Received, LastReceived
|
from nonebot.params import (
|
||||||
|
LastReceived,
|
||||||
|
PausePromptResult,
|
||||||
|
Received,
|
||||||
|
ReceivePromptResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def matcher(m: Matcher) -> Matcher:
|
async def matcher(m: Matcher) -> Matcher:
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
async def postpone_matcher(m: "Matcher") -> Matcher:
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
async def legacy_matcher(matcher):
|
async def legacy_matcher(matcher):
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -27,7 +36,7 @@ class BarMatcher(Matcher): ...
|
|||||||
|
|
||||||
|
|
||||||
async def union_matcher(
|
async def union_matcher(
|
||||||
m: Union[FooMatcher, BarMatcher]
|
m: Union[FooMatcher, BarMatcher],
|
||||||
) -> Union[FooMatcher, BarMatcher]:
|
) -> Union[FooMatcher, BarMatcher]:
|
||||||
return m
|
return m
|
||||||
|
|
||||||
@@ -55,3 +64,11 @@ async def receive(e: Event = Received("test")) -> Event:
|
|||||||
|
|
||||||
async def last_receive(e: Event = LastReceived()) -> Event:
|
async def last_receive(e: Event = LastReceived()) -> Event:
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
async def receive_prompt_result(result: Any = ReceivePromptResult("test")) -> Any:
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def pause_prompt_result(result: Any = PausePromptResult()) -> Any:
|
||||||
|
return result
|
||||||
|
@@ -1,30 +1,34 @@
|
|||||||
from re import Match
|
from re import Match
|
||||||
|
|
||||||
from nonebot.typing import T_State
|
|
||||||
from nonebot.adapters import Message
|
from nonebot.adapters import Message
|
||||||
from nonebot.params import (
|
from nonebot.params import (
|
||||||
Command,
|
Command,
|
||||||
Keyword,
|
|
||||||
Endswith,
|
|
||||||
RegexStr,
|
|
||||||
Fullmatch,
|
|
||||||
RegexDict,
|
|
||||||
CommandArg,
|
CommandArg,
|
||||||
RawCommand,
|
|
||||||
RegexGroup,
|
|
||||||
Startswith,
|
|
||||||
CommandStart,
|
CommandStart,
|
||||||
|
CommandWhitespace,
|
||||||
|
Endswith,
|
||||||
|
Fullmatch,
|
||||||
|
Keyword,
|
||||||
|
RawCommand,
|
||||||
|
RegexDict,
|
||||||
|
RegexGroup,
|
||||||
RegexMatched,
|
RegexMatched,
|
||||||
|
RegexStr,
|
||||||
ShellCommandArgs,
|
ShellCommandArgs,
|
||||||
ShellCommandArgv,
|
ShellCommandArgv,
|
||||||
CommandWhitespace,
|
Startswith,
|
||||||
)
|
)
|
||||||
|
from nonebot.typing import T_State
|
||||||
|
|
||||||
|
|
||||||
async def state(x: T_State) -> T_State:
|
async def state(x: T_State) -> T_State:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
async def postpone_state(x: "T_State") -> T_State:
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
async def legacy_state(state):
|
async def legacy_state(state):
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from nonebot.typing import T_State
|
from nonebot.adapters import Bot, Event, Message
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.params import Arg, Depends
|
from nonebot.params import Arg, Depends
|
||||||
from nonebot.adapters import Bot, Event, Message
|
from nonebot.typing import T_State
|
||||||
|
|
||||||
|
|
||||||
def dependency():
|
def dependency():
|
||||||
|
@@ -1,24 +1,24 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from nonebot.adapters import Event
|
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot import (
|
from nonebot import (
|
||||||
CommandGroup,
|
CommandGroup,
|
||||||
MatcherGroup,
|
MatcherGroup,
|
||||||
on,
|
on,
|
||||||
on_type,
|
|
||||||
on_regex,
|
|
||||||
on_notice,
|
|
||||||
on_command,
|
on_command,
|
||||||
on_keyword,
|
|
||||||
on_message,
|
|
||||||
on_request,
|
|
||||||
on_endswith,
|
on_endswith,
|
||||||
on_fullmatch,
|
on_fullmatch,
|
||||||
|
on_keyword,
|
||||||
|
on_message,
|
||||||
on_metaevent,
|
on_metaevent,
|
||||||
on_startswith,
|
on_notice,
|
||||||
|
on_regex,
|
||||||
|
on_request,
|
||||||
on_shell_command,
|
on_shell_command,
|
||||||
|
on_startswith,
|
||||||
|
on_type,
|
||||||
)
|
)
|
||||||
|
from nonebot.adapters import Event
|
||||||
|
from nonebot.matcher import Matcher
|
||||||
|
|
||||||
|
|
||||||
async def rule() -> bool:
|
async def rule() -> bool:
|
||||||
|
5
tests/pyproject.toml
Normal file
5
tests/pyproject.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[tool.ruff]
|
||||||
|
extend = "../pyproject.toml"
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
known-first-party = ["nonebot", "fake_server", "utils"]
|
@@ -1,23 +1,23 @@
|
|||||||
from typing import Optional
|
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
|
|
||||||
from utils import FakeAdapter
|
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.drivers import (
|
from nonebot.drivers import (
|
||||||
URL,
|
URL,
|
||||||
Driver,
|
Driver,
|
||||||
|
HTTPServerSetup,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
WebSocket,
|
WebSocket,
|
||||||
HTTPServerSetup,
|
|
||||||
WebSocketServerSetup,
|
WebSocketServerSetup,
|
||||||
)
|
)
|
||||||
|
from utils import FakeAdapter
|
||||||
|
|
||||||
|
|
||||||
@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,13 +1,14 @@
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import pytest
|
import anyio
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
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
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import pytest
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from nonebot.adapters import Message, MessageSegment
|
||||||
from nonebot.compat import type_validate_python
|
from nonebot.compat import type_validate_python
|
||||||
from utils import FakeMessage, FakeMessageSegment
|
from utils import FakeMessage, FakeMessageSegment
|
||||||
from nonebot.adapters import Message, MessageSegment
|
|
||||||
|
|
||||||
|
|
||||||
def test_segment_data():
|
def test_segment_data():
|
||||||
|
@@ -1,31 +1,31 @@
|
|||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
|
|
||||||
from nonebot import on_message
|
from nonebot import on_message
|
||||||
import nonebot.message as message
|
|
||||||
from utils import make_fake_event
|
|
||||||
from nonebot.params import Depends
|
|
||||||
from nonebot.typing import T_State
|
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot.adapters import Bot, Event
|
||||||
from nonebot.exception import IgnoredException
|
from nonebot.exception import IgnoredException
|
||||||
from nonebot.log import logger, default_filter, default_format
|
from nonebot.log import default_filter, default_format, logger
|
||||||
|
from nonebot.matcher import Matcher
|
||||||
|
import nonebot.message as message
|
||||||
from nonebot.message import (
|
from nonebot.message import (
|
||||||
run_preprocessor,
|
|
||||||
run_postprocessor,
|
|
||||||
event_preprocessor,
|
|
||||||
event_postprocessor,
|
event_postprocessor,
|
||||||
|
event_preprocessor,
|
||||||
|
run_postprocessor,
|
||||||
|
run_preprocessor,
|
||||||
)
|
)
|
||||||
|
from nonebot.params import Depends
|
||||||
|
from nonebot.typing import T_State
|
||||||
|
from utils import make_fake_event
|
||||||
|
|
||||||
|
|
||||||
async def _dependency() -> int:
|
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,29 +1,28 @@
|
|||||||
from typing import Any, Optional
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Annotated, Any, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ValidationError
|
||||||
import pytest
|
import pytest
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from nonebot.compat import (
|
from nonebot.compat import (
|
||||||
DEFAULT_CONFIG,
|
DEFAULT_CONFIG,
|
||||||
Required,
|
|
||||||
FieldInfo,
|
FieldInfo,
|
||||||
PydanticUndefined,
|
PydanticUndefined,
|
||||||
model_dump,
|
Required,
|
||||||
|
TypeAdapter,
|
||||||
custom_validation,
|
custom_validation,
|
||||||
|
model_dump,
|
||||||
type_validate_json,
|
type_validate_json,
|
||||||
type_validate_python,
|
type_validate_python,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@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
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
from typing import TYPE_CHECKING, Union, Optional
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
import pytest
|
import pytest
|
||||||
from pydantic import Field, BaseModel
|
|
||||||
|
|
||||||
from nonebot.compat import PYDANTIC_V2
|
from nonebot.compat import PYDANTIC_V2
|
||||||
from nonebot.config import DOTENV_TYPE, BaseSettings, SettingsError, SettingsConfig
|
from nonebot.config import DOTENV_TYPE, BaseSettings, SettingsConfig, SettingsError
|
||||||
|
|
||||||
|
|
||||||
class Simple(BaseModel):
|
class Simple(BaseModel):
|
||||||
@@ -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,31 +1,31 @@
|
|||||||
import json
|
|
||||||
import asyncio
|
|
||||||
from typing import Any, Optional
|
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
|
import json
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
import pytest
|
import anyio
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
|
|
||||||
from utils import FakeAdapter
|
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.params import Depends
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.exception import WebSocketClosed
|
|
||||||
from nonebot.drivers import (
|
from nonebot.drivers import (
|
||||||
URL,
|
URL,
|
||||||
Driver,
|
|
||||||
Request,
|
|
||||||
Response,
|
|
||||||
ASGIMixin,
|
ASGIMixin,
|
||||||
WebSocket,
|
Driver,
|
||||||
HTTPClientMixin,
|
HTTPClientMixin,
|
||||||
HTTPServerSetup,
|
HTTPServerSetup,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
WebSocket,
|
||||||
WebSocketClientMixin,
|
WebSocketClientMixin,
|
||||||
WebSocketServerSetup,
|
WebSocketServerSetup,
|
||||||
)
|
)
|
||||||
|
from nonebot.exception import WebSocketClosed
|
||||||
|
from nonebot.params import Depends
|
||||||
|
from utils import FakeAdapter
|
||||||
|
|
||||||
|
|
||||||
@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")
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import pytest
|
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@@ -1,21 +1,20 @@
|
|||||||
import pytest
|
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.drivers import Driver, ASGIMixin, ReverseDriver
|
|
||||||
from nonebot import (
|
from nonebot import (
|
||||||
get_app,
|
|
||||||
get_bot,
|
|
||||||
get_asgi,
|
|
||||||
get_bots,
|
|
||||||
get_driver,
|
|
||||||
get_adapter,
|
get_adapter,
|
||||||
get_adapters,
|
get_adapters,
|
||||||
|
get_app,
|
||||||
|
get_asgi,
|
||||||
|
get_bot,
|
||||||
|
get_bots,
|
||||||
|
get_driver,
|
||||||
)
|
)
|
||||||
|
from nonebot.drivers import ASGIMixin, Driver, ReverseDriver
|
||||||
|
|
||||||
|
|
||||||
@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"):
|
||||||
|
@@ -1,19 +1,18 @@
|
|||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
import pytest
|
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
import pytest
|
||||||
|
|
||||||
from nonebot.rule import Rule
|
|
||||||
from nonebot import get_plugin
|
from nonebot import get_plugin
|
||||||
from nonebot.matcher import Matcher, matchers
|
from nonebot.matcher import Matcher, matchers
|
||||||
from utils import FakeMessage, make_fake_event
|
|
||||||
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
|
||||||
|
from nonebot.permission import Permission, User
|
||||||
|
from nonebot.rule import Rule
|
||||||
|
from utils import FakeMessage, make_fake_event
|
||||||
|
|
||||||
|
|
||||||
@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,9 +209,9 @@ 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_custom_updater, test_type_updater
|
||||||
|
|
||||||
event = make_fake_event()()
|
event = make_fake_event()()
|
||||||
|
|
||||||
@@ -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,11 +273,11 @@ 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,
|
|
||||||
default_permission,
|
default_permission,
|
||||||
|
new_permission,
|
||||||
test_custom_updater,
|
test_custom_updater,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user