mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-08 03:36:44 +00:00
Compare commits
539 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f77dc523e6 | ||
|
0d84bf3592 | ||
|
94dff49e60 | ||
|
5d4cf7e421 | ||
|
0e3e16e809 | ||
|
183fc8defb | ||
|
8712e89322 | ||
|
e2b49f9b65 | ||
|
7e11f3a3d6 | ||
|
71bebb6ec7 | ||
|
842c6ff4c6 | ||
|
7754f6da1d | ||
|
60e0752f1a | ||
|
ede1a20c53 | ||
|
04289fd50f | ||
|
ba3efa9e7c | ||
|
c5a66a6ed0 | ||
|
8a23b1554a | ||
|
d73f226cbd | ||
|
fd9ba678ec | ||
|
d29ba62ff9 | ||
|
00c97fd18f | ||
|
9531c3fa74 | ||
|
94293122e8 | ||
|
7aaa66c8ba | ||
|
0030bf725e | ||
|
22b6062900 | ||
|
005968ab70 | ||
|
dc6c194701 | ||
|
9b8772b590 | ||
|
ae8ba9f55d | ||
|
f4a7ce2c09 | ||
|
c84723668f | ||
|
bd3ed4207a | ||
|
1e8c2cfc9f | ||
|
5ce0238ace | ||
|
4e6b52b85c | ||
|
05fe7bb715 | ||
|
c555e2fac6 | ||
|
fd126ae154 | ||
|
6c7b6a9575 | ||
|
c4716e3e17 | ||
|
3601a33f20 | ||
|
451023518b | ||
|
2bd377a221 | ||
|
66384adad4 | ||
|
ec1f7ba5bc | ||
|
e7fc5b7b7e | ||
|
11477ea9d7 | ||
|
6adf40f45d | ||
|
1bdf169980 | ||
|
81cb356503 | ||
|
805778794c | ||
|
28cd8dd08a | ||
|
139b39984e | ||
|
f9b5fece80 | ||
|
8076c6bc0a | ||
|
44b89d13f8 | ||
|
fbc4225110 | ||
|
f07f35ccc1 | ||
|
111dfbf164 | ||
|
c713c7723b | ||
|
4fa2af41b0 | ||
|
39c09d22d1 | ||
|
4819b21f52 | ||
|
6ef6721527 | ||
|
14cb447874 | ||
|
1b2b89074d | ||
|
75c5678782 | ||
|
45ec5cdfb4 | ||
|
f6dd98825b | ||
|
f59271bd47 | ||
|
79f833b946 | ||
|
9ad562bbfd | ||
|
267b49247d | ||
|
dbda4150fb | ||
|
a4e17f0c49 | ||
|
8d8d1169d1 | ||
|
7bc9e61985 | ||
|
35cc6011b5 | ||
|
086af8fd22 | ||
|
a60d1520e6 | ||
|
30c22ba25a | ||
|
41fbaec42c | ||
|
562ec79e3b | ||
|
f620bd8eb2 | ||
|
13e40458d7 | ||
|
dc4ac6d8d7 | ||
|
41498bdf21 | ||
|
b8eae2eb82 | ||
|
039c2b5509 | ||
|
2e635370bb | ||
|
807a86371d | ||
|
c66953779c | ||
|
117ef18f1c | ||
|
520dd03d77 | ||
|
63f3ca2f6f | ||
|
2e8230e9f4 | ||
|
4bfea99e54 | ||
|
f58eba7975 | ||
|
53d1de4aec | ||
|
00f18c1bd8 | ||
|
ba4fbb2ec3 | ||
|
b3722bd637 | ||
|
012bd6d4fb | ||
|
9c4ca28d61 | ||
|
53bcae04ff | ||
|
754c54e268 | ||
|
f97fbc814e | ||
|
b8856a0577 | ||
|
1c0e88907b | ||
|
31b6df5b39 | ||
|
bca9e4fd08 | ||
|
026ceb5028 | ||
|
47d5a647b7 | ||
|
37d7230949 | ||
|
be458b1d5e | ||
|
f375a4a723 | ||
|
3edce9a630 | ||
|
c525bda1e0 | ||
|
417f586e0d | ||
|
80d7e68835 | ||
|
a284e6df5c | ||
|
7176a69f81 | ||
|
e3a1c02e8a | ||
|
5e789ae4e0 | ||
|
bb684e20cb | ||
|
e11293e46b | ||
|
e0d74a1657 | ||
|
fdd36565b1 | ||
|
28c53fe0d7 | ||
|
26539bf2b1 | ||
|
347889c822 | ||
|
91849b762c | ||
|
5d1319ddb9 | ||
|
d98228926e | ||
|
493997d998 | ||
|
3098b7c153 | ||
|
2b0a050226 | ||
|
1f3abc2bb9 | ||
|
dd5541e658 | ||
|
a76bf27f60 | ||
|
d70ce366cc | ||
|
f94b802c9b | ||
|
17d7bd4e31 | ||
|
76a40b60ff | ||
|
469efedab2 | ||
|
383699a8b4 | ||
|
1b9a07b923 | ||
|
15b76c266c | ||
|
dfdecaddb1 | ||
|
5de9de903d | ||
|
327f3fa441 | ||
|
08fde7580c | ||
|
4ca91ecc7e | ||
|
885db90bc0 | ||
|
c43d631eb5 | ||
|
cfda433d14 | ||
|
ea4a27bf89 | ||
|
23944833f2 | ||
|
4a40782be0 | ||
|
babafcaa87 | ||
|
9b164a6f5a | ||
|
4a07981972 | ||
|
6bb2c46f8a | ||
|
2054655912 | ||
|
062af45367 | ||
|
83c3ed5966 | ||
|
a2f2b818a7 | ||
|
e7941efd9a | ||
|
aa6faba9ae | ||
|
8ca72f3c64 | ||
|
45e10e7139 | ||
|
73d1b19669 | ||
|
ad4cf86a96 | ||
|
48b3e3aaf3 | ||
|
f2b0b1752b | ||
|
81dcc65f99 | ||
|
ac90df929e | ||
|
555268239f | ||
|
7009c8e8c1 | ||
|
2f3cc84f82 | ||
|
9444e01f0f | ||
|
23b7a94b9a | ||
|
70ece41b66 | ||
|
a5bb6e4220 | ||
|
4fc99771c5 | ||
|
6601def5f7 | ||
|
b2edea141e | ||
|
38886b9651 | ||
|
1b225cbbca | ||
|
b4f004c500 | ||
|
7a345714aa | ||
|
cb9fcae64c | ||
|
6ebeefed79 | ||
|
6dc87a9455 | ||
|
7dd7c927bf | ||
|
e167865686 | ||
|
29364679c4 | ||
|
ebbe8beec0 | ||
|
a04580e79e | ||
|
bfe9e7e253 | ||
|
720398198f | ||
|
5ebf349886 | ||
|
f8f5750c3b | ||
|
8d9be61406 | ||
|
42ea650509 | ||
|
a941a0f292 | ||
|
89f8745425 | ||
|
cc476528d8 | ||
|
64f6c2dd4c | ||
|
81d9531b42 | ||
|
3512b0ab98 | ||
|
ab3e916770 | ||
|
21376a5bfa | ||
|
5046b2a86e | ||
|
910c768910 | ||
|
5a526ddb40 | ||
|
4c5c97dca6 | ||
|
b3e0fb4830 | ||
|
258aa7d2d7 | ||
|
5c72fd5ba7 | ||
|
26e4f23a67 | ||
|
28fc6c35f0 | ||
|
3ef1d7d5d7 | ||
|
8474d8987e | ||
|
13ddfa1bdd | ||
|
ec8be10f26 | ||
|
511c521a68 | ||
|
0ef5940d0f | ||
|
eecc881cd8 | ||
|
770141cf0a | ||
|
b2b20ffc4a | ||
|
94a6067a4b | ||
|
77220d9d1f | ||
|
647ad9ff8f | ||
|
04182eefba | ||
|
7b4aa08c54 | ||
|
0033d7c686 | ||
|
c40b95f3e9 | ||
|
1fa44ca5c1 | ||
|
381f6633f6 | ||
|
d617508e32 | ||
|
8248e88686 | ||
|
25649373a6 | ||
|
3bee189598 | ||
|
c1b1742b20 | ||
|
3e826cab72 | ||
|
4ef4bb0042 | ||
|
25ac653623 | ||
|
b35bdfe6dc | ||
|
f06efca8cc | ||
|
a899523607 | ||
|
2c162335cb | ||
|
3a12984d4b | ||
|
7211f24a7d | ||
|
649624ed80 | ||
|
c03ff4e676 | ||
|
0b5a18cb63 | ||
|
518bf16082 | ||
|
b625a5d19a | ||
|
acca22e179 | ||
|
a3009d45dc | ||
|
fd3d1bb115 | ||
|
7282da8b04 | ||
|
7a3c7476fb | ||
|
f1046cfb11 | ||
|
8de25447b3 | ||
|
3cdbf35dc6 | ||
|
0228e255e1 | ||
|
353d16ebfd | ||
|
3d5dd5969c | ||
|
fe21cbfa1d | ||
|
c20f65636f | ||
|
eade8face6 | ||
|
ab75133e9d | ||
|
89596fb708 | ||
|
eedcf0779d | ||
|
05333260b7 | ||
|
55fd447230 | ||
|
263e6b25e2 | ||
|
e00890033e | ||
|
20d3d62bd5 | ||
|
080b876d93 | ||
|
27a3d1f0bb | ||
|
7a47985c2b | ||
|
8d97081948 | ||
|
f4ffa07c8b | ||
|
1b1ddc5c0f | ||
|
30dbd270a6 | ||
|
7d3c7c4933 | ||
|
8c8436a94f | ||
|
8601942ed3 | ||
|
4cc958ca17 | ||
|
472a2c7866 | ||
|
222609182e | ||
|
dccf2f3ca8 | ||
|
156807c365 | ||
|
50941f5259 | ||
|
2de1524a89 | ||
|
bdd17b62cc | ||
|
3a9e800a58 | ||
|
cb8d48c362 | ||
|
a5981c05d5 | ||
|
4cb87e596d | ||
|
2725a0a324 | ||
|
f6b0809e5f | ||
|
6181c1760f | ||
|
324277091c | ||
|
6eef863b70 | ||
|
7d52f5af4d | ||
|
0a70721ec0 | ||
|
f430f061ec | ||
|
572be1eb47 | ||
|
29cf7de1a6 | ||
|
c61e3cab90 | ||
|
77bdc5ecba | ||
|
16054d18c6 | ||
|
f0361295c3 | ||
|
9bd1964ae2 | ||
|
9141c88f77 | ||
|
491855876b | ||
|
6df28dd2a8 | ||
|
142d0f4d95 | ||
|
0127d765ae | ||
|
207c6b3c15 | ||
|
d2e699a13a | ||
|
ce9ba7dd9b | ||
|
2af23c9d89 | ||
|
8ee0f5efc4 | ||
|
8dcfe92f13 | ||
|
f3d5c1f226 | ||
|
8af21f6e76 | ||
|
9bf3dc4274 | ||
|
40d2f975cb | ||
|
784ba287aa | ||
|
3b9cf6cc51 | ||
|
f52abc8314 | ||
|
3199fc454a | ||
|
738f8cae3b | ||
|
9406c117a6 | ||
|
7b0e62c128 | ||
|
5d0d91b87b | ||
|
ed687d8ff6 | ||
|
cc214c320d | ||
|
0897f3b0f7 | ||
|
7986c45b3f | ||
|
8ea0241aa2 | ||
|
fabc2faa4c | ||
|
3216479530 | ||
|
6c66a54223 | ||
|
e760290beb | ||
|
3beefdff72 | ||
|
104f610ea7 | ||
|
2b337e1310 | ||
|
b78455f910 | ||
|
c3d6e20120 | ||
|
b32af0f6ba | ||
|
3469b0dbb7 | ||
|
0fd7396665 | ||
|
c6f41e1975 | ||
|
2cfc20c143 | ||
|
99197f30f6 | ||
|
aa48299d5d | ||
|
dd80191761 | ||
|
34c1c33996 | ||
|
2dcbce9cd7 | ||
|
c4fbd1cac3 | ||
|
575e3fb920 | ||
|
50fd4acccb | ||
|
f9e214de93 | ||
|
f28d354875 | ||
|
648b838a75 | ||
|
157fe31051 | ||
|
170fb94896 | ||
|
9616b4c0ca | ||
|
0c4a040394 | ||
|
8592e77e15 | ||
|
fc8850496e | ||
|
227afbfd8d | ||
|
4672af12fe | ||
|
079996d936 | ||
|
bd9b05b990 | ||
|
f2f3f7ab8e | ||
|
22222e79b6 | ||
|
55164e8ece | ||
|
336822ff5c | ||
|
82e8417b9a | ||
|
9c0ecb441f | ||
|
0c0fabcb89 | ||
|
202f437aea | ||
|
a8b06aa7c7 | ||
|
a5e634319a | ||
|
6d1262f402 | ||
|
a5fd182bd0 | ||
|
771cf8bdcf | ||
|
56304aea8d | ||
|
c6e69ddc17 | ||
|
ae55ec3e1b | ||
|
f72243304f | ||
|
5425180aec | ||
|
a496db4ddf | ||
|
26bad8eb4b | ||
|
f526080611 | ||
|
6c269825c9 | ||
|
17959b7056 | ||
|
17a8ed379a | ||
|
163e5001d3 | ||
|
5d27646ef9 | ||
|
38be147e8a | ||
|
93829aeb80 | ||
|
9098dbae9a | ||
|
9edc51c2a4 | ||
|
f1aa2b1cb2 | ||
|
bbb2cb3a2c | ||
|
7a0a32398b | ||
|
272ed8e85c | ||
|
e308d4cfac | ||
|
0162360cfe | ||
|
4ba4c0bebc | ||
|
4b0b1e69a4 | ||
|
22c0f81054 | ||
|
2494e615fd | ||
|
ab637d217b | ||
|
9d8f16f940 | ||
|
dc2c5e3c80 | ||
|
6cfdbbe597 | ||
|
487867a967 | ||
|
f3a692d294 | ||
|
72b798f7ae | ||
|
50237fb778 | ||
|
fa5b8853af | ||
|
0cd2282640 | ||
|
c2cea75bb7 | ||
|
b832ae742b | ||
|
e98d28f3b4 | ||
|
b2e26cd6bd | ||
|
cfe182f452 | ||
|
729a894a04 | ||
|
7231d493d0 | ||
|
abf1d52168 | ||
|
a5618f163f | ||
|
7d6c512f27 | ||
|
f00c0ae71c | ||
|
93b79ddcb3 | ||
|
6691f6ef70 | ||
|
6173836bdb | ||
|
a2a88c1414 | ||
|
6114867e34 | ||
|
3c5cd6046d | ||
|
7dc3702db5 | ||
|
791f75c13e | ||
|
4cfc8fcb44 | ||
|
57fc04e4aa | ||
|
1e74c4eacf | ||
|
e55052ecfd | ||
|
dc0aea9e3e | ||
|
295b55da44 | ||
|
4b9ae5fd68 | ||
|
cc8b6fa7a2 | ||
|
f28de96ea9 | ||
|
5e225b2898 | ||
|
392502dd68 | ||
|
ba329e5ef2 | ||
|
68b64a6004 | ||
|
0f694aa157 | ||
|
0d7c399094 | ||
|
2f6685ab45 | ||
|
2061887276 | ||
|
2f40024edb | ||
|
9799809ebd | ||
|
2a08bc5a14 | ||
|
ff1ace7a04 | ||
|
96f0daf535 | ||
|
1b2f560ad7 | ||
|
1c033d3a53 | ||
|
99b26fccb0 | ||
|
565aba61dc | ||
|
21eb289411 | ||
|
441e772f48 | ||
|
f360e439e9 | ||
|
98b3affe91 | ||
|
04be5cb8e8 | ||
|
7f925536b5 | ||
|
73dfcc53e1 | ||
|
3e543977c9 | ||
|
97829aa122 | ||
|
e42dd109f9 | ||
|
98a6dfc514 | ||
|
d6fd1b8614 | ||
|
dd57aabfdc | ||
|
44c8b4c29d | ||
|
1bc3a8eb02 | ||
|
7371d3e7bb | ||
|
139eeac6ce | ||
|
d33d2653cf | ||
|
ba3fc6abc4 | ||
|
1d60714054 | ||
|
020705bd4b | ||
|
1495b34e39 | ||
|
e0d11226db | ||
|
8749bc9dc5 | ||
|
716a047aba | ||
|
ed6d436a50 | ||
|
028b51facf | ||
|
8f28124237 | ||
|
f3d7a30c66 | ||
|
8f3e9f87cb | ||
|
36e4c02699 | ||
|
1817102a7c | ||
|
20820e72ad | ||
|
fb4d957025 | ||
|
fe5635db62 | ||
|
30a8230eea | ||
|
908622cf61 | ||
|
8e5ec5c4e7 | ||
|
ca071bfc48 | ||
|
4e22252c3e | ||
|
c0eee74968 | ||
|
1e054a4370 | ||
|
76ccd241fc | ||
|
e984b64fe3 | ||
|
3256cf7fce | ||
|
d9eeb690ac | ||
|
b66f4436bf | ||
|
c11bc7b78f | ||
|
3bbb48dd25 | ||
|
73b92be1e4 | ||
|
e977d79ebd | ||
|
d02896065e | ||
|
f468aa992d | ||
|
3bfbbcf111 | ||
|
e2e8b0a8cd | ||
|
c8c5f17fd1 | ||
|
7f6fc56bd8 | ||
|
40855ade01 | ||
|
d116563958 | ||
|
8f603d3112 | ||
|
998752926f |
@@ -10,9 +10,11 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"python.analysis.diagnosticMode": "workspace",
|
"python.analysis.diagnosticMode": "workspace",
|
||||||
"python.analysis.typeCheckingMode": "basic",
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
|
"ruff.organizeImports": false,
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter",
|
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.ruff": true,
|
||||||
"source.organizeImports": true
|
"source.organizeImports": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
"ms-python.vscode-pylance",
|
"ms-python.vscode-pylance",
|
||||||
"ms-python.isort",
|
"ms-python.isort",
|
||||||
"ms-python.black-formatter",
|
"ms-python.black-formatter",
|
||||||
|
"charliermarsh.ruff",
|
||||||
"EditorConfig.EditorConfig",
|
"EditorConfig.EditorConfig",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"bradlc.vscode-tailwindcss"
|
"bradlc.vscode-tailwindcss"
|
||||||
|
6
.eslintignore
Normal file
6
.eslintignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
.yarn
|
||||||
|
.history
|
||||||
|
build
|
||||||
|
lib
|
85
.eslintrc.js
Normal file
85
.eslintrc.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
commonjs: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ["./tsconfig.json", "./website/tsconfig.json"],
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
JSX: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:import/recommended",
|
||||||
|
"plugin:regexp/recommended",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
"import/resolver": {
|
||||||
|
node: {
|
||||||
|
extensions: [".js", ".jsx", ".ts", ".tsx"],
|
||||||
|
},
|
||||||
|
typescript: true,
|
||||||
|
},
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["*.ts", "*.tsx"],
|
||||||
|
rules: {
|
||||||
|
"import/no-unresolved": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["*.js", "*.cjs"],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
plugins: ["@typescript-eslint"],
|
||||||
|
rules: {
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
quotes: ["error", "double", { avoidEscape: true }],
|
||||||
|
semi: ["error", "always"],
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
"builtin",
|
||||||
|
"external",
|
||||||
|
"internal",
|
||||||
|
"parent",
|
||||||
|
"sibling",
|
||||||
|
"index",
|
||||||
|
],
|
||||||
|
pathGroups: [
|
||||||
|
{ pattern: "react", group: "builtin", position: "before" },
|
||||||
|
{ pattern: "fs-extra", group: "builtin" },
|
||||||
|
{ pattern: "lodash", group: "external", position: "before" },
|
||||||
|
{ pattern: "clsx", group: "external", position: "before" },
|
||||||
|
{ pattern: "@theme/**", group: "internal" },
|
||||||
|
{ pattern: "@site/**", group: "internal" },
|
||||||
|
{ pattern: "@theme-init/**", group: "internal" },
|
||||||
|
{ pattern: "@theme-original/**", group: "internal" },
|
||||||
|
],
|
||||||
|
pathGroupsExcludedImportTypes: [],
|
||||||
|
"newlines-between": "always",
|
||||||
|
alphabetize: {
|
||||||
|
order: "asc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
website/versioned_*/** linguist-documentation
|
57
.github/ISSUE_TEMPLATE/adapter_publish.yml
vendored
Normal file
57
.github/ISSUE_TEMPLATE/adapter_publish.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: 发布适配器
|
||||||
|
title: "Adapter: {name}"
|
||||||
|
description: 发布适配器到 NoneBot 官方商店
|
||||||
|
labels: ["Adapter"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: name
|
||||||
|
attributes:
|
||||||
|
label: 适配器名称
|
||||||
|
description: 适配器名称
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: 适配器描述
|
||||||
|
description: 适配器描述
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: pypi
|
||||||
|
attributes:
|
||||||
|
label: PyPI 项目名
|
||||||
|
description: PyPI 项目名
|
||||||
|
placeholder: e.g. nonebot-adapter-xxx
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: module
|
||||||
|
attributes:
|
||||||
|
label: 适配器 import 包名
|
||||||
|
description: 适配器 import 包名
|
||||||
|
placeholder: e.g. nonebot_adapter_xxx
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: homepage
|
||||||
|
attributes:
|
||||||
|
label: 适配器项目仓库/主页链接
|
||||||
|
description: 适配器项目仓库/主页链接
|
||||||
|
placeholder: e.g. https://github.com/xxx/xxx
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: tags
|
||||||
|
attributes:
|
||||||
|
label: 标签
|
||||||
|
description: 标签
|
||||||
|
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
|
||||||
|
value: "[]"
|
||||||
|
validations:
|
||||||
|
required: true
|
37
.github/ISSUE_TEMPLATE/bot_publish.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/bot_publish.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: 发布机器人
|
||||||
|
title: "Bot: {name}"
|
||||||
|
description: 发布机器人到 NoneBot 官方商店
|
||||||
|
labels: ["Bot"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: name
|
||||||
|
attributes:
|
||||||
|
label: 机器人名称
|
||||||
|
description: 机器人名称
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: 机器人描述
|
||||||
|
description: 机器人描述
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: homepage
|
||||||
|
attributes:
|
||||||
|
label: 机器人项目仓库/主页链接
|
||||||
|
description: 机器人项目仓库/主页链接
|
||||||
|
placeholder: e.g. https://github.com/xxx/xxx
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: tags
|
||||||
|
attributes:
|
||||||
|
label: 标签
|
||||||
|
description: 标签
|
||||||
|
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
|
||||||
|
value: "[]"
|
||||||
|
validations:
|
||||||
|
required: true
|
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a bug report to help us improve
|
|
||||||
title: 'Bug: Something went wrong'
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**描述问题:**
|
|
||||||
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**如何复现?**
|
|
||||||
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**期望的结果**
|
|
||||||
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**环境信息:**
|
|
||||||
|
|
||||||
- OS: [e.g. Linux]
|
|
||||||
- Python Version: [e.g. 3.8]
|
|
||||||
- Nonebot Version: [e.g. 2.0.0]
|
|
||||||
|
|
||||||
**协议端信息:**
|
|
||||||
|
|
||||||
- 协议端: [e.g. go-cqhttp]
|
|
||||||
- 协议端版本: [e.g. 1.0.0]
|
|
||||||
|
|
||||||
**截图或日志**
|
|
||||||
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
name: Bug 反馈
|
||||||
|
title: "Bug: 出现异常"
|
||||||
|
description: 提交 Bug 反馈以帮助我们改进代码
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: dropdown
|
||||||
|
id: env-os
|
||||||
|
attributes:
|
||||||
|
label: 操作系统
|
||||||
|
description: 选择运行 NoneBot 的系统
|
||||||
|
options:
|
||||||
|
- Windows
|
||||||
|
- MacOS
|
||||||
|
- Linux
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: env-python-ver
|
||||||
|
attributes:
|
||||||
|
label: Python 版本
|
||||||
|
description: 填写运行 NoneBot 的 Python 版本
|
||||||
|
placeholder: e.g. 3.11.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: env-nb-ver
|
||||||
|
attributes:
|
||||||
|
label: NoneBot 版本
|
||||||
|
description: 填写 NoneBot 版本
|
||||||
|
placeholder: e.g. 2.0.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: env-adapter
|
||||||
|
attributes:
|
||||||
|
label: 适配器
|
||||||
|
description: 填写使用的适配器以及版本
|
||||||
|
placeholder: e.g. OneBot v11 2.2.2
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: env-protocol
|
||||||
|
attributes:
|
||||||
|
label: 协议端
|
||||||
|
description: 填写连接 NoneBot 的协议端及版本
|
||||||
|
placeholder: e.g. go-cqhttp 1.0.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: describe
|
||||||
|
attributes:
|
||||||
|
label: 描述问题
|
||||||
|
description: 清晰简洁地说明问题是什么
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: 复现步骤
|
||||||
|
description: 提供能复现此问题的详细操作步骤
|
||||||
|
placeholder: |
|
||||||
|
1. 首先……
|
||||||
|
2. 然后……
|
||||||
|
3. 发生……
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: 期望的结果
|
||||||
|
description: 清晰简洁地描述你期望发生的事情
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: 截图或日志
|
||||||
|
description: 提供有助于诊断问题的任何日志和截图
|
13
.github/ISSUE_TEMPLATE/config.yml
vendored
13
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Question
|
- name: NoneBot 论坛
|
||||||
url: https://discussions.nonebot.dev/
|
url: https://discussions.nonebot.dev/
|
||||||
about: Ask questions about nonebot
|
about: 前往 NoneBot 论坛提问
|
||||||
- name: Plugin Publish
|
|
||||||
url: https://v2.nonebot.dev/store
|
|
||||||
about: Publish your plugin to nonebot homepage and nb-cli
|
|
||||||
- name: Adapter Publish
|
|
||||||
url: https://v2.nonebot.dev/store
|
|
||||||
about: Publish your adapter to nonebot homepage and nb-cli
|
|
||||||
- name: Bot Publish
|
|
||||||
url: https://v2.nonebot.dev/store
|
|
||||||
about: Publish your bot to nonebot homepage and nb-cli
|
|
||||||
|
17
.github/ISSUE_TEMPLATE/document.md
vendored
17
.github/ISSUE_TEMPLATE/document.md
vendored
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: Document improvement
|
|
||||||
about: Feedback on documentation, including errors and ideas
|
|
||||||
title: 'Docs: some description'
|
|
||||||
labels: documentation
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**描述问题或主题:**
|
|
||||||
|
|
||||||
|
|
||||||
**需做出的修改:**
|
|
||||||
|
|
||||||
* [ ] 一些修改
|
|
||||||
* [ ] 一些修改
|
|
||||||
* [ ] 一些修改
|
|
18
.github/ISSUE_TEMPLATE/document.yml
vendored
Normal file
18
.github/ISSUE_TEMPLATE/document.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: 文档改进
|
||||||
|
title: "Docs: 描述"
|
||||||
|
description: 文档错误及改进意见反馈
|
||||||
|
labels: ["documentation"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: 描述问题或主题
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: improve
|
||||||
|
attributes:
|
||||||
|
label: 需做出的修改
|
||||||
|
validations:
|
||||||
|
required: true
|
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: 'Feature: Something you want'
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**是否在使用中遇到某些问题而需要新的特性?请描述:**
|
|
||||||
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**描述你所需要的特性:**
|
|
||||||
|
|
||||||
A clear and concise description of what you want to happen.
|
|
20
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: 功能建议
|
||||||
|
title: "Feature: 功能描述"
|
||||||
|
description: 提出关于项目新功能的想法
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: 希望能解决的问题
|
||||||
|
description: 在使用中遇到什么问题而需要新的功能?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: feature
|
||||||
|
attributes:
|
||||||
|
label: 描述所需要的功能
|
||||||
|
description: 请说明需要的功能或解决方法
|
||||||
|
validations:
|
||||||
|
required: true
|
43
.github/ISSUE_TEMPLATE/plugin_publish.yml
vendored
Normal file
43
.github/ISSUE_TEMPLATE/plugin_publish.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
name: 发布插件
|
||||||
|
title: "Plugin: {name}"
|
||||||
|
description: 发布插件到 NoneBot 官方商店
|
||||||
|
labels: ["Plugin"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: pypi
|
||||||
|
attributes:
|
||||||
|
label: PyPI 项目名
|
||||||
|
description: PyPI 项目名
|
||||||
|
placeholder: e.g. nonebot-plugin-xxx
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: module
|
||||||
|
attributes:
|
||||||
|
label: 插件 import 包名
|
||||||
|
description: 插件 import 包名
|
||||||
|
placeholder: e.g. nonebot_plugin_xxx
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: tags
|
||||||
|
attributes:
|
||||||
|
label: 标签
|
||||||
|
description: 标签
|
||||||
|
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
|
||||||
|
value: "[]"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: config
|
||||||
|
attributes:
|
||||||
|
label: 插件配置项
|
||||||
|
description: 插件配置项
|
||||||
|
render: dotenv
|
||||||
|
placeholder: |
|
||||||
|
# e.g.
|
||||||
|
# KEY=VALUE
|
||||||
|
# KEY2=VALUE2
|
16
.github/actions/setup-node/action.yml
vendored
16
.github/actions/setup-node/action.yml
vendored
@@ -4,18 +4,10 @@ description: Setup Node
|
|||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: "18"
|
||||||
|
cache: "yarn"
|
||||||
|
|
||||||
- id: yarn-cache-dir-path
|
- run: yarn install --frozen-lockfile
|
||||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
|
|
||||||
- run: yarn install
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
2
.github/actions/setup-python/action.yml
vendored
2
.github/actions/setup-python/action.yml
vendored
@@ -11,7 +11,7 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Install poetry
|
- name: Install poetry
|
||||||
run: pipx install poetry==1.3.2
|
run: pipx install poetry
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
|
31
.github/dependabot.yml
vendored
31
.github/dependabot.yml
vendored
@@ -4,3 +4,34 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/.github/actions/build-api-doc"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/.github/actions/setup-node"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/.github/actions/setup-python"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
2
.github/workflows/codecov.yml
vendored
2
.github/workflows/codecov.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Python environment
|
- name: Setup Python environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
|
100
.github/workflows/noneflow.yml
vendored
Normal file
100
.github/workflows/noneflow.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
name: NoneFlow
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, reopened, edited]
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed]
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_review:
|
||||||
|
types: [submitted]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
plugin_test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: nonebot2 plugin test
|
||||||
|
if: |
|
||||||
|
!(
|
||||||
|
(
|
||||||
|
github.event.pull_request &&
|
||||||
|
(
|
||||||
|
github.event.pull_request.head.repo.fork ||
|
||||||
|
!(
|
||||||
|
contains(github.event.pull_request.labels.*.name, 'Plugin') ||
|
||||||
|
contains(github.event.pull_request.labels.*.name, 'Adapter') ||
|
||||||
|
contains(github.event.pull_request.labels.*.name, 'Bot')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
github.event_name == 'issue_comment' && github.event.issue.pull_request
|
||||||
|
)
|
||||||
|
)
|
||||||
|
permissions:
|
||||||
|
issues: read
|
||||||
|
outputs:
|
||||||
|
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@v4
|
||||||
|
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:
|
||||||
|
- name: Generate token
|
||||||
|
id: generate-token
|
||||||
|
uses: tibdex/github-app-token@v2
|
||||||
|
with:
|
||||||
|
app_id: ${{ secrets.APP_ID }}
|
||||||
|
private_key: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Cache pre-commit hooks
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: .cache/.pre-commit
|
||||||
|
key: noneflow-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
|
|
||||||
|
- name: NoneFlow
|
||||||
|
uses: docker://ghcr.io/nonebot/noneflow:latest
|
||||||
|
with:
|
||||||
|
config: >
|
||||||
|
{
|
||||||
|
"base": "master",
|
||||||
|
"plugin_path": "website/static/plugins.json",
|
||||||
|
"bot_path": "website/static/bots.json",
|
||||||
|
"adapter_path": "website/static/adapters.json"
|
||||||
|
}
|
||||||
|
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 }}
|
||||||
|
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
|
60
.github/workflows/publish-bot.yml
vendored
60
.github/workflows/publish-bot.yml
vendored
@@ -1,60 +0,0 @@
|
|||||||
name: NoneBot2 Publish Bot
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, reopened, edited]
|
|
||||||
pull_request_target:
|
|
||||||
types: [closed]
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
plugin_test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: nonebot2 plugin test
|
|
||||||
if: github.event_name != 'issue_comment' || !github.event.issue.pull_request
|
|
||||||
permissions:
|
|
||||||
issues: read
|
|
||||||
outputs:
|
|
||||||
result: ${{ steps.plugin-test.outputs.RESULT }}
|
|
||||||
output: ${{ steps.plugin-test.outputs.OUTPUT }}
|
|
||||||
steps:
|
|
||||||
- name: Install Poetry
|
|
||||||
if: ${{ !startsWith(github.event_name, 'pull_request') }}
|
|
||||||
run: pipx install poetry
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: "3.10"
|
|
||||||
- name: Test Plugin
|
|
||||||
id: plugin-test
|
|
||||||
run: |
|
|
||||||
curl -sSL https://github.com/nonebot/nonebot2-publish-bot/releases/latest/download/plugin_test.py -o plugin_test.py
|
|
||||||
python plugin_test.py
|
|
||||||
publish_bot:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: nonebot2 publish bot
|
|
||||||
needs: plugin_test
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- name: NoneBot2 Publish Bot
|
|
||||||
uses: docker://ghcr.io/nonebot/nonebot2-publish-bot:latest
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
config: >
|
|
||||||
{
|
|
||||||
"base": "master",
|
|
||||||
"plugin_path": "website/static/plugins.json",
|
|
||||||
"bot_path": "website/static/bots.json",
|
|
||||||
"adapter_path": "website/static/adapters.json"
|
|
||||||
}
|
|
||||||
env:
|
|
||||||
PLUGIN_TEST_RESULT: ${{ needs.plugin_test.outputs.result }}
|
|
||||||
PLUGIN_TEST_OUTPUT: ${{ needs.plugin_test.outputs.output }}
|
|
26
.github/workflows/pyright.yml
vendored
Normal file
26
.github/workflows/pyright.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Pyright Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "nonebot/**"
|
||||||
|
- "packages/**"
|
||||||
|
- "tests/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pyright:
|
||||||
|
name: Pyright Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python environment
|
||||||
|
uses: ./.github/actions/setup-python
|
||||||
|
|
||||||
|
- run: echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Run Pyright
|
||||||
|
uses: jakebailey/pyright-action@v1
|
77
.github/workflows/release-drafter.yml
vendored
77
.github/workflows/release-drafter.yml
vendored
@@ -18,9 +18,16 @@ jobs:
|
|||||||
group: pull-request-changelog
|
group: pull-request-changelog
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Generate token
|
||||||
|
id: generate-token
|
||||||
|
uses: tibdex/github-app-token@v2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
app_id: ${{ secrets.APP_ID }}
|
||||||
|
private_key: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Node Environment
|
- name: Setup Node Environment
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
@@ -28,7 +35,7 @@ jobs:
|
|||||||
- uses: release-drafter/release-drafter@v5
|
- uses: release-drafter/release-drafter@v5
|
||||||
id: release-drafter
|
id: release-drafter
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Update Changelog
|
- name: Update Changelog
|
||||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||||
@@ -43,8 +50,8 @@ jobs:
|
|||||||
- name: Commit and Push
|
- name: Commit and Push
|
||||||
run: |
|
run: |
|
||||||
yarn prettier
|
yarn prettier
|
||||||
git config user.name github-actions[bot]
|
git config user.name noneflow[bot]
|
||||||
git config user.email github-actions[bot]@users.noreply.github.com
|
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
|
||||||
git add .
|
git add .
|
||||||
git diff-index --quiet HEAD || git commit -m ":memo: Update changelog"
|
git diff-index --quiet HEAD || git commit -m ":memo: Update changelog"
|
||||||
git push
|
git push
|
||||||
@@ -52,8 +59,18 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Generate token
|
||||||
|
id: generate-token
|
||||||
|
uses: tibdex/github-app-token@v2
|
||||||
|
with:
|
||||||
|
app_id: ${{ secrets.APP_ID }}
|
||||||
|
private_key: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Python Environment
|
- name: Setup Python Environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
@@ -64,33 +81,53 @@ jobs:
|
|||||||
- name: Build API Doc
|
- name: Build API Doc
|
||||||
uses: ./.github/actions/build-api-doc
|
uses: ./.github/actions/build-api-doc
|
||||||
|
|
||||||
- run: |
|
- name: Get Version
|
||||||
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(poetry version -s)" >> $GITHUB_OUTPUT
|
||||||
|
echo "TAG_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||||
|
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Check Version
|
||||||
|
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
|
||||||
|
run: exit 1
|
||||||
|
|
||||||
- uses: release-drafter/release-drafter@v5
|
- uses: release-drafter/release-drafter@v5
|
||||||
with:
|
with:
|
||||||
name: Release ${{ env.TAG_NAME }} 🌈
|
name: Release ${{ steps.version.outputs.TAG_NAME }} 🌈
|
||||||
tag: ${{ env.TAG_NAME }}
|
tag: ${{ steps.version.outputs.TAG_NAME }}
|
||||||
publish: true
|
publish: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Build and Publish Package
|
- name: Build Package
|
||||||
run: |
|
run: |
|
||||||
poetry build
|
poetry build
|
||||||
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}
|
|
||||||
gh release upload --clobber ${{ env.TAG_NAME }} dist/*.tar.gz dist/*.whl
|
- name: Publish package to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
||||||
|
- name: Publish package to GitHub
|
||||||
|
run: |
|
||||||
|
gh release upload --clobber ${{ steps.version.outputs.TAG_NAME }} dist/*.tar.gz dist/*.whl
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Build and Publish Doc Package
|
- name: Build and Publish Doc Package
|
||||||
run: |
|
run: |
|
||||||
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
|
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
|
||||||
export NONEBOT_VERSION=`poetry version -s`
|
|
||||||
cd packages/nonebot-plugin-docs/
|
cd packages/nonebot-plugin-docs/
|
||||||
poetry version $NONEBOT_VERSION
|
poetry version ${{ steps.version.outputs.VERSION }}
|
||||||
poetry build
|
poetry build
|
||||||
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}
|
|
||||||
gh release upload --clobber ${{ env.TAG_NAME }} dist/*.tar.gz dist/*.whl
|
- name: Publish Doc Package to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
packages-dir: packages/nonebot-plugin-docs/
|
||||||
|
|
||||||
|
- name: Publish Doc Package to GitHub
|
||||||
|
run: |
|
||||||
|
cd packages/nonebot-plugin-docs/
|
||||||
|
gh release upload --clobber ${{ steps.version.outputs.TAG_NAME }} dist/*.tar.gz dist/*.whl
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
|
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@@ -6,12 +6,17 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Generate token
|
||||||
|
id: generate-token
|
||||||
|
uses: tibdex/github-app-token@v2
|
||||||
with:
|
with:
|
||||||
ref: master
|
app_id: ${{ secrets.APP_ID }}
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
private_key: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Python Environment
|
- name: Setup Python Environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
@@ -39,8 +44,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Push Tag
|
- name: Push Tag
|
||||||
run: |
|
run: |
|
||||||
git config user.name github-actions[bot]
|
git config user.name noneflow[bot]
|
||||||
git config user.email github-actions[bot]@users.noreply.github.com
|
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
|
||||||
git add .
|
git add .
|
||||||
git commit -m ":bookmark: Release $(poetry version -s)"
|
git commit -m ":bookmark: Release $(poetry version -s)"
|
||||||
git tag ${{ env.TAG_NAME }}
|
git tag ${{ env.TAG_NAME }}
|
||||||
|
21
.github/workflows/ruff.yml
vendored
Normal file
21
.github/workflows/ruff.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Ruff Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "nonebot/**"
|
||||||
|
- "packages/**"
|
||||||
|
- "tests/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ruff:
|
||||||
|
name: Ruff Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run Ruff Lint
|
||||||
|
uses: chartboost/ruff-action@v1
|
4
.github/workflows/website-deploy.yml
vendored
4
.github/workflows/website-deploy.yml
vendored
@@ -13,7 +13,9 @@ jobs:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Python Environment
|
- name: Setup Python Environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
|
3
.github/workflows/website-preview.yml
vendored
3
.github/workflows/website-preview.yml
vendored
@@ -11,9 +11,10 @@ jobs:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Python Environment
|
- name: Setup Python Environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -139,7 +139,7 @@ fabric.properties
|
|||||||
.LSOverride
|
.LSOverride
|
||||||
|
|
||||||
# Icon must end with two \r
|
# Icon must end with two \r
|
||||||
Icon
|
# Icon
|
||||||
|
|
||||||
# Thumbnails
|
# Thumbnails
|
||||||
._*
|
._*
|
||||||
|
@@ -6,11 +6,12 @@ ci:
|
|||||||
autoupdate_schedule: monthly
|
autoupdate_schedule: monthly
|
||||||
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/hadialqattan/pycln
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v2.1.3
|
rev: v0.0.291
|
||||||
hooks:
|
hooks:
|
||||||
- id: pycln
|
- id: ruff
|
||||||
args: [--config, pyproject.toml]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 5.12.0
|
rev: 5.12.0
|
||||||
@@ -19,20 +20,20 @@ repos:
|
|||||||
stages: [commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.1.0
|
rev: 23.9.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
stages: [commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v3.0.0-alpha.6
|
rev: v3.0.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
||||||
stages: [commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/nonebot/nonemoji
|
- repo: https://github.com/nonebot/nonemoji
|
||||||
rev: v0.1.3
|
rev: v0.1.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: nonemoji
|
- id: nonemoji
|
||||||
stages: [prepare-commit-msg]
|
stages: [prepare-commit-msg]
|
||||||
|
31
.stylelintrc.js
Normal file
31
.stylelintrc.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ["stylelint-config-standard", "stylelint-prettier/recommended"],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["*.css"],
|
||||||
|
rules: {
|
||||||
|
"function-no-unknown": [true, { ignoreFunctions: ["theme"] }],
|
||||||
|
"selector-class-pattern": [
|
||||||
|
"^([a-z][a-z0-9]*)(-[a-z0-9]+)*$",
|
||||||
|
{
|
||||||
|
resolveNestedSelectors: true,
|
||||||
|
message: (selector) =>
|
||||||
|
`Expected class selector "${selector}" to be kebab-case`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["*.module.css"],
|
||||||
|
rules: {
|
||||||
|
"selector-class-pattern": [
|
||||||
|
"^[a-z][a-zA-Z0-9]+$",
|
||||||
|
{
|
||||||
|
message: (selector) =>
|
||||||
|
`Expected class selector "${selector}" to be lowerCamelCase`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
@@ -1,3 +1,3 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
See [changelog.md](./website/src/pages/changelog.md) or <https://v2.nonebot.dev/changelog>
|
See [changelog.md](./website/src/pages/changelog.md) or <https://nonebot.dev/changelog>
|
||||||
|
@@ -10,12 +10,10 @@
|
|||||||
|
|
||||||
### 报告问题、故障与漏洞
|
### 报告问题、故障与漏洞
|
||||||
|
|
||||||
NoneBot2 仍然是一个不够稳定的开发中项目,如果你在使用过程中发现问题并确信是由 NoneBot2 引起的,欢迎提交 Issue。
|
如果你在使用过程中发现问题并确信是由 NoneBot2 引起的,欢迎提交 Issue。
|
||||||
|
|
||||||
### 建议功能
|
### 建议功能
|
||||||
|
|
||||||
NoneBot2 还未进入正式版,欢迎在 Issue 中提议要加入哪些新功能。
|
|
||||||
|
|
||||||
为了让开发者更好地理解你的意图,请认真描述你所需要的特性,可能的话可以提出你认为可行的解决方案。
|
为了让开发者更好地理解你的意图,请认真描述你所需要的特性,可能的话可以提出你认为可行的解决方案。
|
||||||
|
|
||||||
## Pull Request
|
## Pull Request
|
||||||
@@ -84,7 +82,7 @@ NoneBot2 的代码风格遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/
|
|||||||
|
|
||||||
## 为社区做贡献
|
## 为社区做贡献
|
||||||
|
|
||||||
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://v2.nonebot.dev/docs/developer/plugin-publishing) 一节。
|
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://nonebot.dev/docs/developer/plugin-publishing) 一节。
|
||||||
|
|
||||||
我们仅对插件的兼容性进行简单测试,并会在下一个版本发布前对与该版本不兼容的插件作出处理。
|
我们仅对插件的兼容性进行简单测试,并会在下一个版本发布前对与该版本不兼容的插件作出处理。
|
||||||
|
|
||||||
|
87
README.md
87
README.md
@@ -1,6 +1,6 @@
|
|||||||
<!-- markdownlint-disable MD033 MD041 -->
|
<!-- markdownlint-disable MD033 MD041 -->
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://v2.nonebot.dev/"><img src="https://v2.nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
|
<a href="https://nonebot.dev/"><img src="https://nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
@@ -19,9 +19,19 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
|||||||
<img src="https://img.shields.io/github/license/nonebot/nonebot2" alt="license">
|
<img src="https://img.shields.io/github/license/nonebot/nonebot2" alt="license">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://pypi.python.org/pypi/nonebot2">
|
<a href="https://pypi.python.org/pypi/nonebot2">
|
||||||
<img src="https://img.shields.io/pypi/v/nonebot2" alt="pypi">
|
<img src="https://img.shields.io/pypi/v/nonebot2?logo=python&logoColor=edb641" alt="pypi">
|
||||||
</a>
|
</a>
|
||||||
<img src="https://img.shields.io/badge/python-3.8+-blue" alt="python">
|
<img src="https://img.shields.io/badge/python-3.8+-blue?logo=python&logoColor=edb641" alt="python">
|
||||||
|
<a href="https://github.com/psf/black">
|
||||||
|
<img src="https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=edb641" alt="black">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/Microsoft/pyright">
|
||||||
|
<img src="https://img.shields.io/badge/types-pyright-797952.svg?logo=python&logoColor=edb641" alt="pyright">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/astral-sh/ruff">
|
||||||
|
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="ruff">
|
||||||
|
</a>
|
||||||
|
<br />
|
||||||
<a href="https://codecov.io/gh/nonebot/nonebot2">
|
<a href="https://codecov.io/gh/nonebot/nonebot2">
|
||||||
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
|
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
|
||||||
</a>
|
</a>
|
||||||
@@ -31,6 +41,12 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
|||||||
<a href="https://results.pre-commit.ci/latest/github/nonebot/nonebot2/master">
|
<a href="https://results.pre-commit.ci/latest/github/nonebot/nonebot2/master">
|
||||||
<img src="https://results.pre-commit.ci/badge/github/nonebot/nonebot2/master.svg" alt="pre-commit" />
|
<img src="https://results.pre-commit.ci/badge/github/nonebot/nonebot2/master.svg" alt="pre-commit" />
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/nonebot/nonebot2/actions/workflows/pyright.yml">
|
||||||
|
<img src="https://github.com/nonebot/nonebot2/actions/workflows/pyright.yml/badge.svg?branch=master&event=push" alt="pyright">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/nonebot/nonebot2/actions/workflows/ruff.yml">
|
||||||
|
<img src="https://github.com/nonebot/nonebot2/actions/workflows/ruff.yml/badge.svg?branch=master&event=push" alt="ruff">
|
||||||
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<a href="https://onebot.dev/">
|
<a href="https://onebot.dev/">
|
||||||
<img src="https://img.shields.io/badge/OneBot-v11-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
|
<img src="https://img.shields.io/badge/OneBot-v11-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
|
||||||
@@ -49,9 +65,9 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
|||||||
</a>
|
</a>
|
||||||
<a href="https://bot.q.qq.com/wiki/">
|
<a href="https://bot.q.qq.com/wiki/">
|
||||||
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
|
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
|
||||||
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
|
|
||||||
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk">
|
|
||||||
</a>
|
</a>
|
||||||
|
<!-- <a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
|
||||||
|
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk"> -->
|
||||||
</a>
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
|
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
|
||||||
@@ -69,16 +85,16 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://v2.nonebot.dev/">文档</a>
|
<a href="https://nonebot.dev/">文档</a>
|
||||||
·
|
·
|
||||||
<a href="https://v2.nonebot.dev/docs/quick-start">快速上手</a>
|
<a href="https://nonebot.dev/docs/quick-start">快速上手</a>
|
||||||
·
|
·
|
||||||
<a href="#插件">文档打不开?</a>
|
<a href="#插件">文档打不开?</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://asciinema.org/a/569440">
|
<a href="https://asciinema.org/a/569440">
|
||||||
<img src="https://v2.nonebot.dev/img/setup.svg">
|
<img src="https://nonebot.dev/img/setup.svg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -90,36 +106,40 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
|
|
||||||
- 异步优先:基于 Python 的异步特性,即使是~~非常~~大量的消息,也能吞吐自如
|
- 异步优先:基于 Python 的异步特性,即使是~~非常~~大量的消息,也能吞吐自如
|
||||||
- 易于开发:配合 NB-CLI 脚手架,代码编写上手简单,没有过多的冗余代码,可以让开发者专注于业务逻辑
|
- 易于开发:配合 NB-CLI 脚手架,代码编写上手简单,没有过多的冗余代码,可以让开发者专注于业务逻辑
|
||||||
- 生而可靠:100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://v2.nonebot.dev/docs/editor-support))
|
- 生而可靠:100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://nonebot.dev/docs/editor-support))
|
||||||
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
|
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
|
||||||
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
||||||
|
|
||||||
| 协议名称 | 状态 | 注释 |
|
| 协议名称 | 状态 | 注释 |
|
||||||
| :-----------------------------------------------------------------------: | :--: | :----------------------------------------------------------------: |
|
| :--------------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
|
||||||
| [OneBot 协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号等[平台](https://onebot.dev/ecosystem.html) |
|
| OneBot([仓库](https://github.com/nonebot/adapter-onebot),[协议](https://onebot.dev/)) | ✅ | 支持 QQ、TG、微信公众号、KOOK 等[平台](https://onebot.dev/ecosystem.html) |
|
||||||
| [Telegram](https://core.telegram.org/bots/api) | ✅ | |
|
| Telegram([仓库](https://github.com/nonebot/adapter-telegram),[协议](https://core.telegram.org/bots/api)) | ✅ | |
|
||||||
| [飞书](https://open.feishu.cn/document/home/index) | ✅ | |
|
| 飞书([仓库](https://github.com/nonebot/adapter-feishu),[协议](https://open.feishu.cn/document/home/index)) | ✅ | |
|
||||||
| [GitHub](https://docs.github.com/en/developers/apps) | ✅ | GitHub APP & OAuth APP |
|
| GitHub([仓库](https://github.com/nonebot/adapter-github),[协议](https://docs.github.com/en/apps)) | ✅ | GitHub APP & OAuth APP |
|
||||||
| [QQ 频道](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 |
|
| QQ 频道([仓库](https://github.com/nonebot/adapter-qqguild),[协议](https://bot.q.qq.com/wiki/)) | ✅ | 官方接口调整较多 |
|
||||||
| [钉钉](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer |
|
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||||
| Console | ✅ | 控制台交互 |
|
| Console([仓库](https://github.com/nonebot/adapter-console)) | ✅ | 控制台交互 |
|
||||||
| [开黑啦](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
|
| Red ([仓库](https://github.com/nonebot/adapter-red),[协议](https://chrononeko.github.io/QQNTRedProtocol/)) | ✅ | QQ 协议 |
|
||||||
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | 由社区贡献 |
|
| Discord ([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
||||||
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | ↗️ | 由社区贡献 |
|
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||||
| [MineCraft (Spigot)](https://github.com/17TheWord/nonebot-adapter-spigot) | ↗️ | 由社区贡献 |
|
| Mirai([仓库](https://github.com/ieew/nonebot_adapter_mirai2),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ↗️ | QQ 协议,由社区贡献 |
|
||||||
| [BiliBili Live](https://github.com/wwweww/adapter-bilibili) | ↗️ | 由社区贡献 |
|
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||||
|
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||||
|
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||||
|
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
||||||
|
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa),[协议](https://webstatic.mihoyo.com/vila/bot/doc/)) | ↗️ | 米游社大别野 Bot 协议,由社区贡献 |
|
||||||
|
|
||||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||||
|
|
||||||
| 驱动框架 | 类型 |
|
| 驱动框架 | 类型 |
|
||||||
| :--------------------------------------------------------: | :----: |
|
| :-----------------------------------------------------------------: | :----: |
|
||||||
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
|
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
|
||||||
| [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask) | 服务端 |
|
| [Quart](https://quart.palletsprojects.com/en/latest/)(异步 Flask) | 服务端 |
|
||||||
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
|
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
|
||||||
| [httpx](https://www.python-httpx.org/) | 客户端 |
|
| [httpx](https://www.python-httpx.org/) | 客户端 |
|
||||||
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
|
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
|
||||||
|
|
||||||
更多:[概览](https://v2.nonebot.dev/docs/)
|
更多:[概览](https://nonebot.dev/docs/)
|
||||||
|
|
||||||
## 什么不是 NoneBot2
|
## 什么不是 NoneBot2
|
||||||
|
|
||||||
@@ -131,7 +151,7 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
|
|||||||
|
|
||||||
## 即刻开始
|
## 即刻开始
|
||||||
|
|
||||||
~~完整~~文档可以在 [这里](https://v2.nonebot.dev/) 查看。
|
~~完整~~文档可以在 [这里](https://nonebot.dev/) 查看。
|
||||||
|
|
||||||
懒得看文档?下面是快速安装指南:
|
懒得看文档?下面是快速安装指南:
|
||||||
|
|
||||||
@@ -186,9 +206,8 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
|
|||||||
或者尝试以下镜像:
|
或者尝试以下镜像:
|
||||||
|
|
||||||
- [文档镜像(中国境内)](https://nb2.baka.icu)
|
- [文档镜像(中国境内)](https://nb2.baka.icu)
|
||||||
- [文档镜像(Vercel)](https://nonebot2-vercel-mirror.vercel.app)
|
|
||||||
|
|
||||||
- 其他插件请查看 [商店](https://v2.nonebot.dev/store)
|
- 其他插件请查看 [商店](https://nonebot.dev/store/plugins)
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
@@ -207,7 +226,17 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
请参考 [贡献指南](./CONTRIBUTING.md)
|
请参考 [贡献指南](./CONTRIBUTING.md)
|
||||||
|
|
||||||
### 鸣谢
|
## 鸣谢
|
||||||
|
|
||||||
|
### 赞助者
|
||||||
|
|
||||||
|
感谢以下赞助者对 NoneBot 项目提供的资金支持:
|
||||||
|
|
||||||
|
<a href="https://assets.nonebot.dev/sponsors.svg">
|
||||||
|
<img src='https://assets.nonebot.dev/sponsors.svg'/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### 开发者
|
||||||
|
|
||||||
感谢以下开发者对 NoneBot2 作出的贡献:
|
感谢以下开发者对 NoneBot2 作出的贡献:
|
||||||
|
|
||||||
|
@@ -24,12 +24,17 @@
|
|||||||
- `load_all_plugins` => {ref}``load_all_plugins` <nonebot.plugin.load.load_all_plugins>`
|
- `load_all_plugins` => {ref}``load_all_plugins` <nonebot.plugin.load.load_all_plugins>`
|
||||||
- `load_from_json` => {ref}``load_from_json` <nonebot.plugin.load.load_from_json>`
|
- `load_from_json` => {ref}``load_from_json` <nonebot.plugin.load.load_from_json>`
|
||||||
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
||||||
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
- `load_builtin_plugin` =>
|
||||||
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
{ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
||||||
|
- `load_builtin_plugins` =>
|
||||||
|
{ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
||||||
- `get_plugin` => {ref}``get_plugin` <nonebot.plugin.get_plugin>`
|
- `get_plugin` => {ref}``get_plugin` <nonebot.plugin.get_plugin>`
|
||||||
- `get_plugin_by_module_name` => {ref}``get_plugin_by_module_name` <nonebot.plugin.get_plugin_by_module_name>`
|
- `get_plugin_by_module_name` =>
|
||||||
- `get_loaded_plugins` => {ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
|
{ref}``get_plugin_by_module_name` <nonebot.plugin.get_plugin_by_module_name>`
|
||||||
- `get_available_plugin_names` => {ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
|
- `get_loaded_plugins` =>
|
||||||
|
{ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
|
||||||
|
- `get_available_plugin_names` =>
|
||||||
|
{ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
|
||||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
@@ -48,7 +53,7 @@ from nonebot.config import Env, Config
|
|||||||
from nonebot.log import logger as logger
|
from nonebot.log import logger as logger
|
||||||
from nonebot.adapters import Bot, Adapter
|
from nonebot.adapters import Bot, Adapter
|
||||||
from nonebot.utils import escape_tag, resolve_dot_notation
|
from nonebot.utils import escape_tag, resolve_dot_notation
|
||||||
from nonebot.drivers import Driver, ReverseDriver, combine_driver
|
from nonebot.drivers import Driver, ASGIMixin, combine_driver
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__version__ = version("nonebot2")
|
__version__ = version("nonebot2")
|
||||||
@@ -69,7 +74,8 @@ def get_driver() -> Driver:
|
|||||||
全局 {ref}`nonebot.drivers.Driver` 对象
|
全局 {ref}`nonebot.drivers.Driver` 对象
|
||||||
|
|
||||||
异常:
|
异常:
|
||||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||||
|
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
@@ -83,23 +89,33 @@ def get_driver() -> Driver:
|
|||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_adapter(name: str) -> Adapter:
|
def get_adapter(name: str) -> Adapter:
|
||||||
...
|
"""
|
||||||
|
参数:
|
||||||
|
name: 适配器名称
|
||||||
|
|
||||||
|
返回:
|
||||||
|
指定名称的 {ref}`nonebot.adapters.Adapter` 对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_adapter(name: Type[A]) -> A:
|
def get_adapter(name: Type[A]) -> A:
|
||||||
...
|
"""
|
||||||
|
参数:
|
||||||
|
name: 适配器类型
|
||||||
|
|
||||||
|
返回:
|
||||||
|
指定类型的 {ref}`nonebot.adapters.Adapter` 对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_adapter(name: Union[str, Type[Adapter]]) -> Adapter:
|
def get_adapter(name: Union[str, Type[Adapter]]) -> Adapter:
|
||||||
"""获取已注册的 {ref}`nonebot.adapters.Adapter` 实例。
|
"""获取已注册的 {ref}`nonebot.adapters.Adapter` 实例。
|
||||||
|
|
||||||
返回:
|
|
||||||
指定名称或类型的 {ref}`nonebot.adapters.Adapter` 对象
|
|
||||||
|
|
||||||
异常:
|
异常:
|
||||||
ValueError: 指定的 {ref}`nonebot.adapters.Adapter` 未注册
|
ValueError: 指定的 {ref}`nonebot.adapters.Adapter` 未注册
|
||||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||||
|
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
@@ -121,7 +137,8 @@ def get_adapters() -> Dict[str, Adapter]:
|
|||||||
所有 {ref}`nonebot.adapters.Adapter` 实例字典
|
所有 {ref}`nonebot.adapters.Adapter` 实例字典
|
||||||
|
|
||||||
异常:
|
异常:
|
||||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||||
|
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
@@ -132,14 +149,15 @@ def get_adapters() -> Dict[str, Adapter]:
|
|||||||
|
|
||||||
|
|
||||||
def get_app() -> Any:
|
def get_app() -> Any:
|
||||||
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应的 Server App 对象。
|
"""获取全局 {ref}`nonebot.drivers.ASGIMixin` 对应的 Server App 对象。
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
Server App 对象
|
Server App 对象
|
||||||
|
|
||||||
异常:
|
异常:
|
||||||
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
|
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ASGIMixin` 类型
|
||||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||||
|
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
@@ -147,21 +165,21 @@ def get_app() -> Any:
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
assert isinstance(
|
assert isinstance(driver, ASGIMixin), "app object is only available for asgi driver"
|
||||||
driver, ReverseDriver
|
|
||||||
), "app object is only available for reverse driver"
|
|
||||||
return driver.server_app
|
return driver.server_app
|
||||||
|
|
||||||
|
|
||||||
def get_asgi() -> Any:
|
def get_asgi() -> Any:
|
||||||
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应 [ASGI](https://asgi.readthedocs.io/) 对象。
|
"""获取全局 {ref}`nonebot.drivers.ASGIMixin` 对应的
|
||||||
|
[ASGI](https://asgi.readthedocs.io/) 对象。
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
ASGI 对象
|
ASGI 对象
|
||||||
|
|
||||||
异常:
|
异常:
|
||||||
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
|
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ASGIMixin` 类型
|
||||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||||
|
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
@@ -170,8 +188,8 @@ def get_asgi() -> Any:
|
|||||||
"""
|
"""
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
driver, ReverseDriver
|
driver, ASGIMixin
|
||||||
), "asgi object is only available for reverse driver"
|
), "asgi object is only available for asgi driver"
|
||||||
return driver.asgi
|
return driver.asgi
|
||||||
|
|
||||||
|
|
||||||
@@ -182,7 +200,8 @@ def get_bot(self_id: Optional[str] = None) -> Bot:
|
|||||||
当不提供时,返回一个 {ref}`nonebot.adapters.Bot`。
|
当不提供时,返回一个 {ref}`nonebot.adapters.Bot`。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
self_id: 用来识别 {ref}`nonebot.adapters.Bot` 的 {ref}`nonebot.adapters.Bot.self_id` 属性
|
self_id: 用来识别 {ref}`nonebot.adapters.Bot` 的
|
||||||
|
{ref}`nonebot.adapters.Bot.self_id` 属性
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
{ref}`nonebot.adapters.Bot` 对象
|
{ref}`nonebot.adapters.Bot` 对象
|
||||||
@@ -190,7 +209,8 @@ def get_bot(self_id: Optional[str] = None) -> Bot:
|
|||||||
异常:
|
异常:
|
||||||
KeyError: 对应 self_id 的 Bot 不存在
|
KeyError: 对应 self_id 的 Bot 不存在
|
||||||
ValueError: 没有传入 self_id 且没有 Bot 可用
|
ValueError: 没有传入 self_id 且没有 Bot 可用
|
||||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||||
|
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
@@ -213,10 +233,12 @@ def get_bots() -> Dict[str, Bot]:
|
|||||||
"""获取所有连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
|
"""获取所有连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
一个以 {ref}`nonebot.adapters.Bot.self_id` 为键,{ref}`nonebot.adapters.Bot` 对象为值的字典
|
一个以 {ref}`nonebot.adapters.Bot.self_id` 为键
|
||||||
|
{ref}`nonebot.adapters.Bot` 对象为值的字典
|
||||||
|
|
||||||
异常:
|
异常:
|
||||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||||
|
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
|
@@ -19,6 +19,11 @@ __autodoc__ = {
|
|||||||
"Event": True,
|
"Event": True,
|
||||||
"Adapter": True,
|
"Adapter": True,
|
||||||
"Message": True,
|
"Message": True,
|
||||||
|
"Message.__getitem__": True,
|
||||||
|
"Message.__contains__": True,
|
||||||
|
"Message._construct": True,
|
||||||
"MessageSegment": True,
|
"MessageSegment": True,
|
||||||
|
"MessageSegment.__str__": True,
|
||||||
|
"MessageSegment.__add__": True,
|
||||||
"MessageTemplate": True,
|
"MessageTemplate": True,
|
||||||
}
|
}
|
||||||
|
@@ -1,19 +1,23 @@
|
|||||||
"""本模块定义了 NoneBot 本身运行所需的配置项。
|
"""本模块定义了 NoneBot 本身运行所需的配置项。
|
||||||
|
|
||||||
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
|
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
|
||||||
|
[`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
|
||||||
|
|
||||||
配置项需符合特殊格式或 json 序列化格式。详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
配置项需符合特殊格式或 json 序列化格式
|
||||||
|
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.config 模块
|
description: nonebot.config 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
|
from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
|
||||||
|
|
||||||
from pydantic.utils import deep_update
|
from pydantic.utils import deep_update
|
||||||
|
from pydantic.fields import Undefined, UndefinedType
|
||||||
from pydantic import Extra, Field, BaseSettings, IPvAnyAddress
|
from pydantic import Extra, Field, BaseSettings, IPvAnyAddress
|
||||||
from pydantic.env_settings import (
|
from pydantic.env_settings import (
|
||||||
DotenvType,
|
DotenvType,
|
||||||
@@ -28,9 +32,8 @@ from nonebot.log import logger
|
|||||||
|
|
||||||
class CustomEnvSettings(EnvSettingsSource):
|
class CustomEnvSettings(EnvSettingsSource):
|
||||||
def __call__(self, settings: BaseSettings) -> Dict[str, Any]:
|
def __call__(self, settings: BaseSettings) -> Dict[str, Any]:
|
||||||
"""
|
"""从环境变量和 dotenv 配置文件中读取配置项。"""
|
||||||
Build environment variables suitable for passing to the Model.
|
|
||||||
"""
|
|
||||||
d: Dict[str, Any] = {}
|
d: Dict[str, Any] = {}
|
||||||
|
|
||||||
if settings.__config__.case_sensitive:
|
if settings.__config__.case_sensitive:
|
||||||
@@ -42,21 +45,25 @@ class CustomEnvSettings(EnvSettingsSource):
|
|||||||
env_vars = {**env_file_vars, **env_vars}
|
env_vars = {**env_file_vars, **env_vars}
|
||||||
|
|
||||||
for field in settings.__fields__.values():
|
for field in settings.__fields__.values():
|
||||||
env_val: Optional[str] = None
|
env_val: Union[str, None, UndefinedType] = Undefined
|
||||||
for env_name in field.field_info.extra["env_names"]:
|
for env_name in field.field_info.extra["env_names"]:
|
||||||
env_val = env_vars.get(env_name)
|
env_val = env_vars.get(env_name, Undefined)
|
||||||
if env_name in env_file_vars:
|
if env_name in env_file_vars:
|
||||||
del env_file_vars[env_name]
|
del env_file_vars[env_name]
|
||||||
if env_val is not None:
|
if env_val is not Undefined:
|
||||||
break
|
break
|
||||||
|
|
||||||
is_complex, allow_parse_failure = self.field_is_complex(field)
|
is_complex, allow_parse_failure = self.field_is_complex(field)
|
||||||
if is_complex:
|
if is_complex:
|
||||||
if env_val is None:
|
if isinstance(env_val, UndefinedType):
|
||||||
|
# field is complex but no value found so far, try explode_env_vars
|
||||||
if env_val_built := self.explode_env_vars(field, env_vars):
|
if env_val_built := self.explode_env_vars(field, env_vars):
|
||||||
d[field.alias] = env_val_built
|
d[field.alias] = env_val_built
|
||||||
|
elif env_val is None:
|
||||||
|
d[field.alias] = env_val
|
||||||
else:
|
else:
|
||||||
# field is complex and there's a value, decode that as JSON, then add explode_env_vars
|
# field is complex and there's a value
|
||||||
|
# decode that as JSON, then add explode_env_vars
|
||||||
try:
|
try:
|
||||||
env_val = settings.__config__.parse_env_var(field.name, env_val)
|
env_val = settings.__config__.parse_env_var(field.name, env_val)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -71,8 +78,9 @@ class CustomEnvSettings(EnvSettingsSource):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
d[field.alias] = env_val
|
d[field.alias] = env_val
|
||||||
elif env_val is not None:
|
elif not isinstance(env_val, UndefinedType):
|
||||||
# simplest case, field is not complex, we only need to add the value if it was found
|
# simplest case, field is not complex
|
||||||
|
# we only need to add the value if it was found
|
||||||
d[field.alias] = env_val
|
d[field.alias] = env_val
|
||||||
|
|
||||||
# remain user custom config
|
# remain user custom config
|
||||||
@@ -82,7 +90,7 @@ class CustomEnvSettings(EnvSettingsSource):
|
|||||||
# there's a value, decode that as JSON
|
# there's a value, decode that as JSON
|
||||||
try:
|
try:
|
||||||
env_val = settings.__config__.parse_env_var(env_name, val_striped)
|
env_val = settings.__config__.parse_env_var(env_name, val_striped)
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
logger.trace(
|
logger.trace(
|
||||||
"Error while parsing JSON for "
|
"Error while parsing JSON for "
|
||||||
f"{env_name!r}={val_striped!r}. "
|
f"{env_name!r}={val_striped!r}. "
|
||||||
@@ -139,7 +147,7 @@ class BaseConfig(BaseSettings):
|
|||||||
class Env(BaseConfig):
|
class Env(BaseConfig):
|
||||||
"""运行环境配置。大小写不敏感。
|
"""运行环境配置。大小写不敏感。
|
||||||
|
|
||||||
将会从 `环境变量` > `.env 环境配置文件` 的优先级读取环境信息。
|
将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
environment: str = "prod"
|
environment: str = "prod"
|
||||||
@@ -158,7 +166,7 @@ class Config(BaseConfig):
|
|||||||
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
|
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
|
||||||
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
|
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
|
||||||
|
|
||||||
配置方法参考: [配置](https://v2.nonebot.dev/docs/appendices/config)
|
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_env_file: DotenvType = ".env", ".env.prod"
|
_env_file: DotenvType = ".env", ".env.prod"
|
||||||
@@ -170,15 +178,17 @@ class Config(BaseConfig):
|
|||||||
配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`。
|
配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`。
|
||||||
|
|
||||||
`~` 为 `nonebot.drivers.` 的缩写。
|
`~` 为 `nonebot.drivers.` 的缩写。
|
||||||
|
|
||||||
|
配置方法参考: [配置驱动器](https://nonebot.dev/docs/advanced/driver#%E9%85%8D%E7%BD%AE%E9%A9%B1%E5%8A%A8%E5%99%A8)
|
||||||
"""
|
"""
|
||||||
host: IPvAnyAddress = IPv4Address("127.0.0.1") # type: ignore
|
host: IPvAnyAddress = IPv4Address("127.0.0.1") # type: ignore
|
||||||
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的 IP/主机名。"""
|
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的 IP/主机名。"""
|
||||||
port: int = Field(default=8080, ge=1, le=65535)
|
port: int = Field(default=8080, ge=1, le=65535)
|
||||||
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的端口。"""
|
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的端口。"""
|
||||||
log_level: Union[int, str] = "INFO"
|
log_level: Union[int, str] = "INFO"
|
||||||
"""NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称
|
"""NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称。
|
||||||
|
|
||||||
参考 [`loguru 日志等级`](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
参考 [记录日志](https://nonebot.dev/docs/appendices/log),[loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
||||||
|
|
||||||
:::tip 提示
|
:::tip 提示
|
||||||
日志等级名称应为大写,如 `INFO`。
|
日志等级名称应为大写,如 `INFO`。
|
||||||
@@ -209,6 +219,8 @@ class Config(BaseConfig):
|
|||||||
command_start: Set[str] = {"/"}
|
command_start: Set[str] = {"/"}
|
||||||
"""命令的起始标记,用于判断一条消息是不是命令。
|
"""命令的起始标记,用于判断一条消息是不是命令。
|
||||||
|
|
||||||
|
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```conf
|
```conf
|
||||||
COMMAND_START=["/", ""]
|
COMMAND_START=["/", ""]
|
||||||
@@ -217,6 +229,8 @@ class Config(BaseConfig):
|
|||||||
command_sep: Set[str] = {"."}
|
command_sep: Set[str] = {"."}
|
||||||
"""命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
"""命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
||||||
|
|
||||||
|
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```conf
|
```conf
|
||||||
COMMAND_SEP=["."]
|
COMMAND_SEP=["."]
|
||||||
|
@@ -4,6 +4,7 @@ FrontMatter:
|
|||||||
sidebar_position: 9
|
sidebar_position: 9
|
||||||
description: nonebot.consts 模块
|
description: nonebot.consts 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
@@ -42,12 +43,6 @@ SHELL_ARGV: Literal["_argv"] = "_argv"
|
|||||||
|
|
||||||
REGEX_MATCHED: Literal["_matched"] = "_matched"
|
REGEX_MATCHED: Literal["_matched"] = "_matched"
|
||||||
"""正则匹配结果存储 key"""
|
"""正则匹配结果存储 key"""
|
||||||
REGEX_STR: Literal["_matched_str"] = "_matched_str"
|
|
||||||
"""正则匹配文本存储 key"""
|
|
||||||
REGEX_GROUP: Literal["_matched_groups"] = "_matched_groups"
|
|
||||||
"""正则匹配 group 元组存储 key"""
|
|
||||||
REGEX_DICT: Literal["_matched_dict"] = "_matched_dict"
|
|
||||||
"""正则匹配 group 字典存储 key"""
|
|
||||||
STARTSWITH_KEY: Literal["_startswith"] = "_startswith"
|
STARTSWITH_KEY: Literal["_startswith"] = "_startswith"
|
||||||
"""响应触发前缀 key"""
|
"""响应触发前缀 key"""
|
||||||
ENDSWITH_KEY: Literal["_endswith"] = "_endswith"
|
ENDSWITH_KEY: Literal["_endswith"] = "_endswith"
|
||||||
|
@@ -45,6 +45,10 @@ class Param(abc.ABC, FieldInfo):
|
|||||||
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
|
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, validate: bool = False, **kwargs: Any) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.validate = validate
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
|
||||||
@@ -82,8 +86,8 @@ class Dependent(Generic[R]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
call: _DependentCallable[R]
|
call: _DependentCallable[R]
|
||||||
params: Tuple[ModelField] = field(default_factory=tuple)
|
params: Tuple[ModelField, ...] = field(default_factory=tuple)
|
||||||
parameterless: Tuple[Param] = field(default_factory=tuple)
|
parameterless: Tuple[Param, ...] = field(default_factory=tuple)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
if inspect.isfunction(self.call) or inspect.isclass(self.call):
|
if inspect.isfunction(self.call) or inspect.isclass(self.call):
|
||||||
@@ -97,6 +101,7 @@ class Dependent(Generic[R]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def __call__(self, **kwargs: Any) -> R:
|
async def __call__(self, **kwargs: Any) -> R:
|
||||||
|
try:
|
||||||
# do pre-check
|
# do pre-check
|
||||||
await self.check(**kwargs)
|
await self.check(**kwargs)
|
||||||
|
|
||||||
@@ -108,11 +113,14 @@ 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
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_params(
|
def parse_params(
|
||||||
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
|
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Tuple[ModelField]:
|
) -> Tuple[ModelField, ...]:
|
||||||
fields: List[ModelField] = []
|
fields: List[ModelField] = []
|
||||||
params = get_typed_signature(call).parameters.values()
|
params = get_typed_signature(call).parameters.values()
|
||||||
|
|
||||||
@@ -129,7 +137,8 @@ class Dependent(Generic[R]):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unknown parameter {param.name} for function {call} with type {param.annotation}"
|
f"Unknown parameter {param.name} "
|
||||||
|
f"for function {call} with type {param.annotation}"
|
||||||
)
|
)
|
||||||
|
|
||||||
default_value = field_info.default
|
default_value = field_info.default
|
||||||
@@ -182,7 +191,7 @@ class Dependent(Generic[R]):
|
|||||||
|
|
||||||
params = cls.parse_params(call, allow_types)
|
params = cls.parse_params(call, allow_types)
|
||||||
parameterless_params = (
|
parameterless_params = (
|
||||||
tuple()
|
()
|
||||||
if parameterless is None
|
if parameterless is None
|
||||||
else cls.parse_parameterless(tuple(parameterless), allow_types)
|
else cls.parse_parameterless(tuple(parameterless), allow_types)
|
||||||
)
|
)
|
||||||
@@ -190,25 +199,18 @@ 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:
|
||||||
try:
|
await asyncio.gather(*(param._check(**params) for param in self.parameterless))
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
*(param._check(**params) for param in self.parameterless)
|
*(cast(Param, param.field_info)._check(**params) for param in self.params)
|
||||||
)
|
)
|
||||||
await asyncio.gather(
|
|
||||||
*(
|
|
||||||
cast(Param, param.field_info)._check(**params)
|
|
||||||
for param in self.params
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except SkippedException as e:
|
|
||||||
logger.trace(f"{self} skipped due to {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
||||||
value = await cast(Param, field.field_info)._solve(**params)
|
param = cast(Param, field.field_info)
|
||||||
|
value = await param._solve(**params)
|
||||||
if value is Undefined:
|
if value is Undefined:
|
||||||
value = field.get_default()
|
value = field.get_default()
|
||||||
return check_field_type(field, value)
|
v = check_field_type(field, value)
|
||||||
|
return v if param.validate else value
|
||||||
|
|
||||||
async def solve(self, **params: Any) -> Dict[str, Any]:
|
async def solve(self, **params: Any) -> Dict[str, Any]:
|
||||||
# solve parameterless
|
# solve parameterless
|
||||||
|
@@ -3,8 +3,9 @@ FrontMatter:
|
|||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.dependencies.utils 模块
|
description: nonebot.dependencies.utils 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, Dict, TypeVar, Callable, ForwardRef
|
from typing import Any, Dict, Callable, ForwardRef
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic.fields import ModelField
|
from pydantic.fields import ModelField
|
||||||
@@ -12,11 +13,10 @@ from pydantic.typing import evaluate_forwardref
|
|||||||
|
|
||||||
from nonebot.exception import TypeMisMatch
|
from nonebot.exception import TypeMisMatch
|
||||||
|
|
||||||
V = TypeVar("V")
|
|
||||||
|
|
||||||
|
|
||||||
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||||
"""获取可调用对象签名"""
|
"""获取可调用对象签名"""
|
||||||
|
|
||||||
signature = inspect.signature(call)
|
signature = inspect.signature(call)
|
||||||
globalns = getattr(call, "__globals__", {})
|
globalns = getattr(call, "__globals__", {})
|
||||||
typed_params = [
|
typed_params = [
|
||||||
@@ -33,6 +33,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
|||||||
|
|
||||||
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
||||||
"""获取参数的类型注解"""
|
"""获取参数的类型注解"""
|
||||||
|
|
||||||
annotation = param.annotation
|
annotation = param.annotation
|
||||||
if isinstance(annotation, str):
|
if isinstance(annotation, str):
|
||||||
annotation = ForwardRef(annotation)
|
annotation = ForwardRef(annotation)
|
||||||
@@ -46,8 +47,10 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) ->
|
|||||||
return annotation
|
return annotation
|
||||||
|
|
||||||
|
|
||||||
def check_field_type(field: ModelField, value: V) -> V:
|
def check_field_type(field: ModelField, value: Any) -> Any:
|
||||||
_, errs_ = field.validate(value, {}, loc=())
|
"""检查字段类型是否匹配"""
|
||||||
|
|
||||||
|
v, errs_ = field.validate(value, {}, loc=())
|
||||||
if errs_:
|
if errs_:
|
||||||
raise TypeMisMatch(field, value)
|
raise TypeMisMatch(field, value)
|
||||||
return value
|
return v
|
||||||
|
@@ -8,30 +8,40 @@ FrontMatter:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
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 Driver as Driver
|
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 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 WebSocket as WebSocket
|
||||||
from nonebot.internal.driver import HTTPVersion as HTTPVersion
|
from nonebot.internal.driver import HTTPVersion as HTTPVersion
|
||||||
from nonebot.internal.driver import ForwardMixin as ForwardMixin
|
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 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 combine_driver as combine_driver
|
||||||
|
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
|
||||||
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
|
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
|
||||||
|
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
|
||||||
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"URL": True,
|
"URL": True,
|
||||||
"Driver": True,
|
|
||||||
"Cookies": True,
|
"Cookies": True,
|
||||||
"Request": True,
|
"Request": True,
|
||||||
"Response": True,
|
"Response": True,
|
||||||
"WebSocket": True,
|
"WebSocket": True,
|
||||||
"HTTPVersion": True,
|
"HTTPVersion": True,
|
||||||
|
"Driver": True,
|
||||||
|
"Mixin": True,
|
||||||
"ForwardMixin": True,
|
"ForwardMixin": True,
|
||||||
"ForwardDriver": True,
|
"ForwardDriver": True,
|
||||||
|
"HTTPClientMixin": True,
|
||||||
|
"WebSocketClientMixin": True,
|
||||||
|
"ReverseMixin": True,
|
||||||
"ReverseDriver": True,
|
"ReverseDriver": True,
|
||||||
|
"ASGIMixin": True,
|
||||||
"combine_driver": True,
|
"combine_driver": True,
|
||||||
"HTTPServerSetup": True,
|
"HTTPServerSetup": True,
|
||||||
"WebSocketServerSetup": True,
|
"WebSocketServerSetup": True,
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
|
from typing_extensions import TypeAlias
|
||||||
from typing import Any, List, Union, Callable, Awaitable, cast
|
from typing import Any, List, Union, Callable, Awaitable, cast
|
||||||
|
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
from nonebot.utils import run_sync, is_coroutine_callable
|
||||||
|
|
||||||
SYNC_LIFESPAN_FUNC = Callable[[], Any]
|
SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any]
|
||||||
ASYNC_LIFESPAN_FUNC = Callable[[], Awaitable[Any]]
|
ASYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Awaitable[Any]]
|
||||||
LIFESPAN_FUNC = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
|
LIFESPAN_FUNC: TypeAlias = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
|
||||||
|
|
||||||
|
|
||||||
class Lifespan:
|
class Lifespan:
|
||||||
|
@@ -15,33 +15,39 @@ FrontMatter:
|
|||||||
description: nonebot.drivers.aiohttp 模块
|
description: nonebot.drivers.aiohttp 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Type, AsyncGenerator
|
from typing_extensions import override
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
from typing import TYPE_CHECKING, AsyncGenerator
|
||||||
|
|
||||||
from nonebot.typing import overrides
|
|
||||||
from nonebot.drivers import Request, Response
|
from nonebot.drivers import Request, Response
|
||||||
from nonebot.exception import WebSocketClosed
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
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 HTTPVersion, ForwardMixin, ForwardDriver, combine_driver
|
from nonebot.drivers import (
|
||||||
|
HTTPVersion,
|
||||||
|
HTTPClientMixin,
|
||||||
|
WebSocketClientMixin,
|
||||||
|
combine_driver,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import aiohttp
|
import aiohttp
|
||||||
except ModuleNotFoundError as e: # pragma: no cover
|
except ModuleNotFoundError as e: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`"
|
"Please install aiohttp first to use this driver. "
|
||||||
|
"Install with pip: `pip install nonebot2[aiohttp]`"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
|
||||||
class Mixin(ForwardMixin):
|
class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
||||||
"""AIOHTTP Mixin"""
|
"""AIOHTTP Mixin"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ForwardMixin)
|
@override
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
return "aiohttp"
|
return "aiohttp"
|
||||||
|
|
||||||
@overrides(ForwardMixin)
|
@override
|
||||||
async def request(self, setup: Request) -> Response:
|
async def request(self, setup: Request) -> Response:
|
||||||
if setup.version == HTTPVersion.H10:
|
if setup.version == HTTPVersion.H10:
|
||||||
version = aiohttp.HttpVersion10
|
version = aiohttp.HttpVersion10
|
||||||
@@ -51,11 +57,12 @@ class Mixin(ForwardMixin):
|
|||||||
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
|
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
|
||||||
|
|
||||||
timeout = aiohttp.ClientTimeout(setup.timeout)
|
timeout = aiohttp.ClientTimeout(setup.timeout)
|
||||||
files = None
|
|
||||||
|
data = setup.data
|
||||||
if setup.files:
|
if setup.files:
|
||||||
files = aiohttp.FormData()
|
data = aiohttp.FormData(data or {})
|
||||||
for name, file in setup.files:
|
for name, file in setup.files:
|
||||||
files.add_field(name, file[1], content_type=file[2], filename=file[0])
|
data.add_field(name, file[1], content_type=file[2], filename=file[0])
|
||||||
|
|
||||||
cookies = {
|
cookies = {
|
||||||
cookie.name: cookie.value for cookie in setup.cookies if cookie.value
|
cookie.name: cookie.value for cookie in setup.cookies if cookie.value
|
||||||
@@ -66,7 +73,7 @@ class Mixin(ForwardMixin):
|
|||||||
async with session.request(
|
async with session.request(
|
||||||
setup.method,
|
setup.method,
|
||||||
setup.url,
|
setup.url,
|
||||||
data=setup.content or setup.data or files,
|
data=setup.content or data,
|
||||||
json=setup.json,
|
json=setup.json,
|
||||||
headers=setup.headers,
|
headers=setup.headers,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
@@ -79,7 +86,7 @@ class Mixin(ForwardMixin):
|
|||||||
request=setup,
|
request=setup,
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides(ForwardMixin)
|
@override
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
||||||
if setup.version == HTTPVersion.H10:
|
if setup.version == HTTPVersion.H10:
|
||||||
@@ -115,15 +122,15 @@ class WebSocket(BaseWebSocket):
|
|||||||
self.websocket = websocket
|
self.websocket = websocket
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
def closed(self):
|
def closed(self):
|
||||||
return self.websocket.closed
|
return self.websocket.closed
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def accept(self):
|
async def accept(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def close(self, code: int = 1000):
|
async def close(self, code: int = 1000):
|
||||||
await self.websocket.close(code=code)
|
await self.websocket.close(code=code)
|
||||||
await self.session.close()
|
await self.session.close()
|
||||||
@@ -134,7 +141,7 @@ class WebSocket(BaseWebSocket):
|
|||||||
raise WebSocketClosed(self.websocket.close_code or 1006)
|
raise WebSocketClosed(self.websocket.close_code or 1006)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def receive(self) -> str:
|
async def receive(self) -> str:
|
||||||
msg = await self._receive()
|
msg = await self._receive()
|
||||||
if msg.type not in (aiohttp.WSMsgType.TEXT, aiohttp.WSMsgType.BINARY):
|
if msg.type not in (aiohttp.WSMsgType.TEXT, aiohttp.WSMsgType.BINARY):
|
||||||
@@ -143,7 +150,7 @@ class WebSocket(BaseWebSocket):
|
|||||||
)
|
)
|
||||||
return msg.data
|
return msg.data
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def receive_text(self) -> str:
|
async def receive_text(self) -> str:
|
||||||
msg = await self._receive()
|
msg = await self._receive()
|
||||||
if msg.type != aiohttp.WSMsgType.TEXT:
|
if msg.type != aiohttp.WSMsgType.TEXT:
|
||||||
@@ -152,7 +159,7 @@ class WebSocket(BaseWebSocket):
|
|||||||
)
|
)
|
||||||
return msg.data
|
return msg.data
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def receive_bytes(self) -> bytes:
|
async def receive_bytes(self) -> bytes:
|
||||||
msg = await self._receive()
|
msg = await self._receive()
|
||||||
if msg.type != aiohttp.WSMsgType.BINARY:
|
if msg.type != aiohttp.WSMsgType.BINARY:
|
||||||
@@ -161,14 +168,20 @@ class WebSocket(BaseWebSocket):
|
|||||||
)
|
)
|
||||||
return msg.data
|
return msg.data
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def send_text(self, data: str) -> None:
|
async def send_text(self, data: str) -> None:
|
||||||
await self.websocket.send_str(data)
|
await self.websocket.send_str(data)
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def send_bytes(self, data: bytes) -> None:
|
async def send_bytes(self, data: bytes) -> None:
|
||||||
await self.websocket.send_bytes(data)
|
await self.websocket.send_bytes(data)
|
||||||
|
|
||||||
|
|
||||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
class Driver(Mixin, NoneDriver):
|
||||||
|
...
|
||||||
|
|
||||||
|
else:
|
||||||
|
Driver = combine_driver(NoneDriver, Mixin)
|
||||||
"""AIOHTTP Driver"""
|
"""AIOHTTP Driver"""
|
||||||
|
@@ -19,18 +19,20 @@ FrontMatter:
|
|||||||
import logging
|
import logging
|
||||||
import contextlib
|
import contextlib
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from typing_extensions import override
|
||||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||||
|
|
||||||
from pydantic import BaseSettings
|
from pydantic import BaseSettings
|
||||||
|
|
||||||
from nonebot.config import Env
|
from nonebot.config import Env
|
||||||
from nonebot.typing import overrides
|
from nonebot.drivers import ASGIMixin
|
||||||
from nonebot.exception import WebSocketClosed
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.internal.driver import FileTypes
|
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.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.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
|
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||||
|
|
||||||
from ._lifespan import LIFESPAN_FUNC, Lifespan
|
from ._lifespan import LIFESPAN_FUNC, Lifespan
|
||||||
|
|
||||||
@@ -41,7 +43,8 @@ try:
|
|||||||
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
||||||
except ModuleNotFoundError as e: # pragma: no cover
|
except ModuleNotFoundError as e: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install FastAPI by using `pip install nonebot2[fastapi]`"
|
"Please install FastAPI first to use this driver. "
|
||||||
|
"Install with pip: `pip install nonebot2[fastapi]`"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
|
||||||
@@ -86,11 +89,11 @@ class Config(BaseSettings):
|
|||||||
extra = "ignore"
|
extra = "ignore"
|
||||||
|
|
||||||
|
|
||||||
class Driver(ReverseDriver):
|
class Driver(BaseDriver, ASGIMixin):
|
||||||
"""FastAPI 驱动框架。"""
|
"""FastAPI 驱动框架。"""
|
||||||
|
|
||||||
def __init__(self, env: Env, config: NoneBotConfig):
|
def __init__(self, env: Env, config: NoneBotConfig):
|
||||||
super(Driver, self).__init__(env, config)
|
super().__init__(env, config)
|
||||||
|
|
||||||
self.fastapi_config: Config = Config(**config.dict())
|
self.fastapi_config: Config = Config(**config.dict())
|
||||||
|
|
||||||
@@ -105,30 +108,30 @@ class Driver(ReverseDriver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
"""驱动名称: `fastapi`"""
|
"""驱动名称: `fastapi`"""
|
||||||
return "fastapi"
|
return "fastapi"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def server_app(self) -> FastAPI:
|
def server_app(self) -> FastAPI:
|
||||||
"""`FastAPI APP` 对象"""
|
"""`FastAPI APP` 对象"""
|
||||||
return self._server_app
|
return self._server_app
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def asgi(self) -> FastAPI:
|
def asgi(self) -> FastAPI:
|
||||||
"""`FastAPI APP` 对象"""
|
"""`FastAPI APP` 对象"""
|
||||||
return self._server_app
|
return self._server_app
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def logger(self) -> logging.Logger:
|
def logger(self) -> logging.Logger:
|
||||||
"""fastapi 使用的 logger"""
|
"""fastapi 使用的 logger"""
|
||||||
return logging.getLogger("fastapi")
|
return logging.getLogger("fastapi")
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def setup_http_server(self, setup: HTTPServerSetup):
|
def setup_http_server(self, setup: HTTPServerSetup):
|
||||||
async def _handle(request: Request) -> Response:
|
async def _handle(request: Request) -> Response:
|
||||||
return await self._handle_http(request, setup)
|
return await self._handle_http(request, setup)
|
||||||
@@ -141,7 +144,7 @@ class Driver(ReverseDriver):
|
|||||||
include_in_schema=self.fastapi_config.fastapi_include_adapter_schema,
|
include_in_schema=self.fastapi_config.fastapi_include_adapter_schema,
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def setup_websocket_server(self, setup: WebSocketServerSetup) -> None:
|
def setup_websocket_server(self, setup: WebSocketServerSetup) -> None:
|
||||||
async def _handle(websocket: WebSocket) -> None:
|
async def _handle(websocket: WebSocket) -> None:
|
||||||
await self._handle_ws(websocket, setup)
|
await self._handle_ws(websocket, setup)
|
||||||
@@ -152,11 +155,11 @@ class Driver(ReverseDriver):
|
|||||||
name=setup.name,
|
name=setup.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
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)
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
return self._lifespan.on_shutdown(func)
|
return self._lifespan.on_shutdown(func)
|
||||||
|
|
||||||
@@ -168,7 +171,7 @@ class Driver(ReverseDriver):
|
|||||||
finally:
|
finally:
|
||||||
await self._lifespan.shutdown()
|
await self._lifespan.shutdown()
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def run(
|
def run(
|
||||||
self,
|
self,
|
||||||
host: Optional[str] = None,
|
host: Optional[str] = None,
|
||||||
@@ -178,7 +181,7 @@ class Driver(ReverseDriver):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""使用 `uvicorn` 启动 FastAPI"""
|
"""使用 `uvicorn` 启动 FastAPI"""
|
||||||
super().run(host, port, app, **kwargs)
|
super().run(host, port, app=app, **kwargs)
|
||||||
LOGGING_CONFIG = {
|
LOGGING_CONFIG = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"disable_existing_loggers": False,
|
"disable_existing_loggers": False,
|
||||||
@@ -267,30 +270,30 @@ class Driver(ReverseDriver):
|
|||||||
class FastAPIWebSocket(BaseWebSocket):
|
class FastAPIWebSocket(BaseWebSocket):
|
||||||
"""FastAPI WebSocket Wrapper"""
|
"""FastAPI WebSocket Wrapper"""
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
def __init__(self, *, request: BaseRequest, websocket: WebSocket):
|
def __init__(self, *, request: BaseRequest, websocket: WebSocket):
|
||||||
super().__init__(request=request)
|
super().__init__(request=request)
|
||||||
self.websocket = websocket
|
self.websocket = websocket
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
def closed(self) -> bool:
|
def closed(self) -> bool:
|
||||||
return (
|
return (
|
||||||
self.websocket.client_state == WebSocketState.DISCONNECTED
|
self.websocket.client_state == WebSocketState.DISCONNECTED
|
||||||
or self.websocket.application_state == WebSocketState.DISCONNECTED
|
or self.websocket.application_state == WebSocketState.DISCONNECTED
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def accept(self) -> None:
|
async def accept(self) -> None:
|
||||||
await self.websocket.accept()
|
await self.websocket.accept()
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def close(
|
async def close(
|
||||||
self, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = ""
|
self, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = ""
|
||||||
) -> None:
|
) -> None:
|
||||||
await self.websocket.close(code, reason)
|
await self.websocket.close(code, reason)
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def receive(self) -> Union[str, bytes]:
|
async def receive(self) -> Union[str, bytes]:
|
||||||
# assert self.websocket.application_state == WebSocketState.CONNECTED
|
# assert self.websocket.application_state == WebSocketState.CONNECTED
|
||||||
msg = await self.websocket.receive()
|
msg = await self.websocket.receive()
|
||||||
@@ -298,21 +301,21 @@ class FastAPIWebSocket(BaseWebSocket):
|
|||||||
raise WebSocketClosed(msg["code"])
|
raise WebSocketClosed(msg["code"])
|
||||||
return msg["text"] if "text" in msg else msg["bytes"]
|
return msg["text"] if "text" in msg else msg["bytes"]
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
@catch_closed
|
@catch_closed
|
||||||
async def receive_text(self) -> str:
|
async def receive_text(self) -> str:
|
||||||
return await self.websocket.receive_text()
|
return await self.websocket.receive_text()
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
@catch_closed
|
@catch_closed
|
||||||
async def receive_bytes(self) -> bytes:
|
async def receive_bytes(self) -> bytes:
|
||||||
return await self.websocket.receive_bytes()
|
return await self.websocket.receive_bytes()
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def send_text(self, data: str) -> None:
|
async def send_text(self, data: str) -> None:
|
||||||
await self.websocket.send({"type": "websocket.send", "text": data})
|
await self.websocket.send({"type": "websocket.send", "text": data})
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def send_bytes(self, data: bytes) -> None:
|
async def send_bytes(self, data: bytes) -> None:
|
||||||
await self.websocket.send({"type": "websocket.send", "bytes": data})
|
await self.websocket.send({"type": "websocket.send", "bytes": data})
|
||||||
|
|
||||||
|
@@ -14,18 +14,16 @@ FrontMatter:
|
|||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.drivers.httpx 模块
|
description: nonebot.drivers.httpx 模块
|
||||||
"""
|
"""
|
||||||
from typing import Type, AsyncGenerator
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
|
|
||||||
from nonebot.typing import overrides
|
from typing import TYPE_CHECKING
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
from nonebot.drivers.none import Driver as NoneDriver
|
||||||
from nonebot.drivers import (
|
from nonebot.drivers import (
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
WebSocket,
|
|
||||||
HTTPVersion,
|
HTTPVersion,
|
||||||
ForwardMixin,
|
HTTPClientMixin,
|
||||||
ForwardDriver,
|
|
||||||
combine_driver,
|
combine_driver,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,19 +31,20 @@ try:
|
|||||||
import httpx
|
import httpx
|
||||||
except ModuleNotFoundError as e: # pragma: no cover
|
except ModuleNotFoundError as e: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install httpx by using `pip install nonebot2[httpx]`"
|
"Please install httpx first to use this driver. "
|
||||||
|
"Install with pip: `pip install nonebot2[httpx]`"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
|
||||||
class Mixin(ForwardMixin):
|
class Mixin(HTTPClientMixin):
|
||||||
"""HTTPX Mixin"""
|
"""HTTPX Mixin"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ForwardMixin)
|
@override
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
return "httpx"
|
return "httpx"
|
||||||
|
|
||||||
@overrides(ForwardMixin)
|
@override
|
||||||
async def request(self, setup: Request) -> Response:
|
async def request(self, setup: Request) -> Response:
|
||||||
async with httpx.AsyncClient(
|
async with httpx.AsyncClient(
|
||||||
cookies=setup.cookies.jar,
|
cookies=setup.cookies.jar,
|
||||||
@@ -70,12 +69,12 @@ class Mixin(ForwardMixin):
|
|||||||
request=setup,
|
request=setup,
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides(ForwardMixin)
|
|
||||||
@asynccontextmanager
|
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
|
||||||
async with super(Mixin, self).websocket(setup) as ws:
|
|
||||||
yield ws
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
class Driver(Mixin, NoneDriver):
|
||||||
|
...
|
||||||
|
|
||||||
|
else:
|
||||||
|
Driver = combine_driver(NoneDriver, Mixin)
|
||||||
"""HTTPX Driver"""
|
"""HTTPX Driver"""
|
||||||
|
@@ -9,14 +9,13 @@ FrontMatter:
|
|||||||
description: nonebot.drivers.none 模块
|
description: nonebot.drivers.none 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import signal
|
import signal
|
||||||
import asyncio
|
import asyncio
|
||||||
import threading
|
import threading
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.consts import WINDOWS
|
from nonebot.consts import WINDOWS
|
||||||
from nonebot.typing import overrides
|
|
||||||
from nonebot.config import Env, Config
|
from nonebot.config import Env, Config
|
||||||
from nonebot.drivers import Driver as BaseDriver
|
from nonebot.drivers import Driver as BaseDriver
|
||||||
|
|
||||||
@@ -42,32 +41,28 @@ class Driver(BaseDriver):
|
|||||||
self.force_exit: bool = False
|
self.force_exit: bool = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(BaseDriver)
|
@override
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
"""驱动名称: `none`"""
|
"""驱动名称: `none`"""
|
||||||
return "none"
|
return "none"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(BaseDriver)
|
@override
|
||||||
def logger(self):
|
def logger(self):
|
||||||
"""none driver 使用的 logger"""
|
"""none driver 使用的 logger"""
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
@overrides(BaseDriver)
|
@override
|
||||||
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)
|
||||||
|
|
||||||
@overrides(BaseDriver)
|
@override
|
||||||
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
"""
|
"""注册一个停止时执行的函数"""
|
||||||
注册一个停止时执行的函数
|
|
||||||
"""
|
|
||||||
return self._lifespan.on_shutdown(func)
|
return self._lifespan.on_shutdown(func)
|
||||||
|
|
||||||
@overrides(BaseDriver)
|
@override
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
"""启动 none driver"""
|
"""启动 none driver"""
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
@@ -146,7 +141,15 @@ class Driver(BaseDriver):
|
|||||||
signal.signal(sig, self._handle_exit)
|
signal.signal(sig, self._handle_exit)
|
||||||
|
|
||||||
def _handle_exit(self, sig, frame):
|
def _handle_exit(self, sig, frame):
|
||||||
if self.should_exit.is_set():
|
self.exit(force=self.should_exit.is_set())
|
||||||
self.force_exit = True
|
|
||||||
else:
|
def exit(self, force: bool = False):
|
||||||
|
"""退出 none driver
|
||||||
|
|
||||||
|
参数:
|
||||||
|
force: 强制退出
|
||||||
|
"""
|
||||||
|
if not self.should_exit.is_set():
|
||||||
self.should_exit.set()
|
self.should_exit.set()
|
||||||
|
if force:
|
||||||
|
self.force_exit = True
|
||||||
|
@@ -17,29 +17,44 @@ FrontMatter:
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Any, Dict, List, Tuple, Union, TypeVar, Callable, Optional, Coroutine
|
from typing_extensions import override
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
TypeVar,
|
||||||
|
Callable,
|
||||||
|
Optional,
|
||||||
|
Coroutine,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
|
||||||
from pydantic import BaseSettings
|
from pydantic import BaseSettings
|
||||||
|
|
||||||
from nonebot.config import Env
|
from nonebot.config import Env
|
||||||
from nonebot.typing import overrides
|
from nonebot.drivers import ASGIMixin
|
||||||
from nonebot.exception import WebSocketClosed
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.internal.driver import FileTypes
|
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.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.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
|
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from quart import request as _request
|
from quart import request as _request
|
||||||
from quart import websocket as _websocket
|
from quart.ctx import WebsocketContext
|
||||||
|
from quart.globals import websocket_ctx
|
||||||
from quart import Quart, Request, Response
|
from quart import Quart, Request, Response
|
||||||
from quart.datastructures import FileStorage
|
from quart.datastructures import FileStorage
|
||||||
from quart import Websocket as QuartWebSocket
|
from quart import Websocket as QuartWebSocket
|
||||||
except ModuleNotFoundError as e: # pragma: no cover
|
except ModuleNotFoundError as e: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install Quart by using `pip install nonebot2[quart]`"
|
"Please install Quart first to use this driver. "
|
||||||
|
"Install with pip: `pip install nonebot2[quart]`"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
_AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine])
|
_AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine])
|
||||||
@@ -76,7 +91,7 @@ class Config(BaseSettings):
|
|||||||
extra = "ignore"
|
extra = "ignore"
|
||||||
|
|
||||||
|
|
||||||
class Driver(ReverseDriver):
|
class Driver(BaseDriver, ASGIMixin):
|
||||||
"""Quart 驱动框架"""
|
"""Quart 驱动框架"""
|
||||||
|
|
||||||
def __init__(self, env: Env, config: NoneBotConfig):
|
def __init__(self, env: Env, config: NoneBotConfig):
|
||||||
@@ -89,30 +104,30 @@ class Driver(ReverseDriver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
"""驱动名称: `quart`"""
|
"""驱动名称: `quart`"""
|
||||||
return "quart"
|
return "quart"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def server_app(self) -> Quart:
|
def server_app(self) -> Quart:
|
||||||
"""`Quart` 对象"""
|
"""`Quart` 对象"""
|
||||||
return self._server_app
|
return self._server_app
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def asgi(self):
|
def asgi(self):
|
||||||
"""`Quart` 对象"""
|
"""`Quart` 对象"""
|
||||||
return self._server_app
|
return self._server_app
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def logger(self):
|
def logger(self):
|
||||||
"""Quart 使用的 logger"""
|
"""Quart 使用的 logger"""
|
||||||
return self._server_app.logger
|
return self._server_app.logger
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def setup_http_server(self, setup: HTTPServerSetup):
|
def setup_http_server(self, setup: HTTPServerSetup):
|
||||||
async def _handle() -> Response:
|
async def _handle() -> Response:
|
||||||
return await self._handle_http(setup)
|
return await self._handle_http(setup)
|
||||||
@@ -124,7 +139,7 @@ class Driver(ReverseDriver):
|
|||||||
view_func=_handle,
|
view_func=_handle,
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def setup_websocket_server(self, setup: WebSocketServerSetup) -> None:
|
def setup_websocket_server(self, setup: WebSocketServerSetup) -> None:
|
||||||
async def _handle() -> None:
|
async def _handle() -> None:
|
||||||
return await self._handle_ws(setup)
|
return await self._handle_ws(setup)
|
||||||
@@ -135,17 +150,17 @@ class Driver(ReverseDriver):
|
|||||||
view_func=_handle,
|
view_func=_handle,
|
||||||
)
|
)
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def on_startup(self, func: _AsyncCallable) -> _AsyncCallable:
|
def on_startup(self, func: _AsyncCallable) -> _AsyncCallable:
|
||||||
"""参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
|
"""参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
|
||||||
return self.server_app.before_serving(func) # type: ignore
|
return self.server_app.before_serving(func) # type: ignore
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def on_shutdown(self, func: _AsyncCallable) -> _AsyncCallable:
|
def on_shutdown(self, func: _AsyncCallable) -> _AsyncCallable:
|
||||||
"""参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
|
"""参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
|
||||||
return self.server_app.after_serving(func) # type: ignore
|
return self.server_app.after_serving(func) # type: ignore
|
||||||
|
|
||||||
@overrides(ReverseDriver)
|
@override
|
||||||
def run(
|
def run(
|
||||||
self,
|
self,
|
||||||
host: Optional[str] = None,
|
host: Optional[str] = None,
|
||||||
@@ -188,9 +203,7 @@ class Driver(ReverseDriver):
|
|||||||
async def _handle_http(self, setup: HTTPServerSetup) -> Response:
|
async def _handle_http(self, setup: HTTPServerSetup) -> Response:
|
||||||
request: Request = _request
|
request: Request = _request
|
||||||
|
|
||||||
json = None
|
json = await request.get_json() if request.is_json else None
|
||||||
if request.is_json:
|
|
||||||
json = await request.get_json()
|
|
||||||
|
|
||||||
data = await request.form
|
data = await request.form
|
||||||
files_dict = await request.files
|
files_dict = await request.files
|
||||||
@@ -223,7 +236,8 @@ class Driver(ReverseDriver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _handle_ws(self, setup: WebSocketServerSetup) -> None:
|
async def _handle_ws(self, setup: WebSocketServerSetup) -> None:
|
||||||
websocket: QuartWebSocket = _websocket
|
ctx = cast(WebsocketContext, websocket_ctx.copy())
|
||||||
|
websocket = websocket_ctx.websocket
|
||||||
|
|
||||||
http_request = BaseRequest(
|
http_request = BaseRequest(
|
||||||
websocket.method,
|
websocket.method,
|
||||||
@@ -233,7 +247,7 @@ class Driver(ReverseDriver):
|
|||||||
version=websocket.http_version,
|
version=websocket.http_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
ws = WebSocket(request=http_request, websocket=websocket)
|
ws = WebSocket(request=http_request, websocket_ctx=ctx)
|
||||||
|
|
||||||
await setup.handle_func(ws)
|
await setup.handle_func(ws)
|
||||||
|
|
||||||
@@ -241,30 +255,34 @@ class Driver(ReverseDriver):
|
|||||||
class WebSocket(BaseWebSocket):
|
class WebSocket(BaseWebSocket):
|
||||||
"""Quart WebSocket Wrapper"""
|
"""Quart WebSocket Wrapper"""
|
||||||
|
|
||||||
def __init__(self, *, request: BaseRequest, websocket: QuartWebSocket):
|
def __init__(self, *, request: BaseRequest, websocket_ctx: WebsocketContext):
|
||||||
super().__init__(request=request)
|
super().__init__(request=request)
|
||||||
self.websocket = websocket
|
self.websocket_ctx = websocket_ctx
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(BaseWebSocket)
|
def websocket(self) -> QuartWebSocket:
|
||||||
|
return self.websocket_ctx.websocket
|
||||||
|
|
||||||
|
@property
|
||||||
|
@override
|
||||||
def closed(self):
|
def closed(self):
|
||||||
# FIXME
|
# FIXME
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def accept(self):
|
async def accept(self):
|
||||||
await self.websocket.accept()
|
await self.websocket.accept()
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def close(self, code: int = 1000, reason: str = ""):
|
async def close(self, code: int = 1000, reason: str = ""):
|
||||||
await self.websocket.close(code, reason)
|
await self.websocket.close(code, reason)
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
@catch_closed
|
@catch_closed
|
||||||
async def receive(self) -> Union[str, bytes]:
|
async def receive(self) -> Union[str, bytes]:
|
||||||
return await self.websocket.receive()
|
return await self.websocket.receive()
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
@catch_closed
|
@catch_closed
|
||||||
async def receive_text(self) -> str:
|
async def receive_text(self) -> str:
|
||||||
msg = await self.websocket.receive()
|
msg = await self.websocket.receive()
|
||||||
@@ -272,7 +290,7 @@ class WebSocket(BaseWebSocket):
|
|||||||
raise TypeError("WebSocket received unexpected frame type: bytes")
|
raise TypeError("WebSocket received unexpected frame type: bytes")
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
@catch_closed
|
@catch_closed
|
||||||
async def receive_bytes(self) -> bytes:
|
async def receive_bytes(self) -> bytes:
|
||||||
msg = await self.websocket.receive()
|
msg = await self.websocket.receive()
|
||||||
@@ -280,11 +298,11 @@ class WebSocket(BaseWebSocket):
|
|||||||
raise TypeError("WebSocket received unexpected frame type: str")
|
raise TypeError("WebSocket received unexpected frame type: str")
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def send_text(self, data: str):
|
async def send_text(self, data: str):
|
||||||
await self.websocket.send(data)
|
await self.websocket.send(data)
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def send_bytes(self, data: bytes):
|
async def send_bytes(self, data: bytes):
|
||||||
await self.websocket.send(data)
|
await self.websocket.send(data)
|
||||||
|
|
||||||
|
@@ -14,34 +14,39 @@ FrontMatter:
|
|||||||
sidebar_position: 4
|
sidebar_position: 4
|
||||||
description: nonebot.drivers.websockets 模块
|
description: nonebot.drivers.websockets 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import Type, Union, AsyncGenerator
|
from typing_extensions import ParamSpec, override
|
||||||
|
from typing import TYPE_CHECKING, Union, TypeVar, Callable, Awaitable, AsyncGenerator
|
||||||
|
|
||||||
from nonebot.typing import overrides
|
from nonebot.drivers import Request
|
||||||
from nonebot.log import LoguruHandler
|
from nonebot.log import LoguruHandler
|
||||||
from nonebot.drivers import Request, Response
|
|
||||||
from nonebot.exception import WebSocketClosed
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
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 ForwardMixin, ForwardDriver, combine_driver
|
from nonebot.drivers import WebSocketClientMixin, combine_driver
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from websockets.exceptions import ConnectionClosed
|
from websockets.exceptions import ConnectionClosed
|
||||||
from websockets.legacy.client import Connect, WebSocketClientProtocol
|
from websockets.legacy.client import Connect, WebSocketClientProtocol
|
||||||
except ModuleNotFoundError as e: # pragma: no cover
|
except ModuleNotFoundError as e: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install websockets by using `pip install nonebot2[websockets]`"
|
"Please install websockets first to use this driver. "
|
||||||
|
"Install with pip: `pip install nonebot2[websockets]`"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
P = ParamSpec("P")
|
||||||
|
|
||||||
logger = logging.Logger("websockets.client", "INFO")
|
logger = logging.Logger("websockets.client", "INFO")
|
||||||
logger.addHandler(LoguruHandler())
|
logger.addHandler(LoguruHandler())
|
||||||
|
|
||||||
|
|
||||||
def catch_closed(func):
|
def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
async def decorator(*args, **kwargs):
|
async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||||
try:
|
try:
|
||||||
return await func(*args, **kwargs)
|
return await func(*args, **kwargs)
|
||||||
except ConnectionClosed as e:
|
except ConnectionClosed as e:
|
||||||
@@ -53,19 +58,15 @@ def catch_closed(func):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class Mixin(ForwardMixin):
|
class Mixin(WebSocketClientMixin):
|
||||||
"""Websockets Mixin"""
|
"""Websockets Mixin"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(ForwardMixin)
|
@override
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
return "websockets"
|
return "websockets"
|
||||||
|
|
||||||
@overrides(ForwardMixin)
|
@override
|
||||||
async def request(self, setup: Request) -> Response:
|
|
||||||
return await super(Mixin, self).request(setup)
|
|
||||||
|
|
||||||
@overrides(ForwardMixin)
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
||||||
connection = Connect(
|
connection = Connect(
|
||||||
@@ -80,30 +81,30 @@ class Mixin(ForwardMixin):
|
|||||||
class WebSocket(BaseWebSocket):
|
class WebSocket(BaseWebSocket):
|
||||||
"""Websockets WebSocket Wrapper"""
|
"""Websockets WebSocket Wrapper"""
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
def __init__(self, *, request: Request, websocket: WebSocketClientProtocol):
|
def __init__(self, *, request: Request, websocket: WebSocketClientProtocol):
|
||||||
super().__init__(request=request)
|
super().__init__(request=request)
|
||||||
self.websocket = websocket
|
self.websocket = websocket
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
def closed(self) -> bool:
|
def closed(self) -> bool:
|
||||||
return self.websocket.closed
|
return self.websocket.closed
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def accept(self):
|
async def accept(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def close(self, code: int = 1000, reason: str = ""):
|
async def close(self, code: int = 1000, reason: str = ""):
|
||||||
await self.websocket.close(code, reason)
|
await self.websocket.close(code, reason)
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
@catch_closed
|
@catch_closed
|
||||||
async def receive(self) -> Union[str, bytes]:
|
async def receive(self) -> Union[str, bytes]:
|
||||||
return await self.websocket.recv()
|
return await self.websocket.recv()
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
@catch_closed
|
@catch_closed
|
||||||
async def receive_text(self) -> str:
|
async def receive_text(self) -> str:
|
||||||
msg = await self.websocket.recv()
|
msg = await self.websocket.recv()
|
||||||
@@ -111,7 +112,7 @@ class WebSocket(BaseWebSocket):
|
|||||||
raise TypeError("WebSocket received unexpected frame type: bytes")
|
raise TypeError("WebSocket received unexpected frame type: bytes")
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
@catch_closed
|
@catch_closed
|
||||||
async def receive_bytes(self) -> bytes:
|
async def receive_bytes(self) -> bytes:
|
||||||
msg = await self.websocket.recv()
|
msg = await self.websocket.recv()
|
||||||
@@ -119,14 +120,20 @@ class WebSocket(BaseWebSocket):
|
|||||||
raise TypeError("WebSocket received unexpected frame type: str")
|
raise TypeError("WebSocket received unexpected frame type: str")
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def send_text(self, data: str) -> None:
|
async def send_text(self, data: str) -> None:
|
||||||
await self.websocket.send(data)
|
await self.websocket.send(data)
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@override
|
||||||
async def send_bytes(self, data: bytes) -> None:
|
async def send_bytes(self, data: bytes) -> None:
|
||||||
await self.websocket.send(data)
|
await self.websocket.send(data)
|
||||||
|
|
||||||
|
|
||||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
class Driver(Mixin, NoneDriver):
|
||||||
|
...
|
||||||
|
|
||||||
|
else:
|
||||||
|
Driver = combine_driver(NoneDriver, Mixin)
|
||||||
"""Websockets Driver"""
|
"""Websockets Driver"""
|
||||||
|
@@ -43,9 +43,9 @@ class NoneBotException(Exception):
|
|||||||
|
|
||||||
# Rule Exception
|
# Rule Exception
|
||||||
class ParserExit(NoneBotException):
|
class ParserExit(NoneBotException):
|
||||||
"""{ref}`nonebot.rule.shell_command` 处理消息失败时返回的异常"""
|
"""{ref}`nonebot.rule.shell_command` 处理消息失败时返回的异常。"""
|
||||||
|
|
||||||
def __init__(self, status: int = 0, message: Optional[str] = None):
|
def __init__(self, status: int = 0, message: Optional[str] = None) -> None:
|
||||||
self.status = status
|
self.status = status
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class IgnoredException(ProcessException):
|
|||||||
reason: 忽略事件的原因
|
reason: 忽略事件的原因
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, reason: Any):
|
def __init__(self, reason: Any) -> None:
|
||||||
self.reason: Any = reason
|
self.reason: Any = reason
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@@ -96,7 +96,7 @@ class SkippedException(ProcessException):
|
|||||||
class TypeMisMatch(SkippedException):
|
class TypeMisMatch(SkippedException):
|
||||||
"""当前 `Handler` 的参数类型不匹配。"""
|
"""当前 `Handler` 的参数类型不匹配。"""
|
||||||
|
|
||||||
def __init__(self, param: ModelField, value: Any):
|
def __init__(self, param: ModelField, value: Any) -> None:
|
||||||
self.param: ModelField = param
|
self.param: ModelField = param
|
||||||
self.value: Any = value
|
self.value: Any = value
|
||||||
|
|
||||||
@@ -108,7 +108,8 @@ class TypeMisMatch(SkippedException):
|
|||||||
|
|
||||||
|
|
||||||
class MockApiException(ProcessException):
|
class MockApiException(ProcessException):
|
||||||
"""指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。可由 api hook 抛出。
|
"""指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。
|
||||||
|
可由 api hook 抛出。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
result: 返回的内容
|
result: 返回的内容
|
||||||
@@ -144,7 +145,8 @@ class MatcherException(NoneBotException):
|
|||||||
|
|
||||||
|
|
||||||
class PausedException(MatcherException):
|
class PausedException(MatcherException):
|
||||||
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。可用于用户输入新信息。
|
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。
|
||||||
|
可用于用户输入新信息。
|
||||||
|
|
||||||
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.pause` 抛出。
|
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.pause` 抛出。
|
||||||
|
|
||||||
@@ -158,7 +160,8 @@ class PausedException(MatcherException):
|
|||||||
|
|
||||||
|
|
||||||
class RejectedException(MatcherException):
|
class RejectedException(MatcherException):
|
||||||
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。可用于用户重新输入。
|
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。
|
||||||
|
可用于用户重新输入。
|
||||||
|
|
||||||
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.reject` 抛出。
|
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.reject` 抛出。
|
||||||
|
|
||||||
@@ -187,7 +190,7 @@ class FinishedException(MatcherException):
|
|||||||
|
|
||||||
# Adapter Exceptions
|
# Adapter Exceptions
|
||||||
class AdapterException(NoneBotException):
|
class AdapterException(NoneBotException):
|
||||||
"""代表 `Adapter` 抛出的异常,所有的 `Adapter` 都要在内部继承自这个 `Exception`
|
"""代表 `Adapter` 抛出的异常,所有的 `Adapter` 都要在内部继承自这个 `Exception`。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
adapter_name: 标识 adapter
|
adapter_name: 标识 adapter
|
||||||
@@ -210,7 +213,9 @@ class ApiNotAvailable(AdapterException):
|
|||||||
|
|
||||||
|
|
||||||
class NetworkError(AdapterException):
|
class NetworkError(AdapterException):
|
||||||
"""在网络出现问题时抛出,如: API 请求地址不正确, API 请求无返回或返回状态非正常等。"""
|
"""在网络出现问题时抛出,
|
||||||
|
如: API 请求地址不正确, API 请求无返回或返回状态非正常等。
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ActionFailed(AdapterException):
|
class ActionFailed(AdapterException):
|
||||||
@@ -219,13 +224,13 @@ class ActionFailed(AdapterException):
|
|||||||
|
|
||||||
# Driver Exceptions
|
# Driver Exceptions
|
||||||
class DriverException(NoneBotException):
|
class DriverException(NoneBotException):
|
||||||
"""`Driver` 抛出的异常基类"""
|
"""`Driver` 抛出的异常基类。"""
|
||||||
|
|
||||||
|
|
||||||
class WebSocketClosed(DriverException):
|
class WebSocketClosed(DriverException):
|
||||||
"""WebSocket 连接已关闭"""
|
"""WebSocket 连接已关闭。"""
|
||||||
|
|
||||||
def __init__(self, code: int, reason: Optional[str] = None):
|
def __init__(self, code: int, reason: Optional[str] = None) -> None:
|
||||||
self.code = code
|
self.code = code
|
||||||
self.reason = reason
|
self.reason = reason
|
||||||
|
|
||||||
|
@@ -7,10 +7,11 @@ from nonebot.internal.driver import (
|
|||||||
Driver,
|
Driver,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
|
ASGIMixin,
|
||||||
WebSocket,
|
WebSocket,
|
||||||
ForwardDriver,
|
HTTPClientMixin,
|
||||||
ReverseDriver,
|
|
||||||
HTTPServerSetup,
|
HTTPServerSetup,
|
||||||
|
WebSocketClientMixin,
|
||||||
WebSocketServerSetup,
|
WebSocketServerSetup,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,26 +73,26 @@ class Adapter(abc.ABC):
|
|||||||
|
|
||||||
def setup_http_server(self, setup: HTTPServerSetup):
|
def setup_http_server(self, setup: HTTPServerSetup):
|
||||||
"""设置一个 HTTP 服务器路由配置"""
|
"""设置一个 HTTP 服务器路由配置"""
|
||||||
if not isinstance(self.driver, ReverseDriver):
|
if not isinstance(self.driver, ASGIMixin):
|
||||||
raise TypeError("Current driver does not support http server")
|
raise TypeError("Current driver does not support http server")
|
||||||
self.driver.setup_http_server(setup)
|
self.driver.setup_http_server(setup)
|
||||||
|
|
||||||
def setup_websocket_server(self, setup: WebSocketServerSetup):
|
def setup_websocket_server(self, setup: WebSocketServerSetup):
|
||||||
"""设置一个 WebSocket 服务器路由配置"""
|
"""设置一个 WebSocket 服务器路由配置"""
|
||||||
if not isinstance(self.driver, ReverseDriver):
|
if not isinstance(self.driver, ASGIMixin):
|
||||||
raise TypeError("Current driver does not support websocket server")
|
raise TypeError("Current driver does not support websocket server")
|
||||||
self.driver.setup_websocket_server(setup)
|
self.driver.setup_websocket_server(setup)
|
||||||
|
|
||||||
async def request(self, setup: Request) -> Response:
|
async def request(self, setup: Request) -> Response:
|
||||||
"""进行一个 HTTP 客户端请求"""
|
"""进行一个 HTTP 客户端请求"""
|
||||||
if not isinstance(self.driver, ForwardDriver):
|
if not isinstance(self.driver, HTTPClientMixin):
|
||||||
raise TypeError("Current driver does not support http client")
|
raise TypeError("Current driver does not support http client")
|
||||||
return await self.driver.request(setup)
|
return await self.driver.request(setup)
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
||||||
"""建立一个 WebSocket 客户端连接请求"""
|
"""建立一个 WebSocket 客户端连接请求"""
|
||||||
if not isinstance(self.driver, ForwardDriver):
|
if not isinstance(self.driver, WebSocketClientMixin):
|
||||||
raise TypeError("Current driver does not support websocket client")
|
raise TypeError("Current driver does not support websocket client")
|
||||||
async with self.driver.websocket(setup) as ws:
|
async with self.driver.websocket(setup) as ws:
|
||||||
yield ws
|
yield ws
|
||||||
|
@@ -106,7 +106,10 @@ class Bot(abc.ABC):
|
|||||||
logger.debug("Running CalledAPI hooks...")
|
logger.debug("Running CalledAPI hooks...")
|
||||||
await asyncio.gather(*coros)
|
await asyncio.gather(*coros)
|
||||||
except MockApiException as e:
|
except MockApiException as e:
|
||||||
|
# mock api result
|
||||||
result = e.result
|
result = e.result
|
||||||
|
# ignore exception
|
||||||
|
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."
|
||||||
)
|
)
|
||||||
|
@@ -44,10 +44,11 @@ class Event(abc.ABC, BaseModel):
|
|||||||
def get_log_string(self) -> str:
|
def get_log_string(self) -> str:
|
||||||
"""获取事件日志信息的方法。
|
"""获取事件日志信息的方法。
|
||||||
|
|
||||||
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,可以抛出 `NoLogException` 异常。
|
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,
|
||||||
|
可以抛出 `NoLogException` 异常。
|
||||||
|
|
||||||
异常:
|
异常:
|
||||||
NoLogException:
|
NoLogException: 希望 NoneBot 隐藏该事件日志
|
||||||
"""
|
"""
|
||||||
return f"[{self.get_event_name()}]: {self.get_event_description()}"
|
return f"[{self.get_event_name()}]: {self.get_event_description()}"
|
||||||
|
|
||||||
@@ -58,7 +59,9 @@ class Event(abc.ABC, BaseModel):
|
|||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_session_id(self) -> str:
|
def get_session_id(self) -> str:
|
||||||
"""获取会话 id 的方法,用于判断当前事件属于哪一个会话,通常是用户 id、群组 id 组合。"""
|
"""获取会话 id 的方法,用于判断当前事件属于哪一个会话,
|
||||||
|
通常是用户 id、群组 id 组合。
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import abc
|
import abc
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from typing_extensions import Self
|
||||||
from dataclasses import field, asdict, dataclass
|
from dataclasses import field, asdict, dataclass
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
@@ -12,6 +13,7 @@ from typing import (
|
|||||||
TypeVar,
|
TypeVar,
|
||||||
Iterable,
|
Iterable,
|
||||||
Optional,
|
Optional,
|
||||||
|
SupportsIndex,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +21,6 @@ from pydantic import parse_obj_as
|
|||||||
|
|
||||||
from .template import MessageTemplate
|
from .template import MessageTemplate
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
TMS = TypeVar("TMS", bound="MessageSegment")
|
TMS = TypeVar("TMS", bound="MessageSegment")
|
||||||
TM = TypeVar("TM", bound="Message")
|
TM = TypeVar("TM", bound="Message")
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
|||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(str(self))
|
return len(str(self))
|
||||||
|
|
||||||
def __ne__(self: T, other: T) -> bool:
|
def __ne__(self, other: Self) -> bool:
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||||
@@ -61,7 +62,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
|||||||
yield cls._validate
|
yield cls._validate
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _validate(cls, value):
|
def _validate(cls, value) -> Self:
|
||||||
if isinstance(value, cls):
|
if isinstance(value, cls):
|
||||||
return value
|
return value
|
||||||
if not isinstance(value, dict):
|
if not isinstance(value, dict):
|
||||||
@@ -84,7 +85,10 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
|||||||
def items(self):
|
def items(self):
|
||||||
return asdict(self).items()
|
return asdict(self).items()
|
||||||
|
|
||||||
def copy(self: T) -> T:
|
def join(self: TMS, iterable: Iterable[Union[TMS, TM]]) -> TM:
|
||||||
|
return self.get_message_class()(self).join(iterable)
|
||||||
|
|
||||||
|
def copy(self) -> Self:
|
||||||
return deepcopy(self)
|
return deepcopy(self)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -94,7 +98,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
|||||||
|
|
||||||
|
|
||||||
class Message(List[TMS], abc.ABC):
|
class Message(List[TMS], abc.ABC):
|
||||||
"""消息数组
|
"""消息序列
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
message: 消息内容
|
message: 消息内容
|
||||||
@@ -117,12 +121,12 @@ class Message(List[TMS], abc.ABC):
|
|||||||
self.extend(self._construct(message)) # pragma: no cover
|
self.extend(self._construct(message)) # pragma: no cover
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def template(cls: Type[TM], format_string: Union[str, TM]) -> MessageTemplate[TM]:
|
def template(cls, format_string: Union[str, TM]) -> MessageTemplate[Self]:
|
||||||
"""创建消息模板。
|
"""创建消息模板。
|
||||||
|
|
||||||
用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板
|
用法和 `str.format` 大致相同,支持以 `Message` 对象作为消息模板并输出消息对象。
|
||||||
|
并且提供了拓展的格式化控制符,
|
||||||
并且提供了拓展的格式化控制符, 可以用适用于该消息类型的 `MessageSegment` 的工厂方法创建消息
|
可以通过该消息类型的 `MessageSegment` 工厂方法创建消息。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
format_string: 格式化模板
|
format_string: 格式化模板
|
||||||
@@ -146,7 +150,7 @@ class Message(List[TMS], abc.ABC):
|
|||||||
yield cls._validate
|
yield cls._validate
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _validate(cls, value):
|
def _validate(cls, value) -> Self:
|
||||||
if isinstance(value, cls):
|
if isinstance(value, cls):
|
||||||
return value
|
return value
|
||||||
elif isinstance(value, Message):
|
elif isinstance(value, Message):
|
||||||
@@ -169,16 +173,16 @@ class Message(List[TMS], abc.ABC):
|
|||||||
"""构造消息数组"""
|
"""构造消息数组"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __add__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
def __add__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
|
||||||
result = self.copy()
|
result = self.copy()
|
||||||
result += other
|
result += other
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __radd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
def __radd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
|
||||||
result = self.__class__(other)
|
result = self.__class__(other)
|
||||||
return result + self
|
return result + self
|
||||||
|
|
||||||
def __iadd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
def __iadd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
self.extend(self._construct(other))
|
self.extend(self._construct(other))
|
||||||
elif isinstance(other, MessageSegment):
|
elif isinstance(other, MessageSegment):
|
||||||
@@ -190,57 +194,62 @@ class Message(List[TMS], abc.ABC):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self: TM, __args: str) -> TM:
|
def __getitem__(self, args: str) -> Self:
|
||||||
"""
|
"""获取仅包含指定消息段类型的消息
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
__args: 消息段类型
|
args: 消息段类型
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
所有类型为 `__args` 的消息段
|
所有类型为 `args` 的消息段
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self, __args: Tuple[str, int]) -> TMS:
|
def __getitem__(self, args: Tuple[str, int]) -> TMS:
|
||||||
"""
|
"""索引指定类型的消息段
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
__args: 消息段类型和索引
|
args: 消息段类型和索引
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
类型为 `__args[0]` 的消息段第 `__args[1]` 个
|
类型为 `args[0]` 的消息段第 `args[1]` 个
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self: TM, __args: Tuple[str, slice]) -> TM:
|
def __getitem__(self, args: Tuple[str, slice]) -> Self:
|
||||||
"""
|
"""切片指定类型的消息段
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
__args: 消息段类型和切片
|
args: 消息段类型和切片
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
类型为 `__args[0]` 的消息段切片 `__args[1]`
|
类型为 `args[0]` 的消息段切片 `args[1]`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self, __args: int) -> TMS:
|
def __getitem__(self, args: int) -> TMS:
|
||||||
"""
|
"""索引消息段
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
__args: 索引
|
args: 索引
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
第 `__args` 个消息段
|
第 `args` 个消息段
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self: TM, __args: slice) -> TM:
|
def __getitem__(self, args: slice) -> Self:
|
||||||
"""
|
"""切片消息段
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
__args: 切片
|
args: 切片
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
消息切片 `__args`
|
消息切片 `args`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: TM,
|
self,
|
||||||
args: Union[
|
args: Union[
|
||||||
str,
|
str,
|
||||||
Tuple[str, int],
|
Tuple[str, int],
|
||||||
@@ -248,7 +257,7 @@ class Message(List[TMS], abc.ABC):
|
|||||||
int,
|
int,
|
||||||
slice,
|
slice,
|
||||||
],
|
],
|
||||||
) -> Union[TMS, TM]:
|
) -> Union[TMS, Self]:
|
||||||
arg1, arg2 = args if isinstance(args, tuple) else (args, None)
|
arg1, arg2 = args if isinstance(args, tuple) else (args, None)
|
||||||
if isinstance(arg1, int) and arg2 is None:
|
if isinstance(arg1, int) and arg2 is None:
|
||||||
return super().__getitem__(arg1)
|
return super().__getitem__(arg1)
|
||||||
@@ -263,15 +272,52 @@ class Message(List[TMS], abc.ABC):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Incorrect arguments to slice") # pragma: no cover
|
raise ValueError("Incorrect arguments to slice") # pragma: no cover
|
||||||
|
|
||||||
def index(self, value: Union[TMS, str], *args) -> int:
|
def __contains__(self, value: Union[TMS, str]) -> bool:
|
||||||
|
"""检查消息段是否存在
|
||||||
|
|
||||||
|
参数:
|
||||||
|
value: 消息段或消息段类型
|
||||||
|
返回:
|
||||||
|
消息内是否存在给定消息段或给定类型的消息段
|
||||||
|
"""
|
||||||
|
if isinstance(value, str):
|
||||||
|
return bool(next((seg for seg in self if seg.type == value), None))
|
||||||
|
return super().__contains__(value)
|
||||||
|
|
||||||
|
def has(self, value: Union[TMS, str]) -> bool:
|
||||||
|
"""与 {ref}``__contains__` <nonebot.adapters.Message.__contains__>` 相同"""
|
||||||
|
return value in self
|
||||||
|
|
||||||
|
def index(self, value: Union[TMS, str], *args: SupportsIndex) -> int:
|
||||||
|
"""索引消息段
|
||||||
|
|
||||||
|
参数:
|
||||||
|
value: 消息段或者消息段类型
|
||||||
|
arg: start 与 end
|
||||||
|
|
||||||
|
返回:
|
||||||
|
索引 index
|
||||||
|
|
||||||
|
异常:
|
||||||
|
ValueError: 消息段不存在
|
||||||
|
"""
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
first_segment = next((seg for seg in self if seg.type == value), None)
|
first_segment = next((seg for seg in self if seg.type == value), None)
|
||||||
if first_segment is None:
|
if first_segment is None:
|
||||||
raise ValueError(f"Segment with type {value} is not in message")
|
raise ValueError(f"Segment with type {value!r} is not in message")
|
||||||
return super().index(first_segment, *args)
|
return super().index(first_segment, *args)
|
||||||
return super().index(value, *args)
|
return super().index(value, *args)
|
||||||
|
|
||||||
def get(self: TM, type_: str, count: Optional[int] = None) -> TM:
|
def get(self, type_: str, count: Optional[int] = None) -> Self:
|
||||||
|
"""获取指定类型的消息段
|
||||||
|
|
||||||
|
参数:
|
||||||
|
type_: 消息段类型
|
||||||
|
count: 获取个数
|
||||||
|
|
||||||
|
返回:
|
||||||
|
构建的新消息
|
||||||
|
"""
|
||||||
if count is None:
|
if count is None:
|
||||||
return self[type_]
|
return self[type_]
|
||||||
|
|
||||||
@@ -286,9 +332,30 @@ class Message(List[TMS], abc.ABC):
|
|||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
def count(self, value: Union[TMS, str]) -> int:
|
def count(self, value: Union[TMS, str]) -> int:
|
||||||
|
"""计算指定消息段的个数
|
||||||
|
|
||||||
|
参数:
|
||||||
|
value: 消息段或消息段类型
|
||||||
|
|
||||||
|
返回:
|
||||||
|
个数
|
||||||
|
"""
|
||||||
return len(self[value]) if isinstance(value, str) else super().count(value)
|
return len(self[value]) if isinstance(value, str) else super().count(value)
|
||||||
|
|
||||||
def append(self: TM, obj: Union[str, TMS]) -> TM:
|
def only(self, value: Union[TMS, str]) -> bool:
|
||||||
|
"""检查消息中是否仅包含指定消息段
|
||||||
|
|
||||||
|
参数:
|
||||||
|
value: 指定消息段或消息段类型
|
||||||
|
|
||||||
|
返回:
|
||||||
|
是否仅包含指定消息段
|
||||||
|
"""
|
||||||
|
if isinstance(value, str):
|
||||||
|
return all(seg.type == value for seg in self)
|
||||||
|
return all(seg == value for seg in self)
|
||||||
|
|
||||||
|
def append(self, obj: Union[str, TMS]) -> Self:
|
||||||
"""添加一个消息段到消息数组末尾。
|
"""添加一个消息段到消息数组末尾。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
@@ -302,7 +369,7 @@ class Message(List[TMS], abc.ABC):
|
|||||||
raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
|
raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def extend(self: TM, obj: Union[TM, Iterable[TMS]]) -> TM:
|
def extend(self, obj: Union[Self, Iterable[TMS]]) -> Self:
|
||||||
"""拼接一个消息数组或多个消息段到消息数组末尾。
|
"""拼接一个消息数组或多个消息段到消息数组末尾。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
@@ -312,18 +379,52 @@ class Message(List[TMS], abc.ABC):
|
|||||||
self.append(segment)
|
self.append(segment)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def copy(self: TM) -> TM:
|
def join(self, iterable: Iterable[Union[TMS, Self]]) -> Self:
|
||||||
|
"""将多个消息连接并将自身作为分割
|
||||||
|
|
||||||
|
参数:
|
||||||
|
iterable: 要连接的消息
|
||||||
|
|
||||||
|
返回:
|
||||||
|
连接后的消息
|
||||||
|
"""
|
||||||
|
ret = self.__class__()
|
||||||
|
for index, msg in enumerate(iterable):
|
||||||
|
if index != 0:
|
||||||
|
ret.extend(self)
|
||||||
|
if isinstance(msg, MessageSegment):
|
||||||
|
ret.append(msg.copy())
|
||||||
|
else:
|
||||||
|
ret.extend(msg.copy())
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def copy(self) -> Self:
|
||||||
|
"""深拷贝消息"""
|
||||||
return deepcopy(self)
|
return deepcopy(self)
|
||||||
|
|
||||||
|
def include(self, *types: str) -> Self:
|
||||||
|
"""过滤消息
|
||||||
|
|
||||||
|
参数:
|
||||||
|
types: 包含的消息段类型
|
||||||
|
|
||||||
|
返回:
|
||||||
|
新构造的消息
|
||||||
|
"""
|
||||||
|
return self.__class__(seg for seg in self if seg.type in types)
|
||||||
|
|
||||||
|
def exclude(self, *types: str) -> Self:
|
||||||
|
"""过滤消息
|
||||||
|
|
||||||
|
参数:
|
||||||
|
types: 不包含的消息段类型
|
||||||
|
|
||||||
|
返回:
|
||||||
|
新构造的消息
|
||||||
|
"""
|
||||||
|
return self.__class__(seg for seg in self if seg.type not in types)
|
||||||
|
|
||||||
def extract_plain_text(self) -> str:
|
def extract_plain_text(self) -> str:
|
||||||
"""提取消息内纯文本消息"""
|
"""提取消息内纯文本消息"""
|
||||||
|
|
||||||
return "".join(str(seg) for seg in self if seg.is_text())
|
return "".join(str(seg) for seg in self if seg.is_text())
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {
|
|
||||||
"MessageSegment.__str__": True,
|
|
||||||
"MessageSegment.__add__": True,
|
|
||||||
"Message.__getitem__": True,
|
|
||||||
"Message._construct": True,
|
|
||||||
}
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import functools
|
import functools
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
@@ -25,7 +26,7 @@ if TYPE_CHECKING:
|
|||||||
TM = TypeVar("TM", bound="Message")
|
TM = TypeVar("TM", bound="Message")
|
||||||
TF = TypeVar("TF", str, "Message")
|
TF = TypeVar("TF", str, "Message")
|
||||||
|
|
||||||
FormatSpecFunc = Callable[[Any], str]
|
FormatSpecFunc: TypeAlias = Callable[[Any], str]
|
||||||
FormatSpecFunc_T = TypeVar("FormatSpecFunc_T", bound=FormatSpecFunc)
|
FormatSpecFunc_T = TypeVar("FormatSpecFunc_T", bound=FormatSpecFunc)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
from .model import URL as URL
|
from .model import URL as URL
|
||||||
from .model import RawURL as RawURL
|
from .model import RawURL as RawURL
|
||||||
from .driver import Driver as Driver
|
from .abstract import Mixin as Mixin
|
||||||
from .model import Cookies as Cookies
|
from .model import Cookies as Cookies
|
||||||
from .model import Request as Request
|
from .model import Request as Request
|
||||||
|
from .abstract import Driver as Driver
|
||||||
from .model import FileType as FileType
|
from .model import FileType as FileType
|
||||||
from .model import Response as Response
|
from .model import Response as Response
|
||||||
from .model import DataTypes as DataTypes
|
from .model import DataTypes as DataTypes
|
||||||
@@ -10,16 +11,20 @@ from .model import FileTypes as FileTypes
|
|||||||
from .model import WebSocket as WebSocket
|
from .model import WebSocket as WebSocket
|
||||||
from .model import FilesTypes as FilesTypes
|
from .model import FilesTypes as FilesTypes
|
||||||
from .model import QueryTypes as QueryTypes
|
from .model import QueryTypes as QueryTypes
|
||||||
|
from .abstract import ASGIMixin as ASGIMixin
|
||||||
from .model import CookieTypes as CookieTypes
|
from .model import CookieTypes as CookieTypes
|
||||||
from .model import FileContent as FileContent
|
from .model import FileContent as FileContent
|
||||||
from .model import HTTPVersion as HTTPVersion
|
from .model import HTTPVersion as HTTPVersion
|
||||||
from .model import HeaderTypes as HeaderTypes
|
from .model import HeaderTypes as HeaderTypes
|
||||||
from .model import SimpleQuery as SimpleQuery
|
from .model import SimpleQuery as SimpleQuery
|
||||||
from .model import ContentTypes as ContentTypes
|
from .model import ContentTypes as ContentTypes
|
||||||
from .driver import ForwardMixin as ForwardMixin
|
|
||||||
from .model import QueryVariable as QueryVariable
|
from .model import QueryVariable as QueryVariable
|
||||||
from .driver import ForwardDriver as ForwardDriver
|
from .abstract import ForwardMixin as ForwardMixin
|
||||||
from .driver import ReverseDriver as ReverseDriver
|
from .abstract import ReverseMixin as ReverseMixin
|
||||||
from .driver import combine_driver as combine_driver
|
from .abstract import ForwardDriver as ForwardDriver
|
||||||
|
from .abstract import ReverseDriver as ReverseDriver
|
||||||
|
from .combine import combine_driver as combine_driver
|
||||||
from .model import HTTPServerSetup as HTTPServerSetup
|
from .model import HTTPServerSetup as HTTPServerSetup
|
||||||
|
from .abstract import HTTPClientMixin as HTTPClientMixin
|
||||||
from .model import WebSocketServerSetup as WebSocketServerSetup
|
from .model import WebSocketServerSetup as WebSocketServerSetup
|
||||||
|
from .abstract import WebSocketClientMixin as WebSocketClientMixin
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import abc
|
import abc
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
from contextlib import AsyncExitStack, asynccontextmanager
|
from contextlib import AsyncExitStack, asynccontextmanager
|
||||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, AsyncGenerator
|
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, AsyncGenerator
|
||||||
|
|
||||||
@@ -25,7 +26,9 @@ BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
|
|||||||
|
|
||||||
|
|
||||||
class Driver(abc.ABC):
|
class Driver(abc.ABC):
|
||||||
"""Driver 基类。
|
"""驱动器基类。
|
||||||
|
|
||||||
|
驱动器控制框架的启动和停止,适配器的注册,以及机器人生命周期管理。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
env: 包含环境信息的 Env 对象
|
env: 包含环境信息的 Env 对象
|
||||||
@@ -45,6 +48,7 @@ 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()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@@ -89,13 +93,13 @@ 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).debug(
|
||||||
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)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def on_startup(self, func: Callable) -> Callable:
|
def on_startup(self, func: Callable) -> Callable:
|
||||||
"""注册一个在驱动器启动时执行的函数"""
|
"""注册一个在驱动器启动时执行的函数"""
|
||||||
@@ -152,11 +156,15 @@ class Driver(abc.ABC):
|
|||||||
await asyncio.gather(*coros)
|
await asyncio.gather(*coros)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
"<r><bg #f8bbd0>Error when running WebSocketConnection hook. "
|
"<r><bg #f8bbd0>"
|
||||||
"Running cancelled!</bg #f8bbd0></r>"
|
"Error when running WebSocketConnection hook. "
|
||||||
|
"Running cancelled!"
|
||||||
|
"</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
|
||||||
asyncio.create_task(_run_hook(bot))
|
task = asyncio.create_task(_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 对象"""
|
||||||
@@ -177,27 +185,55 @@ class Driver(abc.ABC):
|
|||||||
await asyncio.gather(*coros)
|
await asyncio.gather(*coros)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
"<r><bg #f8bbd0>Error when running WebSocketDisConnection hook. "
|
"<r><bg #f8bbd0>"
|
||||||
"Running cancelled!</bg #f8bbd0></r>"
|
"Error when running WebSocketDisConnection hook. "
|
||||||
|
"Running cancelled!"
|
||||||
|
"</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
|
||||||
asyncio.create_task(_run_hook(bot))
|
task = asyncio.create_task(_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 ForwardMixin(abc.ABC):
|
class Mixin(abc.ABC):
|
||||||
"""客户端混入基类。"""
|
"""可与其他驱动器共用的混入基类。"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
"""客户端驱动类型名称"""
|
"""混入驱动类型名称"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ForwardMixin(Mixin):
|
||||||
|
"""客户端混入基类。"""
|
||||||
|
|
||||||
|
|
||||||
|
class ReverseMixin(Mixin):
|
||||||
|
"""服务端混入基类。"""
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPClientMixin(ForwardMixin):
|
||||||
|
"""HTTP 客户端混入基类。"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def request(self, setup: Request) -> Response:
|
async def request(self, setup: Request) -> Response:
|
||||||
"""发送一个 HTTP 请求"""
|
"""发送一个 HTTP 请求"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketClientMixin(ForwardMixin):
|
||||||
|
"""WebSocket 客户端混入基类。"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
||||||
@@ -206,12 +242,11 @@ class ForwardMixin(abc.ABC):
|
|||||||
yield # used for static type checking's generator detection
|
yield # used for static type checking's generator detection
|
||||||
|
|
||||||
|
|
||||||
class ForwardDriver(Driver, ForwardMixin):
|
class ASGIMixin(ReverseMixin):
|
||||||
"""客户端基类。将客户端框架封装,以满足适配器使用。"""
|
"""ASGI 服务端基类。
|
||||||
|
|
||||||
|
将后端框架封装,以满足适配器使用。
|
||||||
class ReverseDriver(Driver):
|
"""
|
||||||
"""服务端基类。将后端框架封装,以满足适配器使用。"""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -236,22 +271,14 @@ class ReverseDriver(Driver):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Driver]:
|
ForwardDriver: TypeAlias = ForwardMixin
|
||||||
"""将一个驱动器和多个混入类合并。"""
|
"""支持客户端请求的驱动器。
|
||||||
# check first
|
|
||||||
assert issubclass(driver, Driver), "`driver` must be subclass of Driver"
|
|
||||||
assert all(
|
|
||||||
map(lambda m: issubclass(m, ForwardMixin), mixins)
|
|
||||||
), "`mixins` must be subclass of ForwardMixin"
|
|
||||||
|
|
||||||
if not mixins:
|
**Deprecated**,请使用 {ref}`nonebot.drivers.ForwardMixin` 或其子类代替。
|
||||||
return driver
|
"""
|
||||||
|
|
||||||
def type_(self: ForwardDriver) -> str:
|
ReverseDriver: TypeAlias = ReverseMixin
|
||||||
return (
|
"""支持服务端请求的驱动器。
|
||||||
driver.type.__get__(self)
|
|
||||||
+ "+"
|
|
||||||
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
|
|
||||||
)
|
|
||||||
|
|
||||||
return type("CombinedDriver", (*mixins, driver, ForwardDriver), {"type": property(type_)}) # type: ignore
|
**Deprecated**,请使用 {ref}`nonebot.drivers.ReverseMixin` 或其子类代替。
|
||||||
|
"""
|
45
nonebot/internal/driver/combine.py
Normal file
45
nonebot/internal/driver/combine.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from typing import TYPE_CHECKING, Type, Union, TypeVar, overload
|
||||||
|
|
||||||
|
from .abstract import Mixin, Driver
|
||||||
|
|
||||||
|
D = TypeVar("D", bound="Driver")
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
class CombinedDriver(Driver, Mixin):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def combine_driver(driver: Type[D]) -> Type[D]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def combine_driver(
|
||||||
|
driver: Type[D], *mixins: Type[Mixin]
|
||||||
|
) -> Union[Type[D], Type["CombinedDriver"]]:
|
||||||
|
"""将一个驱动器和多个混入类合并。"""
|
||||||
|
# check first
|
||||||
|
if not issubclass(driver, Driver):
|
||||||
|
raise TypeError("`driver` must be subclass of Driver")
|
||||||
|
if not all(issubclass(m, Mixin) for m in mixins):
|
||||||
|
raise TypeError("`mixins` must be subclass of Mixin")
|
||||||
|
|
||||||
|
if not mixins:
|
||||||
|
return driver
|
||||||
|
|
||||||
|
def type_(self: "CombinedDriver") -> str:
|
||||||
|
return (
|
||||||
|
driver.type.__get__(self)
|
||||||
|
+ "+"
|
||||||
|
+ "+".join(x.type.__get__(self) for x in mixins)
|
||||||
|
)
|
||||||
|
|
||||||
|
return type(
|
||||||
|
"CombinedDriver", (*mixins, driver), {"type": property(type_)}
|
||||||
|
) # type: ignore
|
@@ -2,6 +2,7 @@ import abc
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
from http.cookiejar import Cookie, CookieJar
|
from http.cookiejar import Cookie, CookieJar
|
||||||
from typing import (
|
from typing import (
|
||||||
IO,
|
IO,
|
||||||
@@ -21,28 +22,30 @@ from typing import (
|
|||||||
from yarl import URL as URL
|
from yarl import URL as URL
|
||||||
from multidict import CIMultiDict
|
from multidict import CIMultiDict
|
||||||
|
|
||||||
RawURL = Tuple[bytes, bytes, Optional[int], bytes]
|
RawURL: TypeAlias = Tuple[bytes, bytes, Optional[int], bytes]
|
||||||
|
|
||||||
SimpleQuery = Union[str, int, float]
|
SimpleQuery: TypeAlias = Union[str, int, float]
|
||||||
QueryVariable = Union[SimpleQuery, List[SimpleQuery]]
|
QueryVariable: TypeAlias = Union[SimpleQuery, List[SimpleQuery]]
|
||||||
QueryTypes = Union[
|
QueryTypes: TypeAlias = Union[
|
||||||
None, str, Mapping[str, QueryVariable], List[Tuple[str, QueryVariable]]
|
None, str, Mapping[str, QueryVariable], List[Tuple[str, QueryVariable]]
|
||||||
]
|
]
|
||||||
|
|
||||||
HeaderTypes = Union[
|
HeaderTypes: TypeAlias = Union[
|
||||||
None,
|
None,
|
||||||
CIMultiDict[str],
|
CIMultiDict[str],
|
||||||
Dict[str, str],
|
Dict[str, str],
|
||||||
List[Tuple[str, str]],
|
List[Tuple[str, str]],
|
||||||
]
|
]
|
||||||
|
|
||||||
CookieTypes = Union[None, "Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]]
|
CookieTypes: TypeAlias = Union[
|
||||||
|
None, "Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]
|
||||||
|
]
|
||||||
|
|
||||||
ContentTypes = Union[str, bytes, None]
|
ContentTypes: TypeAlias = Union[str, bytes, None]
|
||||||
DataTypes = Union[dict, None]
|
DataTypes: TypeAlias = Union[dict, None]
|
||||||
FileContent = Union[IO[bytes], bytes]
|
FileContent: TypeAlias = Union[IO[bytes], bytes]
|
||||||
FileType = Tuple[Optional[str], FileContent, Optional[str]]
|
FileType: TypeAlias = Tuple[Optional[str], FileContent, Optional[str]]
|
||||||
FileTypes = Union[
|
FileTypes: TypeAlias = Union[
|
||||||
# file (or bytes)
|
# file (or bytes)
|
||||||
FileContent,
|
FileContent,
|
||||||
# (filename, file (or bytes))
|
# (filename, file (or bytes))
|
||||||
@@ -50,7 +53,7 @@ FileTypes = Union[
|
|||||||
# (filename, file (or bytes), content_type)
|
# (filename, file (or bytes), content_type)
|
||||||
FileType,
|
FileType,
|
||||||
]
|
]
|
||||||
FilesTypes = Union[Dict[str, FileTypes], List[Tuple[str, FileTypes]], None]
|
FilesTypes: TypeAlias = Union[Dict[str, FileTypes], List[Tuple[str, FileTypes]], None]
|
||||||
|
|
||||||
|
|
||||||
class HTTPVersion(Enum):
|
class HTTPVersion(Enum):
|
||||||
@@ -122,7 +125,7 @@ class Request:
|
|||||||
files_ = files.items() if isinstance(files, dict) else files
|
files_ = files.items() if isinstance(files, dict) else files
|
||||||
for name, file_info in files_:
|
for name, file_info in files_:
|
||||||
if not isinstance(file_info, tuple):
|
if not isinstance(file_info, tuple):
|
||||||
self.files.append((name, (None, file_info, None)))
|
self.files.append((name, (name, file_info, None)))
|
||||||
elif len(file_info) == 2:
|
elif len(file_info) == 2:
|
||||||
self.files.append((name, (file_info[0], file_info[1], None)))
|
self.files.append((name, (file_info[0], file_info[1], None)))
|
||||||
else:
|
else:
|
||||||
@@ -160,7 +163,6 @@ class Response:
|
|||||||
|
|
||||||
class WebSocket(abc.ABC):
|
class WebSocket(abc.ABC):
|
||||||
def __init__(self, *, request: Request):
|
def __init__(self, *, request: Request):
|
||||||
# request
|
|
||||||
self.request: Request = request
|
self.request: Request = request
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@@ -169,9 +171,7 @@ class WebSocket(abc.ABC):
|
|||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def closed(self) -> bool:
|
def closed(self) -> bool:
|
||||||
"""
|
"""连接是否已经关闭"""
|
||||||
连接是否已经关闭
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
@@ -6,6 +6,7 @@ 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 current_bot as current_bot
|
||||||
|
from .matcher import MatcherSource as MatcherSource
|
||||||
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,6 +1,5 @@
|
|||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
|
||||||
List,
|
List,
|
||||||
Type,
|
Type,
|
||||||
Tuple,
|
Tuple,
|
||||||
@@ -53,7 +52,7 @@ class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
|||||||
def __delitem__(self, key: int) -> None:
|
def __delitem__(self, key: int) -> None:
|
||||||
del self.provider[key]
|
del self.provider[key]
|
||||||
|
|
||||||
def __eq__(self, other: Any) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
return isinstance(other, MatcherManager) and self.provider == other.provider
|
return isinstance(other, MatcherManager) and self.provider == other.provider
|
||||||
|
|
||||||
def keys(self) -> KeysView[int]:
|
def keys(self) -> KeysView[int]:
|
||||||
|
@@ -1,4 +1,9 @@
|
|||||||
|
import sys
|
||||||
|
import inspect
|
||||||
|
import warnings
|
||||||
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
from dataclasses import dataclass
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@@ -8,6 +13,7 @@ from typing import (
|
|||||||
Any,
|
Any,
|
||||||
List,
|
List,
|
||||||
Type,
|
Type,
|
||||||
|
Tuple,
|
||||||
Union,
|
Union,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Callable,
|
Callable,
|
||||||
@@ -20,7 +26,8 @@ from typing import (
|
|||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.internal.rule import Rule
|
from nonebot.internal.rule import Rule
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.utils import classproperty
|
||||||
|
from nonebot.dependencies import Param, Dependent
|
||||||
from nonebot.internal.permission import User, Permission
|
from nonebot.internal.permission import User, Permission
|
||||||
from nonebot.internal.adapter import (
|
from nonebot.internal.adapter import (
|
||||||
Bot,
|
Bot,
|
||||||
@@ -29,6 +36,13 @@ 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 (
|
from nonebot.consts import (
|
||||||
ARG_KEY,
|
ARG_KEY,
|
||||||
RECEIVE_KEY,
|
RECEIVE_KEY,
|
||||||
@@ -36,14 +50,6 @@ from nonebot.consts import (
|
|||||||
LAST_RECEIVE_KEY,
|
LAST_RECEIVE_KEY,
|
||||||
REJECT_CACHE_TARGET,
|
REJECT_CACHE_TARGET,
|
||||||
)
|
)
|
||||||
from nonebot.typing import (
|
|
||||||
Any,
|
|
||||||
T_State,
|
|
||||||
T_Handler,
|
|
||||||
T_TypeUpdater,
|
|
||||||
T_DependencyCache,
|
|
||||||
T_PermissionUpdater,
|
|
||||||
)
|
|
||||||
from nonebot.exception import (
|
from nonebot.exception import (
|
||||||
PausedException,
|
PausedException,
|
||||||
StopPropagation,
|
StopPropagation,
|
||||||
@@ -75,15 +81,51 @@ current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
|||||||
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MatcherSource:
|
||||||
|
"""Matcher 源代码上下文信息"""
|
||||||
|
|
||||||
|
plugin_name: Optional[str] = None
|
||||||
|
"""事件响应器所在插件名称"""
|
||||||
|
module_name: Optional[str] = None
|
||||||
|
"""事件响应器所在插件模块的路径名"""
|
||||||
|
lineno: Optional[int] = None
|
||||||
|
"""事件响应器所在行号"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugin(self) -> Optional["Plugin"]:
|
||||||
|
"""事件响应器所在插件"""
|
||||||
|
from nonebot.plugin import get_plugin
|
||||||
|
|
||||||
|
if self.plugin_name is not None:
|
||||||
|
return get_plugin(self.plugin_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def module(self) -> Optional[ModuleType]:
|
||||||
|
if self.module_name is not None:
|
||||||
|
return sys.modules.get(self.module_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def file(self) -> Optional[Path]:
|
||||||
|
if self.module is not None and (file := inspect.getsourcefile(self.module)):
|
||||||
|
return Path(file).absolute()
|
||||||
|
|
||||||
|
|
||||||
class MatcherMeta(type):
|
class MatcherMeta(type):
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
module_name: Optional[str]
|
|
||||||
type: str
|
type: str
|
||||||
|
_source: Optional[MatcherSource]
|
||||||
|
module_name: Optional[str]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"{self.__name__}(type={self.type!r}"
|
f"{self.__name__}(type={self.type!r}"
|
||||||
+ (f", module={self.module_name}" if self.module_name else "")
|
+ (f", module={self.module_name}" if self.module_name else "")
|
||||||
|
+ (
|
||||||
|
f", lineno={self._source.lineno}"
|
||||||
|
if self._source and self._source.lineno is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
+ ")"
|
+ ")"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,14 +133,7 @@ class MatcherMeta(type):
|
|||||||
class Matcher(metaclass=MatcherMeta):
|
class Matcher(metaclass=MatcherMeta):
|
||||||
"""事件响应器类"""
|
"""事件响应器类"""
|
||||||
|
|
||||||
plugin: ClassVar[Optional["Plugin"]] = None
|
_source: ClassVar[Optional[MatcherSource]] = None
|
||||||
"""事件响应器所在插件"""
|
|
||||||
module: ClassVar[Optional[ModuleType]] = None
|
|
||||||
"""事件响应器所在插件模块"""
|
|
||||||
plugin_name: ClassVar[Optional[str]] = None
|
|
||||||
"""事件响应器所在插件名"""
|
|
||||||
module_name: ClassVar[Optional[str]] = None
|
|
||||||
"""事件响应器所在点分割插件模块路径"""
|
|
||||||
|
|
||||||
type: ClassVar[str] = ""
|
type: ClassVar[str] = ""
|
||||||
"""事件响应器类型"""
|
"""事件响应器类型"""
|
||||||
@@ -125,7 +160,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
_default_permission_updater: ClassVar[Optional[Dependent[Permission]]] = None
|
_default_permission_updater: ClassVar[Optional[Dependent[Permission]]] = None
|
||||||
"""事件响应器权限更新函数"""
|
"""事件响应器权限更新函数"""
|
||||||
|
|
||||||
HANDLER_PARAM_TYPES = (
|
HANDLER_PARAM_TYPES: ClassVar[Tuple[Type[Param], ...]] = (
|
||||||
DependParam,
|
DependParam,
|
||||||
BotParam,
|
BotParam,
|
||||||
EventParam,
|
EventParam,
|
||||||
@@ -143,6 +178,11 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
return (
|
return (
|
||||||
f"{self.__class__.__name__}(type={self.type!r}"
|
f"{self.__class__.__name__}(type={self.type!r}"
|
||||||
+ (f", module={self.module_name}" if self.module_name else "")
|
+ (f", module={self.module_name}" if self.module_name else "")
|
||||||
|
+ (
|
||||||
|
f", lineno={self._source.lineno}"
|
||||||
|
if self._source and self._source.lineno is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
+ ")"
|
+ ")"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -159,6 +199,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
*,
|
*,
|
||||||
plugin: Optional["Plugin"] = None,
|
plugin: Optional["Plugin"] = None,
|
||||||
module: Optional[ModuleType] = None,
|
module: Optional[ModuleType] = None,
|
||||||
|
source: Optional[MatcherSource] = None,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = None,
|
expire_time: Optional[Union[datetime, timedelta]] = None,
|
||||||
default_state: Optional[T_State] = None,
|
default_state: Optional[T_State] = None,
|
||||||
default_type_updater: Optional[Union[T_TypeUpdater, Dependent[str]]] = None,
|
default_type_updater: Optional[Union[T_TypeUpdater, Dependent[str]]] = None,
|
||||||
@@ -177,22 +218,47 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
temp: 是否为临时事件响应器,即触发一次后删除
|
temp: 是否为临时事件响应器,即触发一次后删除
|
||||||
priority: 响应优先级
|
priority: 响应优先级
|
||||||
block: 是否阻止事件向更低优先级的响应器传播
|
block: 是否阻止事件向更低优先级的响应器传播
|
||||||
plugin: 事件响应器所在插件
|
plugin: **Deprecated.** 事件响应器所在插件
|
||||||
module: 事件响应器所在模块
|
module: **Deprecated.** 事件响应器所在模块
|
||||||
default_state: 默认状态 `state`
|
source: 事件响应器源代码上下文信息
|
||||||
expire_time: 事件响应器最终有效时间点,过时即被删除
|
expire_time: 事件响应器最终有效时间点,过时即被删除
|
||||||
|
default_state: 默认状态 `state`
|
||||||
|
default_type_updater: 默认事件类型更新函数
|
||||||
|
default_permission_updater: 默认会话权限更新函数
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
Type[Matcher]: 新的事件响应器类
|
Type[Matcher]: 新的事件响应器类
|
||||||
"""
|
"""
|
||||||
|
if plugin is not None:
|
||||||
|
warnings.warn(
|
||||||
|
(
|
||||||
|
"Pass `plugin` context info to create Matcher is deprecated. "
|
||||||
|
"Use `source` instead."
|
||||||
|
),
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
if module is not None:
|
||||||
|
warnings.warn(
|
||||||
|
(
|
||||||
|
"Pass `module` context info to create Matcher is deprecated. "
|
||||||
|
"Use `source` instead."
|
||||||
|
),
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
source = source or (
|
||||||
|
MatcherSource(
|
||||||
|
plugin_name=plugin and plugin.name,
|
||||||
|
module_name=module and module.__name__,
|
||||||
|
)
|
||||||
|
if plugin is not None or module is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
NewMatcher = type(
|
NewMatcher = type(
|
||||||
cls.__name__,
|
cls.__name__,
|
||||||
(cls,),
|
(cls,),
|
||||||
{
|
{
|
||||||
"plugin": plugin,
|
"_source": source,
|
||||||
"module": module,
|
|
||||||
"plugin_name": plugin and plugin.name,
|
|
||||||
"module_name": module and module.__name__,
|
|
||||||
"type": type_,
|
"type": type_,
|
||||||
"rule": rule or Rule(),
|
"rule": rule or Rule(),
|
||||||
"permission": permission or Permission(),
|
"permission": permission or Permission(),
|
||||||
@@ -254,6 +320,26 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
"""销毁当前的事件响应器"""
|
"""销毁当前的事件响应器"""
|
||||||
matchers[cls.priority].remove(cls)
|
matchers[cls.priority].remove(cls)
|
||||||
|
|
||||||
|
@classproperty
|
||||||
|
def plugin(cls) -> Optional["Plugin"]:
|
||||||
|
"""事件响应器所在插件"""
|
||||||
|
return cls._source and cls._source.plugin
|
||||||
|
|
||||||
|
@classproperty
|
||||||
|
def module(cls) -> Optional[ModuleType]:
|
||||||
|
"""事件响应器所在插件模块"""
|
||||||
|
return cls._source and cls._source.module
|
||||||
|
|
||||||
|
@classproperty
|
||||||
|
def plugin_name(cls) -> Optional[str]:
|
||||||
|
"""事件响应器所在插件名"""
|
||||||
|
return cls._source and cls._source.plugin_name
|
||||||
|
|
||||||
|
@classproperty
|
||||||
|
def module_name(cls) -> Optional[str]:
|
||||||
|
"""事件响应器所在插件模块路径"""
|
||||||
|
return cls._source and cls._source.module_name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def check_perm(
|
async def check_perm(
|
||||||
cls,
|
cls,
|
||||||
@@ -376,7 +462,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
return
|
return
|
||||||
await matcher.reject()
|
await matcher.reject()
|
||||||
|
|
||||||
_parameterless = (Depends(_receive), *(parameterless or tuple()))
|
_parameterless = (Depends(_receive), *(parameterless or ()))
|
||||||
|
|
||||||
def _decorator(func: T_Handler) -> T_Handler:
|
def _decorator(func: T_Handler) -> T_Handler:
|
||||||
if cls.handlers and cls.handlers[-1].call is func:
|
if cls.handlers and cls.handlers[-1].call is func:
|
||||||
@@ -406,7 +492,8 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
) -> Callable[[T_Handler], T_Handler]:
|
) -> Callable[[T_Handler], T_Handler]:
|
||||||
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
||||||
|
|
||||||
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,如果 `key` 已存在则直接继续运行
|
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,
|
||||||
|
如果 `key` 已存在则直接继续运行
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
key: 参数名
|
key: 参数名
|
||||||
@@ -423,7 +510,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
return
|
return
|
||||||
await matcher.reject(prompt)
|
await matcher.reject(prompt)
|
||||||
|
|
||||||
_parameterless = (Depends(_key_getter), *(parameterless or tuple()))
|
_parameterless = (Depends(_key_getter), *(parameterless or ()))
|
||||||
|
|
||||||
def _decorator(func: T_Handler) -> T_Handler:
|
def _decorator(func: T_Handler) -> T_Handler:
|
||||||
if cls.handlers and cls.handlers[-1].call is func:
|
if cls.handlers and cls.handlers[-1].call is func:
|
||||||
@@ -454,7 +541,8 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
参数:
|
参数:
|
||||||
message: 消息内容
|
message: 消息内容
|
||||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
||||||
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
bot = current_bot.get()
|
bot = current_bot.get()
|
||||||
event = current_event.get()
|
event = current_event.get()
|
||||||
@@ -475,7 +563,8 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
参数:
|
参数:
|
||||||
message: 消息内容
|
message: 消息内容
|
||||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
||||||
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
if message is not None:
|
if message is not None:
|
||||||
await cls.send(message, **kwargs)
|
await cls.send(message, **kwargs)
|
||||||
@@ -491,7 +580,8 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
参数:
|
参数:
|
||||||
prompt: 消息内容
|
prompt: 消息内容
|
||||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
||||||
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
if prompt is not None:
|
if prompt is not None:
|
||||||
await cls.send(prompt, **kwargs)
|
await cls.send(prompt, **kwargs)
|
||||||
@@ -508,7 +598,8 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
参数:
|
参数:
|
||||||
prompt: 消息内容
|
prompt: 消息内容
|
||||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
||||||
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
if prompt is not None:
|
if prompt is not None:
|
||||||
await cls.send(prompt, **kwargs)
|
await cls.send(prompt, **kwargs)
|
||||||
@@ -527,7 +618,8 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
参数:
|
参数:
|
||||||
key: 参数名
|
key: 参数名
|
||||||
prompt: 消息内容
|
prompt: 消息内容
|
||||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
||||||
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
matcher = current_matcher.get()
|
matcher = current_matcher.get()
|
||||||
matcher.set_target(ARG_KEY.format(key=key))
|
matcher.set_target(ARG_KEY.format(key=key))
|
||||||
@@ -548,7 +640,8 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
参数:
|
参数:
|
||||||
id: 消息 id
|
id: 消息 id
|
||||||
prompt: 消息内容
|
prompt: 消息内容
|
||||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
|
||||||
|
请参考对应 adapter 的 bot 对象 api
|
||||||
"""
|
"""
|
||||||
matcher = current_matcher.get()
|
matcher = current_matcher.get()
|
||||||
matcher.set_target(RECEIVE_KEY.format(id=id))
|
matcher.set_target(RECEIVE_KEY.format(id=id))
|
||||||
@@ -767,8 +860,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
temp=True,
|
temp=True,
|
||||||
priority=0,
|
priority=0,
|
||||||
block=True,
|
block=True,
|
||||||
plugin=self.plugin,
|
source=self.__class__._source,
|
||||||
module=self.module,
|
|
||||||
expire_time=bot.config.session_expire_timeout,
|
expire_time=bot.config.session_expire_timeout,
|
||||||
default_state=self.state,
|
default_state=self.state,
|
||||||
default_type_updater=self.__class__._default_type_updater,
|
default_type_updater=self.__class__._default_type_updater,
|
||||||
@@ -788,8 +880,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
temp=True,
|
temp=True,
|
||||||
priority=0,
|
priority=0,
|
||||||
block=True,
|
block=True,
|
||||||
plugin=self.plugin,
|
source=self.__class__._source,
|
||||||
module=self.module,
|
|
||||||
expire_time=bot.config.session_expire_timeout,
|
expire_time=bot.config.session_expire_timeout,
|
||||||
default_state=self.state,
|
default_state=self.state,
|
||||||
default_type_updater=self.__class__._default_type_updater,
|
default_type_updater=self.__class__._default_type_updater,
|
||||||
|
@@ -1,11 +1,21 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Self, Annotated, override
|
||||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||||
from typing import TYPE_CHECKING, Any, Type, Tuple, Literal, Callable, Optional, cast
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Type,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Literal,
|
||||||
|
Callable,
|
||||||
|
Optional,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
|
||||||
from pydantic.typing import get_args, get_origin
|
from pydantic.typing import get_args, get_origin
|
||||||
from pydantic.fields import Required, Undefined, ModelField
|
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
|
||||||
|
|
||||||
from nonebot.dependencies.utils import check_field_type
|
from nonebot.dependencies.utils import check_field_type
|
||||||
from nonebot.dependencies import Param, Dependent, CustomConfig
|
from nonebot.dependencies import Param, Dependent, CustomConfig
|
||||||
@@ -24,6 +34,23 @@ if TYPE_CHECKING:
|
|||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot.adapters import Bot, Event
|
||||||
|
|
||||||
|
EXTRA_FIELD_INFO = (
|
||||||
|
"gt",
|
||||||
|
"lt",
|
||||||
|
"ge",
|
||||||
|
"le",
|
||||||
|
"multiple_of",
|
||||||
|
"allow_inf_nan",
|
||||||
|
"max_digits",
|
||||||
|
"decimal_places",
|
||||||
|
"min_items",
|
||||||
|
"max_items",
|
||||||
|
"unique_items",
|
||||||
|
"min_length",
|
||||||
|
"max_length",
|
||||||
|
"regex",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DependsInner:
|
class DependsInner:
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -31,26 +58,31 @@ class DependsInner:
|
|||||||
dependency: Optional[T_Handler] = None,
|
dependency: Optional[T_Handler] = None,
|
||||||
*,
|
*,
|
||||||
use_cache: bool = True,
|
use_cache: bool = True,
|
||||||
|
validate: Union[bool, FieldInfo] = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.dependency = dependency
|
self.dependency = dependency
|
||||||
self.use_cache = use_cache
|
self.use_cache = use_cache
|
||||||
|
self.validate = validate
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
dep = get_name(self.dependency)
|
dep = get_name(self.dependency)
|
||||||
cache = "" if self.use_cache else ", use_cache=False"
|
cache = "" if self.use_cache else ", use_cache=False"
|
||||||
return f"DependsInner({dep}{cache})"
|
validate = f", validate={self.validate}" if self.validate else ""
|
||||||
|
return f"DependsInner({dep}{cache}{validate})"
|
||||||
|
|
||||||
|
|
||||||
def Depends(
|
def Depends(
|
||||||
dependency: Optional[T_Handler] = None,
|
dependency: Optional[T_Handler] = None,
|
||||||
*,
|
*,
|
||||||
use_cache: bool = True,
|
use_cache: bool = True,
|
||||||
|
validate: Union[bool, FieldInfo] = False,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""子依赖装饰器
|
"""子依赖装饰器
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
dependency: 依赖函数。默认为参数的类型注释。
|
dependency: 依赖函数。默认为参数的类型注释。
|
||||||
use_cache: 是否使用缓存。默认为 `True`。
|
use_cache: 是否使用缓存。默认为 `True`。
|
||||||
|
validate: 是否使用 Pydantic 类型校验。默认为 `False`。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
@@ -63,37 +95,66 @@ def Depends(
|
|||||||
finally:
|
finally:
|
||||||
...
|
...
|
||||||
|
|
||||||
async def handler(param_name: Any = Depends(depend_func), gen: Any = Depends(depend_gen_func)):
|
async def handler(
|
||||||
|
param_name: Any = Depends(depend_func),
|
||||||
|
gen: Any = Depends(depend_gen_func),
|
||||||
|
):
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
return DependsInner(dependency, use_cache=use_cache)
|
return DependsInner(dependency, use_cache=use_cache, validate=validate)
|
||||||
|
|
||||||
|
|
||||||
class DependParam(Param):
|
class DependParam(Param):
|
||||||
"""子依赖参数"""
|
"""子依赖注入参数。
|
||||||
|
|
||||||
|
本注入解析所有子依赖注入,然后将它们的返回值作为参数值传递给父依赖。
|
||||||
|
|
||||||
|
本注入应该具有最高优先级,因此应该在其他参数之前检查。
|
||||||
|
"""
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Depends({self.extra['dependent']})"
|
return f"Depends({self.extra['dependent']})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
def _from_field(
|
||||||
|
cls, sub_dependent: Dependent, use_cache: bool, validate: Union[bool, FieldInfo]
|
||||||
|
) -> Self:
|
||||||
|
kwargs = {}
|
||||||
|
if isinstance(validate, FieldInfo):
|
||||||
|
kwargs.update((k, getattr(validate, k)) for k in EXTRA_FIELD_INFO)
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
Required,
|
||||||
|
validate=bool(validate),
|
||||||
|
**kwargs,
|
||||||
|
dependent=sub_dependent,
|
||||||
|
use_cache=use_cache,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["DependParam"]:
|
) -> Optional[Self]:
|
||||||
type_annotation, depends_inner = param.annotation, None
|
type_annotation, depends_inner = param.annotation, None
|
||||||
|
# extract type annotation and dependency from Annotated
|
||||||
if get_origin(param.annotation) is Annotated:
|
if get_origin(param.annotation) is Annotated:
|
||||||
type_annotation, *extra_args = get_args(param.annotation)
|
type_annotation, *extra_args = get_args(param.annotation)
|
||||||
depends_inner = next(
|
depends_inner = next(
|
||||||
(x for x in extra_args if isinstance(x, DependsInner)), None
|
(x for x in reversed(extra_args) if isinstance(x, DependsInner)), None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# param default value takes higher priority
|
||||||
depends_inner = (
|
depends_inner = (
|
||||||
param.default if isinstance(param.default, DependsInner) else depends_inner
|
param.default if isinstance(param.default, DependsInner) else depends_inner
|
||||||
)
|
)
|
||||||
|
# not a dependent
|
||||||
if depends_inner is None:
|
if depends_inner is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
dependency: T_Handler
|
dependency: T_Handler
|
||||||
|
# sub dependency is not specified, use type annotation
|
||||||
if depends_inner.dependency is None:
|
if depends_inner.dependency is None:
|
||||||
assert (
|
assert (
|
||||||
type_annotation is not inspect.Signature.empty
|
type_annotation is not inspect.Signature.empty
|
||||||
@@ -101,13 +162,18 @@ class DependParam(Param):
|
|||||||
dependency = type_annotation
|
dependency = type_annotation
|
||||||
else:
|
else:
|
||||||
dependency = depends_inner.dependency
|
dependency = depends_inner.dependency
|
||||||
|
# parse sub dependency
|
||||||
sub_dependent = Dependent[Any].parse(
|
sub_dependent = Dependent[Any].parse(
|
||||||
call=dependency,
|
call=dependency,
|
||||||
allow_types=allow_types,
|
allow_types=allow_types,
|
||||||
)
|
)
|
||||||
return cls(Required, use_cache=depends_inner.use_cache, dependent=sub_dependent)
|
|
||||||
|
return cls._from_field(
|
||||||
|
sub_dependent, depends_inner.use_cache, depends_inner.validate
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_parameterless(
|
def _check_parameterless(
|
||||||
cls, value: Any, allow_types: Tuple[Type[Param], ...]
|
cls, value: Any, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["Param"]:
|
) -> Optional["Param"]:
|
||||||
@@ -116,8 +182,9 @@ class DependParam(Param):
|
|||||||
dependent = Dependent[Any].parse(
|
dependent = Dependent[Any].parse(
|
||||||
call=value.dependency, allow_types=allow_types
|
call=value.dependency, allow_types=allow_types
|
||||||
)
|
)
|
||||||
return cls(Required, use_cache=value.use_cache, dependent=dependent)
|
return cls._from_field(dependent, value.use_cache, value.validate)
|
||||||
|
|
||||||
|
@override
|
||||||
async def _solve(
|
async def _solve(
|
||||||
self,
|
self,
|
||||||
stack: Optional[AsyncExitStack] = None,
|
stack: Optional[AsyncExitStack] = None,
|
||||||
@@ -161,6 +228,7 @@ class DependParam(Param):
|
|||||||
dependency_cache[call] = task
|
dependency_cache[call] = task
|
||||||
return await task
|
return await task
|
||||||
|
|
||||||
|
@override
|
||||||
async def _check(self, **kwargs: Any) -> None:
|
async def _check(self, **kwargs: Any) -> None:
|
||||||
# run sub dependent pre-checkers
|
# run sub dependent pre-checkers
|
||||||
sub_dependent: Dependent = self.extra["dependent"]
|
sub_dependent: Dependent = self.extra["dependent"]
|
||||||
@@ -168,7 +236,12 @@ class DependParam(Param):
|
|||||||
|
|
||||||
|
|
||||||
class BotParam(Param):
|
class BotParam(Param):
|
||||||
"""{ref}`nonebot.adapters.Bot` 参数"""
|
"""{ref}`nonebot.adapters.Bot` 注入参数。
|
||||||
|
|
||||||
|
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Bot` 及其子类或 `None` 的参数。
|
||||||
|
|
||||||
|
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
|
||||||
|
"""
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@@ -182,12 +255,13 @@ class BotParam(Param):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["BotParam"]:
|
) -> Optional[Self]:
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
|
|
||||||
if param.default == param.empty:
|
# param type is Bot(s) or subclass(es) of Bot or None
|
||||||
if generic_check_issubclass(param.annotation, Bot):
|
if generic_check_issubclass(param.annotation, Bot):
|
||||||
checker: Optional[ModelField] = None
|
checker: Optional[ModelField] = None
|
||||||
if param.annotation is not Bot:
|
if param.annotation is not Bot:
|
||||||
@@ -200,19 +274,27 @@ class BotParam(Param):
|
|||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
return cls(Required, checker=checker)
|
return cls(Required, checker=checker)
|
||||||
|
# legacy: param is named "bot" and has no type annotation
|
||||||
elif param.annotation == param.empty and param.name == "bot":
|
elif param.annotation == param.empty and param.name == "bot":
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
|
@override
|
||||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||||
return bot
|
return bot
|
||||||
|
|
||||||
|
@override
|
||||||
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
||||||
if checker := self.extra.get("checker"):
|
if checker := self.extra.get("checker"):
|
||||||
check_field_type(checker, bot)
|
check_field_type(checker, bot)
|
||||||
|
|
||||||
|
|
||||||
class EventParam(Param):
|
class EventParam(Param):
|
||||||
"""{ref}`nonebot.adapters.Event` 参数"""
|
"""{ref}`nonebot.adapters.Event` 注入参数
|
||||||
|
|
||||||
|
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Event` 及其子类或 `None` 的参数。
|
||||||
|
|
||||||
|
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
|
||||||
|
"""
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@@ -226,12 +308,13 @@ class EventParam(Param):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["EventParam"]:
|
) -> Optional[Self]:
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
|
|
||||||
if param.default == param.empty:
|
# param type is Event(s) or subclass(es) of Event or None
|
||||||
if generic_check_issubclass(param.annotation, Event):
|
if generic_check_issubclass(param.annotation, Event):
|
||||||
checker: Optional[ModelField] = None
|
checker: Optional[ModelField] = None
|
||||||
if param.annotation is not Event:
|
if param.annotation is not Event:
|
||||||
@@ -244,57 +327,92 @@ class EventParam(Param):
|
|||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
return cls(Required, checker=checker)
|
return cls(Required, checker=checker)
|
||||||
|
# legacy: param is named "event" and has no type annotation
|
||||||
elif param.annotation == param.empty and param.name == "event":
|
elif param.annotation == param.empty and param.name == "event":
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
|
@override
|
||||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
@override
|
||||||
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
||||||
if checker := self.extra.get("checker", None):
|
if checker := self.extra.get("checker", None):
|
||||||
check_field_type(checker, event)
|
check_field_type(checker, event)
|
||||||
|
|
||||||
|
|
||||||
class StateParam(Param):
|
class StateParam(Param):
|
||||||
"""事件处理状态参数"""
|
"""事件处理状态注入参数
|
||||||
|
|
||||||
|
本注入解析所有类型为 `T_State` 的参数。
|
||||||
|
|
||||||
|
为保证兼容性,本注入还会解析名为 `state` 且没有类型注解的参数。
|
||||||
|
"""
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "StateParam()"
|
return "StateParam()"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["StateParam"]:
|
) -> Optional[Self]:
|
||||||
if param.default == param.empty:
|
# param type is T_State
|
||||||
if param.annotation is T_State:
|
if param.annotation is T_State:
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
# 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":
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
|
@override
|
||||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
class MatcherParam(Param):
|
class MatcherParam(Param):
|
||||||
"""事件响应器实例参数"""
|
"""事件响应器实例注入参数
|
||||||
|
|
||||||
|
本注入解析所有类型为且仅为 {ref}`nonebot.matcher.Matcher` 及其子类或 `None` 的参数。
|
||||||
|
|
||||||
|
为保证兼容性,本注入还会解析名为 `matcher` 且没有类型注解的参数。
|
||||||
|
"""
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "MatcherParam()"
|
return "MatcherParam()"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["MatcherParam"]:
|
) -> Optional[Self]:
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
|
|
||||||
if generic_check_issubclass(param.annotation, Matcher) or (
|
# param type is Matcher(s) or subclass(es) of Matcher or None
|
||||||
param.annotation == param.empty and param.name == "matcher"
|
if generic_check_issubclass(param.annotation, Matcher):
|
||||||
):
|
checker: Optional[ModelField] = None
|
||||||
|
if param.annotation is not Matcher:
|
||||||
|
checker = ModelField(
|
||||||
|
name=param.name,
|
||||||
|
type_=param.annotation,
|
||||||
|
class_validators=None,
|
||||||
|
model_config=CustomConfig,
|
||||||
|
default=None,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
return cls(Required, checker=checker)
|
||||||
|
# legacy: param is named "matcher" and has no type annotation
|
||||||
|
elif param.annotation == param.empty and param.name == "matcher":
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
|
@override
|
||||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def _check(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||||
|
if checker := self.extra.get("checker", None):
|
||||||
|
check_field_type(checker, matcher)
|
||||||
|
|
||||||
|
|
||||||
class ArgInner:
|
class ArgInner:
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -308,37 +426,49 @@ class ArgInner:
|
|||||||
|
|
||||||
|
|
||||||
def Arg(key: Optional[str] = None) -> Any:
|
def Arg(key: Optional[str] = None) -> Any:
|
||||||
"""`got` 的 Arg 参数消息"""
|
"""Arg 参数消息"""
|
||||||
return ArgInner(key, "message")
|
return ArgInner(key, "message")
|
||||||
|
|
||||||
|
|
||||||
def ArgStr(key: Optional[str] = None) -> str:
|
def ArgStr(key: Optional[str] = None) -> str:
|
||||||
"""`got` 的 Arg 参数消息文本"""
|
"""Arg 参数消息文本"""
|
||||||
return ArgInner(key, "str") # type: ignore
|
return ArgInner(key, "str") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def ArgPlainText(key: Optional[str] = None) -> str:
|
def ArgPlainText(key: Optional[str] = None) -> str:
|
||||||
"""`got` 的 Arg 参数消息纯文本"""
|
"""Arg 参数消息纯文本"""
|
||||||
return ArgInner(key, "plaintext") # type: ignore
|
return ArgInner(key, "plaintext") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class ArgParam(Param):
|
class ArgParam(Param):
|
||||||
"""`got` 的 Arg 参数"""
|
"""Arg 注入参数
|
||||||
|
|
||||||
|
本注入解析事件响应器操作 `got` 所获取的参数。
|
||||||
|
|
||||||
|
可以通过 `Arg`、`ArgStr`、`ArgPlainText` 等函数参数 `key` 指定获取的参数,
|
||||||
|
留空则会根据参数名称获取。
|
||||||
|
"""
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
|
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["ArgParam"]:
|
) -> Optional[Self]:
|
||||||
if isinstance(param.default, ArgInner):
|
if isinstance(param.default, ArgInner):
|
||||||
return cls(
|
return cls(
|
||||||
Required, key=param.default.key or param.name, type=param.default.type
|
Required, key=param.default.key or param.name, type=param.default.type
|
||||||
)
|
)
|
||||||
|
elif get_origin(param.annotation) is Annotated:
|
||||||
|
for arg in get_args(param.annotation)[:0:-1]:
|
||||||
|
if isinstance(arg, ArgInner):
|
||||||
|
return cls(Required, key=arg.key or param.name, type=arg.type)
|
||||||
|
|
||||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||||
message = matcher.get_arg(self.extra["key"])
|
key: str = self.extra["key"]
|
||||||
|
message = matcher.get_arg(key)
|
||||||
if message is None:
|
if message is None:
|
||||||
return message
|
return message
|
||||||
if self.extra["type"] == "message":
|
if self.extra["type"] == "message":
|
||||||
@@ -350,37 +480,53 @@ class ArgParam(Param):
|
|||||||
|
|
||||||
|
|
||||||
class ExceptionParam(Param):
|
class ExceptionParam(Param):
|
||||||
"""`run_postprocessor` 的异常参数"""
|
"""{ref}`nonebot.message.run_postprocessor` 的异常注入参数
|
||||||
|
|
||||||
|
本注入解析所有类型为 `Exception` 或 `None` 的参数。
|
||||||
|
|
||||||
|
为保证兼容性,本注入还会解析名为 `exception` 且没有类型注解的参数。
|
||||||
|
"""
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "ExceptionParam()"
|
return "ExceptionParam()"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["ExceptionParam"]:
|
) -> Optional[Self]:
|
||||||
if generic_check_issubclass(param.annotation, Exception) or (
|
# param type is Exception(s) or subclass(es) of Exception or None
|
||||||
param.annotation == param.empty and param.name == "exception"
|
if generic_check_issubclass(param.annotation, Exception):
|
||||||
):
|
return cls(Required)
|
||||||
|
# legacy: param is named "exception" and has no type annotation
|
||||||
|
elif param.annotation == param.empty and param.name == "exception":
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
|
@override
|
||||||
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
||||||
return exception
|
return exception
|
||||||
|
|
||||||
|
|
||||||
class DefaultParam(Param):
|
class DefaultParam(Param):
|
||||||
"""默认值参数"""
|
"""默认值注入参数
|
||||||
|
|
||||||
|
本注入解析所有剩余未能解析且具有默认值的参数。
|
||||||
|
|
||||||
|
本注入参数应该具有最低优先级,因此应该在所有其他注入参数之后使用。
|
||||||
|
"""
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"DefaultParam(default={self.default!r})"
|
return f"DefaultParam(default={self.default!r})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@override
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["DefaultParam"]:
|
) -> Optional[Self]:
|
||||||
if param.default != param.empty:
|
if param.default != param.empty:
|
||||||
return cls(param.default)
|
return cls(param.default)
|
||||||
|
|
||||||
|
@override
|
||||||
async def _solve(self, **kwargs: Any) -> Any:
|
async def _solve(self, **kwargs: Any) -> Any:
|
||||||
return Undefined
|
return Undefined
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
|
NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
|
||||||
|
|
||||||
自定义 logger 请参考 [自定义日志](https://v2.nonebot.dev/docs/appendices/log)
|
自定义 logger 请参考 [自定义日志](https://nonebot.dev/docs/appendices/log)
|
||||||
以及 [`loguru`][loguru] 文档。
|
以及 [`loguru`][loguru] 文档。
|
||||||
|
|
||||||
[loguru]: https://github.com/Delgan/loguru
|
[loguru]: https://github.com/Delgan/loguru
|
||||||
@@ -54,7 +54,7 @@ class LoguruHandler(logging.Handler): # pragma: no cover
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
level = record.levelno
|
level = record.levelno
|
||||||
|
|
||||||
frame, depth = logging.currentframe(), 2
|
frame, depth = sys._getframe(6), 6
|
||||||
while frame and frame.f_code.co_filename == logging.__file__:
|
while frame and frame.f_code.co_filename == logging.__file__:
|
||||||
frame = frame.f_back
|
frame = frame.f_back
|
||||||
depth += 1
|
depth += 1
|
||||||
@@ -88,5 +88,6 @@ logger_id = logger.add(
|
|||||||
filter=default_filter,
|
filter=default_filter,
|
||||||
format=default_format,
|
format=default_format,
|
||||||
)
|
)
|
||||||
|
"""默认日志处理器 id"""
|
||||||
|
|
||||||
__autodoc__ = {"logger_id": False}
|
__autodoc__ = {"logger_id": False}
|
||||||
|
@@ -8,6 +8,7 @@ FrontMatter:
|
|||||||
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 matchers as matchers
|
||||||
from nonebot.internal.matcher import current_bot as current_bot
|
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 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
|
||||||
|
@@ -80,7 +80,10 @@ RUN_POSTPCS_PARAMS = (
|
|||||||
|
|
||||||
|
|
||||||
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
||||||
"""事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。"""
|
"""事件预处理。
|
||||||
|
|
||||||
|
装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。
|
||||||
|
"""
|
||||||
_event_preprocessors.add(
|
_event_preprocessors.add(
|
||||||
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||||
)
|
)
|
||||||
@@ -88,7 +91,10 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
|||||||
|
|
||||||
|
|
||||||
def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
|
def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
|
||||||
"""事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。"""
|
"""事件后处理。
|
||||||
|
|
||||||
|
装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。
|
||||||
|
"""
|
||||||
_event_postprocessors.add(
|
_event_postprocessors.add(
|
||||||
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||||
)
|
)
|
||||||
@@ -96,7 +102,10 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
|
|||||||
|
|
||||||
|
|
||||||
def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
|
def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
|
||||||
"""运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。"""
|
"""运行预处理。
|
||||||
|
|
||||||
|
装饰一个函数,使它在每次事件响应器运行前执行。
|
||||||
|
"""
|
||||||
_run_preprocessors.add(
|
_run_preprocessors.add(
|
||||||
Dependent[Any].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
|
Dependent[Any].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
|
||||||
)
|
)
|
||||||
@@ -104,55 +113,149 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
|
|||||||
|
|
||||||
|
|
||||||
def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
||||||
"""运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。"""
|
"""运行后处理。
|
||||||
|
|
||||||
|
装饰一个函数,使它在每次事件响应器运行后执行。
|
||||||
|
"""
|
||||||
_run_postprocessors.add(
|
_run_postprocessors.add(
|
||||||
Dependent[Any].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
|
Dependent[Any].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
|
||||||
)
|
)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
async def _check_matcher(
|
async def _apply_event_preprocessors(
|
||||||
Matcher: Type[Matcher],
|
|
||||||
bot: "Bot",
|
bot: "Bot",
|
||||||
event: "Event",
|
event: "Event",
|
||||||
state: T_State,
|
state: T_State,
|
||||||
stack: Optional[AsyncExitStack] = None,
|
stack: Optional[AsyncExitStack] = None,
|
||||||
dependency_cache: Optional[T_DependencyCache] = None,
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
) -> None:
|
show_log: bool = True,
|
||||||
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
) -> bool:
|
||||||
with contextlib.suppress(Exception):
|
"""运行事件预处理。
|
||||||
Matcher.destroy()
|
|
||||||
return
|
参数:
|
||||||
|
bot: Bot 对象
|
||||||
|
event: Event 对象
|
||||||
|
state: 会话状态
|
||||||
|
stack: 异步上下文栈
|
||||||
|
dependency_cache: 依赖缓存
|
||||||
|
show_log: 是否显示日志
|
||||||
|
|
||||||
|
返回:
|
||||||
|
是否继续处理事件
|
||||||
|
"""
|
||||||
|
if not _event_preprocessors:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if show_log:
|
||||||
|
logger.debug("Running PreProcessors...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not await Matcher.check_perm(
|
await asyncio.gather(
|
||||||
bot, event, stack, dependency_cache
|
*(
|
||||||
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
|
run_coro_with_catch(
|
||||||
return
|
proc(
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
state=state,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
),
|
||||||
|
(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:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
|
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||||
|
"Event ignored!</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
return
|
return False
|
||||||
|
|
||||||
if Matcher.temp:
|
return True
|
||||||
with contextlib.suppress(Exception):
|
|
||||||
Matcher.destroy()
|
|
||||||
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
|
|
||||||
|
|
||||||
|
|
||||||
async def _run_matcher(
|
async def _apply_event_postprocessors(
|
||||||
Matcher: Type[Matcher],
|
|
||||||
bot: "Bot",
|
bot: "Bot",
|
||||||
event: "Event",
|
event: "Event",
|
||||||
state: T_State,
|
state: T_State,
|
||||||
stack: Optional[AsyncExitStack] = None,
|
stack: Optional[AsyncExitStack] = None,
|
||||||
dependency_cache: Optional[T_DependencyCache] = None,
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
|
show_log: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
logger.info(f"Event will be handled by {Matcher}")
|
"""运行事件后处理。
|
||||||
|
|
||||||
matcher = Matcher()
|
参数:
|
||||||
if coros := [
|
bot: Bot 对象
|
||||||
|
event: Event 对象
|
||||||
|
state: 会话状态
|
||||||
|
stack: 异步上下文栈
|
||||||
|
dependency_cache: 依赖缓存
|
||||||
|
show_log: 是否显示日志
|
||||||
|
"""
|
||||||
|
if not _event_postprocessors:
|
||||||
|
return
|
||||||
|
|
||||||
|
if show_log:
|
||||||
|
logger.debug("Running PostProcessors...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await asyncio.gather(
|
||||||
|
*(
|
||||||
|
run_coro_with_catch(
|
||||||
|
proc(
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
state=state,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
),
|
||||||
|
(SkippedException,),
|
||||||
|
)
|
||||||
|
for proc in _event_postprocessors
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _apply_run_preprocessors(
|
||||||
|
bot: "Bot",
|
||||||
|
event: "Event",
|
||||||
|
state: T_State,
|
||||||
|
matcher: Matcher,
|
||||||
|
stack: Optional[AsyncExitStack] = None,
|
||||||
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
|
) -> bool:
|
||||||
|
"""运行事件响应器运行前处理。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
bot: Bot 对象
|
||||||
|
event: Event 对象
|
||||||
|
state: 会话状态
|
||||||
|
matcher: 事件响应器
|
||||||
|
stack: 异步上下文栈
|
||||||
|
dependency_cache: 依赖缓存
|
||||||
|
|
||||||
|
返回:
|
||||||
|
是否继续处理事件
|
||||||
|
"""
|
||||||
|
if not _run_preprocessors:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ensure matcher function can be correctly called
|
||||||
|
with matcher.ensure_context(bot, event):
|
||||||
|
try:
|
||||||
|
await asyncio.gather(
|
||||||
|
*(
|
||||||
run_coro_with_catch(
|
run_coro_with_catch(
|
||||||
proc(
|
proc(
|
||||||
matcher=matcher,
|
matcher=matcher,
|
||||||
@@ -165,33 +268,46 @@ async def _run_matcher(
|
|||||||
(SkippedException,),
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
for proc in _run_preprocessors
|
for proc in _run_preprocessors
|
||||||
]:
|
)
|
||||||
# ensure matcher function can be correctly called
|
)
|
||||||
with matcher.ensure_context(bot, event):
|
|
||||||
try:
|
|
||||||
await asyncio.gather(*coros)
|
|
||||||
except IgnoredException:
|
except IgnoredException:
|
||||||
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
|
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
|
||||||
return
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. Running cancelled!</bg #f8bbd0></r>"
|
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
||||||
|
"Running cancelled!</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _apply_run_postprocessors(
|
||||||
|
bot: "Bot",
|
||||||
|
event: "Event",
|
||||||
|
matcher: Matcher,
|
||||||
|
exception: Optional[Exception] = None,
|
||||||
|
stack: Optional[AsyncExitStack] = None,
|
||||||
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
|
) -> None:
|
||||||
|
"""运行事件响应器运行后处理。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
bot: Bot 对象
|
||||||
|
event: Event 对象
|
||||||
|
matcher: 事件响应器
|
||||||
|
exception: 事件响应器运行异常
|
||||||
|
stack: 异步上下文栈
|
||||||
|
dependency_cache: 依赖缓存
|
||||||
|
"""
|
||||||
|
if not _run_postprocessors:
|
||||||
return
|
return
|
||||||
|
|
||||||
exception = None
|
with matcher.ensure_context(bot, event):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Running {matcher}")
|
await asyncio.gather(
|
||||||
await matcher.run(bot, event, state, stack, dependency_cache)
|
*(
|
||||||
except Exception as e:
|
|
||||||
logger.opt(colors=True, exception=e).error(
|
|
||||||
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
exception = e
|
|
||||||
|
|
||||||
if coros := [
|
|
||||||
run_coro_with_catch(
|
run_coro_with_catch(
|
||||||
proc(
|
proc(
|
||||||
matcher=matcher,
|
matcher=matcher,
|
||||||
@@ -205,20 +321,167 @@ async def _run_matcher(
|
|||||||
(SkippedException,),
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
for proc in _run_postprocessors
|
for proc in _run_postprocessors
|
||||||
]:
|
)
|
||||||
# ensure matcher function can be correctly called
|
)
|
||||||
with matcher.ensure_context(bot, event):
|
|
||||||
try:
|
|
||||||
await asyncio.gather(*coros)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
|
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _check_matcher(
|
||||||
|
Matcher: Type[Matcher],
|
||||||
|
bot: "Bot",
|
||||||
|
event: "Event",
|
||||||
|
state: T_State,
|
||||||
|
stack: Optional[AsyncExitStack] = None,
|
||||||
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
|
) -> bool:
|
||||||
|
"""检查事件响应器是否符合运行条件。
|
||||||
|
|
||||||
|
请注意,过时的事件响应器将被**销毁**。对于未过时的事件响应器,将会一次检查其响应类型、权限和规则。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
Matcher: 要检查的事件响应器
|
||||||
|
bot: Bot 对象
|
||||||
|
event: Event 对象
|
||||||
|
state: 会话状态
|
||||||
|
stack: 异步上下文栈
|
||||||
|
dependency_cache: 依赖缓存
|
||||||
|
|
||||||
|
返回:
|
||||||
|
bool: 是否符合运行条件
|
||||||
|
"""
|
||||||
|
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
Matcher.destroy()
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not await Matcher.check_perm(bot, event, stack, dependency_cache):
|
||||||
|
logger.trace(f"Permission conditions not met for {Matcher}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
f"<r><bg #f8bbd0>Permission check failed for {Matcher}.</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
|
||||||
|
logger.trace(f"Rule conditions not met for {Matcher}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _run_matcher(
|
||||||
|
Matcher: Type[Matcher],
|
||||||
|
bot: "Bot",
|
||||||
|
event: "Event",
|
||||||
|
state: T_State,
|
||||||
|
stack: Optional[AsyncExitStack] = None,
|
||||||
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
|
) -> None:
|
||||||
|
"""运行事件响应器。
|
||||||
|
|
||||||
|
临时事件响应器将在运行前被**销毁**。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
Matcher: 事件响应器
|
||||||
|
bot: Bot 对象
|
||||||
|
event: Event 对象
|
||||||
|
state: 会话状态
|
||||||
|
stack: 异步上下文栈
|
||||||
|
dependency_cache: 依赖缓存
|
||||||
|
|
||||||
|
异常:
|
||||||
|
StopPropagation: 阻止事件继续传播
|
||||||
|
"""
|
||||||
|
logger.info(f"Event will be handled by {Matcher}")
|
||||||
|
|
||||||
|
if Matcher.temp:
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
Matcher.destroy()
|
||||||
|
|
||||||
|
matcher = Matcher()
|
||||||
|
|
||||||
|
if not await _apply_run_preprocessors(
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
state=state,
|
||||||
|
matcher=matcher,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
exception = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.debug(f"Running {matcher}")
|
||||||
|
await matcher.run(bot, event, state, stack, dependency_cache)
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
exception = e
|
||||||
|
|
||||||
|
await _apply_run_postprocessors(
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
matcher=matcher,
|
||||||
|
exception=exception,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
)
|
||||||
|
|
||||||
if matcher.block:
|
if matcher.block:
|
||||||
raise StopPropagation
|
raise StopPropagation
|
||||||
|
|
||||||
|
|
||||||
|
async def check_and_run_matcher(
|
||||||
|
Matcher: Type[Matcher],
|
||||||
|
bot: "Bot",
|
||||||
|
event: "Event",
|
||||||
|
state: T_State,
|
||||||
|
stack: Optional[AsyncExitStack] = None,
|
||||||
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
|
) -> None:
|
||||||
|
"""检查并运行事件响应器。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
Matcher: 事件响应器
|
||||||
|
bot: Bot 对象
|
||||||
|
event: Event 对象
|
||||||
|
state: 会话状态
|
||||||
|
stack: 异步上下文栈
|
||||||
|
dependency_cache: 依赖缓存
|
||||||
|
"""
|
||||||
|
if not await _check_matcher(
|
||||||
|
Matcher=Matcher,
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
state=state,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
await _run_matcher(
|
||||||
|
Matcher=Matcher,
|
||||||
|
bot=bot,
|
||||||
|
event=event,
|
||||||
|
state=state,
|
||||||
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def handle_event(bot: "Bot", event: "Event") -> None:
|
async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||||
"""处理一个事件。调用该函数以实现分发事件。
|
"""处理一个事件。调用该函数以实现分发事件。
|
||||||
@@ -245,34 +508,15 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
state: Dict[Any, Any] = {}
|
state: Dict[Any, Any] = {}
|
||||||
dependency_cache: T_DependencyCache = {}
|
dependency_cache: T_DependencyCache = {}
|
||||||
|
|
||||||
|
# create event scope context
|
||||||
async with AsyncExitStack() as stack:
|
async with AsyncExitStack() as stack:
|
||||||
if coros := [
|
if not await _apply_event_preprocessors(
|
||||||
run_coro_with_catch(
|
|
||||||
proc(
|
|
||||||
bot=bot,
|
bot=bot,
|
||||||
event=event,
|
event=event,
|
||||||
state=state,
|
state=state,
|
||||||
stack=stack,
|
stack=stack,
|
||||||
dependency_cache=dependency_cache,
|
dependency_cache=dependency_cache,
|
||||||
),
|
):
|
||||||
(SkippedException,),
|
|
||||||
)
|
|
||||||
for proc in _event_preprocessors
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
if show_log:
|
|
||||||
logger.debug("Running PreProcessors...")
|
|
||||||
await asyncio.gather(*coros)
|
|
||||||
except IgnoredException as e:
|
|
||||||
logger.opt(colors=True).info(
|
|
||||||
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
logger.opt(colors=True, exception=e).error(
|
|
||||||
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
|
||||||
"Event ignored!</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Trie Match
|
# Trie Match
|
||||||
@@ -284,6 +528,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
break_flag = False
|
break_flag = False
|
||||||
|
# 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:
|
||||||
break
|
break
|
||||||
@@ -292,14 +537,12 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
logger.debug(f"Checking for matchers in priority {priority}...")
|
logger.debug(f"Checking for matchers in priority {priority}...")
|
||||||
|
|
||||||
pending_tasks = [
|
pending_tasks = [
|
||||||
_check_matcher(
|
check_and_run_matcher(
|
||||||
matcher, bot, event, state.copy(), stack, dependency_cache
|
matcher, bot, event, state.copy(), stack, dependency_cache
|
||||||
)
|
)
|
||||||
for matcher in matchers[priority]
|
for matcher in matchers[priority]
|
||||||
]
|
]
|
||||||
|
|
||||||
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
|
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
|
||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
if not isinstance(result, Exception):
|
if not isinstance(result, Exception):
|
||||||
continue
|
continue
|
||||||
@@ -314,24 +557,4 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Checking for matchers completed")
|
logger.debug("Checking for matchers completed")
|
||||||
|
|
||||||
if coros := [
|
await _apply_event_postprocessors(bot, event, state, stack, dependency_cache)
|
||||||
run_coro_with_catch(
|
|
||||||
proc(
|
|
||||||
bot=bot,
|
|
||||||
event=event,
|
|
||||||
state=state,
|
|
||||||
stack=stack,
|
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
),
|
|
||||||
(SkippedException,),
|
|
||||||
)
|
|
||||||
for proc in _event_postprocessors
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
if show_log:
|
|
||||||
logger.debug("Running PostProcessors...")
|
|
||||||
await asyncio.gather(*coros)
|
|
||||||
except Exception as e:
|
|
||||||
logger.opt(colors=True, exception=e).error(
|
|
||||||
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
|
@@ -5,8 +5,7 @@ FrontMatter:
|
|||||||
description: nonebot.params 模块
|
description: nonebot.params 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import warnings
|
from typing import Any, Dict, List, Match, Tuple, Union, Optional
|
||||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
|
||||||
|
|
||||||
from nonebot.typing import T_State
|
from nonebot.typing import T_State
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
@@ -25,15 +24,12 @@ from nonebot.internal.params import MatcherParam as MatcherParam
|
|||||||
from nonebot.internal.params import ExceptionParam as ExceptionParam
|
from nonebot.internal.params import ExceptionParam as ExceptionParam
|
||||||
from nonebot.consts import (
|
from nonebot.consts import (
|
||||||
CMD_KEY,
|
CMD_KEY,
|
||||||
REGEX_STR,
|
|
||||||
PREFIX_KEY,
|
PREFIX_KEY,
|
||||||
REGEX_DICT,
|
|
||||||
SHELL_ARGS,
|
SHELL_ARGS,
|
||||||
SHELL_ARGV,
|
SHELL_ARGV,
|
||||||
CMD_ARG_KEY,
|
CMD_ARG_KEY,
|
||||||
KEYWORD_KEY,
|
KEYWORD_KEY,
|
||||||
RAW_CMD_KEY,
|
RAW_CMD_KEY,
|
||||||
REGEX_GROUP,
|
|
||||||
ENDSWITH_KEY,
|
ENDSWITH_KEY,
|
||||||
CMD_START_KEY,
|
CMD_START_KEY,
|
||||||
FULLMATCH_KEY,
|
FULLMATCH_KEY,
|
||||||
@@ -142,23 +138,17 @@ def ShellCommandArgv() -> Any:
|
|||||||
return Depends(_shell_command_argv, use_cache=False)
|
return Depends(_shell_command_argv, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
def _regex_matched(state: T_State) -> str:
|
def _regex_matched(state: T_State) -> Match[str]:
|
||||||
return state[REGEX_MATCHED]
|
return state[REGEX_MATCHED]
|
||||||
|
|
||||||
|
|
||||||
def RegexMatched() -> str:
|
def RegexMatched() -> Match[str]:
|
||||||
"""正则匹配结果"""
|
"""正则匹配结果"""
|
||||||
warnings.warn(
|
|
||||||
'"RegexMatched()" will be changed to "re.Match" object, '
|
|
||||||
'use "RegexStr()" instead. '
|
|
||||||
"See https://github.com/nonebot/nonebot2/pull/1453 .",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return Depends(_regex_matched, use_cache=False)
|
return Depends(_regex_matched, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
def _regex_str(state: T_State) -> str:
|
def _regex_str(state: T_State) -> str:
|
||||||
return state[REGEX_STR]
|
return _regex_matched(state).group()
|
||||||
|
|
||||||
|
|
||||||
def RegexStr() -> str:
|
def RegexStr() -> str:
|
||||||
@@ -167,7 +157,7 @@ def RegexStr() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _regex_group(state: T_State) -> Tuple[Any, ...]:
|
def _regex_group(state: T_State) -> Tuple[Any, ...]:
|
||||||
return state[REGEX_GROUP]
|
return _regex_matched(state).groups()
|
||||||
|
|
||||||
|
|
||||||
def RegexGroup() -> Tuple[Any, ...]:
|
def RegexGroup() -> Tuple[Any, ...]:
|
||||||
@@ -176,7 +166,7 @@ def RegexGroup() -> Tuple[Any, ...]:
|
|||||||
|
|
||||||
|
|
||||||
def _regex_dict(state: T_State) -> Dict[str, Any]:
|
def _regex_dict(state: T_State) -> Dict[str, Any]:
|
||||||
return state[REGEX_DICT]
|
return _regex_matched(state).groupdict()
|
||||||
|
|
||||||
|
|
||||||
def RegexDict() -> Dict[str, Any]:
|
def RegexDict() -> Dict[str, Any]:
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
"""本模块是 {ref}`nonebot.matcher.Matcher.permission` 的类型定义。
|
"""本模块是 {ref}`nonebot.matcher.Matcher.permission` 的类型定义。
|
||||||
|
|
||||||
每个 {ref}`nonebot.matcher.Matcher` 拥有一个 {ref}`nonebot.permission.Permission` ,
|
每个{ref}`事件响应器 <nonebot.matcher.Matcher>`
|
||||||
其中是 `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
拥有一个 {ref}`nonebot.permission.Permission`,其中是 `PermissionChecker` 的集合。
|
||||||
|
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
sidebar_position: 6
|
sidebar_position: 6
|
||||||
|
@@ -24,10 +24,12 @@
|
|||||||
- `load_all_plugins` => {ref}``load_all_plugins` <nonebot.plugin.load.load_all_plugins>`
|
- `load_all_plugins` => {ref}``load_all_plugins` <nonebot.plugin.load.load_all_plugins>`
|
||||||
- `load_from_json` => {ref}``load_from_json` <nonebot.plugin.load.load_from_json>`
|
- `load_from_json` => {ref}``load_from_json` <nonebot.plugin.load.load_from_json>`
|
||||||
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
||||||
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
- `load_builtin_plugin` =>
|
||||||
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
{ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
||||||
|
- `load_builtin_plugins` =>
|
||||||
|
{ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
||||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||||
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.plugin.PluginMetadata>`
|
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.model.PluginMetadata>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
@@ -42,7 +44,7 @@ from typing import Set, Dict, List, Tuple, Optional
|
|||||||
_plugins: Dict[str, "Plugin"] = {}
|
_plugins: Dict[str, "Plugin"] = {}
|
||||||
_managers: List["PluginManager"] = []
|
_managers: List["PluginManager"] = []
|
||||||
_current_plugin_chain: ContextVar[Tuple["Plugin", ...]] = ContextVar(
|
_current_plugin_chain: ContextVar[Tuple["Plugin", ...]] = ContextVar(
|
||||||
"_current_plugin_chain", default=tuple()
|
"_current_plugin_chain", default=()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ def get_plugin(name: str) -> Optional["Plugin"]:
|
|||||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
name: 插件名,即 {ref}`nonebot.plugin.plugin.Plugin.name`。
|
name: 插件名,即 {ref}`nonebot.plugin.model.Plugin.name`。
|
||||||
"""
|
"""
|
||||||
return _plugins.get(name)
|
return _plugins.get(name)
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
|
|||||||
如果提供的模块名为某个插件的子模块,同样会返回该插件。
|
如果提供的模块名为某个插件的子模块,同样会返回该插件。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
module_name: 模块名,即 {ref}`nonebot.plugin.plugin.Plugin.module_name`。
|
module_name: 模块名,即 {ref}`nonebot.plugin.model.Plugin.module_name`。
|
||||||
"""
|
"""
|
||||||
loaded = {plugin.module_name: plugin for plugin in _plugins.values()}
|
loaded = {plugin.module_name: plugin for plugin in _plugins.values()}
|
||||||
has_parent = True
|
has_parent = True
|
||||||
@@ -109,9 +111,9 @@ def get_available_plugin_names() -> Set[str]:
|
|||||||
from .on import on as on
|
from .on import on as on
|
||||||
from .manager import PluginManager
|
from .manager import PluginManager
|
||||||
from .on import on_type as on_type
|
from .on import on_type as on_type
|
||||||
|
from .model import Plugin as Plugin
|
||||||
from .load import require as require
|
from .load import require as require
|
||||||
from .on import on_regex as on_regex
|
from .on import on_regex as on_regex
|
||||||
from .plugin import Plugin as Plugin
|
|
||||||
from .on import on_notice as on_notice
|
from .on import on_notice as on_notice
|
||||||
from .on import on_command as on_command
|
from .on import on_command as on_command
|
||||||
from .on import on_keyword as on_keyword
|
from .on import on_keyword as on_keyword
|
||||||
@@ -127,8 +129,9 @@ from .load import load_plugins as load_plugins
|
|||||||
from .on import on_startswith as on_startswith
|
from .on import on_startswith as on_startswith
|
||||||
from .load import load_from_json as load_from_json
|
from .load import load_from_json as load_from_json
|
||||||
from .load import load_from_toml as load_from_toml
|
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 .on import on_shell_command as on_shell_command
|
||||||
from .plugin import PluginMetadata as PluginMetadata
|
|
||||||
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
|
||||||
|
@@ -4,6 +4,7 @@ FrontMatter:
|
|||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.plugin.load 模块
|
description: nonebot.plugin.load 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
@@ -11,12 +12,12 @@ from typing import Set, Union, Iterable, Optional
|
|||||||
|
|
||||||
from nonebot.utils import path_to_module_name
|
from nonebot.utils import path_to_module_name
|
||||||
|
|
||||||
from .plugin import Plugin
|
from .model import Plugin
|
||||||
from .manager import PluginManager
|
from .manager import PluginManager
|
||||||
from . import _managers, get_plugin, _module_name_to_plugin_name
|
from . import _managers, get_plugin, _current_plugin_chain, _module_name_to_plugin_name
|
||||||
|
|
||||||
try: # pragma: py-gte-311
|
try: # pragma: py-gte-311
|
||||||
import tomllib # pyright: reportMissingImports=false
|
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
|
||||||
|
|
||||||
@@ -25,7 +26,8 @@ def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
|||||||
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
module_path: 插件名称 `path.to.your.plugin` 或插件路径 `pathlib.Path(path/to/your/plugin)`
|
module_path: 插件名称 `path.to.your.plugin`
|
||||||
|
或插件路径 `pathlib.Path(path/to/your/plugin)`
|
||||||
"""
|
"""
|
||||||
module_path = (
|
module_path = (
|
||||||
path_to_module_name(module_path)
|
path_to_module_name(module_path)
|
||||||
@@ -63,7 +65,8 @@ def load_all_plugins(
|
|||||||
|
|
||||||
|
|
||||||
def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||||
"""导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件,以 `_` 开头的插件不会被导入!
|
"""导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件。
|
||||||
|
以 `_` 开头的插件不会被导入!
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
file_path: 指定 json 文件路径
|
file_path: 指定 json 文件路径
|
||||||
@@ -81,7 +84,7 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
|||||||
nonebot.load_from_json("plugins.json")
|
nonebot.load_from_json("plugins.json")
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
with open(file_path, "r", encoding=encoding) as f:
|
with open(file_path, encoding=encoding) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
raise TypeError("json file must contains a dict!")
|
raise TypeError("json file must contains a dict!")
|
||||||
@@ -93,7 +96,9 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
|||||||
|
|
||||||
|
|
||||||
def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||||
"""导入指定 toml 文件 `[tool.nonebot]` 中的 `plugins` 以及 `plugin_dirs` 下多个插件,以 `_` 开头的插件不会被导入!
|
"""导入指定 toml 文件 `[tool.nonebot]` 中的
|
||||||
|
`plugins` 以及 `plugin_dirs` 下多个插件。
|
||||||
|
以 `_` 开头的插件不会被导入!
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
file_path: 指定 toml 文件路径
|
file_path: 指定 toml 文件路径
|
||||||
@@ -110,7 +115,7 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
|||||||
nonebot.load_from_toml("pyproject.toml")
|
nonebot.load_from_toml("pyproject.toml")
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
with open(file_path, "r", encoding=encoding) as f:
|
with open(file_path, encoding=encoding) as f:
|
||||||
data = tomllib.loads(f.read())
|
data = tomllib.loads(f.read())
|
||||||
|
|
||||||
nonebot_data = data.get("tool", {}).get("nonebot")
|
nonebot_data = data.get("tool", {}).get("nonebot")
|
||||||
@@ -155,17 +160,61 @@ def require(name: str) -> ModuleType:
|
|||||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
name: 插件名,即 {ref}`nonebot.plugin.plugin.Plugin.name`。
|
name: 插件名,即 {ref}`nonebot.plugin.model.Plugin.name`。
|
||||||
|
|
||||||
异常:
|
异常:
|
||||||
RuntimeError: 插件无法加载
|
RuntimeError: 插件无法加载
|
||||||
"""
|
"""
|
||||||
plugin = get_plugin(_module_name_to_plugin_name(name))
|
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||||
|
# if plugin not loaded
|
||||||
if not plugin:
|
if not plugin:
|
||||||
|
# plugin already declared
|
||||||
if manager := _find_manager_by_name(name):
|
if manager := _find_manager_by_name(name):
|
||||||
plugin = manager.load_plugin(name)
|
plugin = manager.load_plugin(name)
|
||||||
|
# plugin not declared, try to declare and load it
|
||||||
else:
|
else:
|
||||||
|
# clear current plugin chain, ensure plugin loaded in a new context
|
||||||
|
_t = _current_plugin_chain.set(())
|
||||||
|
try:
|
||||||
plugin = load_plugin(name)
|
plugin = load_plugin(name)
|
||||||
|
finally:
|
||||||
|
_current_plugin_chain.reset(_t)
|
||||||
if not plugin:
|
if not plugin:
|
||||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||||
return plugin.module
|
return plugin.module
|
||||||
|
|
||||||
|
|
||||||
|
def inherit_supported_adapters(*names: str) -> Optional[Set[str]]:
|
||||||
|
"""获取已加载插件的适配器支持状态集合。
|
||||||
|
|
||||||
|
如果传入了多个插件名称,返回值会自动取交集。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
names: 插件名称列表。
|
||||||
|
|
||||||
|
异常:
|
||||||
|
RuntimeError: 插件未加载
|
||||||
|
ValueError: 插件缺少元数据
|
||||||
|
"""
|
||||||
|
final_supported: Optional[Set[str]] = None
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||||
|
if plugin is None:
|
||||||
|
raise RuntimeError(f'Plugin "{name}" is not loaded!')
|
||||||
|
meta = plugin.metadata
|
||||||
|
if meta is None:
|
||||||
|
raise ValueError(f'Plugin "{name}" has no metadata!')
|
||||||
|
support = meta.supported_adapters
|
||||||
|
if support is None:
|
||||||
|
continue
|
||||||
|
final_supported = (
|
||||||
|
support if final_supported is None else (final_supported & support)
|
||||||
|
)
|
||||||
|
|
||||||
|
return final_supported and {
|
||||||
|
f"nonebot.adapters.{adapter_name[1:]}"
|
||||||
|
if adapter_name.startswith("~")
|
||||||
|
else adapter_name
|
||||||
|
for adapter_name in final_supported
|
||||||
|
}
|
||||||
|
@@ -6,6 +6,7 @@ FrontMatter:
|
|||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.plugin.manager 模块
|
description: nonebot.plugin.manager 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import importlib
|
import importlib
|
||||||
@@ -19,7 +20,7 @@ from typing import Set, Dict, List, Iterable, Optional, Sequence
|
|||||||
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 .plugin import Plugin, PluginMetadata
|
from .model import Plugin, PluginMetadata
|
||||||
from . import (
|
from . import (
|
||||||
_managers,
|
_managers,
|
||||||
_new_plugin,
|
_new_plugin,
|
||||||
@@ -228,6 +229,7 @@ class PluginLoader(SourceFileLoader):
|
|||||||
# detect parent plugin before entering current plugin context
|
# detect parent plugin before entering current plugin context
|
||||||
parent_plugins = _current_plugin_chain.get()
|
parent_plugins = _current_plugin_chain.get()
|
||||||
for pre_plugin in reversed(parent_plugins):
|
for pre_plugin in reversed(parent_plugins):
|
||||||
|
# ensure parent plugin is declared before current plugin
|
||||||
if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
|
if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
|
||||||
plugin.parent_plugin = pre_plugin
|
plugin.parent_plugin = pre_plugin
|
||||||
pre_plugin.sub_plugins.add(plugin)
|
pre_plugin.sub_plugins.add(plugin)
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
"""本模块定义插件对象。
|
"""本模块定义插件相关信息。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.plugin.plugin 模块
|
description: nonebot.plugin.model 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from dataclasses import field, dataclass
|
from dataclasses import field, dataclass
|
||||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||||
@@ -11,11 +13,11 @@ from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
|
from nonebot.utils import resolve_dot_notation
|
||||||
# FIXME: backport for nonebug
|
|
||||||
from . import _plugins as plugins # nopycln: import
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from nonebot.adapters import Adapter
|
||||||
|
|
||||||
from .manager import PluginManager
|
from .manager import PluginManager
|
||||||
|
|
||||||
|
|
||||||
@@ -24,14 +26,39 @@ class PluginMetadata:
|
|||||||
"""插件元信息,由插件编写者提供"""
|
"""插件元信息,由插件编写者提供"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
"""插件可阅读名称"""
|
"""插件名称"""
|
||||||
description: str
|
description: str
|
||||||
"""插件功能介绍"""
|
"""插件功能介绍"""
|
||||||
usage: str
|
usage: str
|
||||||
"""插件使用方法"""
|
"""插件使用方法"""
|
||||||
|
type: Optional[str] = None
|
||||||
|
"""插件类型,用于商店分类"""
|
||||||
|
homepage: Optional[str] = None
|
||||||
|
"""插件主页"""
|
||||||
config: Optional[Type[BaseModel]] = None
|
config: Optional[Type[BaseModel]] = None
|
||||||
"""插件配置项"""
|
"""插件配置项"""
|
||||||
|
supported_adapters: Optional[Set[str]] = None
|
||||||
|
"""插件支持的适配器模块路径
|
||||||
|
|
||||||
|
格式为 `<module>[:<Adapter>]`,`~` 为 `nonebot.adapters.` 的缩写。
|
||||||
|
|
||||||
|
`None` 表示支持**所有适配器**。
|
||||||
|
"""
|
||||||
extra: Dict[Any, Any] = field(default_factory=dict)
|
extra: Dict[Any, Any] = field(default_factory=dict)
|
||||||
|
"""插件额外信息,可由插件编写者自由扩展定义"""
|
||||||
|
|
||||||
|
def get_supported_adapters(self) -> Optional[Set[Type["Adapter"]]]:
|
||||||
|
"""获取当前已安装的插件支持适配器类列表"""
|
||||||
|
if self.supported_adapters is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
adapters = set()
|
||||||
|
for adapter in self.supported_adapters:
|
||||||
|
with contextlib.suppress(ModuleNotFoundError, AttributeError):
|
||||||
|
adapters.add(
|
||||||
|
resolve_dot_notation(adapter, "Adapter", "nonebot.adapters.")
|
||||||
|
)
|
||||||
|
return adapters
|
||||||
|
|
||||||
|
|
||||||
@dataclass(eq=False)
|
@dataclass(eq=False)
|
@@ -4,16 +4,18 @@ FrontMatter:
|
|||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.plugin.on 模块
|
description: nonebot.plugin.on 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import inspect
|
import inspect
|
||||||
|
import warnings
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
||||||
|
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
|
from nonebot.matcher import Matcher, MatcherSource
|
||||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
||||||
from nonebot.rule import (
|
from nonebot.rule import (
|
||||||
Rule,
|
Rule,
|
||||||
@@ -28,7 +30,7 @@ from nonebot.rule import (
|
|||||||
shell_command,
|
shell_command,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .plugin import Plugin
|
from .model import Plugin
|
||||||
from . import get_plugin_by_module_name
|
from . import get_plugin_by_module_name
|
||||||
from .manager import _current_plugin_chain
|
from .manager import _current_plugin_chain
|
||||||
|
|
||||||
@@ -44,25 +46,39 @@ def store_matcher(matcher: Type[Matcher]) -> None:
|
|||||||
plugin_chain[-1].matcher.add(matcher)
|
plugin_chain[-1].matcher.add(matcher)
|
||||||
|
|
||||||
|
|
||||||
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]:
|
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]: # pragma: no cover
|
||||||
"""获取事件响应器定义所在插件。
|
"""获取事件响应器定义所在插件。
|
||||||
|
|
||||||
|
**Deprecated**, 请使用 {ref}`nonebot.plugin.on.get_matcher_source` 获取信息。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
depth: 调用栈深度
|
depth: 调用栈深度
|
||||||
"""
|
"""
|
||||||
# matcher defined when plugin loading
|
warnings.warn(
|
||||||
if plugin_chain := _current_plugin_chain.get():
|
"`get_matcher_plugin` is deprecated, please use `get_matcher_source` instead",
|
||||||
return plugin_chain[-1]
|
DeprecationWarning,
|
||||||
|
)
|
||||||
# matcher defined when plugin running
|
return (source := get_matcher_source(depth + 1)) and source.plugin
|
||||||
if module := get_matcher_module(depth + 1):
|
|
||||||
if plugin := get_plugin_by_module_name(module.__name__):
|
|
||||||
return plugin
|
|
||||||
|
|
||||||
|
|
||||||
def get_matcher_module(depth: int = 1) -> Optional[ModuleType]:
|
def get_matcher_module(depth: int = 1) -> Optional[ModuleType]: # pragma: no cover
|
||||||
"""获取事件响应器定义所在模块。
|
"""获取事件响应器定义所在模块。
|
||||||
|
|
||||||
|
**Deprecated**, 请使用 {ref}`nonebot.plugin.on.get_matcher_source` 获取信息。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
depth: 调用栈深度
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"`get_matcher_module` is deprecated, please use `get_matcher_source` instead",
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
return (source := get_matcher_source(depth + 1)) and source.module
|
||||||
|
|
||||||
|
|
||||||
|
def get_matcher_source(depth: int = 1) -> Optional[MatcherSource]:
|
||||||
|
"""获取事件响应器定义所在源码信息。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
depth: 调用栈深度
|
depth: 调用栈深度
|
||||||
"""
|
"""
|
||||||
@@ -70,7 +86,22 @@ def get_matcher_module(depth: int = 1) -> Optional[ModuleType]:
|
|||||||
if current_frame is None:
|
if current_frame is None:
|
||||||
return None
|
return None
|
||||||
frame = inspect.getouterframes(current_frame)[depth + 1].frame
|
frame = inspect.getouterframes(current_frame)[depth + 1].frame
|
||||||
return inspect.getmodule(frame)
|
|
||||||
|
module_name = (module := inspect.getmodule(frame)) and module.__name__
|
||||||
|
|
||||||
|
plugin: Optional["Plugin"] = None
|
||||||
|
# matcher defined when plugin loading
|
||||||
|
if plugin_chain := _current_plugin_chain.get():
|
||||||
|
plugin = plugin_chain[-1]
|
||||||
|
# matcher defined when plugin running
|
||||||
|
elif module_name:
|
||||||
|
plugin = get_plugin_by_module_name(module_name)
|
||||||
|
|
||||||
|
return MatcherSource(
|
||||||
|
plugin_name=plugin and plugin.name,
|
||||||
|
module_name=module_name,
|
||||||
|
lineno=frame.f_lineno,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def on(
|
def on(
|
||||||
@@ -108,8 +139,7 @@ def on(
|
|||||||
priority=priority,
|
priority=priority,
|
||||||
block=block,
|
block=block,
|
||||||
handlers=handlers,
|
handlers=handlers,
|
||||||
plugin=get_matcher_plugin(_depth + 1),
|
source=get_matcher_source(_depth + 1),
|
||||||
module=get_matcher_module(_depth + 1),
|
|
||||||
default_state=state,
|
default_state=state,
|
||||||
)
|
)
|
||||||
store_matcher(matcher)
|
store_matcher(matcher)
|
||||||
@@ -322,7 +352,8 @@ def on_shell_command(
|
|||||||
|
|
||||||
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
||||||
|
|
||||||
并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]` 中
|
可以通过 {ref}`nonebot.params.ShellCommandArgv` 获取原始参数列表,
|
||||||
|
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
cmd: 指定命令内容
|
cmd: 指定命令内容
|
||||||
@@ -427,6 +458,7 @@ class CommandGroup(_Group):
|
|||||||
|
|
||||||
参数:
|
参数:
|
||||||
cmd: 指定命令内容
|
cmd: 指定命令内容
|
||||||
|
prefix_aliases: 是否影响命令别名,给命令别名加前缀
|
||||||
rule: 事件响应规则
|
rule: 事件响应规则
|
||||||
permission: 事件响应权限
|
permission: 事件响应权限
|
||||||
handlers: 事件处理函数列表
|
handlers: 事件处理函数列表
|
||||||
@@ -437,11 +469,14 @@ class CommandGroup(_Group):
|
|||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
|
def __init__(
|
||||||
|
self, cmd: Union[str, Tuple[str, ...]], prefix_aliases: bool = False, **kwargs
|
||||||
|
):
|
||||||
"""命令前缀"""
|
"""命令前缀"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||||
self.base_kwargs.pop("aliases", None)
|
self.base_kwargs.pop("aliases", None)
|
||||||
|
self.prefix_aliases = prefix_aliases
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"CommandGroup(cmd={self.basecmd}, matchers={len(self.matchers)})"
|
return f"CommandGroup(cmd={self.basecmd}, matchers={len(self.matchers)})"
|
||||||
@@ -464,6 +499,11 @@ class CommandGroup(_Group):
|
|||||||
"""
|
"""
|
||||||
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
||||||
cmd = self.basecmd + sub_cmd
|
cmd = self.basecmd + sub_cmd
|
||||||
|
if self.prefix_aliases and (aliases := kwargs.get("aliases")):
|
||||||
|
kwargs["aliases"] = {
|
||||||
|
self.basecmd + ((alias,) if isinstance(alias, str) else alias)
|
||||||
|
for alias in aliases
|
||||||
|
}
|
||||||
matcher = on_command(cmd, **self._get_final_kwargs(kwargs))
|
matcher = on_command(cmd, **self._get_final_kwargs(kwargs))
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
@@ -488,6 +528,11 @@ class CommandGroup(_Group):
|
|||||||
"""
|
"""
|
||||||
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
||||||
cmd = self.basecmd + sub_cmd
|
cmd = self.basecmd + sub_cmd
|
||||||
|
if self.prefix_aliases and (aliases := kwargs.get("aliases")):
|
||||||
|
kwargs["aliases"] = {
|
||||||
|
self.basecmd + ((alias,) if isinstance(alias, str) else alias)
|
||||||
|
for alias in aliases
|
||||||
|
}
|
||||||
matcher = on_shell_command(cmd, **self._get_final_kwargs(kwargs))
|
matcher = on_shell_command(cmd, **self._get_final_kwargs(kwargs))
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
@@ -712,7 +757,8 @@ class MatcherGroup(_Group):
|
|||||||
|
|
||||||
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
||||||
|
|
||||||
并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]` 中
|
可以通过 {ref}`nonebot.params.ShellCommandArgv` 获取原始参数列表,
|
||||||
|
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
cmd: 指定命令内容
|
cmd: 指定命令内容
|
||||||
|
@@ -1,410 +1,421 @@
|
|||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Set, List, Type, Tuple, Union, Optional
|
|
||||||
|
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.rule import Rule, ArgumentParser
|
from nonebot.rule import Rule, ArgumentParser
|
||||||
|
from nonebot.matcher import Matcher, MatcherSource
|
||||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
||||||
|
|
||||||
from .plugin import Plugin
|
from .model import Plugin
|
||||||
|
|
||||||
def store_matcher(matcher: Type[Matcher]) -> None: ...
|
def store_matcher(matcher: type[Matcher]) -> None: ...
|
||||||
def get_matcher_plugin(depth: int = ...) -> Optional[Plugin]: ...
|
def get_matcher_plugin(depth: int = ...) -> Plugin | None: ...
|
||||||
def get_matcher_module(depth: int = ...) -> Optional[ModuleType]: ...
|
def get_matcher_module(depth: int = ...) -> ModuleType | None: ...
|
||||||
|
def get_matcher_source(depth: int = ...) -> MatcherSource | None: ...
|
||||||
def on(
|
def on(
|
||||||
type: str = "",
|
type: str = "",
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_metaevent(
|
def on_metaevent(
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_message(
|
def on_message(
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_notice(
|
def on_notice(
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_request(
|
def on_request(
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_startswith(
|
def on_startswith(
|
||||||
msg: Union[str, Tuple[str, ...]],
|
msg: str | tuple[str, ...],
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
*,
|
*,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_endswith(
|
def on_endswith(
|
||||||
msg: Union[str, Tuple[str, ...]],
|
msg: str | tuple[str, ...],
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
*,
|
*,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_fullmatch(
|
def on_fullmatch(
|
||||||
msg: Union[str, Tuple[str, ...]],
|
msg: str | tuple[str, ...],
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
*,
|
*,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_keyword(
|
def on_keyword(
|
||||||
keywords: Set[str],
|
keywords: set[str],
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_command(
|
def on_command(
|
||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: str | tuple[str, ...],
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||||
force_whitespace: Optional[Union[str, bool]] = ...,
|
force_whitespace: str | bool | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_shell_command(
|
def on_shell_command(
|
||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: str | tuple[str, ...],
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||||
parser: Optional[ArgumentParser] = ...,
|
parser: ArgumentParser | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_regex(
|
def on_regex(
|
||||||
pattern: str,
|
pattern: str,
|
||||||
flags: Union[int, re.RegexFlag] = ...,
|
flags: int | re.RegexFlag = ...,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_type(
|
def on_type(
|
||||||
types: Union[Type[Event], Tuple[Type[Event], ...]],
|
types: type[Event] | tuple[type[Event], ...],
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
*,
|
*,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
|
|
||||||
class CommandGroup:
|
class _Group:
|
||||||
|
matchers: list[type[Matcher]] = ...
|
||||||
|
base_kwargs: dict[str, Any] = ...
|
||||||
|
def _get_final_kwargs(
|
||||||
|
self, update: dict[str, Any], *, exclude: set[str] | None = None
|
||||||
|
) -> dict[str, Any]: ...
|
||||||
|
|
||||||
|
class CommandGroup(_Group):
|
||||||
|
basecmd: tuple[str, ...] = ...
|
||||||
|
prefix_aliases: bool = ...
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: str | tuple[str, ...],
|
||||||
|
prefix_aliases: bool = ...,
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
): ...
|
): ...
|
||||||
def command(
|
def command(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: str | tuple[str, ...],
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||||
force_whitespace: Optional[Union[str, bool]] = ...,
|
force_whitespace: str | bool | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def shell_command(
|
def shell_command(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: str | tuple[str, ...],
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||||
parser: Optional[ArgumentParser] = ...,
|
parser: ArgumentParser | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
|
|
||||||
class MatcherGroup:
|
class MatcherGroup(_Group):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
type: str = ...,
|
type: str = ...,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
): ...
|
): ...
|
||||||
def on(
|
def on(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
type: str = ...,
|
type: str = ...,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_metaevent(
|
def on_metaevent(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_message(
|
def on_message(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_notice(
|
def on_notice(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_request(
|
def on_request(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_startswith(
|
def on_startswith(
|
||||||
self,
|
self,
|
||||||
msg: Union[str, Tuple[str, ...]],
|
msg: str | tuple[str, ...],
|
||||||
*,
|
*,
|
||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_endswith(
|
def on_endswith(
|
||||||
self,
|
self,
|
||||||
msg: Union[str, Tuple[str, ...]],
|
msg: str | tuple[str, ...],
|
||||||
*,
|
*,
|
||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_fullmatch(
|
def on_fullmatch(
|
||||||
self,
|
self,
|
||||||
msg: Union[str, Tuple[str, ...]],
|
msg: str | tuple[str, ...],
|
||||||
*,
|
*,
|
||||||
ignorecase: bool = ...,
|
ignorecase: bool = ...,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_keyword(
|
def on_keyword(
|
||||||
self,
|
self,
|
||||||
keywords: Set[str],
|
keywords: set[str],
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_command(
|
def on_command(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: str | tuple[str, ...],
|
||||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||||
force_whitespace: Optional[Union[str, bool]] = ...,
|
force_whitespace: str | bool | None = ...,
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_shell_command(
|
def on_shell_command(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: str | tuple[str, ...],
|
||||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||||
parser: Optional[ArgumentParser] = ...,
|
parser: ArgumentParser | None = ...,
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_regex(
|
def on_regex(
|
||||||
self,
|
self,
|
||||||
pattern: str,
|
pattern: str,
|
||||||
flags: Union[int, re.RegexFlag] = ...,
|
flags: int | re.RegexFlag = ...,
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
def on_type(
|
def on_type(
|
||||||
self,
|
self,
|
||||||
types: Union[Type[Event], Tuple[Type[Event]]],
|
types: type[Event] | tuple[type[Event]],
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Rule | T_RuleChecker | None = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Permission | T_PermissionChecker | None = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: list[T_Handler | Dependent] | None = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
expire_time: datetime | timedelta | None = ...,
|
||||||
priority: int = ...,
|
priority: int = ...,
|
||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: T_State | None = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> type[Matcher]: ...
|
||||||
|
@@ -1,7 +1,18 @@
|
|||||||
|
from nonebot import on_command
|
||||||
from nonebot.rule import to_me
|
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 on_command
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="echo",
|
||||||
|
description="重复你说的话",
|
||||||
|
usage="/echo [text]",
|
||||||
|
type="application",
|
||||||
|
homepage="https://github.com/nonebot/nonebot2/blob/master/nonebot/plugins/echo.py",
|
||||||
|
config=None,
|
||||||
|
supported_adapters=None,
|
||||||
|
)
|
||||||
|
|
||||||
echo = on_command("echo", to_me())
|
echo = on_command("echo", to_me())
|
||||||
|
|
||||||
|
@@ -2,8 +2,19 @@ from typing import Dict, AsyncGenerator
|
|||||||
|
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
from nonebot.params import Depends
|
from nonebot.params import Depends
|
||||||
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot.message import IgnoredException, event_preprocessor
|
from nonebot.message import IgnoredException, event_preprocessor
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="唯一会话",
|
||||||
|
description="限制同一会话内同时只能运行一个响应器",
|
||||||
|
usage="加载插件后自动生效",
|
||||||
|
type="application",
|
||||||
|
homepage="https://github.com/nonebot/nonebot2/blob/master/nonebot/plugins/single_session.py",
|
||||||
|
config=None,
|
||||||
|
supported_adapters=None,
|
||||||
|
)
|
||||||
|
|
||||||
_running_matcher: Dict[str, int] = {}
|
_running_matcher: Dict[str, int] = {}
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +26,7 @@ async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
|
|||||||
yield result
|
yield result
|
||||||
else:
|
else:
|
||||||
current_event_id = id(event)
|
current_event_id = id(event)
|
||||||
if event_id := _running_matcher.get(session_id, None):
|
if event_id := _running_matcher.get(session_id):
|
||||||
result = event_id != current_event_id
|
result = event_id != current_event_id
|
||||||
else:
|
else:
|
||||||
_running_matcher[session_id] = current_event_id
|
_running_matcher[session_id] = current_event_id
|
||||||
|
109
nonebot/rule.py
109
nonebot/rule.py
@@ -1,7 +1,8 @@
|
|||||||
"""本模块是 {ref}`nonebot.matcher.Matcher.rule` 的类型定义。
|
"""本模块是 {ref}`nonebot.matcher.Matcher.rule` 的类型定义。
|
||||||
|
|
||||||
每个事件响应器 {ref}`nonebot.matcher.Matcher` 拥有一个匹配规则 {ref}`nonebot.rule.Rule`
|
每个{ref}`事件响应器 <nonebot.matcher.Matcher>`拥有一个
|
||||||
其中是 `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
{ref}`nonebot.rule.Rule`,其中是 `RuleChecker` 的集合。
|
||||||
|
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
@@ -11,6 +12,7 @@ FrontMatter:
|
|||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
from argparse import Action
|
from argparse import Action
|
||||||
|
from gettext import gettext
|
||||||
from argparse import ArgumentError
|
from argparse import ArgumentError
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from itertools import chain, product
|
from itertools import chain, product
|
||||||
@@ -43,15 +45,12 @@ from nonebot.adapters import Bot, Event, Message, MessageSegment
|
|||||||
from nonebot.params import Command, EventToMe, CommandArg, CommandWhitespace
|
from nonebot.params import Command, EventToMe, CommandArg, CommandWhitespace
|
||||||
from nonebot.consts import (
|
from nonebot.consts import (
|
||||||
CMD_KEY,
|
CMD_KEY,
|
||||||
REGEX_STR,
|
|
||||||
PREFIX_KEY,
|
PREFIX_KEY,
|
||||||
REGEX_DICT,
|
|
||||||
SHELL_ARGS,
|
SHELL_ARGS,
|
||||||
SHELL_ARGV,
|
SHELL_ARGV,
|
||||||
CMD_ARG_KEY,
|
CMD_ARG_KEY,
|
||||||
KEYWORD_KEY,
|
KEYWORD_KEY,
|
||||||
RAW_CMD_KEY,
|
RAW_CMD_KEY,
|
||||||
REGEX_GROUP,
|
|
||||||
ENDSWITH_KEY,
|
ENDSWITH_KEY,
|
||||||
CMD_START_KEY,
|
CMD_START_KEY,
|
||||||
FULLMATCH_KEY,
|
FULLMATCH_KEY,
|
||||||
@@ -62,20 +61,19 @@ from nonebot.consts import (
|
|||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
CMD_RESULT = TypedDict(
|
|
||||||
"CMD_RESULT",
|
|
||||||
{
|
|
||||||
"command": Optional[Tuple[str, ...]],
|
|
||||||
"raw_command": Optional[str],
|
|
||||||
"command_arg": Optional[Message[MessageSegment]],
|
|
||||||
"command_start": Optional[str],
|
|
||||||
"command_whitespace": Optional[str],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
TRIE_VALUE = NamedTuple(
|
class CMD_RESULT(TypedDict):
|
||||||
"TRIE_VALUE", [("command_start", str), ("command", Tuple[str, ...])]
|
command: Optional[Tuple[str, ...]]
|
||||||
)
|
raw_command: Optional[str]
|
||||||
|
command_arg: Optional[Message]
|
||||||
|
command_start: Optional[str]
|
||||||
|
command_whitespace: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class TRIE_VALUE(NamedTuple):
|
||||||
|
command_start: str
|
||||||
|
command: Tuple[str, ...]
|
||||||
|
|
||||||
|
|
||||||
parser_message: ContextVar[str] = ContextVar("parser_message")
|
parser_message: ContextVar[str] = ContextVar("parser_message")
|
||||||
|
|
||||||
@@ -378,11 +376,12 @@ class CommandRule:
|
|||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||||
|
cmd_arg: Optional[Message] = CommandArg(),
|
||||||
cmd_whitespace: Optional[str] = CommandWhitespace(),
|
cmd_whitespace: Optional[str] = CommandWhitespace(),
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if cmd not in self.cmds:
|
if cmd not in self.cmds:
|
||||||
return False
|
return False
|
||||||
if self.force_whitespace is None:
|
if self.force_whitespace is None or not cmd_arg:
|
||||||
return True
|
return True
|
||||||
if isinstance(self.force_whitespace, str):
|
if isinstance(self.force_whitespace, str):
|
||||||
return self.force_whitespace == cmd_whitespace
|
return self.force_whitespace == cmd_whitespace
|
||||||
@@ -407,7 +406,7 @@ def command(
|
|||||||
force_whitespace: 是否强制命令后必须有指定空白符
|
force_whitespace: 是否强制命令后必须有指定空白符
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
使用默认 `command_start`, `command_sep` 配置
|
使用默认 `command_start`, `command_sep` 配置情况下:
|
||||||
|
|
||||||
命令 `("test",)` 可以匹配: `/test` 开头的消息
|
命令 `("test",)` 可以匹配: `/test` 开头的消息
|
||||||
命令 `("test", "sub")` 可以匹配: `/test.sub` 开头的消息
|
命令 `("test", "sub")` 可以匹配: `/test.sub` 开头的消息
|
||||||
@@ -442,6 +441,8 @@ def command(
|
|||||||
class ArgumentParser(ArgParser):
|
class ArgumentParser(ArgParser):
|
||||||
"""`shell_like` 命令参数解析器,解析出错时不会退出程序。
|
"""`shell_like` 命令参数解析器,解析出错时不会退出程序。
|
||||||
|
|
||||||
|
支持 {ref}`nonebot.adapters.Message` 富文本解析。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
用法与 `argparse.ArgumentParser` 相同,
|
用法与 `argparse.ArgumentParser` 相同,
|
||||||
参考文档: [argparse](https://docs.python.org/3/library/argparse.html)
|
参考文档: [argparse](https://docs.python.org/3/library/argparse.html)
|
||||||
@@ -450,16 +451,39 @@ class ArgumentParser(ArgParser):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_args(
|
def parse_known_args(
|
||||||
self, args: Optional[Sequence[Union[str, MessageSegment]]] = ...
|
self,
|
||||||
) -> Namespace:
|
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||||
|
namespace: None = None,
|
||||||
|
) -> Tuple[Namespace, List[Union[str, MessageSegment]]]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_known_args(
|
||||||
|
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||||
|
) -> Tuple[T, List[Union[str, MessageSegment]]]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_known_args(
|
||||||
|
self, *, namespace: T
|
||||||
|
) -> Tuple[T, List[Union[str, MessageSegment]]]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def parse_known_args(
|
||||||
|
self,
|
||||||
|
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||||
|
namespace: Optional[T] = None,
|
||||||
|
) -> Tuple[Union[Namespace, T], List[Union[str, MessageSegment]]]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_args(
|
def parse_args(
|
||||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: None
|
self,
|
||||||
|
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||||
|
namespace: None = None,
|
||||||
) -> Namespace:
|
) -> Namespace:
|
||||||
... # type: ignore[misc]
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_args(
|
def parse_args(
|
||||||
@@ -467,12 +491,20 @@ class ArgumentParser(ArgParser):
|
|||||||
) -> T:
|
) -> T:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_args(self, *, namespace: T) -> T:
|
||||||
|
...
|
||||||
|
|
||||||
def parse_args(
|
def parse_args(
|
||||||
self,
|
self,
|
||||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||||
namespace: Optional[T] = None,
|
namespace: Optional[T] = None,
|
||||||
) -> Union[Namespace, T]:
|
) -> Union[Namespace, T]:
|
||||||
...
|
result, argv = self.parse_known_args(args, namespace)
|
||||||
|
if argv:
|
||||||
|
msg = gettext("unrecognized arguments: %s")
|
||||||
|
self.error(msg % " ".join(map(str, argv)))
|
||||||
|
return cast(Union[Namespace, T], result)
|
||||||
|
|
||||||
def _parse_optional(
|
def _parse_optional(
|
||||||
self, arg_string: Union[str, MessageSegment]
|
self, arg_string: Union[str, MessageSegment]
|
||||||
@@ -558,12 +590,16 @@ def shell_command(
|
|||||||
根据配置里提供的 {ref}``command_start` <nonebot.config.Config.command_start>`,
|
根据配置里提供的 {ref}``command_start` <nonebot.config.Config.command_start>`,
|
||||||
{ref}``command_sep` <nonebot.config.Config.command_sep>` 判断消息是否为命令。
|
{ref}``command_sep` <nonebot.config.Config.command_sep>` 判断消息是否为命令。
|
||||||
|
|
||||||
可以通过 {ref}`nonebot.params.Command` 获取匹配成功的命令(例: `("test",)`),
|
可以通过 {ref}`nonebot.params.Command` 获取匹配成功的命令
|
||||||
通过 {ref}`nonebot.params.RawCommand` 获取匹配成功的原始命令文本(例: `"/test"`),
|
(例: `("test",)`),
|
||||||
通过 {ref}`nonebot.params.ShellCommandArgv` 获取解析前的参数列表(例: `["arg", "-h"]`),
|
通过 {ref}`nonebot.params.RawCommand` 获取匹配成功的原始命令文本
|
||||||
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典(例: `{"arg": "arg", "h": True}`)。
|
(例: `"/test"`),
|
||||||
|
通过 {ref}`nonebot.params.ShellCommandArgv` 获取解析前的参数列表
|
||||||
|
(例: `["arg", "-h"]`),
|
||||||
|
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典
|
||||||
|
(例: `{"arg": "arg", "h": True}`)。
|
||||||
|
|
||||||
:::warning 警告
|
:::caution 警告
|
||||||
如果参数解析失败,则通过 {ref}`nonebot.params.ShellCommandArgs`
|
如果参数解析失败,则通过 {ref}`nonebot.params.ShellCommandArgs`
|
||||||
获取的将是 {ref}`nonebot.exception.ParserExit` 异常。
|
获取的将是 {ref}`nonebot.exception.ParserExit` 异常。
|
||||||
:::
|
:::
|
||||||
@@ -573,7 +609,8 @@ def shell_command(
|
|||||||
parser: {ref}`nonebot.rule.ArgumentParser` 对象
|
parser: {ref}`nonebot.rule.ArgumentParser` 对象
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
使用默认 `command_start`, `command_sep` 配置,更多示例参考 `argparse` 标准库文档。
|
使用默认 `command_start`, `command_sep` 配置,更多示例参考
|
||||||
|
[argparse](https://docs.python.org/3/library/argparse.html) 标准库文档。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot.rule import ArgumentParser
|
from nonebot.rule import ArgumentParser
|
||||||
@@ -646,10 +683,7 @@ class RegexRule:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
if matched := re.search(self.regex, str(msg), self.flags):
|
if matched := re.search(self.regex, str(msg), self.flags):
|
||||||
state[REGEX_MATCHED] = matched.group()
|
state[REGEX_MATCHED] = matched
|
||||||
state[REGEX_STR] = matched.group()
|
|
||||||
state[REGEX_GROUP] = matched.groups()
|
|
||||||
state[REGEX_DICT] = matched.groupdict()
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -671,7 +705,8 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip 提示
|
:::tip 提示
|
||||||
正则表达式匹配使用 `EventMessage` 的 `str` 字符串,而非 `EventMessage` 的 `PlainText` 纯文本字符串
|
正则表达式匹配使用 `EventMessage` 的 `str` 字符串,
|
||||||
|
而非 `EventMessage` 的 `PlainText` 纯文本字符串
|
||||||
:::
|
:::
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
"""本模块定义了 NoneBot 模块中共享的一些类型。
|
"""本模块定义了 NoneBot 模块中共享的一些类型。
|
||||||
|
|
||||||
下面的文档中,「类型」部分使用 Python 的 Type Hint 语法,
|
使用 Python 的 Type Hint 语法,
|
||||||
参考 [`PEP 484`](https://www.python.org/dev/peps/pep-0484/),
|
参考 [`PEP 484`](https://www.python.org/dev/peps/pep-0484/),
|
||||||
[`PEP 526`](https://www.python.org/dev/peps/pep-0526/) 和
|
[`PEP 526`](https://www.python.org/dev/peps/pep-0526/) 和
|
||||||
[`typing`](https://docs.python.org/3/library/typing.html)。
|
[`typing`](https://docs.python.org/3/library/typing.html)。
|
||||||
|
|
||||||
除了 Python 内置的类型,下面还出现了如下 NoneBot 自定类型,实际上它们是 Python 内置类型的别名。
|
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
sidebar_position: 11
|
sidebar_position: 11
|
||||||
description: nonebot.typing 模块
|
description: nonebot.typing 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from typing_extensions import ParamSpec, TypeAlias, override
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
@@ -30,28 +30,31 @@ if TYPE_CHECKING:
|
|||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
P = ParamSpec("P")
|
||||||
|
|
||||||
T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
|
T_Wrapped: TypeAlias = Callable[P, T]
|
||||||
|
|
||||||
|
|
||||||
def overrides(InterfaceClass: object) -> Callable[[T_Wrapped], T_Wrapped]:
|
def overrides(InterfaceClass: object):
|
||||||
"""标记一个方法为父类 interface 的 implement"""
|
"""标记一个方法为父类 interface 的 implement"""
|
||||||
|
|
||||||
def overrider(func: T_Wrapped) -> T_Wrapped:
|
warnings.warn(
|
||||||
assert func.__name__ in dir(InterfaceClass), f"Error method: {func.__name__}"
|
"overrides is deprecated and will be removed in a future version, "
|
||||||
return func
|
"use @typing_extensions.override instead. "
|
||||||
|
"See [PEP 698](https://peps.python.org/pep-0698/) for more details.",
|
||||||
return overrider
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
return override
|
||||||
|
|
||||||
|
|
||||||
# state
|
# state
|
||||||
T_State = Dict[Any, Any]
|
T_State: TypeAlias = Dict[Any, Any]
|
||||||
"""事件处理状态 State 类型"""
|
"""事件处理状态 State 类型"""
|
||||||
|
|
||||||
_DependentCallable = Union[Callable[..., T], Callable[..., Awaitable[T]]]
|
_DependentCallable: TypeAlias = Union[Callable[..., T], Callable[..., Awaitable[T]]]
|
||||||
|
|
||||||
# driver hooks
|
# driver hooks
|
||||||
T_BotConnectionHook = _DependentCallable[Any]
|
T_BotConnectionHook: TypeAlias = _DependentCallable[Any]
|
||||||
"""Bot 连接建立时钩子函数
|
"""Bot 连接建立时钩子函数
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -60,7 +63,7 @@ T_BotConnectionHook = _DependentCallable[Any]
|
|||||||
- BotParam: Bot 对象
|
- BotParam: Bot 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_BotDisconnectionHook = _DependentCallable[Any]
|
T_BotDisconnectionHook: TypeAlias = _DependentCallable[Any]
|
||||||
"""Bot 连接断开时钩子函数
|
"""Bot 连接断开时钩子函数
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -71,15 +74,15 @@ T_BotDisconnectionHook = _DependentCallable[Any]
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# api hooks
|
# api hooks
|
||||||
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
|
T_CallingAPIHook: TypeAlias = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
|
||||||
"""`bot.call_api` 钩子函数"""
|
"""`bot.call_api` 钩子函数"""
|
||||||
T_CalledAPIHook = Callable[
|
T_CalledAPIHook: TypeAlias = Callable[
|
||||||
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[Any]
|
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[Any]
|
||||||
]
|
]
|
||||||
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
||||||
|
|
||||||
# event hooks
|
# event hooks
|
||||||
T_EventPreProcessor = _DependentCallable[Any]
|
T_EventPreProcessor: TypeAlias = _DependentCallable[Any]
|
||||||
"""事件预处理函数 EventPreProcessor 类型
|
"""事件预处理函数 EventPreProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -90,7 +93,7 @@ T_EventPreProcessor = _DependentCallable[Any]
|
|||||||
- StateParam: State 对象
|
- StateParam: State 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_EventPostProcessor = _DependentCallable[Any]
|
T_EventPostProcessor: TypeAlias = _DependentCallable[Any]
|
||||||
"""事件预处理函数 EventPostProcessor 类型
|
"""事件预处理函数 EventPostProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -103,7 +106,7 @@ T_EventPostProcessor = _DependentCallable[Any]
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# matcher run hooks
|
# matcher run hooks
|
||||||
T_RunPreProcessor = _DependentCallable[Any]
|
T_RunPreProcessor: TypeAlias = _DependentCallable[Any]
|
||||||
"""事件响应器运行前预处理函数 RunPreProcessor 类型
|
"""事件响应器运行前预处理函数 RunPreProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -115,7 +118,7 @@ T_RunPreProcessor = _DependentCallable[Any]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_RunPostProcessor = _DependentCallable[Any]
|
T_RunPostProcessor: TypeAlias = _DependentCallable[Any]
|
||||||
"""事件响应器运行后后处理函数 RunPostProcessor 类型
|
"""事件响应器运行后后处理函数 RunPostProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -130,7 +133,7 @@ T_RunPostProcessor = _DependentCallable[Any]
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# rule, permission
|
# rule, permission
|
||||||
T_RuleChecker = _DependentCallable[bool]
|
T_RuleChecker: TypeAlias = _DependentCallable[bool]
|
||||||
"""RuleChecker 即判断是否响应事件的处理函数。
|
"""RuleChecker 即判断是否响应事件的处理函数。
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -141,7 +144,7 @@ T_RuleChecker = _DependentCallable[bool]
|
|||||||
- StateParam: State 对象
|
- StateParam: State 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_PermissionChecker = _DependentCallable[bool]
|
T_PermissionChecker: TypeAlias = _DependentCallable[bool]
|
||||||
"""PermissionChecker 即判断事件是否满足权限的处理函数。
|
"""PermissionChecker 即判断事件是否满足权限的处理函数。
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -152,10 +155,11 @@ T_PermissionChecker = _DependentCallable[bool]
|
|||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
|
|
||||||
T_Handler = _DependentCallable[Any]
|
T_Handler: TypeAlias = _DependentCallable[Any]
|
||||||
"""Handler 处理函数。"""
|
"""Handler 处理函数。"""
|
||||||
T_TypeUpdater = _DependentCallable[str]
|
T_TypeUpdater: TypeAlias = _DependentCallable[str]
|
||||||
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 `message`。
|
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。
|
||||||
|
默认会更新为 `message`。
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
|
|
||||||
@@ -166,8 +170,9 @@ T_TypeUpdater = _DependentCallable[str]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_PermissionUpdater = _DependentCallable["Permission"]
|
T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
|
||||||
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。默认会更新为当前事件的触发对象。
|
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。
|
||||||
|
默认会更新为当前事件的触发对象。
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
|
|
||||||
@@ -178,5 +183,5 @@ T_PermissionUpdater = _DependentCallable["Permission"]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_DependencyCache = Dict[_DependentCallable[Any], "Task[Any]"]
|
T_DependencyCache: TypeAlias = Dict[_DependentCallable[Any], "Task[Any]"]
|
||||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||||
|
@@ -12,14 +12,16 @@ import inspect
|
|||||||
import importlib
|
import importlib
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from contextvars import copy_context
|
||||||
from functools import wraps, partial
|
from functools import wraps, partial
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing_extensions import ParamSpec, get_args, get_origin
|
from typing_extensions import ParamSpec, get_args, override, get_origin
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Type,
|
Type,
|
||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
Union,
|
||||||
|
Generic,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Callable,
|
Callable,
|
||||||
Optional,
|
Optional,
|
||||||
@@ -32,7 +34,6 @@ from typing import (
|
|||||||
from pydantic.typing import is_union, is_none_type
|
from pydantic.typing import is_union, is_none_type
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.typing import overrides
|
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
R = TypeVar("R")
|
R = TypeVar("R")
|
||||||
@@ -57,8 +58,13 @@ def generic_check_issubclass(
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。
|
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。
|
||||||
|
|
||||||
特别的,如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
|
特别的:
|
||||||
则会检查其中的类型是否是 class_or_tuple 中的一个类型子类。(None 会被忽略)
|
|
||||||
|
- 如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
|
||||||
|
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
|
||||||
|
- 如果 cls 是 `typing.TypeVar` 类型,
|
||||||
|
则会检查其 `__bound__` 或 `__constraints__`
|
||||||
|
是否是 class_or_tuple 中一个类型的子类或 None。
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return issubclass(cls, class_or_tuple)
|
return issubclass(cls, class_or_tuple)
|
||||||
@@ -69,8 +75,18 @@ def generic_check_issubclass(
|
|||||||
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
|
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
|
||||||
for type_ in get_args(cls)
|
for type_ in get_args(cls)
|
||||||
)
|
)
|
||||||
|
# ensure generic List, Dict can be checked
|
||||||
elif origin:
|
elif origin:
|
||||||
return issubclass(origin, class_or_tuple)
|
return issubclass(origin, class_or_tuple)
|
||||||
|
elif isinstance(cls, TypeVar):
|
||||||
|
if cls.__constraints__:
|
||||||
|
return all(
|
||||||
|
is_none_type(type_)
|
||||||
|
or generic_check_issubclass(type_, class_or_tuple)
|
||||||
|
for type_ in cls.__constraints__
|
||||||
|
)
|
||||||
|
elif cls.__bound__:
|
||||||
|
return generic_check_issubclass(cls.__bound__, class_or_tuple)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -111,7 +127,8 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
|
|||||||
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()
|
loop = asyncio.get_running_loop()
|
||||||
pfunc = partial(call, *args, **kwargs)
|
pfunc = partial(call, *args, **kwargs)
|
||||||
result = await loop.run_in_executor(None, pfunc)
|
context = copy_context()
|
||||||
|
result = await loop.run_in_executor(None, partial(context.run, pfunc))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return _wrapper
|
return _wrapper
|
||||||
@@ -136,6 +153,7 @@ async def run_sync_ctx_manager(
|
|||||||
async def run_coro_with_catch(
|
async def run_coro_with_catch(
|
||||||
coro: Coroutine[Any, Any, T],
|
coro: Coroutine[Any, Any, T],
|
||||||
exc: Tuple[Type[Exception], ...],
|
exc: Tuple[Type[Exception], ...],
|
||||||
|
return_on_err: None = None,
|
||||||
) -> Union[T, None]:
|
) -> Union[T, None]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -154,6 +172,17 @@ async def run_coro_with_catch(
|
|||||||
exc: Tuple[Type[Exception], ...],
|
exc: Tuple[Type[Exception], ...],
|
||||||
return_on_err: Optional[R] = None,
|
return_on_err: Optional[R] = None,
|
||||||
) -> Optional[Union[T, R]]:
|
) -> Optional[Union[T, R]]:
|
||||||
|
"""运行协程并当遇到指定异常时返回指定值。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
coro: 要运行的协程
|
||||||
|
exc: 要捕获的异常
|
||||||
|
return_on_err: 当发生异常时返回的值
|
||||||
|
|
||||||
|
返回:
|
||||||
|
协程的返回值或发生异常时的指定值
|
||||||
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return await coro
|
return await coro
|
||||||
except exc:
|
except exc:
|
||||||
@@ -192,10 +221,20 @@ def resolve_dot_notation(
|
|||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class DataclassEncoder(json.JSONEncoder):
|
class classproperty(Generic[T]):
|
||||||
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
|
"""类属性装饰器"""
|
||||||
|
|
||||||
@overrides(json.JSONEncoder)
|
def __init__(self, func: Callable[[Any], T]) -> None:
|
||||||
|
self.func = func
|
||||||
|
|
||||||
|
def __get__(self, instance: Any, owner: Optional[Type[Any]] = None) -> T:
|
||||||
|
return self.func(type(instance) if owner is None else owner)
|
||||||
|
|
||||||
|
|
||||||
|
class DataclassEncoder(json.JSONEncoder):
|
||||||
|
"""可以序列化 {ref}`nonebot.adapters.Message`(List[Dataclass]) 的 `JSONEncoder`"""
|
||||||
|
|
||||||
|
@override
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if dataclasses.is_dataclass(o):
|
if dataclasses.is_dataclass(o):
|
||||||
return {f.name: getattr(o, f.name) for f in dataclasses.fields(o)}
|
return {f.name: getattr(o, f.name) for f in dataclasses.fields(o)}
|
||||||
@@ -211,6 +250,8 @@ def logger_wrapper(logger_name: str):
|
|||||||
返回:
|
返回:
|
||||||
日志记录函数
|
日志记录函数
|
||||||
|
|
||||||
|
日志记录函数的参数:
|
||||||
|
|
||||||
- level: 日志等级
|
- level: 日志等级
|
||||||
- message: 日志信息
|
- message: 日志信息
|
||||||
- exception: 异常信息
|
- exception: 异常信息
|
||||||
|
25
package.json
25
package.json
@@ -11,10 +11,31 @@
|
|||||||
"start": "yarn workspace nonebot start",
|
"start": "yarn workspace nonebot start",
|
||||||
"serve": "yarn workspace nonebot serve",
|
"serve": "yarn workspace nonebot serve",
|
||||||
"clear": "yarn workspace nonebot clear",
|
"clear": "yarn workspace nonebot clear",
|
||||||
"prettier": "prettier --config ./.prettierrc --write \"./website/\""
|
"prettier": "prettier --config ./.prettierrc --write \"./website/\"",
|
||||||
|
"lint": "yarn lint:js && yarn lint:style",
|
||||||
|
"lint:js": "eslint --cache --report-unused-disable-directives \"**/*.{js,jsx,ts,tsx,mjs}\"",
|
||||||
|
"lint:js:fix": "eslint --cache --report-unused-disable-directives --fix \"**/*.{js,jsx,ts,tsx,mjs}\"",
|
||||||
|
"lint:style": "stylelint \"**/*.css\"",
|
||||||
|
"lint:style:fix": "stylelint --fix \"**/*.css\"",
|
||||||
|
"pyright": "pyright"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
||||||
|
"@typescript-eslint/parser": "^6.6.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"prettier": "^2.5.0"
|
"eslint": "^8.48.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-import-resolver-typescript": "^3.6.0",
|
||||||
|
"eslint-plugin-import": "^2.28.1",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-regexp": "^1.15.0",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"pyright": "^1.1.317",
|
||||||
|
"stylelint": "^15.10.3",
|
||||||
|
"stylelint-config-standard": "^34.0.0",
|
||||||
|
"stylelint-prettier": "^4.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://v2.nonebot.dev/"><img src="https://raw.githubusercontent.com/nonebot/nonebot2/master/docs/.vuepress/public/logo.png" width="200" height="200" alt="nonebot"></a>
|
<a href="https://nonebot.dev/"><img src="https://nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
@@ -22,6 +22,6 @@ _✨ NoneBot 本地文档插件 ✨_
|
|||||||
|
|
||||||
## 使用方式
|
## 使用方式
|
||||||
|
|
||||||
加载插件并启动 Bot ,在浏览器内打开 `http://host:port/docs/`。
|
加载插件并启动 Bot ,在浏览器内打开 `http://host:port/website/`。
|
||||||
|
|
||||||
具体网址会在控制台内输出。
|
具体网址会在控制台内输出。
|
||||||
|
@@ -2,6 +2,17 @@ import importlib
|
|||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="NoneBot 离线文档",
|
||||||
|
description="在本地查看 NoneBot 文档",
|
||||||
|
usage="启动机器人后访问 http://localhost:port/website/ 查看文档",
|
||||||
|
type="application",
|
||||||
|
homepage="https://github.com/nonebot/nonebot2/blob/master/packages/nonebot-plugin-docs",
|
||||||
|
config=None,
|
||||||
|
supported_adapters=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
@@ -17,7 +28,7 @@ def init():
|
|||||||
register_route(driver)
|
register_route(driver)
|
||||||
host = str(driver.config.host)
|
host = str(driver.config.host)
|
||||||
port = driver.config.port
|
port = driver.config.port
|
||||||
if host in ["0.0.0.0", "127.0.0.1"]:
|
if host in {"0.0.0.0", "127.0.0.1"}:
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
logger.opt(colors=True).info(
|
logger.opt(colors=True).info(
|
||||||
f"Nonebot docs will be running at: "
|
f"Nonebot docs will be running at: "
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "nonebot-plugin-docs"
|
name = "nonebot-plugin-docs"
|
||||||
version = "2.0.0-beta.1"
|
version = "2.0.0"
|
||||||
description = "View NoneBot2 Docs Locally"
|
description = "View NoneBot2 Docs Locally"
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -13,7 +13,7 @@ include = ["nonebot_plugin_docs/dist/**/*"]
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
nonebot2 = "^2.0.0-beta.1"
|
nonebot2 = "^2.0.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
2306
poetry.lock
generated
2306
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,55 +1,61 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "nonebot2"
|
name = "nonebot2"
|
||||||
version = "2.0.0rc4"
|
version = "2.1.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"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://v2.nonebot.dev/"
|
homepage = "https://nonebot.dev/"
|
||||||
repository = "https://github.com/nonebot/nonebot2"
|
repository = "https://github.com/nonebot/nonebot2"
|
||||||
documentation = "https://v2.nonebot.dev/"
|
documentation = "https://nonebot.dev/"
|
||||||
keywords = ["bot", "qq", "qqbot", "mirai", "coolq"]
|
keywords = ["bot", "qq", "qqbot", "mirai", "coolq"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Framework :: Robot Framework",
|
"Framework :: Robot Framework",
|
||||||
"Framework :: Robot Framework :: Library",
|
"Framework :: Robot Framework :: Library",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python :: 3"
|
"Programming Language :: Python :: 3",
|
||||||
]
|
|
||||||
packages = [
|
|
||||||
{ include = "nonebot" },
|
|
||||||
]
|
]
|
||||||
|
packages = [{ include = "nonebot" }]
|
||||||
include = ["nonebot/py.typed"]
|
include = ["nonebot/py.typed"]
|
||||||
|
|
||||||
|
[tool.poetry.urls]
|
||||||
|
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
||||||
|
"Changelog" = "https://nonebot.dev/changelog"
|
||||||
|
"Funding" = "https://afdian.net/@nonebot"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
yarl = "^1.7.2"
|
yarl = "^1.7.2"
|
||||||
loguru = "^0.6.0"
|
|
||||||
pygtrie = "^2.4.1"
|
pygtrie = "^2.4.1"
|
||||||
typing-extensions = ">=4.0.0,<5.0.0"
|
loguru = ">=0.6.0,<1.0.0"
|
||||||
|
typing-extensions = ">=4.4.0,<5.0.0"
|
||||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
tomli = { version = "^2.0.1", python = "<3.11" }
|
||||||
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
|
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
|
||||||
|
|
||||||
websockets = { version = "^10.0", optional = true }
|
websockets = { version = ">=10.0", optional = true }
|
||||||
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
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.7.4", extras = ["speedups"], optional = true }
|
aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }
|
||||||
httpx = { version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true }
|
httpx = { version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true }
|
||||||
uvicorn = { version = ">=0.20.0,<1.0.0", extras = ["standard"], optional = true }
|
uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
||||||
|
"standard",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pycln = "^2.1.2"
|
|
||||||
isort = "^5.10.1"
|
isort = "^5.10.1"
|
||||||
black = "^23.1.0"
|
black = "^23.1.0"
|
||||||
nonemoji = "^0.1.2"
|
nonemoji = "^0.1.2"
|
||||||
pre-commit = "^3.0.0"
|
pre-commit = "^3.0.0"
|
||||||
|
ruff = ">=0.0.272,<1.0.0"
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
nonebug = "^0.3.0"
|
nonebug = "^0.3.0"
|
||||||
|
werkzeug = "^2.3.6"
|
||||||
pytest-cov = "^4.0.0"
|
pytest-cov = "^4.0.0"
|
||||||
pytest-xdist = "^3.0.2"
|
pytest-xdist = "^3.0.2"
|
||||||
pytest-asyncio = "^0.21.0"
|
pytest-asyncio = "^0.21.0"
|
||||||
coverage-conditional-plugin = "^0.8.0"
|
coverage-conditional-plugin = "^0.9.0"
|
||||||
|
|
||||||
[tool.poetry.group.docs.dependencies]
|
[tool.poetry.group.docs.dependencies]
|
||||||
nb-autodoc = "^1.0.0a5"
|
nb-autodoc = "^1.0.0a5"
|
||||||
@@ -63,12 +69,9 @@ 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 = "auto"
|
asyncio_mode = "strict"
|
||||||
addopts = "--cov=nonebot --cov-append --cov-report=term-missing"
|
addopts = "--cov=nonebot --cov-append --cov-report=term-missing"
|
||||||
filterwarnings = [
|
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
||||||
"error",
|
|
||||||
"ignore::DeprecationWarning",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
@@ -86,19 +89,30 @@ force_sort_within_sections = true
|
|||||||
src_paths = ["nonebot", "tests"]
|
src_paths = ["nonebot", "tests"]
|
||||||
extra_standard_library = ["typing_extensions"]
|
extra_standard_library = ["typing_extensions"]
|
||||||
|
|
||||||
[tool.pycln]
|
[tool.ruff]
|
||||||
path = "."
|
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
|
||||||
all = false
|
ignore = ["E402", "C901", "UP037"]
|
||||||
|
|
||||||
|
line-length = 88
|
||||||
|
target-version = "py38"
|
||||||
|
|
||||||
|
[tool.ruff.flake8-pytest-style]
|
||||||
|
fixture-parentheses = false
|
||||||
|
mark-parentheses = false
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
reportShadowedImports = false
|
|
||||||
pythonVersion = "3.8"
|
pythonVersion = "3.8"
|
||||||
pythonPlatform = "All"
|
pythonPlatform = "All"
|
||||||
executionEnvironments = [
|
executionEnvironments = [
|
||||||
{ root = "./tests", extraPaths = ["./"] },
|
{ root = "./tests", extraPaths = [
|
||||||
|
"./",
|
||||||
|
] },
|
||||||
{ root = "./" },
|
{ root = "./" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
typeCheckingMode = "basic"
|
||||||
|
reportShadowedImports = false
|
||||||
|
disableBytesTypePromotions = true
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry_core>=1.0.0"]
|
requires = ["poetry_core>=1.0.0"]
|
||||||
|
@@ -11,6 +11,7 @@ exclude_lines =
|
|||||||
if (typing\.)?TYPE_CHECKING( is True)?:
|
if (typing\.)?TYPE_CHECKING( is True)?:
|
||||||
@(abc\.)?abstractmethod
|
@(abc\.)?abstractmethod
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
warnings\.warn
|
||||||
\.\.\.
|
\.\.\.
|
||||||
pass
|
pass
|
||||||
if __name__ == .__main__.:
|
if __name__ == .__main__.:
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
LOG_LEVEL=TRACE
|
LOG_LEVEL=TRACE
|
||||||
NICKNAME=["test"]
|
NICKNAME=["test"]
|
||||||
SUPERUSERS=["test", "fake:faketest"]
|
SUPERUSERS=["test", "fake:faketest"]
|
||||||
|
API_TIMEOUT
|
||||||
|
SIMPLE_NONE
|
||||||
COMMON_OVERRIDE=new
|
COMMON_OVERRIDE=new
|
||||||
CONFIG_FROM_ENV=
|
CONFIG_FROM_ENV=
|
||||||
CONFIG_OVERRIDE=old
|
CONFIG_OVERRIDE=old
|
||||||
|
@@ -1,11 +1,17 @@
|
|||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Set
|
from typing import TYPE_CHECKING, Set, Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import NONEBOT_INIT_KWARGS
|
from nonebug import NONEBOT_INIT_KWARGS
|
||||||
|
from werkzeug.serving import BaseWSGIServer, make_server
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
|
from nonebot.config import Env
|
||||||
|
from fake_server import request_handler
|
||||||
|
from nonebot.drivers import URL, Driver
|
||||||
|
from nonebot import _resolve_combine_expr
|
||||||
|
|
||||||
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
|
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
|
||||||
os.environ["CONFIG_OVERRIDE"] = "new"
|
os.environ["CONFIG_OVERRIDE"] = "new"
|
||||||
@@ -18,6 +24,17 @@ def pytest_configure(config: pytest.Config) -> None:
|
|||||||
config.stash[NONEBOT_INIT_KWARGS] = {"config_from_init": "init"}
|
config.stash[NONEBOT_INIT_KWARGS] = {"config_from_init": "init"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="driver")
|
||||||
|
def load_driver(request: pytest.FixtureRequest) -> Driver:
|
||||||
|
driver_name = getattr(request, "param", None)
|
||||||
|
global_driver = nonebot.get_driver()
|
||||||
|
if driver_name is None:
|
||||||
|
return global_driver
|
||||||
|
|
||||||
|
DriverClass = _resolve_combine_expr(driver_name)
|
||||||
|
return DriverClass(Env(environment=global_driver.env), global_driver.config)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def load_plugin(nonebug_init: None) -> Set["Plugin"]:
|
def load_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||||
# preload global plugins
|
# preload global plugins
|
||||||
@@ -25,6 +42,23 @@ def load_plugin(nonebug_init: None) -> Set["Plugin"]:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def load_example(nonebug_init: None) -> Set["Plugin"]:
|
def load_builtin_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||||
# preload example plugins
|
# preload builtin plugins
|
||||||
return nonebot.load_plugins(str(Path(__file__).parent / "examples"))
|
return nonebot.load_builtin_plugins("echo", "single_session")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def server() -> Generator[BaseWSGIServer, None, None]:
|
||||||
|
server = make_server("127.0.0.1", 0, app=request_handler)
|
||||||
|
thread = threading.Thread(target=server.serve_forever)
|
||||||
|
thread.start()
|
||||||
|
try:
|
||||||
|
yield server
|
||||||
|
finally:
|
||||||
|
server.shutdown()
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def server_url(server: BaseWSGIServer) -> URL:
|
||||||
|
return URL(f"http://{server.host}:{server.port}")
|
||||||
|
@@ -1,29 +0,0 @@
|
|||||||
from nonebot import on_command
|
|
||||||
from nonebot.rule import to_me
|
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot.adapters import Message
|
|
||||||
from nonebot.params import Arg, CommandArg, ArgPlainText
|
|
||||||
|
|
||||||
weather = on_command("weather", rule=to_me(), aliases={"天气", "天气预报"}, priority=5)
|
|
||||||
|
|
||||||
|
|
||||||
@weather.handle()
|
|
||||||
async def handle_first_receive(matcher: Matcher, args: Message = CommandArg()):
|
|
||||||
plain_text = args.extract_plain_text() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海
|
|
||||||
if plain_text:
|
|
||||||
matcher.set_arg("city", args) # 如果用户发送了参数则直接赋值
|
|
||||||
|
|
||||||
|
|
||||||
@weather.got("city", prompt="你想查询哪个城市的天气呢?")
|
|
||||||
async def handle_city(city: Message = Arg(), city_name: str = ArgPlainText("city")):
|
|
||||||
if city_name not in ["北京", "上海"]: # 如果参数不符合要求,则提示用户重新输入
|
|
||||||
# 可以使用平台的 Message 类直接构造模板消息
|
|
||||||
await weather.reject(city.template("你想查询的城市 {city} 暂不支持,请重新输入!"))
|
|
||||||
|
|
||||||
city_weather = await get_weather(city_name)
|
|
||||||
await weather.finish(city_weather)
|
|
||||||
|
|
||||||
|
|
||||||
# 在这里编写获取天气信息的函数
|
|
||||||
async def get_weather(city: str) -> str:
|
|
||||||
return f"{city}的天气是..."
|
|
69
tests/fake_server.py
Normal file
69
tests/fake_server.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import json
|
||||||
|
import base64
|
||||||
|
from typing import Dict, List, Union, TypeVar
|
||||||
|
|
||||||
|
from werkzeug import Request, Response
|
||||||
|
from werkzeug.datastructures import MultiDict
|
||||||
|
|
||||||
|
K = TypeVar("K")
|
||||||
|
V = TypeVar("V")
|
||||||
|
|
||||||
|
|
||||||
|
def json_safe(string, content_type="application/octet-stream") -> str:
|
||||||
|
try:
|
||||||
|
string = string.decode("utf-8")
|
||||||
|
json.dumps(string)
|
||||||
|
return string
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return b"".join(
|
||||||
|
[
|
||||||
|
b"data:",
|
||||||
|
content_type.encode("utf-8"),
|
||||||
|
b";base64,",
|
||||||
|
base64.b64encode(string),
|
||||||
|
]
|
||||||
|
).decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def flattern(d: "MultiDict[K, V]") -> Dict[K, Union[V, List[V]]]:
|
||||||
|
return {k: v[0] if len(v) == 1 else v for k, v in d.to_dict(flat=False).items()}
|
||||||
|
|
||||||
|
|
||||||
|
@Request.application
|
||||||
|
def request_handler(request: Request) -> Response:
|
||||||
|
try:
|
||||||
|
_json = json.loads(request.data.decode("utf-8"))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
_json = None
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"url": request.url,
|
||||||
|
"method": request.method,
|
||||||
|
"origin": request.headers.get("X-Forwarded-For", request.remote_addr),
|
||||||
|
"headers": flattern(
|
||||||
|
MultiDict((k, v) for k, v in request.headers.items())
|
||||||
|
),
|
||||||
|
"args": flattern(request.args),
|
||||||
|
"form": flattern(request.form),
|
||||||
|
"data": json_safe(request.data),
|
||||||
|
"json": _json,
|
||||||
|
"files": flattern(
|
||||||
|
MultiDict(
|
||||||
|
(
|
||||||
|
k,
|
||||||
|
json_safe(
|
||||||
|
v.read(),
|
||||||
|
request.files[k].content_type
|
||||||
|
or "application/octet-stream",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for k, v in request.files.items()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
status=200,
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
@@ -1 +1,3 @@
|
|||||||
assert False
|
import pytest
|
||||||
|
|
||||||
|
pytest.fail("should not be imported")
|
||||||
|
3
tests/plugins/matcher/matcher_info.py
Normal file
3
tests/plugins/matcher/matcher_info.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from nonebot import on
|
||||||
|
|
||||||
|
matcher = on("message", temp=False, expire_time=None, priority=1, block=True)
|
@@ -2,6 +2,7 @@ from nonebot.matcher import Matcher
|
|||||||
from nonebot.permission import USER, Permission
|
from nonebot.permission import USER, Permission
|
||||||
|
|
||||||
default_permission = Permission()
|
default_permission = Permission()
|
||||||
|
new_permission = Permission()
|
||||||
|
|
||||||
test_permission_updater = Matcher.new(permission=default_permission)
|
test_permission_updater = Matcher.new(permission=default_permission)
|
||||||
|
|
||||||
@@ -14,4 +15,4 @@ test_custom_updater = Matcher.new(permission=default_permission)
|
|||||||
|
|
||||||
@test_custom_updater.permission_updater
|
@test_custom_updater.permission_updater
|
||||||
async def _() -> Permission:
|
async def _() -> Permission:
|
||||||
return default_permission
|
return new_permission
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from nonebot.adapters import Adapter
|
||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
|
|
||||||
@@ -7,10 +8,17 @@ class Config(BaseModel):
|
|||||||
custom: str = ""
|
custom: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class FakeAdapter(Adapter):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="测试插件",
|
name="测试插件",
|
||||||
description="测试插件元信息",
|
description="测试插件元信息",
|
||||||
usage="无法使用",
|
usage="无法使用",
|
||||||
|
type="application",
|
||||||
|
homepage="https://nonebot.dev",
|
||||||
config=Config,
|
config=Config,
|
||||||
|
supported_adapters={"~onebot.v11", "plugins.metadata:FakeAdapter"},
|
||||||
extra={"author": "NoneBot"},
|
extra={"author": "NoneBot"},
|
||||||
)
|
)
|
||||||
|
11
tests/plugins/metadata_2.py
Normal file
11
tests/plugins/metadata_2.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="测试插件2",
|
||||||
|
description="测试继承适配器",
|
||||||
|
usage="无法使用",
|
||||||
|
type="application",
|
||||||
|
homepage="https://nonebot.dev",
|
||||||
|
supported_adapters={"~onebot.v11", "~onebot.v12"},
|
||||||
|
extra={"author": "NoneBot"},
|
||||||
|
)
|
@@ -1,6 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import nonebot
|
|
||||||
from nonebot.plugin import PluginManager, _managers
|
from nonebot.plugin import PluginManager, _managers
|
||||||
|
|
||||||
manager = PluginManager(
|
manager = PluginManager(
|
||||||
|
@@ -1 +1 @@
|
|||||||
from .nested_subplugin2 import a # nopycln: import
|
from .nested_subplugin2 import a # noqa: F401
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
from nonebot.adapters import Event, Message
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
from nonebot.adapters import Message
|
||||||
from nonebot.params import Arg, ArgStr, ArgPlainText
|
from nonebot.params import Arg, ArgStr, ArgPlainText
|
||||||
|
|
||||||
|
|
||||||
@@ -12,3 +14,26 @@ async def arg_str(key: str = ArgStr()) -> str:
|
|||||||
|
|
||||||
async def arg_plain_text(key: str = ArgPlainText()) -> str:
|
async def arg_plain_text(key: str = ArgPlainText()) -> str:
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
async def annotated_arg(key: Annotated[Message, Arg()]) -> Message:
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
async def annotated_arg_str(key: Annotated[str, ArgStr()]) -> str:
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
async def annotated_arg_plain_text(key: Annotated[str, ArgPlainText()]) -> str:
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
# test dependency priority
|
||||||
|
async def annotated_prior_arg(key: Annotated[str, ArgStr("foo")] = ArgPlainText()):
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
async def annotated_multi_arg(
|
||||||
|
key: Annotated[Annotated[str, ArgStr("foo")], ArgPlainText()]
|
||||||
|
):
|
||||||
|
return key
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from typing import Union
|
from typing import Union, TypeVar
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
|
|
||||||
@@ -31,5 +31,19 @@ async def union_bot(b: Union[FooBot, BarBot]) -> Union[FooBot, BarBot]:
|
|||||||
return b
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
B = TypeVar("B", bound=Bot)
|
||||||
|
|
||||||
|
|
||||||
|
async def generic_bot(b: B) -> B:
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
CB = TypeVar("CB", Bot, None)
|
||||||
|
|
||||||
|
|
||||||
|
async def generic_bot_none(b: CB) -> CB:
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
async def not_bot(b: Union[int, Bot]):
|
async def not_bot(b: Union[int, Bot]):
|
||||||
...
|
...
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
from nonebot import on_message
|
from nonebot import on_message
|
||||||
|
from nonebot.adapters import Bot
|
||||||
from nonebot.params import Depends
|
from nonebot.params import Depends
|
||||||
|
|
||||||
test_depends = on_message()
|
test_depends = on_message()
|
||||||
@@ -33,6 +36,14 @@ class ClassDependency:
|
|||||||
y: int = Depends(gen_async)
|
y: int = Depends(gen_async)
|
||||||
|
|
||||||
|
|
||||||
|
class FooBot(Bot):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
async def sub_bot(b: FooBot) -> FooBot:
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
# test parameterless
|
# test parameterless
|
||||||
@test_depends.handle(parameterless=[Depends(parameterless)])
|
@test_depends.handle(parameterless=[Depends(parameterless)])
|
||||||
async def depends(x: int = Depends(dependency)):
|
async def depends(x: int = Depends(dependency)):
|
||||||
@@ -46,19 +57,52 @@ async def depends_cache(y: int = Depends(dependency, use_cache=True)):
|
|||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
# test class dependency
|
||||||
async def class_depend(c: ClassDependency = Depends()):
|
async def class_depend(c: ClassDependency = Depends()):
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
# test annotated dependency
|
||||||
async def annotated_depend(x: Annotated[int, Depends(dependency)]):
|
async def annotated_depend(x: Annotated[int, Depends(dependency)]):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
# test annotated class dependency
|
||||||
async def annotated_class_depend(c: Annotated[ClassDependency, Depends()]):
|
async def annotated_class_depend(c: Annotated[ClassDependency, Depends()]):
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
# 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(
|
||||||
|
x: Annotated[Annotated[int, Depends(lambda: 2)], Depends(dependency)]
|
||||||
|
):
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
# test sub dependency type mismatch
|
||||||
|
async def sub_type_mismatch(b: FooBot = Depends(sub_bot)):
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
# test type validate
|
||||||
|
async def validate(x: int = Depends(lambda: "1", validate=True)):
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_fail(x: int = Depends(lambda: "not_number", validate=True)):
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
# test FieldInfo validate
|
||||||
|
async def validate_field(x: int = Depends(lambda: "1", validate=Field(gt=0))):
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_field_fail(x: int = Depends(lambda: "0", validate=Field(gt=0))):
|
||||||
|
return x
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from typing import Union
|
from typing import Union, TypeVar
|
||||||
|
|
||||||
from nonebot.adapters import Event, Message
|
from nonebot.adapters import Event, Message
|
||||||
from nonebot.params import EventToMe, EventType, EventMessage, EventPlainText
|
from nonebot.params import EventToMe, EventType, EventMessage, EventPlainText
|
||||||
@@ -32,6 +32,20 @@ async def union_event(e: Union[FooEvent, BarEvent]) -> Union[FooEvent, BarEvent]
|
|||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
E = TypeVar("E", bound=Event)
|
||||||
|
|
||||||
|
|
||||||
|
async def generic_event(e: E) -> E:
|
||||||
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
CE = TypeVar("CE", Event, None)
|
||||||
|
|
||||||
|
|
||||||
|
async def generic_event_none(e: CE) -> CE:
|
||||||
|
return e
|
||||||
|
|
||||||
|
|
||||||
async def not_event(e: Union[int, Event]):
|
async def not_event(e: Union[int, Event]):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import Union, TypeVar
|
||||||
|
|
||||||
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 Received, LastReceived
|
||||||
@@ -7,6 +9,50 @@ async def matcher(m: Matcher) -> Matcher:
|
|||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
async def legacy_matcher(matcher):
|
||||||
|
return matcher
|
||||||
|
|
||||||
|
|
||||||
|
async def not_legacy_matcher(matcher: int):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class FooMatcher(Matcher):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
async def sub_matcher(m: FooMatcher) -> FooMatcher:
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
class BarMatcher(Matcher):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
async def union_matcher(
|
||||||
|
m: Union[FooMatcher, BarMatcher]
|
||||||
|
) -> Union[FooMatcher, BarMatcher]:
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
M = TypeVar("M", bound=Matcher)
|
||||||
|
|
||||||
|
|
||||||
|
async def generic_matcher(m: M) -> M:
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
CM = TypeVar("CM", Matcher, None)
|
||||||
|
|
||||||
|
|
||||||
|
async def generic_matcher_none(m: CM) -> CM:
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
async def not_matcher(m: Union[int, Matcher]):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
async def receive(e: Event = Received("test")) -> Event:
|
async def receive(e: Event = Received("test")) -> Event:
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from typing import List, Tuple
|
from typing import List, Match, Tuple
|
||||||
|
|
||||||
from nonebot.typing import T_State
|
from nonebot.typing import T_State
|
||||||
from nonebot.adapters import Message
|
from nonebot.adapters import Message
|
||||||
@@ -73,12 +73,12 @@ async def regex_group(regex_group: Tuple = RegexGroup()) -> Tuple:
|
|||||||
return regex_group
|
return regex_group
|
||||||
|
|
||||||
|
|
||||||
async def regex_matched(regex_matched: str = RegexMatched()) -> str:
|
async def regex_matched(regex_matched: Match[str] = RegexMatched()) -> Match[str]:
|
||||||
return regex_matched
|
return regex_matched
|
||||||
|
|
||||||
|
|
||||||
async def regex_str(regex_matched: str = RegexStr()) -> str:
|
async def regex_str(regex_str: str = RegexStr()) -> str:
|
||||||
return regex_matched
|
return regex_str
|
||||||
|
|
||||||
|
|
||||||
async def startswith(startswith: str = Startswith()) -> str:
|
async def startswith(startswith: str = Startswith()) -> str:
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user