mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 03:07:07 +00:00
Compare commits
263 Commits
v2.4.1
...
ci/noneflo
Author | SHA1 | Date | |
---|---|---|---|
|
dbb0a330e5 | ||
|
4c01b033c9 | ||
|
c235faf9a5 | ||
|
c55baf6f88 | ||
|
93d1dba8ae | ||
|
02deb5821f | ||
|
1950a9594f | ||
|
20bbae77ee | ||
|
2f4f46335a | ||
|
7f2265faad | ||
|
8667a348b8 | ||
|
8cd5970aa7 | ||
|
5c2f38a234 | ||
|
90abb30495 | ||
|
97c5bbfb6d | ||
|
7dfde4b4de | ||
|
a37dbe7d0f | ||
|
60610f7a54 | ||
|
f8317444df | ||
|
b54faf7ff0 | ||
|
aa7004a65a | ||
|
2717ce8e6a | ||
|
cbbe0e4acd | ||
|
56964cc6ba | ||
|
e5081b6256 | ||
|
a4dabd69dd | ||
|
225923814b | ||
|
f238daf975 | ||
|
f275feb1cb | ||
|
de8d9779cc | ||
|
ada9d64e85 | ||
|
125c75cf43 | ||
|
0475b7cc16 | ||
|
2608f44b6e | ||
|
e9d6a359bb | ||
|
78a011143d | ||
|
5baea88a75 | ||
|
bf5a67ba26 | ||
|
192fb5fcd4 | ||
|
bb0071e57c | ||
|
86153e58c9 | ||
|
7ab4a10bdb | ||
|
d59ebdf3d9 | ||
|
78af0bfe3a | ||
|
0996e56220 | ||
|
f4b1a7ffbb | ||
|
55fb4d07fe | ||
|
71f91f9383 | ||
|
d99ff8cd4d | ||
|
9bac4abe32 | ||
|
e9b80d6d9b | ||
|
bf95b83ed3 | ||
|
4442cb2974 | ||
|
a0ffc12370 | ||
|
fb93b569e6 | ||
|
50f5987b5b | ||
|
d0c6bc2fec | ||
|
f4cd16cf0b | ||
|
6dfa40427e | ||
|
b8f851673d | ||
|
4d253ab95e | ||
|
a6c52a3c33 | ||
|
c2129c6df9 | ||
|
15fb9e4b60 | ||
|
cf2784d337 | ||
|
0483e5e5e6 | ||
|
aa35d224bb | ||
|
f20aa3846e | ||
|
55caa2a918 | ||
|
e161e5a671 | ||
|
69b6b17226 | ||
|
ab308f1d0f | ||
|
0eb1a95ab5 | ||
|
68a259ccc8 | ||
|
3afbb2e609 | ||
|
c100f88c28 | ||
|
87ab34f0e8 | ||
|
8db4b18014 | ||
|
e86adc2364 | ||
|
586ba47950 | ||
|
df1c531fdf | ||
|
9bca2cb6a3 | ||
|
d0d9eef7e3 | ||
|
a8549140c5 | ||
|
a338d71f1d | ||
|
56d6c8c92a | ||
|
133fa480d4 | ||
|
65d8649f3a | ||
|
2b2b2b142f | ||
|
4cdd3c2acd | ||
|
2f3b050d75 | ||
|
4460037426 | ||
|
9dc0168fc8 | ||
|
48cec02877 | ||
|
aec6ebdd58 | ||
|
8b9ba9ed05 | ||
|
0cadc5443f | ||
|
dd4e7193f1 | ||
|
65762441eb | ||
|
1ee99abc21 | ||
|
870f84afaf | ||
|
275bbfc158 | ||
|
9e46dce173 | ||
|
209dd763f1 | ||
|
2fa2ca8b45 | ||
|
dd957d5631 | ||
|
3d181fe20a | ||
|
aaedc6156b | ||
|
e80bfdbba0 | ||
|
570bd9587a | ||
|
87f3ff3c33 | ||
|
5a19393719 | ||
|
38eafe35df | ||
|
d0ef7c2826 | ||
|
1f8670160a | ||
|
dd0525eaee | ||
|
cc5dbca826 | ||
|
b5479e88aa | ||
|
9d1a3474cd | ||
|
09d3f23dda | ||
|
790f125661 | ||
|
91f3be341d | ||
|
1810144092 | ||
|
3a71909638 | ||
|
435060784e | ||
|
8f5e3a838b | ||
|
c4a44a5496 | ||
|
e11526c015 | ||
|
55a06c221d | ||
|
681f9cb541 | ||
|
fa717f8e6e | ||
|
9a6e1e383b | ||
|
4df7c53639 | ||
|
d6511697d9 | ||
|
af7e18166a | ||
|
5fe979133a | ||
|
536c939f64 | ||
|
51dba09211 | ||
|
7ce559aa15 | ||
|
ac79ae2bfc | ||
|
d3c5dec67b | ||
|
d1e2eb4d32 | ||
|
c91fdad9f3 | ||
|
dc42a6acca | ||
|
d64427a31f | ||
|
871636ced9 | ||
|
09343c332e | ||
|
ecab229133 | ||
|
9aa55d1864 | ||
|
7d488928ce | ||
|
5f9e48b900 | ||
|
37e0c997e4 | ||
|
22d5e2fbca | ||
|
4b70e7a19d | ||
|
10a775b9ad | ||
|
e7b76a1dde | ||
|
ca5dd18761 | ||
|
de6395819c | ||
|
1694611f48 | ||
|
bc1b55b952 | ||
|
6f006f3c1a | ||
|
cee2d62f9b | ||
|
8a019ab3fe | ||
|
d301815351 | ||
|
fe6b35f90f | ||
|
734cf423d3 | ||
|
5911c77fd7 | ||
|
4077745776 | ||
|
6cff660af0 | ||
|
db857b11fa | ||
|
44cecade7a | ||
|
c97ea83ec7 | ||
|
cb3a17b8bb | ||
|
6f467158a5 | ||
|
555c208884 | ||
|
462d74a59b | ||
|
6c1ad5387a | ||
|
2cac645edf | ||
|
7b4e6d338f | ||
|
6563c28873 | ||
|
3d4a1b14ae | ||
|
212d4c72d0 | ||
|
3ff8993cdb | ||
|
e324642303 | ||
|
c350574484 | ||
|
af5dfcee72 | ||
|
4d9f5f0953 | ||
|
6e024bfe1f | ||
|
3235358fb6 | ||
|
541295fd86 | ||
|
2f03e11918 | ||
|
950461b556 | ||
|
cf30dc6264 | ||
|
3c616e758a | ||
|
b16ddf380e | ||
|
938aceda78 | ||
|
5b280b038a | ||
|
c218f3c84f | ||
|
2e8eb89562 | ||
|
b851c2eace | ||
|
a73d9544a3 | ||
|
d84c07a7ba | ||
|
3a017d2173 | ||
|
18bfb8aac4 | ||
|
6f1bb3217b | ||
|
7d6f921791 | ||
|
19ef7561e5 | ||
|
30c6bf7e71 | ||
|
27ee0fa8d8 | ||
|
83ae128c56 | ||
|
603138cf8c | ||
|
f9270d3f77 | ||
|
c769e2801d | ||
|
9c4722a5e2 | ||
|
55a9a55008 | ||
|
be1a501145 | ||
|
cdde118ef1 | ||
|
55a73ff25f | ||
|
9b39fa9ded | ||
|
a6a28af639 | ||
|
c3e655e937 | ||
|
5a277fe3de | ||
|
379c9e50df | ||
|
12f9eed9fb | ||
|
02856f6a4a | ||
|
0e203df6e9 | ||
|
685c0fd54e | ||
|
61b42ab45d | ||
|
93f943d88e | ||
|
004cb9225a | ||
|
7e55406560 | ||
|
334445fee2 | ||
|
e00490021d | ||
|
d203c6999f | ||
|
6a785aaf96 | ||
|
b8df31c142 | ||
|
f3720c0736 | ||
|
5ab7d1083c | ||
|
6ffbce6c81 | ||
|
ac2a99dd8a | ||
|
e32fd6521e | ||
|
97702f8b57 | ||
|
e736fd5435 | ||
|
edde99f8bd | ||
|
6f2fde89bc | ||
|
bd6a8448d9 | ||
|
4989faf518 | ||
|
3285230078 | ||
|
99f58948e7 | ||
|
4d3de1479f | ||
|
47493c3a5e | ||
|
7f19b65ca9 | ||
|
21bc367173 | ||
|
6471d58bcd | ||
|
41e5824acc | ||
|
7c43c055b5 | ||
|
2e649aabb3 | ||
|
7e91b6ddd0 | ||
|
a3688c8695 | ||
|
fbaaf4c0d4 | ||
|
4353f8355e | ||
|
c942815021 | ||
|
fbfac80eb8 |
@@ -2,7 +2,7 @@
|
|||||||
"name": "Default Linux Universal",
|
"name": "Default Linux Universal",
|
||||||
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
|
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
|
"ghcr.io/devcontainers-extra/features/poetry:2": {}
|
||||||
},
|
},
|
||||||
"postCreateCommand": "./scripts/setup-envs.sh",
|
"postCreateCommand": "./scripts/setup-envs.sh",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
394
.eslintrc.js
394
.eslintrc.js
@@ -1,3 +1,10 @@
|
|||||||
|
const OFF = 0;
|
||||||
|
const WARNING = 1;
|
||||||
|
const ERROR = 2;
|
||||||
|
|
||||||
|
// Prevent importing lodash, usually for browser bundle size reasons
|
||||||
|
const LodashImportPatterns = ["lodash", "lodash.**", "lodash/**"];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
@@ -6,80 +13,391 @@ module.exports = {
|
|||||||
node: true,
|
node: true,
|
||||||
},
|
},
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {},
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
project: ["./tsconfig.json", "./website/tsconfig.json"],
|
|
||||||
},
|
|
||||||
globals: {
|
globals: {
|
||||||
JSX: true,
|
JSX: true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:react-hooks/recommended",
|
"plugin:react-hooks/recommended",
|
||||||
|
"airbnb",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:import/recommended",
|
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||||
|
// 'plugin:@typescript-eslint/strict',
|
||||||
"plugin:regexp/recommended",
|
"plugin:regexp/recommended",
|
||||||
"plugin:prettier/recommended",
|
"prettier",
|
||||||
|
"plugin:@docusaurus/all",
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
node: {
|
node: {
|
||||||
extensions: [".js", ".jsx", ".ts", ".tsx"],
|
extensions: [".js", ".jsx", ".ts", ".tsx"],
|
||||||
},
|
},
|
||||||
typescript: true,
|
|
||||||
},
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
overrides: [
|
reportUnusedDisableDirectives: true,
|
||||||
{
|
plugins: ["react-hooks", "@typescript-eslint", "regexp", "@docusaurus"],
|
||||||
files: ["*.ts", "*.tsx"],
|
|
||||||
rules: {
|
|
||||||
"import/no-unresolved": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["*.js", "*.cjs"],
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
plugins: ["@typescript-eslint"],
|
|
||||||
rules: {
|
rules: {
|
||||||
"linebreak-style": ["error", "unix"],
|
"react/jsx-uses-react": OFF, // JSX runtime: automatic
|
||||||
quotes: ["error", "double", { avoidEscape: true }],
|
"react/react-in-jsx-scope": OFF, // JSX runtime: automatic
|
||||||
semi: ["error", "always"],
|
"array-callback-return": WARNING,
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
camelcase: WARNING,
|
||||||
|
"class-methods-use-this": OFF, // It's a way of allowing private variables.
|
||||||
|
curly: [WARNING, "all"],
|
||||||
|
"global-require": WARNING,
|
||||||
|
"lines-between-class-members": OFF,
|
||||||
|
"max-classes-per-file": OFF,
|
||||||
|
"max-len": [
|
||||||
|
WARNING,
|
||||||
|
{
|
||||||
|
code: Infinity, // Code width is already enforced by Prettier
|
||||||
|
tabWidth: 2,
|
||||||
|
comments: 80,
|
||||||
|
ignoreUrls: true,
|
||||||
|
ignorePattern: "(eslint-disable|@)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"arrow-body-style": OFF,
|
||||||
|
"no-await-in-loop": OFF,
|
||||||
|
"no-case-declarations": WARNING,
|
||||||
|
"no-console": OFF,
|
||||||
|
"no-constant-binary-expression": ERROR,
|
||||||
|
"no-continue": OFF,
|
||||||
|
"no-control-regex": WARNING,
|
||||||
|
"no-else-return": OFF,
|
||||||
|
"no-empty": [WARNING, { allowEmptyCatch: true }],
|
||||||
|
"no-lonely-if": WARNING,
|
||||||
|
"no-nested-ternary": WARNING,
|
||||||
|
"no-param-reassign": [WARNING, { props: false }],
|
||||||
|
"no-prototype-builtins": WARNING,
|
||||||
|
"no-restricted-exports": OFF,
|
||||||
|
"no-restricted-properties": [
|
||||||
|
ERROR,
|
||||||
|
.../** @type {[string, string][]} */ ([
|
||||||
|
// TODO: TS doesn't make Boolean a narrowing function yet,
|
||||||
|
// so filter(Boolean) is problematic type-wise
|
||||||
|
// ['compact', 'Array#filter(Boolean)'],
|
||||||
|
["concat", "Array#concat"],
|
||||||
|
["drop", "Array#slice(n)"],
|
||||||
|
["dropRight", "Array#slice(0, -n)"],
|
||||||
|
["fill", "Array#fill"],
|
||||||
|
["filter", "Array#filter"],
|
||||||
|
["find", "Array#find"],
|
||||||
|
["findIndex", "Array#findIndex"],
|
||||||
|
["first", "foo[0]"],
|
||||||
|
["flatten", "Array#flat"],
|
||||||
|
["flattenDeep", "Array#flat(Infinity)"],
|
||||||
|
["flatMap", "Array#flatMap"],
|
||||||
|
["fromPairs", "Object.fromEntries"],
|
||||||
|
["head", "foo[0]"],
|
||||||
|
["indexOf", "Array#indexOf"],
|
||||||
|
["initial", "Array#slice(0, -1)"],
|
||||||
|
["join", "Array#join"],
|
||||||
|
// Unfortunately there's no great alternative to _.last yet
|
||||||
|
// Candidates: foo.slice(-1)[0]; foo[foo.length - 1]
|
||||||
|
// Array#at is ES2022; could replace _.nth as well
|
||||||
|
// ['last'],
|
||||||
|
["map", "Array#map"],
|
||||||
|
["reduce", "Array#reduce"],
|
||||||
|
["reverse", "Array#reverse"],
|
||||||
|
["slice", "Array#slice"],
|
||||||
|
["take", "Array#slice(0, n)"],
|
||||||
|
["takeRight", "Array#slice(-n)"],
|
||||||
|
["tail", "Array#slice(1)"],
|
||||||
|
]).map(([property, alternative]) => ({
|
||||||
|
object: "_",
|
||||||
|
property,
|
||||||
|
message: `Use ${alternative} instead.`,
|
||||||
|
})),
|
||||||
|
...[
|
||||||
|
"readdirSync",
|
||||||
|
"readFileSync",
|
||||||
|
"statSync",
|
||||||
|
"lstatSync",
|
||||||
|
"existsSync",
|
||||||
|
"pathExistsSync",
|
||||||
|
"realpathSync",
|
||||||
|
"mkdirSync",
|
||||||
|
"mkdirpSync",
|
||||||
|
"mkdirsSync",
|
||||||
|
"writeFileSync",
|
||||||
|
"writeJsonSync",
|
||||||
|
"outputFileSync",
|
||||||
|
"outputJsonSync",
|
||||||
|
"moveSync",
|
||||||
|
"copySync",
|
||||||
|
"copyFileSync",
|
||||||
|
"ensureFileSync",
|
||||||
|
"ensureDirSync",
|
||||||
|
"ensureLinkSync",
|
||||||
|
"ensureSymlinkSync",
|
||||||
|
"unlinkSync",
|
||||||
|
"removeSync",
|
||||||
|
"emptyDirSync",
|
||||||
|
].map((property) => ({
|
||||||
|
object: "fs",
|
||||||
|
property,
|
||||||
|
message: "Do not use sync fs methods.",
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
WARNING,
|
||||||
|
// Copied from airbnb, removed for...of statement, added export all
|
||||||
|
{
|
||||||
|
selector: "ForInStatement",
|
||||||
|
message:
|
||||||
|
"for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "LabeledStatement",
|
||||||
|
message:
|
||||||
|
"Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "WithStatement",
|
||||||
|
message:
|
||||||
|
"`with` is disallowed in strict mode because it makes code impossible to predict and optimize.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "ExportAllDeclaration",
|
||||||
|
message:
|
||||||
|
"Export all does't work well if imported in ESM due to how they are transpiled, and they can also lead to unexpected exposure of internal methods.",
|
||||||
|
},
|
||||||
|
// TODO make an internal plugin to ensure this
|
||||||
|
// {
|
||||||
|
// selector:
|
||||||
|
// @ 'ExportDefaultDeclaration > Identifier, ExportNamedDeclaration[source=null] > ExportSpecifier',
|
||||||
|
// message: 'Export in one statement'
|
||||||
|
// },
|
||||||
|
...["path", "fs-extra", "webpack", "lodash"].map((m) => ({
|
||||||
|
selector: `ImportDeclaration[importKind=value]:has(Literal[value=${m}]) > ImportSpecifier[importKind=value]`,
|
||||||
|
message:
|
||||||
|
"Default-import this, both for readability and interoperability with ESM",
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
"no-template-curly-in-string": WARNING,
|
||||||
|
"no-unused-expressions": [
|
||||||
|
WARNING,
|
||||||
|
{ allowTaggedTemplates: true, allowShortCircuit: true },
|
||||||
|
],
|
||||||
|
"no-useless-escape": WARNING,
|
||||||
|
"no-void": [ERROR, { allowAsStatement: true }],
|
||||||
|
"prefer-destructuring": WARNING,
|
||||||
|
"prefer-named-capture-group": WARNING,
|
||||||
|
"prefer-template": WARNING,
|
||||||
|
yoda: WARNING,
|
||||||
|
|
||||||
|
"import/extensions": OFF,
|
||||||
|
// This rule doesn't yet support resolving .js imports when the actual file
|
||||||
|
// is .ts. Plus it's not all that useful when our code is fully TS-covered.
|
||||||
|
"import/no-unresolved": [
|
||||||
|
OFF,
|
||||||
|
{
|
||||||
|
// Ignore certain webpack aliases because they can't be resolved
|
||||||
|
ignore: [
|
||||||
|
"^@theme",
|
||||||
|
"^@docusaurus",
|
||||||
|
"^@generated",
|
||||||
|
"^@site",
|
||||||
|
"^@testing-utils",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
"import/order": [
|
"import/order": [
|
||||||
"error",
|
WARNING,
|
||||||
{
|
{
|
||||||
groups: [
|
groups: [
|
||||||
"builtin",
|
"builtin",
|
||||||
"external",
|
"external",
|
||||||
"internal",
|
"internal",
|
||||||
"parent",
|
["parent", "sibling", "index"],
|
||||||
"sibling",
|
"type",
|
||||||
"index",
|
|
||||||
],
|
],
|
||||||
|
"newlines-between": "always",
|
||||||
pathGroups: [
|
pathGroups: [
|
||||||
|
// always put css import to the last, ref:
|
||||||
|
// https://github.com/import-js/eslint-plugin-import/issues/1239
|
||||||
|
{
|
||||||
|
pattern: "*.+(css|sass|less|scss|pcss|styl)",
|
||||||
|
group: "unknown",
|
||||||
|
patternOptions: { matchBase: true },
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
{ pattern: "react", group: "builtin", position: "before" },
|
{ pattern: "react", group: "builtin", position: "before" },
|
||||||
|
{ pattern: "react-dom", group: "builtin", position: "before" },
|
||||||
|
{ pattern: "react-dom/**", group: "builtin", position: "before" },
|
||||||
|
{ pattern: "stream", group: "builtin", position: "before" },
|
||||||
{ pattern: "fs-extra", group: "builtin" },
|
{ pattern: "fs-extra", group: "builtin" },
|
||||||
{ pattern: "lodash", group: "external", position: "before" },
|
{ pattern: "lodash", group: "external", position: "before" },
|
||||||
{ pattern: "clsx", group: "external", position: "before" },
|
{ pattern: "clsx", group: "external", position: "before" },
|
||||||
|
// 'Bit weird to not use the `import/internal-regex` option, but this
|
||||||
|
// way, we can make `import type { Props } from "@theme/*"` appear
|
||||||
|
// before `import styles from "styles.module.css"`, which is what we
|
||||||
|
// always did. This should be removable once we stop using ambient
|
||||||
|
// module declarations for theme aliases.
|
||||||
{ pattern: "@theme/**", group: "internal" },
|
{ pattern: "@theme/**", group: "internal" },
|
||||||
{ pattern: "@site/**", group: "internal" },
|
{ pattern: "@site/**", group: "internal" },
|
||||||
{ pattern: "@theme-init/**", group: "internal" },
|
{ pattern: "@theme-init/**", group: "internal" },
|
||||||
{ pattern: "@theme-original/**", group: "internal" },
|
{ pattern: "@theme-original/**", group: "internal" },
|
||||||
|
{ pattern: "@/components/**", group: "internal" },
|
||||||
|
{ pattern: "@/libs/**", group: "internal" },
|
||||||
|
{ pattern: "@/types/**", group: "type" },
|
||||||
],
|
],
|
||||||
pathGroupsExcludedImportTypes: [],
|
pathGroupsExcludedImportTypes: [],
|
||||||
"newlines-between": "always",
|
// example: let `import './nprogress.css';` after importing others
|
||||||
alphabetize: {
|
// in `packages/docusaurus-theme-classic/src/nprogress.ts`
|
||||||
order: "asc",
|
// see more: https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md#warnonunassignedimports-truefalse
|
||||||
},
|
warnOnUnassignedImports: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"import/prefer-default-export": OFF,
|
||||||
|
|
||||||
|
"jsx-a11y/click-events-have-key-events": WARNING,
|
||||||
|
"jsx-a11y/no-noninteractive-element-interactions": WARNING,
|
||||||
|
"jsx-a11y/html-has-lang": OFF,
|
||||||
|
|
||||||
|
"react-hooks/rules-of-hooks": ERROR,
|
||||||
|
"react-hooks/exhaustive-deps": ERROR,
|
||||||
|
|
||||||
|
// Sometimes we do need the props as a whole, e.g. when spreading
|
||||||
|
"react/destructuring-assignment": OFF,
|
||||||
|
"react/function-component-definition": [
|
||||||
|
WARNING,
|
||||||
|
{
|
||||||
|
namedComponents: "function-declaration",
|
||||||
|
unnamedComponents: "arrow-function",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"react/jsx-filename-extension": OFF,
|
||||||
|
"react/jsx-key": [ERROR, { checkFragmentShorthand: true }],
|
||||||
|
"react/jsx-no-useless-fragment": [ERROR, { allowExpressions: true }],
|
||||||
|
"react/jsx-props-no-spreading": OFF,
|
||||||
|
"react/no-array-index-key": OFF, // We build a static site, and nearly all components don't change.
|
||||||
|
"react/no-unstable-nested-components": [WARNING, { allowAsProps: true }],
|
||||||
|
"react/prefer-stateless-function": WARNING,
|
||||||
|
"react/prop-types": OFF,
|
||||||
|
"react/require-default-props": [
|
||||||
|
ERROR,
|
||||||
|
{ ignoreFunctionalComponents: true },
|
||||||
|
],
|
||||||
|
|
||||||
|
"@typescript-eslint/consistent-type-definitions": OFF,
|
||||||
|
"@typescript-eslint/require-await": OFF,
|
||||||
|
|
||||||
|
"@typescript-eslint/ban-ts-comment": [
|
||||||
|
ERROR,
|
||||||
|
{ "ts-expect-error": "allow-with-description" },
|
||||||
|
],
|
||||||
|
"@typescript-eslint/consistent-indexed-object-style": OFF,
|
||||||
|
"@typescript-eslint/consistent-type-imports": [
|
||||||
|
WARNING,
|
||||||
|
{ disallowTypeAnnotations: false },
|
||||||
|
],
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": WARNING,
|
||||||
|
"@typescript-eslint/method-signature-style": ERROR,
|
||||||
|
"@typescript-eslint/no-empty-function": OFF,
|
||||||
|
"@typescript-eslint/no-empty-interface": [
|
||||||
|
ERROR,
|
||||||
|
{
|
||||||
|
allowSingleExtends: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-inferrable-types": OFF,
|
||||||
|
"@typescript-eslint/no-namespace": [WARNING, { allowDeclarations: true }],
|
||||||
|
"no-use-before-define": OFF,
|
||||||
|
"@typescript-eslint/no-use-before-define": [
|
||||||
|
ERROR,
|
||||||
|
{ functions: false, classes: false, variables: true },
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-non-null-assertion": OFF,
|
||||||
|
"no-redeclare": OFF,
|
||||||
|
"@typescript-eslint/no-redeclare": ERROR,
|
||||||
|
"no-shadow": OFF,
|
||||||
|
"@typescript-eslint/no-shadow": ERROR,
|
||||||
|
"no-unused-vars": OFF,
|
||||||
|
// We don't provide any escape hatches for this rule. Rest siblings and
|
||||||
|
// function placeholder params are always ignored, and any other unused
|
||||||
|
// locals must be justified with a disable comment.
|
||||||
|
"@typescript-eslint/no-unused-vars": [ERROR, { ignoreRestSiblings: true }],
|
||||||
|
"@typescript-eslint/prefer-optional-chain": ERROR,
|
||||||
|
"@docusaurus/no-html-links": ERROR,
|
||||||
|
"@docusaurus/prefer-docusaurus-heading": ERROR,
|
||||||
|
"@docusaurus/no-untranslated-text": [
|
||||||
|
WARNING,
|
||||||
|
{
|
||||||
|
ignoredStrings: [
|
||||||
|
"·",
|
||||||
|
"-",
|
||||||
|
"—",
|
||||||
|
"×",
|
||||||
|
"", // zwj: ​
|
||||||
|
"@",
|
||||||
|
"WebContainers",
|
||||||
|
"Twitter",
|
||||||
|
"GitHub",
|
||||||
|
"Dev.to",
|
||||||
|
"1.x",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["packages/*/src/theme/**/*.{js,ts,tsx}"],
|
||||||
|
excludedFiles: "*.test.{js,ts,tsx}",
|
||||||
|
rules: {
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
patterns: LodashImportPatterns.concat(
|
||||||
|
// Prevents relative imports between React theme components
|
||||||
|
[
|
||||||
|
"../**",
|
||||||
|
"./**",
|
||||||
|
// Allows relative styles module import with consistent filename
|
||||||
|
"!./styles.module.css",
|
||||||
|
// Allows relative tailwind css import with consistent filename
|
||||||
|
"!./styles.css",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["packages/*/src/theme/**/*.{js,ts,tsx}"],
|
||||||
|
rules: {
|
||||||
|
"import/no-named-export": ERROR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["*.d.ts"],
|
||||||
|
rules: {
|
||||||
|
"import/no-duplicates": OFF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["*.{ts,tsx}"],
|
||||||
|
rules: {
|
||||||
|
"no-undef": OFF,
|
||||||
|
"import/no-import-module-exports": OFF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["*.{js,mjs,cjs}"],
|
||||||
|
rules: {
|
||||||
|
// Make JS code directly runnable in Node.
|
||||||
|
"@typescript-eslint/no-var-requires": OFF,
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": OFF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Internal files where extraneous deps don't matter much at long as
|
||||||
|
// they run
|
||||||
|
files: ["website/**"],
|
||||||
|
rules: {
|
||||||
|
"import/no-extraneous-dependencies": OFF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
18
.github/workflows/noneflow.yml
vendored
18
.github/workflows/noneflow.yml
vendored
@@ -3,16 +3,16 @@ name: NoneFlow
|
|||||||
on:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [opened, reopened, edited]
|
types: [opened, reopened, edited]
|
||||||
pull_request_target:
|
|
||||||
types: [closed]
|
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed]
|
||||||
pull_request_review:
|
pull_request_review:
|
||||||
types: [submitted]
|
types: [submitted]
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.event.issue.number && format('publish/issue{0}', github.event.issue.number) || github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: ${{ startsWith(github.head_ref, 'publish/issue')}}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
noneflow:
|
noneflow:
|
||||||
@@ -58,8 +58,16 @@ jobs:
|
|||||||
"plugin_path": "assets/plugins.json5",
|
"plugin_path": "assets/plugins.json5",
|
||||||
"bot_path": "assets/bots.json5",
|
"bot_path": "assets/bots.json5",
|
||||||
"adapter_path": "assets/adapters.json5",
|
"adapter_path": "assets/adapters.json5",
|
||||||
"registry_repository": "nonebot/registry"
|
"registry_repository": "nonebot/registry",
|
||||||
|
"artifact_path": "artifact"
|
||||||
}
|
}
|
||||||
env:
|
env:
|
||||||
APP_ID: ${{ secrets.APP_ID }}
|
APP_ID: ${{ secrets.APP_ID }}
|
||||||
PRIVATE_KEY: ${{ secrets.APP_KEY }}
|
PRIVATE_KEY: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: noneflow
|
||||||
|
path: artifact/*
|
||||||
|
if-no-files-found: ignore
|
||||||
|
2
.github/workflows/pyright.yml
vendored
2
.github/workflows/pyright.yml
vendored
@@ -45,3 +45,5 @@ jobs:
|
|||||||
|
|
||||||
- name: Run Pyright
|
- name: Run Pyright
|
||||||
uses: jakebailey/pyright-action@v2
|
uses: jakebailey/pyright-action@v2
|
||||||
|
with:
|
||||||
|
pylance-version: latest-release
|
||||||
|
4
.github/workflows/release-drafter.yml
vendored
4
.github/workflows/release-drafter.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
- name: Setup Node Environment
|
- name: Setup Node Environment
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
|
|
||||||
- uses: release-drafter/release-drafter@v6
|
- uses: release-drafter/release-drafter@v6.0.0
|
||||||
id: release-drafter
|
id: release-drafter
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
|
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
|
||||||
run: exit 1
|
run: exit 1
|
||||||
|
|
||||||
- uses: release-drafter/release-drafter@v6
|
- uses: release-drafter/release-drafter@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: Release ${{ steps.version.outputs.TAG_NAME }} 🌈
|
name: Release ${{ steps.version.outputs.TAG_NAME }} 🌈
|
||||||
tag: ${{ steps.version.outputs.TAG_NAME }}
|
tag: ${{ steps.version.outputs.TAG_NAME }}
|
||||||
|
4
.github/workflows/ruff.yml
vendored
4
.github/workflows/ruff.yml
vendored
@@ -20,11 +20,11 @@ jobs:
|
|||||||
name: Ruff Lint
|
name: Ruff Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
concurrency:
|
concurrency:
|
||||||
group: pyright-${{ github.ref }}
|
group: ruff-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run Ruff Lint
|
- name: Run Ruff Lint
|
||||||
uses: chartboost/ruff-action@v1
|
uses: astral-sh/ruff-action@v3
|
||||||
|
@@ -7,7 +7,7 @@ ci:
|
|||||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.8.1
|
rev: v0.11.12
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix]
|
args: [--fix]
|
||||||
|
@@ -118,12 +118,13 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
| GitHub([仓库](https://github.com/nonebot/adapter-github),[协议](https://docs.github.com/en/apps)) | ✅ | GitHub APP & OAuth APP |
|
| GitHub([仓库](https://github.com/nonebot/adapter-github),[协议](https://docs.github.com/en/apps)) | ✅ | GitHub APP & OAuth APP |
|
||||||
| QQ([仓库](https://github.com/nonebot/adapter-qq),[协议](https://bot.q.qq.com/wiki/)) | ✅ | QQ 官方接口调整较多 |
|
| QQ([仓库](https://github.com/nonebot/adapter-qq),[协议](https://bot.q.qq.com/wiki/)) | ✅ | QQ 官方接口调整较多 |
|
||||||
| Console([仓库](https://github.com/nonebot/adapter-console)) | ✅ | 控制台交互 |
|
| Console([仓库](https://github.com/nonebot/adapter-console)) | ✅ | 控制台交互 |
|
||||||
| Red([仓库](https://github.com/nonebot/adapter-red),[协议](https://chrononeko.github.io/QQNTRedProtocol/)) | ✅ | QQ 协议 |
|
| Red([仓库](https://github.com/nonebot/adapter-red),[协议](https://chrononeko.github.io/QQNTRedProtocol/)) | ✅ | QQNT 协议 |
|
||||||
| Satori([仓库](https://github.com/nonebot/adapter-satori),[协议](https://satori.js.org/zh-CN)) | ✅ | 支持 Onebot、TG、飞书、微信公众号、Koishi 等 |
|
| Satori([仓库](https://github.com/nonebot/adapter-satori),[协议](https://satori.js.org/zh-CN)) | ✅ | 支持 Onebot、TG、飞书、微信公众号、Koishi 等 |
|
||||||
| Discord([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
| Discord([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
||||||
| DoDo([仓库](https://github.com/nonebot/adapter-dodo),[协议](https://open.imdodo.com/)) | ✅ | DoDo Bot 协议 |
|
| DoDo([仓库](https://github.com/nonebot/adapter-dodo),[协议](https://open.imdodo.com/)) | ✅ | DoDo Bot 协议 |
|
||||||
| Kritor([仓库](https://github.com/nonebot/adapter-kritor),[协议](https://github.com/KarinJS/kritor)) | ✅ | Kritor (OnebotX) 协议,QQ 机器人接口标准 |
|
| Kritor([仓库](https://github.com/nonebot/adapter-kritor),[协议](https://github.com/KarinJS/kritor)) | ✅ | Kritor (OnebotX) 协议,QQNT 机器人接口标准 |
|
||||||
| Mirai([仓库](https://github.com/nonebot/adapter-mirai),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ✅ | QQ 协议 |
|
| Mirai([仓库](https://github.com/nonebot/adapter-mirai),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ✅ | QQ 协议 |
|
||||||
|
| Milky([仓库](https://github.com/nonebot/adapter-milky),[协议](https://milky.ntqqrev.org/)) | ✅ | QQNT 机器人应用接口标准 |
|
||||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||||
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||||
@@ -134,6 +135,9 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
| Rocket.Chat([仓库](https://github.com/IUnlimit/nonebot-adapter-rocketchat),[协议](https://developer.rocket.chat/)) | ↗️ | Rocket.Chat Bot 协议,由社区贡献 |
|
| Rocket.Chat([仓库](https://github.com/IUnlimit/nonebot-adapter-rocketchat),[协议](https://developer.rocket.chat/)) | ↗️ | Rocket.Chat Bot 协议,由社区贡献 |
|
||||||
| Tailchat([仓库](https://github.com/eya46/nonebot-adapter-tailchat),[协议](https://tailchat.msgbyte.com/)) | ↗️ | Tailchat 开放平台 Bot 协议,由社区贡献 |
|
| Tailchat([仓库](https://github.com/eya46/nonebot-adapter-tailchat),[协议](https://tailchat.msgbyte.com/)) | ↗️ | Tailchat 开放平台 Bot 协议,由社区贡献 |
|
||||||
| Mail([仓库](https://github.com/mobyw/nonebot-adapter-mail)) | ↗️ | 邮件收发协议,由社区贡献 |
|
| Mail([仓库](https://github.com/mobyw/nonebot-adapter-mail)) | ↗️ | 邮件收发协议,由社区贡献 |
|
||||||
|
| 黑盒语音([仓库](https://github.com/lclbm/adapter-heybox),[协议](https://github.com/QingFengOpen/HeychatDoc)) | ↗️ | 黑盒语音机器人协议,由社区贡献 |
|
||||||
|
| 微信公众平台([仓库](https://github.com/YangRucheng/nonebot-adapter-wxmp),[协议](https://developers.weixin.qq.com/doc/))| ↗️ | 微信公众平台协议,由社区贡献 |
|
||||||
|
| Gewechat([仓库](https://github.com/Shine-Light/nonebot-adapter-gewechat),[协议](https://github.com/Devo919/Gewechat))| ❌ | Gewechat 微信协议,Gewechat不再维护及可用 |
|
||||||
|
|
||||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||||
|
|
||||||
|
@@ -259,4 +259,39 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.heybox",
|
||||||
|
"project_link": "nonebot-adapter-heybox",
|
||||||
|
"name": "黑盒语音",
|
||||||
|
"desc": "黑盒语音机器人适配",
|
||||||
|
"author_id": 54730982,
|
||||||
|
"homepage": "https://github.com/lclbm/adapter-heybox",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.wxmp",
|
||||||
|
"project_link": "nonebot-adapter-wxmp",
|
||||||
|
"name": "WXMP",
|
||||||
|
"desc": "微信公众平台 客服适配器",
|
||||||
|
"author_id": 60175467,
|
||||||
|
"homepage": "https://github.com/YangRucheng/nonebot-adapter-wxmp",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "微信公众平台",
|
||||||
|
"color": "#843dbc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.milky",
|
||||||
|
"project_link": "nonebot-adapter-milky",
|
||||||
|
"name": "nonebot-adapter-milky",
|
||||||
|
"desc": "Milky 协议适配器",
|
||||||
|
"author_id": 42648639,
|
||||||
|
"homepage": "https://github.com/nonebot/adapter-milky",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": true
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
@@ -663,4 +663,76 @@
|
|||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "AntiFraudBot",
|
||||||
|
"desc": "反诈机器人",
|
||||||
|
"author_id": 104713034,
|
||||||
|
"homepage": "https://github.com/itsevin/AntiFraudBot",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "反诈",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nekro Agent Bot",
|
||||||
|
"desc": "基于生成式人工智能与沙盒技术的 Nekro Agent 代理执行 AI 机器人,支持聊天、识图、通用文件处理等扩展能力,提供了 WebUI 维护界面、一键部署脚本",
|
||||||
|
"author_id": 57167362,
|
||||||
|
"homepage": "https://github.com/KroMiose/nekro-agent",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "聊天",
|
||||||
|
"color": "#c65856"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "大模型",
|
||||||
|
"color": "#46a34c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "WebUI",
|
||||||
|
"color": "#4a96c6"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PickStarsBot",
|
||||||
|
"desc": "欢迎使用PickStarsBot!这是一款基于NoneBot2构建的智能QQ机器人,提供丰富的功能,包括一言、历史上的今天、60秒早报等,快来试试吧!",
|
||||||
|
"author_id": 183461085,
|
||||||
|
"homepage": "https://github.com/PickStars308/PickStarsBot",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LiteBot",
|
||||||
|
"desc": "Web功能/MC/数据功能Bot",
|
||||||
|
"author_id": 67693593,
|
||||||
|
"homepage": "https://github.com/LiteSuggarDEV/LiteBot-NEO/",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "Minecraft",
|
||||||
|
"color": "#ea5252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Web",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Muicebot",
|
||||||
|
"desc": "Muice-Chatbot 的 Nonebot2 实现,支持调用主流大模型,支持 Function Call 和内置 MCP Host 实现",
|
||||||
|
"author_id": 72406624,
|
||||||
|
"homepage": "https://github.com/Moemu/MuiceBot",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "LLM",
|
||||||
|
"color": "#3e97ff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
1332
assets/plugins.json5
1332
assets/plugins.json5
File diff suppressed because it is too large
Load Diff
1092
envs/pydantic-v1/poetry.lock
generated
1092
envs/pydantic-v1/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
1007
envs/pydantic-v2/poetry.lock
generated
1007
envs/pydantic-v2/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
448
envs/test/poetry.lock
generated
448
envs/test/poetry.lock
generated
@@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
@@ -6,6 +6,7 @@ version = "0.7.0"
|
|||||||
description = "Reusable constraint types to use with typing.Annotated"
|
description = "Reusable constraint types to use with typing.Annotated"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||||
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||||
@@ -13,13 +14,14 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "4.7.0"
|
version = "4.8.0"
|
||||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
|
{file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"},
|
||||||
{file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
|
{file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -30,7 +32,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
|
|||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
||||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
|
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
|
||||||
trio = ["trio (>=0.26.1)"]
|
trio = ["trio (>=0.26.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -39,6 +41,7 @@ version = "3.8.1"
|
|||||||
description = "ASGI specs, helper code, and adapters"
|
description = "ASGI specs, helper code, and adapters"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
|
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
|
||||||
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
|
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
|
||||||
@@ -56,6 +59,7 @@ version = "1.4.11"
|
|||||||
description = "Async client for testing ASGI web applications"
|
description = "Async client for testing ASGI web applications"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "async-asgi-testclient-1.4.11.tar.gz", hash = "sha256:4449ac85d512d661998ec61f91c9ae01851639611d748d81ae7f816736551792"},
|
{file = "async-asgi-testclient-1.4.11.tar.gz", hash = "sha256:4449ac85d512d661998ec61f91c9ae01851639611d748d81ae7f816736551792"},
|
||||||
]
|
]
|
||||||
@@ -66,32 +70,34 @@ requests = ">=2.21,<3.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "24.3.0"
|
version = "25.1.0"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"},
|
{file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"},
|
||||||
{file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"},
|
{file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
|
||||||
cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
|
||||||
dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
|
||||||
docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
||||||
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
|
||||||
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
|
tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2024.12.14"
|
version = "2025.1.31"
|
||||||
description = "Python package for providing Mozilla's CA Bundle."
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
|
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
|
||||||
{file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
|
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -100,6 +106,8 @@ version = "1.17.1"
|
|||||||
description = "Foreign Function Interface for Python calling C code."
|
description = "Foreign Function Interface for Python calling C code."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "os_name == \"nt\" and implementation_name != \"pypy\""
|
||||||
files = [
|
files = [
|
||||||
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
||||||
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
||||||
@@ -179,6 +187,7 @@ version = "3.4.1"
|
|||||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
|
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
|
||||||
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
|
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
|
||||||
@@ -280,6 +289,8 @@ version = "0.4.6"
|
|||||||
description = "Cross-platform colored terminal text."
|
description = "Cross-platform colored terminal text."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "sys_platform == \"win32\""
|
||||||
files = [
|
files = [
|
||||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
@@ -287,80 +298,82 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "7.6.9"
|
version = "7.6.12"
|
||||||
description = "Code coverage measurement for Python"
|
description = "Code coverage measurement for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"},
|
{file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"},
|
{file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"},
|
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"},
|
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"},
|
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"},
|
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"},
|
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"},
|
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"},
|
{file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"},
|
||||||
{file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"},
|
{file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"},
|
{file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"},
|
{file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"},
|
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"},
|
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"},
|
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"},
|
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"},
|
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"},
|
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"},
|
{file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"},
|
||||||
{file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"},
|
{file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"},
|
{file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"},
|
{file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"},
|
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"},
|
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"},
|
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"},
|
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"},
|
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"},
|
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"},
|
{file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"},
|
||||||
{file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"},
|
{file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"},
|
{file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"},
|
{file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"},
|
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"},
|
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"},
|
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"},
|
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"},
|
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"},
|
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"},
|
{file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"},
|
{file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"},
|
{file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"},
|
{file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"},
|
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"},
|
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"},
|
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"},
|
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"},
|
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"},
|
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"},
|
{file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"},
|
||||||
{file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"},
|
{file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"},
|
{file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"},
|
{file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"},
|
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"},
|
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"},
|
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"},
|
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"},
|
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"},
|
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"},
|
{file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"},
|
||||||
{file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"},
|
{file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"},
|
||||||
{file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"},
|
{file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"},
|
||||||
{file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"},
|
{file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"},
|
||||||
|
{file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
toml = ["tomli"]
|
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage-conditional-plugin"
|
name = "coverage-conditional-plugin"
|
||||||
@@ -368,6 +381,7 @@ version = "0.9.0"
|
|||||||
description = "Conditional coverage based on any rules you define!"
|
description = "Conditional coverage based on any rules you define!"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7,<4.0"
|
python-versions = ">=3.7,<4.0"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "coverage_conditional_plugin-0.9.0-py3-none-any.whl", hash = "sha256:1b37bc469019d2ab5b01f5eee453abe1846b3431e64e209720c2a9ec4afb8130"},
|
{file = "coverage_conditional_plugin-0.9.0-py3-none-any.whl", hash = "sha256:1b37bc469019d2ab5b01f5eee453abe1846b3431e64e209720c2a9ec4afb8130"},
|
||||||
{file = "coverage_conditional_plugin-0.9.0.tar.gz", hash = "sha256:6893dab0542695dbd5ea714281dae0dfec8d0e36480ba32d839e9fa7344f8215"},
|
{file = "coverage_conditional_plugin-0.9.0.tar.gz", hash = "sha256:6893dab0542695dbd5ea714281dae0dfec8d0e36480ba32d839e9fa7344f8215"},
|
||||||
@@ -384,6 +398,7 @@ version = "1.2.2"
|
|||||||
description = "Backport of PEP 654 (exception groups)"
|
description = "Backport of PEP 654 (exception groups)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||||
@@ -398,6 +413,7 @@ version = "2.1.1"
|
|||||||
description = "execnet: rapid multi-Python deployment"
|
description = "execnet: rapid multi-Python deployment"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
||||||
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
||||||
@@ -412,6 +428,7 @@ version = "0.14.0"
|
|||||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||||
@@ -423,6 +440,7 @@ version = "3.10"
|
|||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
|
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
|
||||||
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
|
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
|
||||||
@@ -433,25 +451,27 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "importlib-metadata"
|
name = "importlib-metadata"
|
||||||
version = "8.5.0"
|
version = "8.6.1"
|
||||||
description = "Read metadata from Python packages"
|
description = "Read metadata from Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_version < \"3.10\""
|
||||||
files = [
|
files = [
|
||||||
{file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"},
|
{file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"},
|
||||||
{file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"},
|
{file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
zipp = ">=3.20"
|
zipp = ">=3.20"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
|
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||||
cover = ["pytest-cov"]
|
cover = ["pytest-cov"]
|
||||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
enabler = ["pytest-enabler (>=2.2)"]
|
enabler = ["pytest-enabler (>=2.2)"]
|
||||||
perf = ["ipython"]
|
perf = ["ipython"]
|
||||||
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
|
test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
|
||||||
type = ["pytest-mypy"]
|
type = ["pytest-mypy"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -460,6 +480,7 @@ version = "2.0.0"
|
|||||||
description = "brain-dead simple config-ini parsing"
|
description = "brain-dead simple config-ini parsing"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
@@ -471,6 +492,7 @@ version = "0.7.3"
|
|||||||
description = "Python logging made (stupidly) simple"
|
description = "Python logging made (stupidly) simple"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.5"
|
python-versions = "<4.0,>=3.5"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"},
|
{file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"},
|
||||||
{file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"},
|
{file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"},
|
||||||
@@ -481,7 +503,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
|
|||||||
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"]
|
dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
@@ -489,6 +511,7 @@ version = "3.0.2"
|
|||||||
description = "Safely add untrusted strings to HTML/XML markup."
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
|
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
|
||||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
|
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
|
||||||
@@ -559,6 +582,7 @@ version = "6.1.0"
|
|||||||
description = "multidict implementation"
|
description = "multidict implementation"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"},
|
{file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"},
|
||||||
{file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"},
|
{file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"},
|
||||||
@@ -659,20 +683,21 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nonebot2"
|
name = "nonebot2"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
description = "An asynchronous python bot framework."
|
description = "An asynchronous python bot framework."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.9"
|
python-versions = "<4.0,>=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "nonebot2-2.4.0-py3-none-any.whl", hash = "sha256:7c712e05561afa4795c9135a5b27a43d076220f4538ffec518e68c344e3e51d4"},
|
{file = "nonebot2-2.4.1-py3-none-any.whl", hash = "sha256:fec95f075efc89dbe9ce148618b413b02f46ba284200367749b035e794695111"},
|
||||||
{file = "nonebot2-2.4.0.tar.gz", hash = "sha256:4b10e33d389847500c9bde9ef3c5533b604a90ca1529750245f1aaf82b28f1e1"},
|
{file = "nonebot2-2.4.1.tar.gz", hash = "sha256:8fea364318501ed79721403a8ecd76587bc884d58c356260f691a8bbda9b05e6"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
anyio = ">=4.4.0,<5.0.0"
|
anyio = ">=4.4.0,<5.0.0"
|
||||||
exceptiongroup = ">=1.2.2,<2.0.0"
|
exceptiongroup = ">=1.2.2,<2.0.0"
|
||||||
loguru = ">=0.6.0,<1.0.0"
|
loguru = ">=0.6.0,<1.0.0"
|
||||||
pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0"
|
pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<2.10.0 || >2.10.0,<2.10.1 || >2.10.1,<3.0.0"
|
||||||
pygtrie = ">=2.4.1,<3.0.0"
|
pygtrie = ">=2.4.1,<3.0.0"
|
||||||
python-dotenv = ">=0.21.0,<2.0.0"
|
python-dotenv = ">=0.21.0,<2.0.0"
|
||||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
||||||
@@ -680,10 +705,10 @@ typing-extensions = ">=4.4.0,<5.0.0"
|
|||||||
yarl = ">=1.7.2,<2.0.0"
|
yarl = ">=1.7.2,<2.0.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
aiohttp = ["aiohttp[speedups] (>=3.9.0b0,<4.0.0)"]
|
aiohttp = ["aiohttp[speedups] (>=3.11.0,<4.0.0)"]
|
||||||
all = ["Quart (>=0.18.0,<1.0.0)", "aiohttp[speedups] (>=3.9.0b0,<4.0.0)", "fastapi (>=0.93.0,<1.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)", "websockets (>=10.0)"]
|
all = ["Quart (>=0.18.0,<1.0.0)", "aiohttp[speedups] (>=3.11.0,<4.0.0)", "fastapi (>=0.93.0,<1.0.0)", "httpx[http2] (>=0.26.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)", "websockets (>=10.0)"]
|
||||||
fastapi = ["fastapi (>=0.93.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"]
|
fastapi = ["fastapi (>=0.93.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"]
|
||||||
httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"]
|
httpx = ["httpx[http2] (>=0.26.0,<1.0.0)"]
|
||||||
quart = ["Quart (>=0.18.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"]
|
quart = ["Quart (>=0.18.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"]
|
||||||
websockets = ["websockets (>=10.0)"]
|
websockets = ["websockets (>=10.0)"]
|
||||||
|
|
||||||
@@ -693,6 +718,7 @@ version = "0.4.3"
|
|||||||
description = "nonebot2 test framework"
|
description = "nonebot2 test framework"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.9"
|
python-versions = "<4.0,>=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "nonebug-0.4.3-py3-none-any.whl", hash = "sha256:eb9b2c8ab3d45459a4f00ebdaae90729e9e9628575c0685fca4c871dd4cfd425"},
|
{file = "nonebug-0.4.3-py3-none-any.whl", hash = "sha256:eb9b2c8ab3d45459a4f00ebdaae90729e9e9628575c0685fca4c871dd4cfd425"},
|
||||||
{file = "nonebug-0.4.3.tar.gz", hash = "sha256:e9592d2c7a42b76f4a336f98726cba92e1300f6bab155c8822e865919786f10c"},
|
{file = "nonebug-0.4.3.tar.gz", hash = "sha256:e9592d2c7a42b76f4a336f98726cba92e1300f6bab155c8822e865919786f10c"},
|
||||||
@@ -711,6 +737,7 @@ version = "1.3.0.post0"
|
|||||||
description = "Capture the outcome of Python function calls."
|
description = "Capture the outcome of Python function calls."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"},
|
{file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"},
|
||||||
{file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"},
|
{file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"},
|
||||||
@@ -725,6 +752,7 @@ version = "24.2"
|
|||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||||
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||||
@@ -736,6 +764,7 @@ version = "1.5.0"
|
|||||||
description = "plugin and hook calling mechanisms for python"
|
description = "plugin and hook calling mechanisms for python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||||
@@ -747,93 +776,110 @@ testing = ["pytest", "pytest-benchmark"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "propcache"
|
name = "propcache"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
description = "Accelerated property cache"
|
description = "Accelerated property cache"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"},
|
{file = "propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"},
|
{file = "propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"},
|
{file = "propcache-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e7420211f5a65a54675fd860ea04173cde60a7cc20ccfbafcccd155225f8bc"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"},
|
{file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3302c5287e504d23bb0e64d2a921d1eb4a03fb93a0a0aa3b53de059f5a5d737d"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"},
|
{file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2e068a83552ddf7a39a99488bcba05ac13454fb205c847674da0352602082f"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"},
|
{file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d913d36bdaf368637b4f88d554fb9cb9d53d6920b9c5563846555938d5450bf"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"},
|
{file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ee1983728964d6070ab443399c476de93d5d741f71e8f6e7880a065f878e0b9"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"},
|
{file = "propcache-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36ca5e9a21822cc1746023e88f5c0af6fce3af3b85d4520efb1ce4221bed75cc"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"},
|
{file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9ecde3671e62eeb99e977f5221abcf40c208f69b5eb986b061ccec317c82ebd0"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"},
|
{file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d383bf5e045d7f9d239b38e6acadd7b7fdf6c0087259a84ae3475d18e9a2ae8b"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"},
|
{file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8cb625bcb5add899cb8ba7bf716ec1d3e8f7cdea9b0713fa99eadf73b6d4986f"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"},
|
{file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5fa159dcee5dba00c1def3231c249cf261185189205073bde13797e57dd7540a"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"},
|
{file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7080b0159ce05f179cfac592cda1a82898ca9cd097dacf8ea20ae33474fbb25"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"},
|
{file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7161bccab7696a473fe7ddb619c1d75963732b37da4618ba12e60899fefe4f"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"},
|
{file = "propcache-0.3.0-cp310-cp310-win32.whl", hash = "sha256:bf0d9a171908f32d54f651648c7290397b8792f4303821c42a74e7805bfb813c"},
|
||||||
{file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"},
|
{file = "propcache-0.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:42924dc0c9d73e49908e35bbdec87adedd651ea24c53c29cac103ede0ea1d340"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"},
|
{file = "propcache-0.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"},
|
{file = "propcache-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"},
|
{file = "propcache-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"},
|
{file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"},
|
{file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"},
|
{file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"},
|
{file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"},
|
{file = "propcache-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"},
|
{file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"},
|
{file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"},
|
{file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"},
|
{file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"},
|
{file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"},
|
{file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"},
|
{file = "propcache-0.3.0-cp311-cp311-win32.whl", hash = "sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d"},
|
||||||
{file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"},
|
{file = "propcache-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"},
|
{file = "propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"},
|
{file = "propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"},
|
{file = "propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"},
|
{file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"},
|
{file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"},
|
{file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"},
|
{file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"},
|
{file = "propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"},
|
{file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"},
|
{file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"},
|
{file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"},
|
{file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"},
|
{file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"},
|
{file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"},
|
{file = "propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d"},
|
||||||
{file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"},
|
{file = "propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"},
|
{file = "propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"},
|
{file = "propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"},
|
{file = "propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"},
|
{file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"},
|
{file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"},
|
{file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"},
|
{file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"},
|
{file = "propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"},
|
{file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"},
|
{file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"},
|
{file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"},
|
{file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"},
|
{file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"},
|
{file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"},
|
{file = "propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374"},
|
||||||
{file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"},
|
{file = "propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"},
|
{file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"},
|
{file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"},
|
{file = "propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"},
|
{file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"},
|
{file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"},
|
{file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"},
|
{file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"},
|
{file = "propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"},
|
{file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"},
|
{file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"},
|
{file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"},
|
{file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"},
|
{file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"},
|
{file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"},
|
{file = "propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863"},
|
||||||
{file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"},
|
{file = "propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46"},
|
||||||
{file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"},
|
{file = "propcache-0.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:03c091bb752349402f23ee43bb2bff6bd80ccab7c9df6b88ad4322258d6960fc"},
|
||||||
{file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"},
|
{file = "propcache-0.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46ed02532cb66612d42ae5c3929b5e98ae330ea0f3900bc66ec5f4862069519b"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11ae6a8a01b8a4dc79093b5d3ca2c8a4436f5ee251a9840d7790dccbd96cb649"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df03cd88f95b1b99052b52b1bb92173229d7a674df0ab06d2b25765ee8404bce"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03acd9ff19021bd0567582ac88f821b66883e158274183b9e5586f678984f8fe"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd54895e4ae7d32f1e3dd91261df46ee7483a735017dc6f987904f194aa5fd14"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a67e5c04e3119594d8cfae517f4b9330c395df07ea65eab16f3d559b7068fe"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee25f1ac091def37c4b59d192bbe3a206298feeb89132a470325bf76ad122a1e"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58e6d2a5a7cb3e5f166fd58e71e9a4ff504be9dc61b88167e75f835da5764d07"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:be90c94570840939fecedf99fa72839aed70b0ced449b415c85e01ae67422c90"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49ea05212a529c2caffe411e25a59308b07d6e10bf2505d77da72891f9a05641"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:119e244ab40f70a98c91906d4c1f4c5f2e68bd0b14e7ab0a06922038fae8a20f"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:507c5357a8d8b4593b97fb669c50598f4e6cccbbf77e22fa9598aba78292b4d7"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8526b0941ec5a40220fc4dfde76aed58808e2b309c03e9fa8e2260083ef7157f"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-win32.whl", hash = "sha256:7cedd25e5f678f7738da38037435b340694ab34d424938041aa630d8bac42663"},
|
||||||
|
{file = "propcache-0.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf4298f366ca7e1ad1d21bbb58300a6985015909964077afd37559084590c929"},
|
||||||
|
{file = "propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043"},
|
||||||
|
{file = "propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -842,6 +888,8 @@ version = "2.22"
|
|||||||
description = "C parser in Python"
|
description = "C parser in Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "os_name == \"nt\" and implementation_name != \"pypy\""
|
||||||
files = [
|
files = [
|
||||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||||
@@ -849,13 +897,14 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.10.4"
|
version = "2.10.6"
|
||||||
description = "Data validation using Python type hints"
|
description = "Data validation using Python type hints"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"},
|
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
|
||||||
{file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"},
|
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -865,7 +914,7 @@ typing-extensions = ">=4.12.2"
|
|||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
email = ["email-validator (>=2.0.0)"]
|
email = ["email-validator (>=2.0.0)"]
|
||||||
timezone = ["tzdata"]
|
timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-core"
|
name = "pydantic-core"
|
||||||
@@ -873,6 +922,7 @@ version = "2.27.2"
|
|||||||
description = "Core functionality for Pydantic validation and serialization"
|
description = "Core functionality for Pydantic validation and serialization"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
|
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
|
||||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
|
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
|
||||||
@@ -985,6 +1035,7 @@ version = "2.5.0"
|
|||||||
description = "A pure Python trie data structure implementation."
|
description = "A pure Python trie data structure implementation."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16"},
|
{file = "pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16"},
|
||||||
{file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"},
|
{file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"},
|
||||||
@@ -992,13 +1043,14 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.4"
|
version = "8.3.5"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
|
||||||
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1018,6 +1070,7 @@ version = "6.0.0"
|
|||||||
description = "Pytest plugin for measuring coverage."
|
description = "Pytest plugin for measuring coverage."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"},
|
{file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"},
|
||||||
{file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"},
|
{file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"},
|
||||||
@@ -1036,6 +1089,7 @@ version = "3.6.1"
|
|||||||
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
||||||
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
||||||
@@ -1056,6 +1110,7 @@ version = "1.0.1"
|
|||||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
|
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
|
||||||
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
|
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
|
||||||
@@ -1070,6 +1125,7 @@ version = "2.32.3"
|
|||||||
description = "Python HTTP for Humans."
|
description = "Python HTTP for Humans."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||||
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||||
@@ -1091,6 +1147,7 @@ version = "1.3.1"
|
|||||||
description = "Sniff out which async library your code is running under"
|
description = "Sniff out which async library your code is running under"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||||
@@ -1102,6 +1159,7 @@ version = "2.4.0"
|
|||||||
description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
|
description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
|
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
|
||||||
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
|
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
|
||||||
@@ -1113,6 +1171,8 @@ version = "2.2.1"
|
|||||||
description = "A lil' TOML parser"
|
description = "A lil' TOML parser"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_full_version <= \"3.11.0a6\""
|
||||||
files = [
|
files = [
|
||||||
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||||
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||||
@@ -1154,6 +1214,7 @@ version = "0.27.0"
|
|||||||
description = "A friendly Python library for async concurrency and I/O"
|
description = "A friendly Python library for async concurrency and I/O"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "trio-0.27.0-py3-none-any.whl", hash = "sha256:68eabbcf8f457d925df62da780eff15ff5dc68fd6b367e2dde59f7aaf2a0b884"},
|
{file = "trio-0.27.0-py3-none-any.whl", hash = "sha256:68eabbcf8f457d925df62da780eff15ff5dc68fd6b367e2dde59f7aaf2a0b884"},
|
||||||
{file = "trio-0.27.0.tar.gz", hash = "sha256:1dcc95ab1726b2da054afea8fd761af74bad79bd52381b84eae408e983c76831"},
|
{file = "trio-0.27.0.tar.gz", hash = "sha256:1dcc95ab1726b2da054afea8fd761af74bad79bd52381b84eae408e983c76831"},
|
||||||
@@ -1174,6 +1235,7 @@ version = "4.12.2"
|
|||||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||||
@@ -1185,13 +1247,14 @@ version = "2.3.0"
|
|||||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
|
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
|
||||||
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
|
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
|
||||||
h2 = ["h2 (>=4,<5)"]
|
h2 = ["h2 (>=4,<5)"]
|
||||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
zstd = ["zstandard (>=0.18.0)"]
|
zstd = ["zstandard (>=0.18.0)"]
|
||||||
@@ -1202,6 +1265,7 @@ version = "3.1.3"
|
|||||||
description = "The comprehensive WSGI web application library."
|
description = "The comprehensive WSGI web application library."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"},
|
{file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"},
|
||||||
{file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"},
|
{file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"},
|
||||||
@@ -1219,13 +1283,15 @@ version = "1.2.0"
|
|||||||
description = "A small Python utility to set file creation time on Windows"
|
description = "A small Python utility to set file creation time on Windows"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "sys_platform == \"win32\""
|
||||||
files = [
|
files = [
|
||||||
{file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"},
|
{file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"},
|
||||||
{file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"},
|
{file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
|
dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wsproto"
|
name = "wsproto"
|
||||||
@@ -1233,6 +1299,7 @@ version = "1.2.0"
|
|||||||
description = "WebSockets state-machine based protocol implementation"
|
description = "WebSockets state-machine based protocol implementation"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7.0"
|
python-versions = ">=3.7.0"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
|
{file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
|
||||||
{file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
|
{file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
|
||||||
@@ -1247,6 +1314,7 @@ version = "1.18.3"
|
|||||||
description = "Yet another URL library"
|
description = "Yet another URL library"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"},
|
{file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"},
|
||||||
{file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"},
|
{file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"},
|
||||||
@@ -1343,20 +1411,22 @@ version = "3.21.0"
|
|||||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_version < \"3.10\""
|
||||||
files = [
|
files = [
|
||||||
{file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
|
{file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
|
||||||
{file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
|
{file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
|
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||||
cover = ["pytest-cov"]
|
cover = ["pytest-cov"]
|
||||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
enabler = ["pytest-enabler (>=2.2)"]
|
enabler = ["pytest-enabler (>=2.2)"]
|
||||||
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
|
test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
|
||||||
type = ["pytest-mypy"]
|
type = ["pytest-mypy"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "c4d88f65571fb527c36e8c1f95de59aab3fb86c029341cc0c02ad431593d6d85"
|
content-hash = "c4d88f65571fb527c36e8c1f95de59aab3fb86c029341cc0c02ad431593d6d85"
|
||||||
|
@@ -190,9 +190,9 @@ def get_asgi() -> Any:
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
assert isinstance(
|
assert isinstance(driver, ASGIMixin), (
|
||||||
driver, ASGIMixin
|
"asgi object is only available for asgi driver"
|
||||||
), "asgi object is only available for asgi driver"
|
)
|
||||||
return driver.asgi
|
return driver.asgi
|
||||||
|
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ from typing import (
|
|||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
Generic,
|
Generic,
|
||||||
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
Protocol,
|
Protocol,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
@@ -45,6 +46,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"DEFAULT_CONFIG",
|
"DEFAULT_CONFIG",
|
||||||
|
"PYDANTIC_V2",
|
||||||
"ConfigDict",
|
"ConfigDict",
|
||||||
"FieldInfo",
|
"FieldInfo",
|
||||||
"ModelField",
|
"ModelField",
|
||||||
@@ -54,9 +56,11 @@ __all__ = (
|
|||||||
"TypeAdapter",
|
"TypeAdapter",
|
||||||
"custom_validation",
|
"custom_validation",
|
||||||
"extract_field_info",
|
"extract_field_info",
|
||||||
|
"field_validator",
|
||||||
"model_config",
|
"model_config",
|
||||||
"model_dump",
|
"model_dump",
|
||||||
"model_fields",
|
"model_fields",
|
||||||
|
"model_validator",
|
||||||
"type_validate_json",
|
"type_validate_json",
|
||||||
"type_validate_python",
|
"type_validate_python",
|
||||||
)
|
)
|
||||||
@@ -70,6 +74,8 @@ __autodoc__ = {
|
|||||||
if PYDANTIC_V2: # pragma: pydantic-v2
|
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||||
from pydantic import GetCoreSchemaHandler
|
from pydantic import GetCoreSchemaHandler
|
||||||
from pydantic import TypeAdapter as TypeAdapter
|
from pydantic import TypeAdapter as TypeAdapter
|
||||||
|
from pydantic import field_validator as field_validator
|
||||||
|
from pydantic import model_validator as model_validator
|
||||||
from pydantic._internal._repr import display_as_type
|
from pydantic._internal._repr import display_as_type
|
||||||
from pydantic.fields import FieldInfo as BaseFieldInfo
|
from pydantic.fields import FieldInfo as BaseFieldInfo
|
||||||
from pydantic_core import CoreSchema, core_schema
|
from pydantic_core import CoreSchema, core_schema
|
||||||
@@ -254,7 +260,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
|||||||
|
|
||||||
else: # pragma: pydantic-v1
|
else: # pragma: pydantic-v1
|
||||||
from pydantic import BaseConfig as PydanticConfig
|
from pydantic import BaseConfig as PydanticConfig
|
||||||
from pydantic import Extra, parse_obj_as, parse_raw_as
|
from pydantic import Extra, parse_obj_as, parse_raw_as, root_validator, validator
|
||||||
from pydantic.fields import FieldInfo as BaseFieldInfo
|
from pydantic.fields import FieldInfo as BaseFieldInfo
|
||||||
from pydantic.fields import ModelField as BaseModelField
|
from pydantic.fields import ModelField as BaseModelField
|
||||||
from pydantic.schema import get_annotation_from_field_info
|
from pydantic.schema import get_annotation_from_field_info
|
||||||
@@ -367,6 +373,44 @@ else: # pragma: pydantic-v1
|
|||||||
kwargs.update(field_info.extra)
|
kwargs.update(field_info.extra)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def field_validator(
|
||||||
|
field: str,
|
||||||
|
/,
|
||||||
|
*fields: str,
|
||||||
|
mode: Literal["before"],
|
||||||
|
check_fields: Optional[bool] = None,
|
||||||
|
): ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def field_validator(
|
||||||
|
field: str,
|
||||||
|
/,
|
||||||
|
*fields: str,
|
||||||
|
mode: Literal["after"] = ...,
|
||||||
|
check_fields: Optional[bool] = None,
|
||||||
|
): ...
|
||||||
|
|
||||||
|
def field_validator(
|
||||||
|
field: str,
|
||||||
|
/,
|
||||||
|
*fields: str,
|
||||||
|
mode: Literal["before", "after"] = "after",
|
||||||
|
check_fields: Optional[bool] = None,
|
||||||
|
):
|
||||||
|
if mode == "before":
|
||||||
|
return validator(
|
||||||
|
field,
|
||||||
|
*fields,
|
||||||
|
pre=True,
|
||||||
|
check_fields=check_fields or True,
|
||||||
|
allow_reuse=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return validator(
|
||||||
|
field, *fields, check_fields=check_fields or True, allow_reuse=True
|
||||||
|
)
|
||||||
|
|
||||||
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
||||||
"""Get field list of a model."""
|
"""Get field list of a model."""
|
||||||
|
|
||||||
@@ -404,6 +448,18 @@ else: # pragma: pydantic-v1
|
|||||||
exclude_none=exclude_none,
|
exclude_none=exclude_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def model_validator(*, mode: Literal["before"]): ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def model_validator(*, mode: Literal["after"]): ...
|
||||||
|
|
||||||
|
def model_validator(*, mode: Literal["before", "after"]):
|
||||||
|
if mode == "before":
|
||||||
|
return root_validator(pre=True, allow_reuse=True)
|
||||||
|
else:
|
||||||
|
return root_validator(skip_on_failure=True, allow_reuse=True)
|
||||||
|
|
||||||
def type_validate_python(type_: type[T], data: Any) -> T:
|
def type_validate_python(type_: type[T], data: Any) -> T:
|
||||||
"""Validate data with given type."""
|
"""Validate data with given type."""
|
||||||
return parse_obj_as(type_, data)
|
return parse_obj_as(type_, data)
|
||||||
|
@@ -175,8 +175,7 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# delete from file vars when used
|
# delete from file vars when used
|
||||||
if env_name in env_file_vars:
|
env_file_vars.pop(env_name, None)
|
||||||
del env_file_vars[env_name]
|
|
||||||
|
|
||||||
_, *keys, last_key = env_name.split(self.env_nested_delimiter)
|
_, *keys, last_key = env_name.split(self.env_nested_delimiter)
|
||||||
env_var = result
|
env_var = result
|
||||||
|
@@ -103,8 +103,7 @@ class Driver(BaseDriver):
|
|||||||
"</bg #f8bbd0></r>"
|
"</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
logger.error(
|
logger.error(
|
||||||
"<r><bg #f8bbd0>Application startup failed. "
|
"<r><bg #f8bbd0>Application startup failed. Exiting.</bg #f8bbd0></r>"
|
||||||
"Exiting.</bg #f8bbd0></r>"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with catch({Exception: handle_exception}):
|
with catch({Exception: handle_exception}):
|
||||||
@@ -136,8 +135,7 @@ class Driver(BaseDriver):
|
|||||||
"</bg #f8bbd0></r>"
|
"</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
logger.error(
|
logger.error(
|
||||||
"<r><bg #f8bbd0>Application shutdown failed. "
|
"<r><bg #f8bbd0>Application shutdown failed. Exiting.</bg #f8bbd0></r>"
|
||||||
"Exiting.</bg #f8bbd0></r>"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with catch({Exception: handle_exception}):
|
with catch({Exception: handle_exception}):
|
||||||
|
@@ -17,10 +17,11 @@ FrontMatter:
|
|||||||
description: nonebot.drivers.websockets 模块
|
description: nonebot.drivers.websockets 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import AsyncGenerator, Coroutine
|
from collections.abc import AsyncGenerator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
|
from types import CoroutineType
|
||||||
from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union
|
from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union
|
||||||
from typing_extensions import ParamSpec, override
|
from typing_extensions import ParamSpec, override
|
||||||
|
|
||||||
@@ -47,8 +48,8 @@ logger.addHandler(LoguruHandler())
|
|||||||
|
|
||||||
|
|
||||||
def catch_closed(
|
def catch_closed(
|
||||||
func: Callable[P, Coroutine[Any, Any, T]],
|
func: Callable[P, "CoroutineType[Any, Any, T]"],
|
||||||
) -> Callable[P, Coroutine[Any, Any, T]]:
|
) -> Callable[P, "CoroutineType[Any, Any, T]"]:
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
|
async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||||
try:
|
try:
|
||||||
|
@@ -51,10 +51,10 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
def __add__(self, other: Union[str, Self, Iterable[Self]]) -> TM:
|
||||||
return self.get_message_class()(self) + other
|
return self.get_message_class()(self) + other
|
||||||
|
|
||||||
def __radd__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
def __radd__(self, other: Union[str, Self, Iterable[Self]]) -> TM:
|
||||||
return self.get_message_class()(other) + self
|
return self.get_message_class()(other) + self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -87,7 +87,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
|||||||
def items(self):
|
def items(self):
|
||||||
return asdict(self).items()
|
return asdict(self).items()
|
||||||
|
|
||||||
def join(self: TMS, iterable: Iterable[Union[TMS, TM]]) -> TM:
|
def join(self, iterable: Iterable[Union[Self, TM]]) -> TM:
|
||||||
return self.get_message_class()(self).join(iterable)
|
return self.get_message_class()(self).join(iterable)
|
||||||
|
|
||||||
def copy(self) -> Self:
|
def copy(self) -> Self:
|
||||||
|
@@ -19,7 +19,7 @@ class MatcherProvider(abc.ABC, MutableMapping[int, list[type["Matcher"]]]):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class _DictProvider(defaultdict, MatcherProvider):
|
class _DictProvider(defaultdict[int, list[type["Matcher"]]], MatcherProvider): # type: ignore
|
||||||
def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]):
|
def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]):
|
||||||
super().__init__(list, matchers)
|
super().__init__(list, matchers)
|
||||||
|
|
||||||
|
@@ -228,9 +228,9 @@ class DependParam(Param):
|
|||||||
dependency: T_Handler
|
dependency: T_Handler
|
||||||
# sub dependency is not specified, use type annotation
|
# 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
|
"Dependency cannot be empty"
|
||||||
), "Dependency cannot be empty"
|
)
|
||||||
dependency = type_annotation
|
dependency = type_annotation
|
||||||
else:
|
else:
|
||||||
dependency = depends_inner.dependency
|
dependency = depends_inner.dependency
|
||||||
@@ -291,9 +291,9 @@ class DependParam(Param):
|
|||||||
return await dependency_cache[call].wait()
|
return await dependency_cache[call].wait()
|
||||||
|
|
||||||
if is_gen_callable(call) or is_async_gen_callable(call):
|
if is_gen_callable(call) or is_async_gen_callable(call):
|
||||||
assert isinstance(
|
assert isinstance(stack, AsyncExitStack), (
|
||||||
stack, AsyncExitStack
|
"Generator dependency should be called in context"
|
||||||
), "Generator dependency should be called in context"
|
)
|
||||||
if is_gen_callable(call):
|
if is_gen_callable(call):
|
||||||
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
||||||
else:
|
else:
|
||||||
|
@@ -509,7 +509,7 @@ class ArgumentParser(ArgParser):
|
|||||||
super()._parse_optional(arg_string) if isinstance(arg_string, str) else None
|
super()._parse_optional(arg_string) if isinstance(arg_string, str) else None
|
||||||
)
|
)
|
||||||
|
|
||||||
def _print_message(self, message: str, file: Optional[IO[str]] = None):
|
def _print_message(self, message: str, file: Optional[IO[str]] = None): # type: ignore
|
||||||
if (msg := parser_message.get(None)) is not None:
|
if (msg := parser_message.get(None)) is not None:
|
||||||
parser_message.set(msg + message)
|
parser_message.set(msg + message)
|
||||||
else:
|
else:
|
||||||
@@ -557,12 +557,22 @@ class ShellCommandRule:
|
|||||||
if cmd not in self.cmds or msg is None:
|
if cmd not in self.cmds or msg is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
state[SHELL_ARGV] = list(
|
try:
|
||||||
chain.from_iterable(
|
state[SHELL_ARGV] = list(
|
||||||
shlex.split(str(seg)) if cast(MessageSegment, seg).is_text() else (seg,)
|
chain.from_iterable(
|
||||||
for seg in msg
|
shlex.split(str(seg))
|
||||||
|
if cast(MessageSegment, seg).is_text()
|
||||||
|
else (seg,)
|
||||||
|
for seg in msg
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
except Exception as e:
|
||||||
|
# set SHELL_ARGV to none indicating shlex error
|
||||||
|
state[SHELL_ARGV] = None
|
||||||
|
# ensure SHELL_ARGS is set to ParserExit if parser is provided
|
||||||
|
if self.parser:
|
||||||
|
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
|
||||||
|
return True
|
||||||
|
|
||||||
if self.parser:
|
if self.parser:
|
||||||
t = parser_message.set("")
|
t = parser_message.set("")
|
||||||
|
@@ -253,6 +253,8 @@ async def run_coro_with_shield(coro: Coroutine[Any, Any, T]) -> T:
|
|||||||
with anyio.CancelScope(shield=True):
|
with anyio.CancelScope(shield=True):
|
||||||
return await coro
|
return await coro
|
||||||
|
|
||||||
|
raise RuntimeError("This should not happen")
|
||||||
|
|
||||||
|
|
||||||
def flatten_exception_group(
|
def flatten_exception_group(
|
||||||
exc_group: BaseExceptionGroup[E],
|
exc_group: BaseExceptionGroup[E],
|
||||||
|
16
package.json
16
package.json
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"packageManager": "yarn@1.22.22",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"website"
|
"website"
|
||||||
],
|
],
|
||||||
@@ -20,20 +21,19 @@
|
|||||||
"pyright": "pyright"
|
"pyright": "pyright"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^6.6.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.48.0",
|
"eslint": "^8.48.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-import-resolver-typescript": "^3.6.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-import": "^2.28.1",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-regexp": "^1.15.0",
|
"eslint-plugin-regexp": "^1.15.0",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"pyright": "^1.1.317",
|
"pyright": "1.1.393",
|
||||||
"stylelint": "^15.10.3",
|
"stylelint": "^15.10.3",
|
||||||
"stylelint-config-standard": "^34.0.0",
|
"stylelint-config-standard": "^34.0.0",
|
||||||
"stylelint-prettier": "^4.0.2"
|
"stylelint-prettier": "^4.0.2"
|
||||||
|
@@ -31,8 +31,7 @@ def init():
|
|||||||
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: <b><u>http://{host}:{port}/website/</u></b>"
|
||||||
f"<b><u>http://{host}:{port}/website/</u></b>"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
1131
poetry.lock
generated
1131
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "nonebot2"
|
name = "nonebot2"
|
||||||
version = "2.4.1"
|
version = "2.4.2"
|
||||||
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"
|
||||||
@@ -46,7 +46,7 @@ uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
|||||||
], optional = true }
|
], optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "^0.8.0"
|
ruff = "^0.9.0"
|
||||||
nonemoji = "^0.1.2"
|
nonemoji = "^0.1.2"
|
||||||
pre-commit = "^4.0.0"
|
pre-commit = "^4.0.0"
|
||||||
|
|
||||||
@@ -112,7 +112,6 @@ mark-parentheses = false
|
|||||||
[tool.ruff.lint.pyupgrade]
|
[tool.ruff.lint.pyupgrade]
|
||||||
keep-runtime-typing = true
|
keep-runtime-typing = true
|
||||||
|
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
pythonVersion = "3.9"
|
pythonVersion = "3.9"
|
||||||
pythonPlatform = "All"
|
pythonPlatform = "All"
|
||||||
|
@@ -33,8 +33,8 @@ async def gen_async():
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ClassDependency:
|
class ClassDependency:
|
||||||
x: int = Depends(gen_sync) # noqa: RUF009
|
x: int = Depends(gen_sync)
|
||||||
y: int = Depends(gen_async) # noqa: RUF009
|
y: int = Depends(gen_async)
|
||||||
|
|
||||||
|
|
||||||
class FooBot(Bot): ...
|
class FooBot(Bot): ...
|
||||||
|
@@ -11,7 +11,9 @@ from nonebot.compat import (
|
|||||||
Required,
|
Required,
|
||||||
TypeAdapter,
|
TypeAdapter,
|
||||||
custom_validation,
|
custom_validation,
|
||||||
|
field_validator,
|
||||||
model_dump,
|
model_dump,
|
||||||
|
model_validator,
|
||||||
type_validate_json,
|
type_validate_json,
|
||||||
type_validate_python,
|
type_validate_python,
|
||||||
)
|
)
|
||||||
@@ -30,6 +32,32 @@ def test_field_info():
|
|||||||
assert FieldInfo(test="test").extra["test"] == "test"
|
assert FieldInfo(test="test").extra["test"] == "test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_field_validator():
|
||||||
|
class TestModel(BaseModel):
|
||||||
|
foo: int
|
||||||
|
bar: str
|
||||||
|
|
||||||
|
@field_validator("foo")
|
||||||
|
@classmethod
|
||||||
|
def test_validator(cls, v: Any) -> Any:
|
||||||
|
if v > 0:
|
||||||
|
return v
|
||||||
|
raise ValueError("test must be greater than 0")
|
||||||
|
|
||||||
|
@field_validator("bar", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def test_validator_before(cls, v: Any) -> Any:
|
||||||
|
if not isinstance(v, str):
|
||||||
|
v = str(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
assert type_validate_python(TestModel, {"foo": 1, "bar": "test"}).foo == 1
|
||||||
|
assert type_validate_python(TestModel, {"foo": 1, "bar": 123}).bar == "123"
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
TestModel(foo=0, bar="test")
|
||||||
|
|
||||||
|
|
||||||
def test_type_adapter():
|
def test_type_adapter():
|
||||||
t = TypeAdapter(Annotated[int, FieldInfo(ge=1)])
|
t = TypeAdapter(Annotated[int, FieldInfo(ge=1)])
|
||||||
|
|
||||||
@@ -53,6 +81,35 @@ def test_model_dump():
|
|||||||
assert model_dump(TestModel(test1=1, test2=2), exclude={"test1"}) == {"test2": 2}
|
assert model_dump(TestModel(test1=1, test2=2), exclude={"test1"}) == {"test2": 2}
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_validator():
|
||||||
|
class TestModel(BaseModel):
|
||||||
|
foo: int
|
||||||
|
bar: str
|
||||||
|
|
||||||
|
@model_validator(mode="before")
|
||||||
|
@classmethod
|
||||||
|
def test_validator_before(cls, data: Any) -> Any:
|
||||||
|
if isinstance(data, dict):
|
||||||
|
if "foo" not in data:
|
||||||
|
data["foo"] = 1
|
||||||
|
return data
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
@classmethod
|
||||||
|
def test_validator_after(cls, data: Any) -> Any:
|
||||||
|
if isinstance(data, dict):
|
||||||
|
if data["bar"] == "test":
|
||||||
|
raise ValueError("bar should not be test")
|
||||||
|
elif data.bar == "test":
|
||||||
|
raise ValueError("bar should not be test")
|
||||||
|
return data
|
||||||
|
|
||||||
|
assert type_validate_python(TestModel, {"bar": "aaa"}).foo == 1
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
type_validate_python(TestModel, {"foo": 1, "bar": "test"})
|
||||||
|
|
||||||
|
|
||||||
def test_custom_validation():
|
def test_custom_validation():
|
||||||
called = []
|
called = []
|
||||||
|
|
||||||
|
@@ -251,9 +251,9 @@ async def test_http_client(driver: Driver, server_url: URL):
|
|||||||
cookies={"session": "test"},
|
cookies={"session": "test"},
|
||||||
content="test",
|
content="test",
|
||||||
)
|
)
|
||||||
assert (
|
assert request.url == request_raw_url.url, (
|
||||||
request.url == request_raw_url.url
|
"request.url should be equal to request_raw_url.url"
|
||||||
), "request.url should be equal to request_raw_url.url"
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.content
|
assert response.content
|
||||||
data = json.loads(response.content)
|
data = json.loads(response.content)
|
||||||
|
@@ -371,9 +371,31 @@ async def test_shell_command():
|
|||||||
assert state[SHELL_ARGV] == []
|
assert state[SHELL_ARGV] == []
|
||||||
assert SHELL_ARGS not in state
|
assert SHELL_ARGS not in state
|
||||||
|
|
||||||
|
test_lexical_error = shell_command(CMD)
|
||||||
|
dependent = next(iter(test_lexical_error.checkers))
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = Message("-a '1")
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||||
|
assert await dependent(event=event, state=state)
|
||||||
|
assert state[SHELL_ARGV] is None
|
||||||
|
|
||||||
parser = ArgumentParser("test")
|
parser = ArgumentParser("test")
|
||||||
parser.add_argument("-a", required=True)
|
parser.add_argument("-a", required=True)
|
||||||
|
|
||||||
|
test_lexical_error_with_parser = shell_command(CMD, parser=ArgumentParser("test"))
|
||||||
|
dependent = next(iter(test_lexical_error_with_parser.checkers))
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = Message("-a '1")
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||||
|
assert await dependent(event=event, state=state)
|
||||||
|
assert state[SHELL_ARGV] is None
|
||||||
|
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||||
|
assert state[SHELL_ARGS].status != 0
|
||||||
|
|
||||||
test_simple_parser = shell_command(CMD, parser=parser)
|
test_simple_parser = shell_command(CMD, parser=parser)
|
||||||
dependent = next(iter(test_simple_parser.checkers))
|
dependent = next(iter(test_simple_parser.checkers))
|
||||||
checker = dependent.call
|
checker = dependent.call
|
||||||
|
@@ -843,21 +843,39 @@ async def _(foo: str = CommandWhitespace()): ...
|
|||||||
|
|
||||||
### ShellCommandArgv
|
### ShellCommandArgv
|
||||||
|
|
||||||
获取 shell 命令解析前的参数列表,列表中可能包含文本字符串和富文本消息段(如:图片)。
|
获取 shell 命令解析前的参数列表,列表中可能包含文本字符串和富文本消息段(如:图片)。当词法解析出错的时候,返回值将为 `None`。通过重载机制即可处理两种不同的情况。
|
||||||
|
|
||||||
<Tabs groupId="python">
|
<Tabs groupId="python">
|
||||||
<TabItem value="3.10" label="Python 3.10+" default>
|
<TabItem value="3.10" label="Python 3.10+" default>
|
||||||
|
|
||||||
```python {4}
|
```python {4}
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from nonebot.params import ShellCommandArgs
|
from nonebot import on_shell_command
|
||||||
|
from nonebot.params import ShellCommandArgv
|
||||||
|
|
||||||
|
matcher = on_shell_command("cmd")
|
||||||
|
|
||||||
|
# 解析失败
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(foo: Annotated[None, ShellCommandArgv()]): ...
|
||||||
|
|
||||||
|
# 解析成功
|
||||||
|
@matcher.handle()
|
||||||
async def _(foo: Annotated[list[str | MessageSegment], ShellCommandArgv()]): ...
|
async def _(foo: Annotated[list[str | MessageSegment], ShellCommandArgv()]): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
```python {4}
|
```python {4}
|
||||||
from nonebot.params import ShellCommandArgs
|
from nonebot import on_shell_command
|
||||||
|
from nonebot.params import ShellCommandArgv
|
||||||
|
|
||||||
|
matcher = on_shell_command("cmd")
|
||||||
|
|
||||||
|
# 解析失败
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(foo: None = ShellCommandArgv()): ...
|
||||||
|
|
||||||
|
# 解析成功
|
||||||
|
@matcher.handle()
|
||||||
async def _(foo: list[str | MessageSegment] = ShellCommandArgv()): ...
|
async def _(foo: list[str | MessageSegment] = ShellCommandArgv()): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -866,15 +884,33 @@ async def _(foo: list[str | MessageSegment] = ShellCommandArgv()): ...
|
|||||||
|
|
||||||
```python {4}
|
```python {4}
|
||||||
from typing import Union, Annotated
|
from typing import Union, Annotated
|
||||||
from nonebot.params import ShellCommandArgs
|
from nonebot import on_shell_command
|
||||||
|
from nonebot.params import ShellCommandArgv
|
||||||
|
|
||||||
|
matcher = on_shell_command("cmd")
|
||||||
|
|
||||||
|
# 解析失败
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(foo: Annotated[None, ShellCommandArgv()]): ...
|
||||||
|
|
||||||
|
# 解析成功
|
||||||
|
@matcher.handle()
|
||||||
async def _(foo: Annotated[list[Union[str, MessageSegment]], ShellCommandArgv()]): ...
|
async def _(foo: Annotated[list[Union[str, MessageSegment]], ShellCommandArgv()]): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
```python {4}
|
```python {4}
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from nonebot.params import ShellCommandArgs
|
from nonebot import on_shell_command
|
||||||
|
from nonebot.params import ShellCommandArgv
|
||||||
|
|
||||||
|
matcher = on_shell_command("cmd")
|
||||||
|
|
||||||
|
# 解析失败
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(foo: None = ShellCommandArgv()): ...
|
||||||
|
|
||||||
|
# 解析成功
|
||||||
|
@matcher.handle()
|
||||||
async def _(foo: list[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
async def _(foo: list[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -886,7 +922,7 @@ async def _(foo: list[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
|||||||
获取 shell 命令解析后的参数 Namespace,支持 MessageSegment 富文本(如:图片)。
|
获取 shell 命令解析后的参数 Namespace,支持 MessageSegment 富文本(如:图片)。
|
||||||
|
|
||||||
:::tip 提示
|
:::tip 提示
|
||||||
如果参数解析成功,则为 parser 返回的 Namespace;如果参数解析失败,则为 [`ParserExit`](../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。通过重载机制即可处理两种不同的情况。
|
如果参数解析成功,则为 parser 返回的 Namespace;如果参数解析失败,则为 [`ParserExit`](../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。在前置词法解析失败时,返回值也为 [`ParserExit`](../api/exception.md#ParserExit) 异常。通过重载机制即可处理两种不同的情况。
|
||||||
|
|
||||||
由于 `ArgumentParser` 在解析到 `--help` 参数时也会抛出异常,这种情况下错误码为 `0` 且错误信息即为帮助信息。
|
由于 `ArgumentParser` 在解析到 `--help` 参数时也会抛出异常,这种情况下错误码为 `0` 且错误信息即为帮助信息。
|
||||||
:::
|
:::
|
||||||
|
@@ -1,32 +1,52 @@
|
|||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
description: Alconna 命令解析拓展
|
|
||||||
|
|
||||||
slug: /best-practice/alconna/
|
|
||||||
---
|
|
||||||
|
|
||||||
import Tabs from "@theme/Tabs";
|
import Tabs from "@theme/Tabs";
|
||||||
import TabItem from "@theme/TabItem";
|
import TabItem from "@theme/TabItem";
|
||||||
|
|
||||||
# Alconna 插件
|
# Alconna 插件
|
||||||
|
|
||||||
[`nonebot-plugin-alconna`](https://github.com/nonebot/plugin-alconna) 是一类提供了拓展响应规则的插件。
|
[`nonebot-plugin-alconna`](https://github.com/nonebot/plugin-alconna) 是一类极大地提升了 NoneBot 开发体验的插件。
|
||||||
该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
|
|
||||||
是一个简单、灵活、高效的命令参数解析器,并且不局限于解析命令式字符串。
|
|
||||||
|
|
||||||
该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。
|
该插件可分为三个部分:
|
||||||
|
|
||||||
该插件声明了一个 `Matcher` 的子类 `AlconnaMatcher`,并在 `AlconnaMatcher` 中添加了一些新的方法,例如:
|
- 增强的命令解析: 基于 [Alconna](https://github.com/ArcletProject/Alconna), 提供一类新的事件响应器辅助函数 `on_alconna`. 相比 `on_command`, `on_shell`, `on_regex` 等函数,`on_alconna` 提供了更强大的命令解析能力与诸多特性。
|
||||||
|
- 通用消息组件: 实现了跨平台接收、发送、撤回、编辑、表态消息的功能。
|
||||||
|
- `UniMessage` 通用消息模型,支持各适配器下的消息转换和导出,发送。
|
||||||
|
- `Text`, `Image`, `At` 等通用消息段模型,既与 `UniMessage` 配合使用,又能用于 `Alconna` 的命令解析。
|
||||||
|
- `message_recall`, `message_edit`, `message_reaction` 等功能函数。
|
||||||
|
- `Target` 通用消息目标模型,并通过该模型进行主动消息发送。
|
||||||
|
- `UniMsg`, `MsgId`, `MsgTarget`, `at_in`, `at_me` 等提供给 nonebot 使用的依赖注入和 `Rule`。
|
||||||
|
- 内置功能插件:基于上述部分实现的内置功能插件。
|
||||||
|
- `echo`: 通过 `on_alconna` 实现的 echo 插件,支持回显回复消息。
|
||||||
|
- `help`: 列出所有 `on_alconna` 事件响应器的帮助信息或其对应的插件信息。
|
||||||
|
- `lang`: 切换 `Alconna` 使用的语言
|
||||||
|
- `switch`: 禁用/启用某个指令
|
||||||
|
- `with`: 针对具有多个子命令的指令,通过 `with` 在当前会话中载入命令头以节省输入。
|
||||||
|
|
||||||
- `assign`:基于 `Alconna` 解析结果,执行满足目标路径的处理函数
|
以最新版本为例 (v0.57), 本插件已支持 NoneBot 生态中几乎所有的适配器, 包括:
|
||||||
- `dispatch`:类似 `CommandGroup`,对目标路径创建一个新的 `AlconnaMatcher`,并将解析结果分配给该 `AlconnaMatcher`
|
|
||||||
- `got_path`:类似 `got`,但是可以指定目标路径,并且能够验证解析结果是否可用
|
|
||||||
- ...
|
|
||||||
|
|
||||||
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
|
| 协议名称 | 路径 |
|
||||||
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
|
| ------------------------------------------------------------------- | ------------------------------------ |
|
||||||
|
| [OneBot 协议](https://onebot.dev/) | adapters.onebot11, adapters.onebot12 |
|
||||||
该插件同时通过提供 `UniMessage` (通用消息模型) 实现了**跨平台接收和发送消息**的功能。
|
| [Telegram](https://core.telegram.org/bots/api) | adapters.telegram |
|
||||||
|
| [飞书](https://open.feishu.cn/document/home/index) | adapters.feishu |
|
||||||
|
| [GitHub](https://docs.github.com/en/developers/apps) | adapters.github |
|
||||||
|
| [QQ bot](https://github.com/nonebot/adapter-qq) | adapters.qq |
|
||||||
|
| [钉钉](https://open.dingtalk.com/document/) | adapters.ding |
|
||||||
|
| [Console](https://github.com/nonebot/adapter-console) | adapters.console |
|
||||||
|
| [开黑啦](https://developer.kookapp.cn/) | adapters.kook |
|
||||||
|
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | adapters.mirai |
|
||||||
|
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | adapters.ntchat |
|
||||||
|
| [MineCraft](https://github.com/17TheWord/nonebot-adapter-minecraft) | adapters.minecraft |
|
||||||
|
| [Walle-Q](https://github.com/onebot-walle/nonebot_adapter_walleq) | adapters.onebot12 |
|
||||||
|
| [Discord](https://github.com/nonebot/adapter-discord) | adapters.discord |
|
||||||
|
| [Red 协议](https://github.com/nonebot/adapter-red) | adapters.red |
|
||||||
|
| [Satori](https://github.com/nonebot/adapter-satori) | adapters.satori |
|
||||||
|
| [Dodo IM](https://github.com/nonebot/adapter-dodo) | adapters.dodo |
|
||||||
|
| [Kritor](https://github.com/nonebot/adapter-kritor) | adapters.kritor |
|
||||||
|
| [Tailchat](https://github.com/eya46/nonebot-adapter-tailchat) | adapters.tailchat |
|
||||||
|
| [Mail](https://github.com/mobyw/nonebot-adapter-mail) | adapters.mail |
|
||||||
|
| [微信公众号](https://github.com/YangRucheng/nonebot-adapter-wxmp) | adapters.wxmp |
|
||||||
|
| [黑盒语音](https://github.com/lclbm/adapter-heybox) | adapters.heybox |
|
||||||
|
| [Gewechat](https://github.com/Shine-Light/nonebot-adapter-gewechat) | adapters.gewechat |
|
||||||
|
|
||||||
## 安装插件
|
## 安装插件
|
||||||
|
|
||||||
@@ -61,14 +81,14 @@ pdm add nonebot-plugin-alconna
|
|||||||
|
|
||||||
## 导入插件
|
## 导入插件
|
||||||
|
|
||||||
由于 `nonebot-plugin-alconna` 作为插件,因此需要在使用前对其进行**加载**并**导入**其中的 `on_alconna` 来使用命令拓展。使用 `require` 方法可轻松完成这一过程,可参考 [跨插件访问](../../advanced/requiring.md) 一节进行了解。
|
由于 `nonebot-plugin-alconna` 作为插件,因此需要在使用前对其进行**加载**。使用 `require` 方法可轻松完成这一过程,可参考 [跨插件访问](../../advanced/requiring.md) 一节进行了解。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
|
|
||||||
from nonebot_plugin_alconna import on_alconna
|
from nonebot_plugin_alconna import ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## 使用插件
|
## 使用插件
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Alconna 命令解析拓展",
|
"label": "命令解析拓展",
|
||||||
"position": 6
|
"position": 6
|
||||||
}
|
}
|
||||||
|
294
website/docs/best-practice/alconna/builtins.mdx
Normal file
294
website/docs/best-practice/alconna/builtins.mdx
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 7
|
||||||
|
description: 内置组件
|
||||||
|
---
|
||||||
|
|
||||||
|
import Messenger from "@site/src/components/Messenger";
|
||||||
|
|
||||||
|
# 内置组件
|
||||||
|
|
||||||
|
`nonebot_plugin_alconna` 插件提供了一系列内置组件以提升开发者和用户体验。
|
||||||
|
|
||||||
|
## 内置插件
|
||||||
|
|
||||||
|
类似于 Nonebot 本身提供的内置插件,`nonebot_plugin_alconna` 提供了多个内置插件。
|
||||||
|
|
||||||
|
### 加载
|
||||||
|
|
||||||
|
你可以用本插件的 `load_builtin_plugin(s)` 来加载它们:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import load_builtin_plugin, load_builtin_plugins
|
||||||
|
|
||||||
|
load_builtin_plugins("echo")
|
||||||
|
load_builtin_plugins("help", "with")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用
|
||||||
|
|
||||||
|
#### echo
|
||||||
|
|
||||||
|
`echo` 插件能将用户发送的消息原样返回。
|
||||||
|
|
||||||
|
<Messenger
|
||||||
|
msgs={[
|
||||||
|
{ position: "right", msg: "/echo hello world!" },
|
||||||
|
{ position: "left", msg: "hello world!" },
|
||||||
|
{ position: "right", msg: "/echo [图片]" },
|
||||||
|
{ position: "left", msg: "[图片]" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
#### help
|
||||||
|
|
||||||
|
`help` 插件能列出所有 Alconna 指令。同时还能查询某个指令对应的插件信息。
|
||||||
|
|
||||||
|
<Messenger
|
||||||
|
msgs={[
|
||||||
|
{ position: "right", msg: "/帮助" },
|
||||||
|
{
|
||||||
|
position: "left",
|
||||||
|
msg: "# 当前可用的命令有:\n 【0】/echo : echo 指令\n 【1】/help : 显示所有命令帮助\n# 输入'命令名 -h|--help' 查看特定命令的语法",
|
||||||
|
},
|
||||||
|
{ position: "right", msg: "/help --plugin-info echo" },
|
||||||
|
{
|
||||||
|
position: "left",
|
||||||
|
msg: "插件名称: echo\n插件标识: nonebot_plugin_alconna:echo\n插件模块: nonebot-plugin-alconna\n插件版本: 0.57.2\n插件路径: nonebot_plugin_alconna.builtins.plugins.echo",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
help 插件的帮助信息如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
/help <query: str = -1>
|
||||||
|
## 注释
|
||||||
|
query: 选择某条命令的id或者名称查看具体帮助
|
||||||
|
显示所有命令帮助
|
||||||
|
用法:
|
||||||
|
可以使用 --hide 参数来显示隐藏命令,使用 -P 参数来显示命令所属插件名称
|
||||||
|
|
||||||
|
可用的子命令有:
|
||||||
|
* 是否列出命令所属命名空间
|
||||||
|
-N│--namespace│命名空间 [target: str]
|
||||||
|
## 注释
|
||||||
|
target: 指定的命名空间
|
||||||
|
该子命令内可用的选项有:
|
||||||
|
* 列出所有命名空间
|
||||||
|
--list
|
||||||
|
可用的选项有:
|
||||||
|
* 查看指定页数的命令帮助
|
||||||
|
--page <index: int>
|
||||||
|
* 查看命令所属插件的信息
|
||||||
|
-P│插件信息│--plugin-info
|
||||||
|
* 是否列出隐藏命令
|
||||||
|
隐藏│-H│--hide
|
||||||
|
```
|
||||||
|
|
||||||
|
#### lang
|
||||||
|
|
||||||
|
`lang` 插件能切换 i18n 的语言设置。
|
||||||
|
|
||||||
|
<Messenger
|
||||||
|
msgs={[
|
||||||
|
{ position: "right", msg: "/lang list" },
|
||||||
|
{
|
||||||
|
position: "left",
|
||||||
|
msg: "支持的语言列表:\n * en-US\n * zh-CN",
|
||||||
|
},
|
||||||
|
{ position: "right", msg: "/lang switch en-US" },
|
||||||
|
{ position: "left", msg: "Switch to 'en-US' successfully." },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
lang 插件的帮助信息如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
/lang
|
||||||
|
i18n配置相关功能
|
||||||
|
|
||||||
|
可用的选项有:
|
||||||
|
* 查看支持的语言列表
|
||||||
|
list [name: str]
|
||||||
|
* 切换语言
|
||||||
|
switch [locale: str]
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `list` 选项可以查找某一插件下的语言支持情况 (例如 `/lang list nonebot_plugin_alconna`)。
|
||||||
|
|
||||||
|
#### switch
|
||||||
|
|
||||||
|
`switch` 插件能用来启用/禁用某个命令,其使用方法与 `help` 类似。
|
||||||
|
|
||||||
|
<Messenger
|
||||||
|
msgs={[
|
||||||
|
{ position: "right", msg: "/disable" },
|
||||||
|
{
|
||||||
|
position: "left",
|
||||||
|
msg: "【0】/echo : echo 指令\n【1】/help : 显示所有命令帮助\n【2】/lang : i18n配置相关功能",
|
||||||
|
},
|
||||||
|
{ position: "right", msg: "/disable 0" },
|
||||||
|
{ position: "left", msg: "已禁用 /echo" },
|
||||||
|
{ position: "right", msg: "/echo 1234" },
|
||||||
|
{ position: "right", msg: "/enable echo" },
|
||||||
|
{ position: "left", msg: "已启用 /echo" },
|
||||||
|
{ position: "right", msg: "/echo 1234" },
|
||||||
|
{ position: "left", msg: "1234" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
#### with
|
||||||
|
|
||||||
|
`with` 插件能在当前会话中设置一个局部命令前缀,以便于有多个子命令的指令使用。
|
||||||
|
|
||||||
|
<Messenger
|
||||||
|
msgs={[
|
||||||
|
{ position: "right", msg: "/with" },
|
||||||
|
{
|
||||||
|
position: "left",
|
||||||
|
msg: "当前群组未设置前缀",
|
||||||
|
},
|
||||||
|
{ position: "right", msg: "/with lang" },
|
||||||
|
{ position: "left", msg: "设置前缀成功" },
|
||||||
|
{ position: "right", msg: "list" },
|
||||||
|
{
|
||||||
|
position: "left",
|
||||||
|
msg: "支持的语言列表:\n * en-US\n * zh-CN",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
with 插件的帮助信息如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
.with [name: str]
|
||||||
|
with 指令
|
||||||
|
用法:
|
||||||
|
设置局部命令前缀
|
||||||
|
|
||||||
|
可用的选项有:
|
||||||
|
* 设置可能的生效时间
|
||||||
|
--expire│expire <time: datetime>
|
||||||
|
* 取消当前前缀
|
||||||
|
unset│--unset
|
||||||
|
|
||||||
|
快捷命令:
|
||||||
|
'[.]局部前缀' => [.]with
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置
|
||||||
|
|
||||||
|
内置插件也有其配置项,并且均以 `NBP_ALC` 开头。
|
||||||
|
|
||||||
|
- `nbp_alc_echo_tome`: 是否让 `echo` 插件的消息经过 `to_me` 处理
|
||||||
|
- `nbp_alc_page_size`: `help` 与 `switch` 插件的共同配置项,表示每页显示的命令数量
|
||||||
|
- `nbp_alc_help_text`: `help` 指令的指令名,默认为 "help"
|
||||||
|
- `nbp_alc_help_alias`: `help` 指令的别名,默认为 "帮助", "命令帮助"
|
||||||
|
- `nbp_alc_help_all_alias`: `help` 指令显示隐藏指令时的别名,默认为 "所有帮助", "所有命令帮助"
|
||||||
|
- `nbp_alc_switch_enable`: `switch` 插件的 `enable` 指令的指令名,默认为 "enable"
|
||||||
|
- `nbp_alc_switch_enable_alias`: `switch` 插件的 `enable` 指令的别名,默认为 "启用", "启用指令"
|
||||||
|
- `nbp_alc_switch_disable`: `switch` 插件的 `disable` 指令的指令名,默认为 "disable"
|
||||||
|
- `nbp_alc_switch_disable_alias`: `switch` 插件的 `disable` 指令的别名,默认为 "disable", "禁用", "禁用指令"
|
||||||
|
- `nbp_alc_with_text`: `with` 插件的指令名,默认为 "with"
|
||||||
|
- `nbp_alc_with_alias`: `with` 插件的别名,默认为 "局部前缀"
|
||||||
|
|
||||||
|
## 内置匹配拓展
|
||||||
|
|
||||||
|
目前插件提供了 5 个内置的 `Extension`,它们在 `nonebot_plugin_alconna.builtins.extensions` 下:
|
||||||
|
|
||||||
|
### ReplyRecordExtension
|
||||||
|
|
||||||
|
`ReplyRecordExtension` 可将消息事件中的回复暂存在 extension 中,使得解析用的消息不带回复信息,同时可以在后续的处理中获取回复信息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import MsgId, on_alconna
|
||||||
|
from nonebot_plugin_alconna.builtins.extensions import ReplyRecordExtension
|
||||||
|
|
||||||
|
matcher = on_alconna("...", extensions=[ReplyRecordExtension()])
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def handle(msg_id: MsgId, ext: ReplyRecordExtension):
|
||||||
|
if reply := ext.get_reply(msg_id):
|
||||||
|
...
|
||||||
|
else:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### ReplyMergeExtension
|
||||||
|
|
||||||
|
`ReplyMergeExtension` 可将消息事件中的回复指向的原消息合并到当前消息中作为一部分参数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import Match, on_alconna
|
||||||
|
from nonebot_plugin_alconna.builtins.extensions.reply import ReplyMergeExtension
|
||||||
|
|
||||||
|
matcher = on_alconna("...", extensions=[ReplyMergeExtension()])
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def handle(content: Match[str]):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
其构造时可传入两个参数:
|
||||||
|
|
||||||
|
- `add_left`: 否在当前消息的左侧合并回复消息,默认为 False
|
||||||
|
- `sep`: 合并时的分隔符,默认为空格
|
||||||
|
|
||||||
|
### DiscordSlashExtension
|
||||||
|
|
||||||
|
`DiscordSlashExtension` 可自动将 Alconna 对象翻译成 Discord 的 slash 指令并注册,且将收到的指令交互事件转为指令供命令解析:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import Match, on_alconna
|
||||||
|
from nonebot_plugin_alconna.builtins.extensions.discord import DiscordSlashExtension
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna(
|
||||||
|
["/"],
|
||||||
|
"permission",
|
||||||
|
Subcommand("add", Args["plugin", str]["priority?", int]),
|
||||||
|
Option("remove", Args["plugin", str]["time?", int]),
|
||||||
|
meta=CommandMeta(description="权限管理"),
|
||||||
|
)
|
||||||
|
|
||||||
|
matcher = on_alconna(alc, extensions=[DiscordSlashExtension()])
|
||||||
|
|
||||||
|
@matcher.assign("add")
|
||||||
|
async def add(plugin: Match[str], priority: Match[int], ext: DiscordSlashExtension):
|
||||||
|
await ext.send_followup_msg(f"added {plugin.result} with {priority.result if priority.available else 0}")
|
||||||
|
|
||||||
|
@matcher.assign("remove")
|
||||||
|
async def remove(plugin: Match[str], time: Match[int]):
|
||||||
|
await matcher.finish(f"removed {plugin.result} with {time.result if time.available else -1}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### MarkdownOutputExtension
|
||||||
|
|
||||||
|
`MarkdownOutputExtension` 可将 Alconna 的自动输出转换为 Markdown 格式
|
||||||
|
|
||||||
|
其构造时可传入两个参数:
|
||||||
|
|
||||||
|
- `escape_dot`: 是否转义句中的点号(用来避免被识别为 url)
|
||||||
|
- `text_to_image` 将文本转换为图片的函数,可不传入。一般用来设置渲染 markdown 为图片的函数
|
||||||
|
|
||||||
|
### TelegramSlashExtension
|
||||||
|
|
||||||
|
`TelegramSlashExtension` 可将 Alconna 的命令注册在 Telegram 上以获得提示,类似于 `DiscordSlashExtension`。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import on_alconna
|
||||||
|
from nonebot.adapters.telegram.model import BotCommandScopeChat
|
||||||
|
from nonebot_plugin_alconna.builtins.extensions.telegram import TelegramSlashExtension
|
||||||
|
|
||||||
|
TelegramSlashExtension.set_scope(BotCommandScopeChat())
|
||||||
|
|
||||||
|
matcher = on_alconna("...", extensions=[TelegramSlashExtension()])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内置自定义消息段
|
||||||
|
|
||||||
|
目前插件提供了 3 个内置的 `Segment`,它们在 `nonebot_plugin_alconna.builtins.segments` 下:
|
||||||
|
|
||||||
|
- `Markdown`: 可以传入 **markdown模板** 的元素
|
||||||
|
- `MarketFace`: 特指 QQ 的商城表情
|
||||||
|
- `MusicShare`: 特指 QQ 的音乐分享卡片
|
@@ -7,7 +7,7 @@ description: Alconna 基本介绍
|
|||||||
|
|
||||||
[`Alconna`](https://github.com/ArcletProject/Alconna) 隶属于 `ArcletProject`,是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
[`Alconna`](https://github.com/ArcletProject/Alconna) 隶属于 `ArcletProject`,是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||||
|
|
||||||
我们通过一个例子来讲解 **Alconna** 的核心 —— `Args`, `Subcommand`, `Option`:
|
我们先通过一个例子来讲解 **Alconna** 的核心 —— `Args`, `Subcommand`, `Option`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from arclet.alconna import Alconna, Args, Subcommand, Option
|
from arclet.alconna import Alconna, Args, Subcommand, Option
|
||||||
@@ -38,14 +38,16 @@ print(res.all_matched_args)
|
|||||||
|
|
||||||
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 !help 中的 ! 与 help。
|
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 !help 中的 ! 与 help。
|
||||||
|
|
||||||
|
命令构造时, `Alconna([prefix], command)` 与 `Alconna(command, [prefix])` 是等价的。
|
||||||
|
|
||||||
| 前缀 | 命令名 | 匹配内容 | 说明 |
|
| 前缀 | 命令名 | 匹配内容 | 说明 |
|
||||||
| :--------------------------: | :--------: | :---------------------------------------------------------: | :--------------: |
|
| :--------------------------: | :--------: | :---------------------------------------------------------: | :--------------: |
|
||||||
| - | "foo" | `"foo"` | 无前缀的纯文字头 |
|
| 不传入 | "foo" | `"foo"` | 无前缀的纯文字头 |
|
||||||
| - | 123 | `123` | 无前缀的元素头 |
|
| 不传入 | 123 | `123` | 无前缀的元素头 |
|
||||||
| - | "re:\d{2}" | `"32"` | 无前缀的正则头 |
|
| 不传入 | "re:\d{2}" | `"32"` | 无前缀的正则头 |
|
||||||
| - | int | `123` 或 `"456"` | 无前缀的类型头 |
|
| 不传入 | int | `123` 或 `"456"` | 无前缀的类型头 |
|
||||||
| [int, bool] | - | `True` 或 `123` | 无名的元素类头 |
|
| [int, bool] | 不传入 | `True` 或 `123` | 无名的元素类头 |
|
||||||
| ["foo", "bar"] | - | `"foo"` 或 `"bar"` | 无名的纯文字头 |
|
| ["foo", "bar"] | 不传入 | `"foo"` 或 `"bar"` | 无名的纯文字头 |
|
||||||
| ["foo", "bar"] | "baz" | `"foobaz"` 或 `"barbaz"` | 纯文字头 |
|
| ["foo", "bar"] | "baz" | `"foobaz"` 或 `"barbaz"` | 纯文字头 |
|
||||||
| [int, bool] | "foo" | `[123, "foo"]` 或 `[False, "foo"]` | 类型头 |
|
| [int, bool] | "foo" | `[123, "foo"]` 或 `[False, "foo"]` | 类型头 |
|
||||||
| [123, 4567] | "foo" | `[123, "foo"]` 或 `[4567, "foo"]` | 元素头 |
|
| [123, 4567] | "foo" | `[123, "foo"]` 或 `[4567, "foo"]` | 元素头 |
|
||||||
@@ -64,9 +66,6 @@ print(res.all_matched_args)
|
|||||||
除了通过传入 `re:xxx` 来使用正则表达式外,Alconna 还提供了一种更加简洁的方式来使用正则表达式,称为 Bracket Header:
|
除了通过传入 `re:xxx` 来使用正则表达式外,Alconna 还提供了一种更加简洁的方式来使用正则表达式,称为 Bracket Header:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from alconna import Alconna
|
|
||||||
|
|
||||||
|
|
||||||
alc = Alconna(".rd{roll:int}")
|
alc = Alconna(".rd{roll:int}")
|
||||||
assert alc.parse(".rd123").header["roll"] == 123
|
assert alc.parse(".rd123").header["roll"] == 123
|
||||||
```
|
```
|
||||||
@@ -206,6 +205,18 @@ args = Args["foo", BasePattern("@\d+")]
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
#### AllParam
|
||||||
|
|
||||||
|
`AllParam` 是一个特殊的标注,用于告知解析器该参数接收命令中在此位置之后的所有参数并**结束解析**,可以认为是**泛匹配参数**。
|
||||||
|
|
||||||
|
`AllParam` 可直接使用 (`Args["xxx", AllParam]`), 也可以传入指定的接收类型 (`Args["xxx", AllParam(str)]`)。
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
在 `nonebot_plugin_alconna` 下,`AllParam` 的返回值为 [`UniMessage`](./uniseg/message.mdx)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
### default
|
### default
|
||||||
|
|
||||||
`default` 传入的是该参数的默认值或者 `Field`,以携带对于该参数的更多信息。
|
`default` 传入的是该参数的默认值或者 `Field`,以携带对于该参数的更多信息。
|
||||||
@@ -271,7 +282,7 @@ opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
|||||||
- `append`,`append_value`
|
- `append`,`append_value`
|
||||||
- `count`
|
- `count`
|
||||||
|
|
||||||
## 解析结果(Arparma)
|
## 解析结果
|
||||||
|
|
||||||
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果
|
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果
|
||||||
|
|
||||||
@@ -292,18 +303,31 @@ opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
|||||||
- other_args: 除主参数外的其他解析结果
|
- other_args: 除主参数外的其他解析结果
|
||||||
- all_matched_args: 所有 Args 的解析结果
|
- all_matched_args: 所有 Args 的解析结果
|
||||||
|
|
||||||
|
### 路径查询
|
||||||
|
|
||||||
`Arparma` 同时提供了便捷的查询方法 `query[type]()`,会根据传入的 `path` 查找参数并返回
|
`Arparma` 同时提供了便捷的查询方法 `query[type]()`,会根据传入的 `path` 查找参数并返回
|
||||||
|
|
||||||
`path` 支持如下:
|
`path` 支持如下:
|
||||||
|
|
||||||
- `main_args`, `options`, ...: 返回对应的属性
|
- `main_args`, `options`, ...: 返回对应的属性
|
||||||
- `args`: 返回 all_matched_args
|
- `args`: 返回 all_matched_args
|
||||||
- `main_args.xxx`, `options.xxx`, ...: 返回字典中 `xxx`键对应的值
|
- `args.<key>`: 返回 all_matched_args 中 `key` 键对应的值
|
||||||
- `args.xxx`: 返回 all_matched_args 中 `xxx`键对应的值
|
- `main_args.<key>`: 返回主命令的解析参数字典中 `key` 键对应的值
|
||||||
- `options.foo`, `foo`: 返回选项 `foo` 的解析结果 (OptionResult)
|
- `<node>`: 返回选项/子命令 `node` 的解析结果 (OptionResult | SubcommandResult)
|
||||||
- `options.foo.value`, `foo.value`: 返回选项 `foo` 的解析值
|
- `<node>.value`: 返回选项/子命令 `node` 的解析值
|
||||||
- `options.foo.args`, `foo.args`: 返回选项 `foo` 的解析参数字典
|
- `<node>.args`: 返回选项/子命令 `node` 的解析参数字典
|
||||||
- `options.foo.args.bar`, `foo.bar`: 返回选项 `foo` 的参数字典中 `bar` 键对应的值 ...
|
- `<node>.<key>`, `<node>.args.<key>`: 返回选项/子命令 `node` 的参数字典中 `key` 键对应的值
|
||||||
|
|
||||||
|
以及:
|
||||||
|
|
||||||
|
- `options.<opt>`: 返回选项 `opt` 的解析结果 (OptionResult)
|
||||||
|
- `options.<opt>.value`: 返回选项 `opt` 的解析值
|
||||||
|
- `options.<opt>.args`: 返回选项 `opt` 的解析参数字典
|
||||||
|
- `options.<opt>.<key>`, `options.<node>.args.<key>`: 返回选项 `opt` 的参数字典中 `key` 键对应的值
|
||||||
|
- `subcommands.<subcmd>`: 返回子命令 `subcmd` 的解析结果 (SubcommandResult)
|
||||||
|
- `subcommands.<subcmd>.value`: 返回子命令 `subcmd` 的解析值
|
||||||
|
- `subcommands.<subcmd>.args`: 返回子命令 `subcmd` 的解析参数字典
|
||||||
|
- `subcommands.<subcmd>.<key>`, `subcommands.<node>.args.<key>`: 返回子命令 `subcmd` 的参数字典中 `key` 键对应的值
|
||||||
|
|
||||||
## 元数据(CommandMeta)
|
## 元数据(CommandMeta)
|
||||||
|
|
||||||
|
@@ -7,8 +7,8 @@ description: 配置项
|
|||||||
|
|
||||||
## alconna_auto_send_output
|
## alconna_auto_send_output
|
||||||
|
|
||||||
- **类型**: `bool`
|
- **类型**: `bool | None`
|
||||||
- **默认值**: `False`
|
- **默认值**: `None`
|
||||||
|
|
||||||
是否全局启用输出信息自动发送,不启用则会在触发特殊内置选项后仍然将解析结果传递至响应器。
|
是否全局启用输出信息自动发送,不启用则会在触发特殊内置选项后仍然将解析结果传递至响应器。
|
||||||
|
|
||||||
@@ -19,12 +19,12 @@ description: 配置项
|
|||||||
|
|
||||||
是否读取 Nonebot 的配置项 `COMMAND_START` 来作为全局的 Alconna 命令前缀
|
是否读取 Nonebot 的配置项 `COMMAND_START` 来作为全局的 Alconna 命令前缀
|
||||||
|
|
||||||
## alconna_auto_completion
|
## alconna_global_completion
|
||||||
|
|
||||||
- **类型**: `bool`
|
- **类型**: [`CompConfig | None`](./matcher.mdx#补全会话)
|
||||||
- **默认值**: `False`
|
- **默认值**: `None`
|
||||||
|
|
||||||
是否全局启用命令自动补全,启用后会在参数缺失或触发 `--comp` 选项时自自动启用交互式补全。
|
全局的补全会话配置 (不代表全局启用补全会话)。
|
||||||
|
|
||||||
## alconna_use_origin
|
## alconna_use_origin
|
||||||
|
|
||||||
@@ -42,10 +42,13 @@ description: 配置项
|
|||||||
|
|
||||||
## alconna_global_extensions
|
## alconna_global_extensions
|
||||||
|
|
||||||
- **类型**: `List[str]`
|
- **类型**: `list[str]`
|
||||||
- **默认值**: `[]`
|
- **默认值**: `[]`
|
||||||
|
|
||||||
全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`。
|
全局加载的扩展,其读取路径以 . 分隔,如 `foo.bar.baz:DemoExtension`。
|
||||||
|
|
||||||
|
对于内置扩展,路径为 `nonebot_plugin_alconna.builtins.extensions` 下的模块名,如 `ReplyMergeExtension`,可以使用 `@` 来缩写路径,
|
||||||
|
如 `@reply:ReplyMergeExtension`。
|
||||||
|
|
||||||
## alconna_context_style
|
## alconna_context_style
|
||||||
|
|
||||||
@@ -73,4 +76,30 @@ description: 配置项
|
|||||||
- **类型**: `bool`
|
- **类型**: `bool`
|
||||||
- **默认值**: `False`
|
- **默认值**: `False`
|
||||||
|
|
||||||
是否启动时拉取一次发送对象列表。
|
是否启动时拉取一次[发送对象](./uniseg/utils.mdx#发送对象)列表。
|
||||||
|
|
||||||
|
## alconna_builtin_plugins
|
||||||
|
|
||||||
|
- **类型**: `set[str]`
|
||||||
|
- **默认值**: `set()`
|
||||||
|
|
||||||
|
需要加载的内置插件列表。
|
||||||
|
|
||||||
|
## alconna_conflict_resolver
|
||||||
|
|
||||||
|
- **类型**: `Literal["raise", "default", "ignore", "replace"]`
|
||||||
|
- **默认值**: `"default"`
|
||||||
|
|
||||||
|
命令冲突解决策略,决定当不同插件之间或者同一插件之间存在两个以上相同的命令时的处理方式:
|
||||||
|
|
||||||
|
- `default`: 默认处理方式,保留两个命令
|
||||||
|
- `raise`: 抛出异常
|
||||||
|
- `ignore`: 忽略较新的命令
|
||||||
|
- `replace`: 替换较旧的命令
|
||||||
|
|
||||||
|
## alconna_response_self
|
||||||
|
|
||||||
|
- **类型**: `bool`
|
||||||
|
- **默认值**: `False`
|
||||||
|
|
||||||
|
是否让响应器处理由 bot 自身发送的消息。
|
||||||
|
@@ -4,136 +4,132 @@ description: 响应规则的使用
|
|||||||
---
|
---
|
||||||
|
|
||||||
import Messenger from "@site/src/components/Messenger";
|
import Messenger from "@site/src/components/Messenger";
|
||||||
|
import Tabs from "@theme/Tabs";
|
||||||
|
import TabItem from "@theme/TabItem";
|
||||||
|
|
||||||
# Alconna 插件
|
# `on_alconna` 响应器
|
||||||
|
|
||||||
展示:
|
`nonebot_plugin_alconna` 插件本体的大部分功能都围绕着 `on_alconna` 响应器展开。
|
||||||
|
|
||||||
|
该响应器类似于 `on_command`,基于 `Alconna` 解析器来解析命令。
|
||||||
|
|
||||||
|
以下是一个简单的 `on_alconna` 响应器的例子:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna import At, Image, on_alconna
|
from nonebot_plugin_alconna import At, Image, Match, on_alconna
|
||||||
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
|
from arclet.alconna import Args, Option, Alconna, MultiVar, Subcommand
|
||||||
|
|
||||||
|
|
||||||
alc = Alconna(
|
alc = Alconna(
|
||||||
["/", "!"],
|
|
||||||
"role-group",
|
"role-group",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
"add",
|
"add|添加",
|
||||||
Args["name", str],
|
Args["name", str],
|
||||||
Option("member", Args["target", MultiVar(At)]),
|
Option("member", Args["target", MultiVar(At)]),
|
||||||
|
dest="add",
|
||||||
|
compact=True,
|
||||||
),
|
),
|
||||||
Option("list"),
|
Option("list"),
|
||||||
Option("icon", Args["icon", Image])
|
Option("icon", Args["icon", Image])
|
||||||
)
|
)
|
||||||
rg = on_alconna(alc, auto_send_output=True)
|
rg = on_alconna(alc, use_command_start=True, aliases={"角色组"})
|
||||||
|
|
||||||
|
|
||||||
@rg.handle()
|
@rg.assign("list")
|
||||||
async def _(result: Arparma):
|
async def list_role_group():
|
||||||
if result.find("list"):
|
img: bytes = await gen_role_group_list_image()
|
||||||
img: bytes = await gen_role_group_list_image()
|
await rg.finish(Image(raw=img))
|
||||||
await rg.finish(Image(raw=img))
|
|
||||||
if result.find("add"):
|
@rg.assign("add")
|
||||||
group = await create_role_group(result.query[str]("add.name"))
|
async def _(name: str, target: Match[tuple[At, ...]]):
|
||||||
if result.find("add.member"):
|
group = await create_role_group(name)
|
||||||
ats = result.query[tuple[At, ...]]("add.member.target")
|
if target.available:
|
||||||
group.extend(member.target for member in ats)
|
ats: tuple[At, ...] = target.result
|
||||||
await rg.finish("添加成功")
|
group.extend(member.target for member in ats)
|
||||||
|
await rg.finish("添加成功")
|
||||||
```
|
```
|
||||||
|
|
||||||
## 响应器使用
|
<Messenger
|
||||||
|
msgs={[
|
||||||
|
{ position: "right", msg: "/role-group list" },
|
||||||
|
{
|
||||||
|
position: "left",
|
||||||
|
msg: "[图片]",
|
||||||
|
},
|
||||||
|
{ position: "right", msg: "/角色组 添加foo @bar @baz" },
|
||||||
|
{ position: "left", msg: "添加成功" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
本插件基于 **Alconna**,为 **Nonebot** 提供了一类新的事件响应器辅助函数 `on_alconna`:
|
## 声明
|
||||||
|
|
||||||
|
`on_alconna` 的参数如下:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def on_alconna(
|
def on_alconna(
|
||||||
command: Alconna | str,
|
command: Alconna | str,
|
||||||
|
rule: Rule | T_RuleChecker | None = None,
|
||||||
skip_for_unmatch: bool = True,
|
skip_for_unmatch: bool = True,
|
||||||
auto_send_output: bool = False,
|
auto_send_output: bool | None = None,
|
||||||
aliases: set[str | tuple[str, ...]] | None = None,
|
aliases: set[str] | tuple[str, ...] | None = None,
|
||||||
comp_config: CompConfig | None = None,
|
comp_config: CompConfig | None = None,
|
||||||
extensions: list[type[Extension] | Extension] | None = None,
|
extensions: list[type[Extension] | Extension] | None = None,
|
||||||
exclude_ext: list[type[Extension] | str] | None = None,
|
exclude_ext: list[type[Extension] | str] | None = None,
|
||||||
use_origin: bool = False,
|
use_origin: bool | None = None,
|
||||||
use_cmd_start: bool = False,
|
use_cmd_start: bool | None = None,
|
||||||
use_cmd_sep: bool = False,
|
use_cmd_sep: bool | None = None,
|
||||||
**kwargs,
|
response_self: bool | None = None,
|
||||||
...,
|
**kwargs: Any,
|
||||||
):
|
) -> type[AlconnaMatcher]:
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
- `command`: Alconna 命令或字符串,字符串将通过 `AlconnaFormat` 转换为 Alconna 命令
|
- `command`: Alconna 命令或字符串,字符串将通过 `AlconnaFormat` 转换为 Alconna 命令
|
||||||
- `skip_for_unmatch`: 是否在命令不匹配时跳过该响应
|
- `rule`: 事件响应规则, 详见 [响应器规则](../../advanced/matcher.md#事件响应规则)
|
||||||
- `auto_send_output`: 是否自动发送输出信息并跳过响应
|
- `skip_for_unmatch`: 是否在命令不匹配时跳过该响应, 默认为 `True`
|
||||||
|
- `auto_send_output`: 是否自动发送输出信息并跳过该响应。
|
||||||
|
- `True`:自动发送输出信息并跳过该响应
|
||||||
|
- `False`:不自动发送输出信息,而是传递进行处理
|
||||||
|
- `None`:跟随全局配置项 `alconna_auto_send_output`,默认值为 `True`
|
||||||
- `aliases`: 命令别名, 作用类似于 `on_command` 中的 aliases
|
- `aliases`: 命令别名, 作用类似于 `on_command` 中的 aliases
|
||||||
- `comp_config`: 补全会话配置, 不传入则不启用补全会话
|
- `comp_config`: 补全会话配置, 不传入则不启用补全会话
|
||||||
- `extensions`: 需要加载的匹配扩展, 可以是扩展类或扩展实例
|
- `extensions`: 需要加载的匹配扩展, 可以是扩展类或扩展实例
|
||||||
- `exclude_ext`: 需要排除的匹配扩展, 可以是扩展类或扩展的id
|
- `exclude_ext`: 需要排除的匹配扩展, 可以是扩展类或扩展的id
|
||||||
- `use_origin`: 是否使用未经 to_me 等处理过的消息
|
- `use_origin`: 是否使用未经 to_me 等处理过的消息。`None` 时跟随全局配置项 `alconna_use_origin`,默认值为 `False`
|
||||||
- `use_cmd_start`: 是否使用 COMMAND_START 作为命令前缀
|
- `use_cmd_start`: 是否使用 COMMAND_START 作为命令前缀。`None` 时跟随全局配置项 `alconna_use_command_start`,默认值为 `False`
|
||||||
- `use_cmd_sep`: 是否使用 COMMAND_SEP 作为命令分隔符
|
- `use_cmd_sep`: 是否使用 COMMAND_SEP 作为命令分隔符。`None` 时跟随全局配置项 `alconna_use_command_sep`,默认值为 `False`
|
||||||
|
- `response_self`: 是否响应自身消息。`None` 时跟随全局配置项 `alconna_response_self`,默认值为 `False`
|
||||||
|
|
||||||
`on_alconna` 返回的是 `Matcher` 的子类 `AlconnaMatcher` ,其拓展了如下方法:
|
`on_alconna` 返回的是 `Matcher` 的子类 `AlconnaMatcher` ,其拓展了如下方法:
|
||||||
|
|
||||||
- `.assign(path, value, or_not)`: 用于对包含多个选项/子命令的命令的分派处理(具体请看[条件控制](./matcher.mdx#条件控制))
|
- `.assign(path, value, or_not)`: 用于对包含多个选项/子命令的命令的分派处理
|
||||||
- `.got_path(path, prompt, middleware)`: 在 `got` 方法的基础上,会以 path 对应的参数为准,读取传入 message 的最后一个消息段并验证转换
|
|
||||||
- `.set_path_arg(key, value)`, `.get_path_arg(key)`: 类似 `set_arg` 和 `got_arg`,为 `got_path` 的特化版本
|
|
||||||
- `.reject_path(path[, prompt, fallback])`: 类似于 `reject_arg`,对应 `got_path`
|
|
||||||
- `.dispatch`: 同样的分派处理,但是是类似 `CommandGroup` 一样返回新的 `AlconnaMatcher`
|
- `.dispatch`: 同样的分派处理,但是是类似 `CommandGroup` 一样返回新的 `AlconnaMatcher`
|
||||||
|
- `.got_path(path, prompt, middleware)`: 在 `got` 方法的基础上,会以 path 对应的参数为准,读取传入 message 的最后一个消息段并验证转换
|
||||||
- `.got`, `send`, `reject`, ... : 拓展了 prompt 类型,即支持使用 `UniMessage` 作为 prompt
|
- `.got`, `send`, `reject`, ... : 拓展了 prompt 类型,即支持使用 `UniMessage` 作为 prompt
|
||||||
|
- ...
|
||||||
|
|
||||||
实例:
|
除了标准的创建方式,本插件也提供了 `funcommand` 和 `Command` 两种快捷方式来创建 `AlconnaMatcher`, 详见 [快捷方式](./shortcut.md)。
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import require
|
|
||||||
require("nonebot_plugin_alconna")
|
|
||||||
|
|
||||||
from arclet.alconna import Alconna, Option, Args
|
|
||||||
from nonebot_plugin_alconna import on_alconna, Match, UniMessage
|
|
||||||
|
|
||||||
|
|
||||||
login = on_alconna(Alconna(["/"], "login", Args["password?", str], Option("-r|--recall"))) # 这里["/"]指命令前缀必须是/
|
|
||||||
|
|
||||||
# /login -r 触发
|
|
||||||
@login.assign("recall")
|
|
||||||
async def login_exit():
|
|
||||||
await login.finish("已退出")
|
|
||||||
|
|
||||||
# /login xxx 触发
|
|
||||||
@login.assign("password")
|
|
||||||
async def login_handle(pw: Match[str]):
|
|
||||||
if pw.available:
|
|
||||||
login.set_path_arg("password", pw.result)
|
|
||||||
|
|
||||||
# /login 触发
|
|
||||||
@login.got_path("password", prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请输入密码"))
|
|
||||||
async def login_got(password: str):
|
|
||||||
assert password
|
|
||||||
await login.send("登录成功")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 依赖注入
|
## 依赖注入
|
||||||
|
|
||||||
本插件提供了一系列依赖注入函数,便于在响应函数中获取解析结果:
|
`AlconnaMatcher` 的特性之一是拓展了依赖注入的功能。
|
||||||
|
|
||||||
- `AlconnaResult`: `CommandResult` 类型的依赖注入函数
|
### 注入模型
|
||||||
- `AlconnaMatches`: `Arparma` 类型的依赖注入函数
|
|
||||||
- `AlconnaDuplication`: `Duplication` 类型的依赖注入函数
|
|
||||||
- `AlconnaMatch`: `Match` 类型的依赖注入函数
|
|
||||||
- `AlconnaQuery`: `Query` 类型的依赖注入函数
|
|
||||||
|
|
||||||
同时,基于 [`Annotated` 支持](https://github.com/nonebot/nonebot2/pull/1832),添加了两类注解:
|
插件提供了几种用来处理解析结果的模型:
|
||||||
|
|
||||||
- `AlcMatches`:同 `AlconnaMatches`
|
- `CommandResult`: 用于快捷访问命令解析结果
|
||||||
- `AlcResult`:同 `AlconnaResult`
|
- `result (Arparma)`: 解析结果
|
||||||
|
- `source (Alconna)`: 源命令
|
||||||
可以看到,本插件提供了几类额外的模型:
|
- `matched (bool)`: 是否匹配
|
||||||
|
- `context (dict)`: 命令的上下文
|
||||||
- `CommandResult`: 解析结果,包括了源命令 `source: Alconna` ,解析结果 `result: Arparma`,以及可能的输出信息 `output: str | None` 字段
|
- `output (str | None)`: 命令的输出
|
||||||
- `Match`: 匹配项,表示参数是否存在于 `all_matched_args` 内,可用 `Match.available` 判断是否匹配,`Match.result` 获取匹配的值
|
- `Match`: 匹配项,表示参数是否存在于 `Arparma.all_matched_args` 内,可用 `Match.available` 判断是否匹配,`Match.result` 获取匹配的值
|
||||||
|
- `Match` 只能查找到 `Arparma.all_matched_args` 中的参数。对于特定选项/子命令的参数,需要使用 `Query` 来查询
|
||||||
- `Query`: 查询项,表示参数是否可由 `Arparma.query` 查询并获得结果,可用 `Query.available` 判断是否查询成功,`Query.result` 获取查询结果
|
- `Query`: 查询项,表示参数是否可由 `Arparma.query` 查询并获得结果,可用 `Query.available` 判断是否查询成功,`Query.result` 获取查询结果
|
||||||
|
- `Query` 除了查询参数,也可以查询某个选项/子命令是否存在
|
||||||
|
|
||||||
**Alconna** 默认依赖注入的目标参数皆不需要使用依赖注入函数, 该效果对于 `AlconnaMatcher.got_path` 下的 Arg 同样有效:
|
### 编写
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def handle(
|
async def handle(
|
||||||
@@ -141,13 +137,33 @@ async def handle(
|
|||||||
arp: Arparma,
|
arp: Arparma,
|
||||||
dup: Duplication,
|
dup: Duplication,
|
||||||
source: Alconna,
|
source: Alconna,
|
||||||
abc: str, # 类似 Match, 但是若匹配结果不存在对应字段则跳过该 handler
|
ext: Extension,
|
||||||
|
exts: SelectedExtensions,
|
||||||
|
abc: str,
|
||||||
foo: Match[str],
|
foo: Match[str],
|
||||||
bar: Query[int] = Query("ttt.bar", 0) # Query 仍然需要一个默认值来传递 path 参数
|
bar: Query[int] = Query("ttt.bar", 0)
|
||||||
):
|
):
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`AlconnaMatcher` 的依赖注入拓展支持以下情况:
|
||||||
|
|
||||||
|
- `xxx: CommandResult`
|
||||||
|
- `xxx: Arparma`:命令的[解析结果](./command.md#解析结果)
|
||||||
|
- `xxx: Duplication`:命令的解析结果的 [`Duplication`](./command.md#Duplication)
|
||||||
|
- `xxx: Alconna`:命令的源命令
|
||||||
|
- `<key>: Match[<type>]`:上述的匹配项,使用 `key` 作为查询路径
|
||||||
|
- `xxx: Query[<type>] = Query(<path>, default)`:上述的查询项,必需声明默认值以设置查询路径 `path`
|
||||||
|
- 当用来查询选项/子命令是否存在时,可不写 `Query[<type>]`
|
||||||
|
- `xxx: Extension`:当前 `AlconnaMatcher` 使用的指定类型的匹配扩展
|
||||||
|
- `xxx: SelectedExtensions`:当前 `AlconnaMatcher` 使用的所有可用的匹配扩展
|
||||||
|
- `<key>: <type>`: 其他情况
|
||||||
|
- 当 `key` 的名称是 "ctx" 或 "context" 并且类型为 `dict` 时,会注入命令的上下文
|
||||||
|
- 当 `key` 存在于命令的上下文中时,会注入对应的值
|
||||||
|
- 当 `key` 存在于 `Arparma` 的 `all_matched_args` 中时,会注入对应的值, 类似于 `Match` 的用法,但当该值不存在时将跳过响应器。
|
||||||
|
- 当 `key` 属于 `got_path` 的参数时,会注入对应的值
|
||||||
|
- 当 `key` 被某个 `Extension.before_catch` 确认为需要注入的参数时,会调用 `Extension.catch` 来注入对应的值
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
|
|
||||||
如果你更喜欢 Depends 式的依赖注入,`nonebot_plugin_alconna` 同时提供了一系列的依赖注入函数,他们包括:
|
如果你更喜欢 Depends 式的依赖注入,`nonebot_plugin_alconna` 同时提供了一系列的依赖注入函数,他们包括:
|
||||||
@@ -162,19 +178,13 @@ async def handle(
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
实例:
|
示例:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot import require
|
from nonebot import require
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
|
|
||||||
from nonebot_plugin_alconna import (
|
from nonebot_plugin_alconna import AlconnaQuery, AlcResult, Match, Query, on_alconna
|
||||||
on_alconna,
|
|
||||||
Match,
|
|
||||||
Query,
|
|
||||||
AlconnaMatch,
|
|
||||||
AlcResult
|
|
||||||
)
|
|
||||||
from arclet.alconna import Alconna, Args, Option, Arparma
|
from arclet.alconna import Alconna, Args, Option, Arparma
|
||||||
|
|
||||||
|
|
||||||
@@ -183,8 +193,7 @@ test = on_alconna(
|
|||||||
"test",
|
"test",
|
||||||
Option("foo", Args["bar", int]),
|
Option("foo", Args["bar", int]),
|
||||||
Option("baz", Args["qux", bool, False])
|
Option("baz", Args["qux", bool, False])
|
||||||
),
|
)
|
||||||
auto_send_output=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@test.handle()
|
@test.handle()
|
||||||
@@ -198,99 +207,98 @@ async def handle_test2(result: Arparma):
|
|||||||
await test.send(f"args: {result.all_matched_args}")
|
await test.send(f"args: {result.all_matched_args}")
|
||||||
|
|
||||||
@test.handle()
|
@test.handle()
|
||||||
async def handle_test3(bar: Match[int] = AlconnaMatch("bar")):
|
async def handle_test3(bar: Match[int]):
|
||||||
if bar.available:
|
if bar.available:
|
||||||
await test.send(f"foo={bar.result}")
|
await test.send(f"foo={bar.result}")
|
||||||
|
|
||||||
@test.handle()
|
@test.handle()
|
||||||
async def handle_test4(qux: Query[bool] = Query("baz.qux", False)):
|
async def handle_test4(qux: Query[bool] = AlconnaQuery("baz.qux", False)):
|
||||||
if qux.available:
|
if qux.available:
|
||||||
await test.send(f"baz.qux={qux.result}")
|
await test.send(f"baz.qux={qux.result}")
|
||||||
```
|
```
|
||||||
|
|
||||||
## 多平台适配
|
|
||||||
|
|
||||||
本插件提供了通用消息段标注, 通用消息段序列, 使插件使用者可以忽略平台之间字段的差异
|
|
||||||
|
|
||||||
响应器使用示例中使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
|
|
||||||
|
|
||||||
具体介绍和使用请查看 [通用信息组件](./uniseg.mdx#通用消息段)
|
|
||||||
|
|
||||||
本插件为以下适配器提供了专门的适配器标注:
|
|
||||||
|
|
||||||
| 协议名称 | 路径 |
|
|
||||||
| ------------------------------------------------------------------- | ------------------------------------ |
|
|
||||||
| [OneBot 协议](https://github.com/nonebot/adapter-onebot) | adapters.onebot11, adapters.onebot12 |
|
|
||||||
| [Telegram](https://github.com/nonebot/adapter-telegram) | adapters.telegram |
|
|
||||||
| [飞书](https://github.com/nonebot/adapter-feishu) | adapters.feishu |
|
|
||||||
| [GitHub](https://github.com/nonebot/adapter-github) | adapters.github |
|
|
||||||
| [QQ bot](https://github.com/nonebot/adapter-qq) | adapters.qq |
|
|
||||||
| [钉钉](https://github.com/nonebot/adapter-ding) | adapters.ding |
|
|
||||||
| [Dodo](https://github.com/nonebot/adapter-dodo) | adapters.dodo |
|
|
||||||
| [Console](https://github.com/nonebot/adapter-console) | adapters.console |
|
|
||||||
| [开黑啦](https://github.com/Tian-que/nonebot-adapter-kaiheila) | adapters.kook |
|
|
||||||
| [Mirai](https://github.com/ieew/nonebot_adapter_mirai2) | adapters.mirai |
|
|
||||||
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | adapters.ntchat |
|
|
||||||
| [MineCraft](https://github.com/17TheWord/nonebot-adapter-minecraft) | adapters.minecraft |
|
|
||||||
| [BiliBili Live](https://github.com/wwweww/adapter-bilibili) | adapters.bilibili |
|
|
||||||
| [Walle-Q](https://github.com/onebot-walle/nonebot_adapter_walleq) | adapters.onebot12 |
|
|
||||||
| [Discord](https://github.com/nonebot/adapter-discord) | adapters.discord |
|
|
||||||
| [Red 协议](https://github.com/nonebot/adapter-red) | adapters.red |
|
|
||||||
| [Satori 协议](https://github.com/nonebot/adapter-satori) | adapters.satori |
|
|
||||||
|
|
||||||
## 条件控制
|
## 条件控制
|
||||||
|
|
||||||
本插件可以通过 `assign` 来控制一个具体的响应函数是否在不满足条件时跳过响应。
|
### `assign` 方法
|
||||||
|
|
||||||
|
`AlconnaMatcher` 的 `assign` 方法与 `handle` 类似,但是可以控制响应函数是否在不满足条件时跳过响应。
|
||||||
|
|
||||||
|
`assign` 方法的参数如下:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
...
|
def assign(
|
||||||
from nonebot import require
|
cls,
|
||||||
require("nonebot_plugin_alconna")
|
path: str,
|
||||||
...
|
value: Any = _seminal,
|
||||||
|
or_not: bool = False,
|
||||||
from arclet.alconna import Alconna, Subcommand, Option, Args
|
additional: CHECK | None = None,
|
||||||
from nonebot_plugin_alconna import on_alconna, CommandResult
|
parameterless: Iterable[Any] | None = None,
|
||||||
|
):
|
||||||
|
|
||||||
pip = Alconna(
|
|
||||||
"pip",
|
|
||||||
Subcommand(
|
|
||||||
"install", Args["pak", str],
|
|
||||||
Option("--upgrade"),
|
|
||||||
Option("--force-reinstall")
|
|
||||||
),
|
|
||||||
Subcommand("list", Option("--out-dated"))
|
|
||||||
)
|
|
||||||
|
|
||||||
pip_cmd = on_alconna(pip)
|
|
||||||
|
|
||||||
# 仅在命令为 `pip install pip` 时响应
|
|
||||||
@pip_cmd.assign("install.pak", "pip")
|
|
||||||
async def update(res: CommandResult):
|
|
||||||
...
|
|
||||||
|
|
||||||
# 仅在命令为 `pip list` 时响应
|
|
||||||
@pip_cmd.assign("list")
|
|
||||||
async def list_(res: CommandResult):
|
|
||||||
...
|
|
||||||
|
|
||||||
# 在命令为 `pip install xxx` 时响应
|
|
||||||
@pip_cmd.assign("install")
|
|
||||||
async def install(res: CommandResult):
|
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
此外,使用 `AlconnaMatcher.dispatch` 还能像 `CommandGroup` 一样为每个分发设置独立的 matcher:
|
- `path`: 指定的[查询路径](./command.md#路径查询)
|
||||||
|
- "$main" 表示没有任何选项/子命令匹配的时候
|
||||||
|
- "\~XX" 时会把 "\~" 替换为父级路径
|
||||||
|
- `value`: 可能的指定查询值
|
||||||
|
- `or_not`: 是否同时处理没有查询成功的情况
|
||||||
|
- `additional`: 额外的条件检查函数
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
update_cmd = pip_cmd.dispatch("install.pak", "pip")
|
# 处理没有任何选项/子命令匹配的情况
|
||||||
|
@rg.assign("$main")
|
||||||
|
async def handle_main(): ...
|
||||||
|
|
||||||
@update_cmd.handle()
|
# 处理 list 选项
|
||||||
async def update(arp: CommandResult):
|
@rg.assign("list")
|
||||||
...
|
async def handle_list(): ...
|
||||||
|
|
||||||
|
# 处理 add 选项,且 name 为 admin
|
||||||
|
@rg.assign("add.name", "admin")
|
||||||
|
async def handle_add_admin(): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
另外,`AlconnaMatcher` 有类似于 `got` 的 `got_path`:
|
### `dispatch` 方法
|
||||||
|
|
||||||
|
此外,使用 `.dispatch` 还能像 `CommandGroup` 一样为每个分发设置独立的 matcher:
|
||||||
|
|
||||||
|
```python
|
||||||
|
rg_list_cmd = rg.dispatch("list")
|
||||||
|
|
||||||
|
@rg_list_cmd.handle()
|
||||||
|
async def handle_list(): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`dispatch` 的参数与 `assign` 相同。
|
||||||
|
|
||||||
|
当使用 `dispatch` 时,父级路径表示为传入 `dispatch` 的 `path`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
rg_add_cmd = rg.dispatch("add")
|
||||||
|
|
||||||
|
# 此时 ~name 表示 add.name
|
||||||
|
@rg_add_cmd.assign("~name", "admin")
|
||||||
|
async def handle_add_admin(): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
在 `dispatch` 下, `Query` 的 `path` 也同样支持 `~` 前缀来表示父级路径
|
||||||
|
|
||||||
|
```python
|
||||||
|
@rg_add_cmd.assign("~name", "admin")
|
||||||
|
async def handle_add_admin(target: Query[tuple[At, ...]] = Query("~target")):
|
||||||
|
if target.available:
|
||||||
|
await rg.send(f"添加成功: {target.result}")
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### `got_path` 方法
|
||||||
|
|
||||||
|
另外,`AlconnaMatcher` 有类似于 [`got`](../../appendices/session-control.mdx#got) 的 `got_path` 与配套的 `get_path_arg`, `set_path_arg`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna import At, Match, UniMessage, on_alconna
|
from nonebot_plugin_alconna import At, Match, UniMessage, on_alconna
|
||||||
@@ -312,95 +320,26 @@ async def tt(target: Union[str, At]):
|
|||||||
|
|
||||||
`got_path` 会获取消息的最后一个消息段并转为 path 对应的类型,例如示例中 `target` 对应的 Arg 里要求 str 或 At,则 got 后用户输入的消息只有为 text 或 at 才能进入处理函数。
|
`got_path` 会获取消息的最后一个消息段并转为 path 对应的类型,例如示例中 `target` 对应的 Arg 里要求 str 或 At,则 got 后用户输入的消息只有为 text 或 at 才能进入处理函数。
|
||||||
|
|
||||||
:::tip
|
`got_path` 中可以使用依赖注入函数 `AlconnaArg`, 类似于 [`Arg`](../../advanced/dependency.mdx#arg).
|
||||||
|
|
||||||
`path` 支持 ~XXX 语法,其会把 ~ 替换为可能的父级路径:
|
### `prompt` 方法
|
||||||
|
|
||||||
|
基于 [`Waiter`](https://github.com/RF-Tar-Railt/nonebot-plugin-waiter) 插件,`AlconnaMatcher` 提供了 `prompt` 方法来实现更灵活的交互式提示。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
pip = Alconna(
|
from nonebot_plugin_alconna import At, Match, UniMessage, on_alconna
|
||||||
"pip",
|
|
||||||
Subcommand(
|
|
||||||
"install",
|
|
||||||
Args["pak", str],
|
|
||||||
Option("--upgrade|-U"),
|
|
||||||
Option("--force-reinstall"),
|
|
||||||
),
|
|
||||||
Subcommand("list", Option("--out-dated")),
|
|
||||||
)
|
|
||||||
|
|
||||||
pipcmd = on_alconna(pip)
|
|
||||||
pip_install_cmd = pipcmd.dispatch("install")
|
|
||||||
|
|
||||||
|
|
||||||
@pip_install_cmd.assign("~upgrade")
|
test_cmd = on_alconna(Alconna("test", Args["target?", Union[str, At]]))
|
||||||
async def pip1_u(pak: Query[str] = Query("~pak")):
|
|
||||||
await pip_install_cmd.finish(f"pip upgrading {pak.result}...")
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
@test_cmd.handle()
|
||||||
|
async def tt_h(target: Match[Union[str, At]]):
|
||||||
## 响应器创建装饰
|
if target.available:
|
||||||
|
await test_cmd.finish(UniMessage(["ok\n", target]))
|
||||||
本插件提供了一个 `funcommand` 装饰器, 其用于将一个接受任意参数, 返回 `str` 或 `Message` 或 `MessageSegment` 的函数转换为命令响应器:
|
resp = await test_cmd.prompt("请输入目标", timeout=30) # 等待 30 秒
|
||||||
|
if resp is None:
|
||||||
```python
|
await test_cmd.finish("超时")
|
||||||
from nonebot_plugin_alconna import funcommand
|
await test_cmd.finish(UniMessage(["ok\n", resp[-1]]))
|
||||||
|
|
||||||
|
|
||||||
@funcommand()
|
|
||||||
async def echo(msg: str):
|
|
||||||
return msg
|
|
||||||
```
|
|
||||||
|
|
||||||
其等同于:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from arclet.alconna import Alconna, Args
|
|
||||||
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match
|
|
||||||
|
|
||||||
|
|
||||||
echo = on_alconna(Alconna("echo", Args["msg", str]))
|
|
||||||
|
|
||||||
@echo.handle()
|
|
||||||
async def echo_exit(msg: Match[str] = AlconnaMatch("msg")):
|
|
||||||
await echo.finish(msg.result)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## 类Koishi构造器
|
|
||||||
|
|
||||||
本插件提供了一个 `Command` 构造器,其基于 `arclet.alconna.tools` 中的 `AlconnaString`, 以类似 `Koishi` 中注册命令的方式来构建一个 **AlconnaMatcher** :
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna import Command, Arparma
|
|
||||||
|
|
||||||
|
|
||||||
book = (
|
|
||||||
Command("book", "测试")
|
|
||||||
.option("writer", "-w <id:int>")
|
|
||||||
.option("writer", "--anonymous", {"id": 0})
|
|
||||||
.usage("book [-w <id:int> | --anonymous]")
|
|
||||||
.shortcut("测试", {"args": ["--anonymous"]})
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
|
|
||||||
@book.handle()
|
|
||||||
async def _(arp: Arparma):
|
|
||||||
await book.send(str(arp.options))
|
|
||||||
```
|
|
||||||
|
|
||||||
甚至,你可以设置 `action` 来设定响应行为:
|
|
||||||
|
|
||||||
```python
|
|
||||||
book = (
|
|
||||||
Command("book", "测试")
|
|
||||||
.option("writer", "-w <id:int>")
|
|
||||||
.option("writer", "--anonymous", {"id": 0})
|
|
||||||
.usage("book [-w <id:int> | --anonymous]")
|
|
||||||
.shortcut("测试", {"args": ["--anonymous"]})
|
|
||||||
.action(lambda options: str(options)) # 会自动通过 bot.send 发送
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 返回值中间件
|
## 返回值中间件
|
||||||
@@ -411,9 +350,7 @@ book = (
|
|||||||
from nonebot_plugin_alconna import image_fetch
|
from nonebot_plugin_alconna import image_fetch
|
||||||
|
|
||||||
|
|
||||||
mask_cmd = on_alconna(
|
mask_cmd = on_alconna(Alconna("search", Args["img?", Image]))
|
||||||
Alconna("search", Args["img?", Image]),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@mask_cmd.handle()
|
@mask_cmd.handle()
|
||||||
@@ -424,10 +361,106 @@ async def mask_h(matcher: AlconnaMatcher, img: Match[bytes] = AlconnaMatch("img"
|
|||||||
|
|
||||||
其中,`image_fetch` 是一个中间件,其接受一个 `Image` 对象,并提取图片的二进制数据返回。
|
其中,`image_fetch` 是一个中间件,其接受一个 `Image` 对象,并提取图片的二进制数据返回。
|
||||||
|
|
||||||
|
## i18n
|
||||||
|
|
||||||
|
本插件基于 `tarina.lang` 模块提供了 i18n 的支持,参见 [Lang 用法](https://github.com/nonebot/plugin-alconna/discussions/50)。
|
||||||
|
|
||||||
|
当你编写完语言文件后,你便可以通过 `AlconnaMatcher.i18n` 来快速地将语言文件中的内容转为 UniMessage.
|
||||||
|
|
||||||
|
<Tabs groupId="i18n">
|
||||||
|
<TabItem value="zh" label="中文">
|
||||||
|
|
||||||
|
```yaml title="zh-CN.yml"
|
||||||
|
# 中文语言文件
|
||||||
|
demo:
|
||||||
|
command:
|
||||||
|
role-group:
|
||||||
|
add: 添加 {name} 成功!
|
||||||
|
```
|
||||||
|
|
||||||
|
<Messenger
|
||||||
|
msgs={[
|
||||||
|
{ position: "right", msg: "/角色组 添加 foo" },
|
||||||
|
{ position: "left", msg: "添加 foo 成功!" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="en" label="英文">
|
||||||
|
|
||||||
|
```yaml title="en-US.yml"
|
||||||
|
# 英文语言文件
|
||||||
|
demo:
|
||||||
|
command:
|
||||||
|
role-group:
|
||||||
|
add: Add {name} successfully!
|
||||||
|
```
|
||||||
|
|
||||||
|
<Messenger
|
||||||
|
msgs={[
|
||||||
|
{ position: "right", msg: "/role-group add foo" },
|
||||||
|
{ position: "left", msg: "Add foo successfully!" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
```python title="使用 i18n"
|
||||||
|
@rg.assign("add")
|
||||||
|
async def handle_add(name: str):
|
||||||
|
await rg.i18n("demo", "command.role-group.add", name=name).finish()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 匹配测试
|
||||||
|
|
||||||
|
`AlconnaMatcher.test` 方法允许你在 NoneBot 启动时对命令进行测试。
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test(
|
||||||
|
cls,
|
||||||
|
message: str | UniMessage,
|
||||||
|
expected: dict[str, Any] | None = None,
|
||||||
|
prefix: bool = True
|
||||||
|
): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
- `message`: 测试的消息
|
||||||
|
- `expected`: 预期的解析结果,若为 None 则表示只测试是否匹配
|
||||||
|
- `prefix`: 是否使用命令前缀,默认为 True
|
||||||
|
|
||||||
## 匹配拓展
|
## 匹配拓展
|
||||||
|
|
||||||
本插件提供了一个 `Extension` 类,其用于自定义 AlconnaMatcher 的部分行为
|
本插件提供了一个 `Extension` 类,其用于自定义 AlconnaMatcher 的部分行为
|
||||||
|
|
||||||
|
目前 `Extension` 的功能有:
|
||||||
|
|
||||||
|
- `validate`: 对于事件的来源适配器或 bot 选择是否接受响应
|
||||||
|
- `output_converter`: 输出信息的自定义转换方法
|
||||||
|
- `message_provider`: 从传入事件中自定义提取消息的方法
|
||||||
|
- `receive_provider`: 对传入的消息 (UniMessage) 的额外处理
|
||||||
|
- `context_provider`: 对命令上下文的额外处理
|
||||||
|
- `permission_check`: 命令对消息解析并确认头部匹配(即确认选择响应)时对发送者的权限判断
|
||||||
|
- `parse_wrapper`: 对命令解析结果的额外处理
|
||||||
|
- `send_wrapper`: 对发送的消息 (Message 或 UniMessage) 的额外处理
|
||||||
|
- `before_catch`: 自定义依赖注入的绑定确认函数
|
||||||
|
- `catch`: 自定义依赖注入处理函数
|
||||||
|
- `post_init`: 响应器创建后对命令对象的额外处理
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
Extension 可以通过 `add_global_extension` 方法来全局添加。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import add_global_extension
|
||||||
|
from nonebot_plugin_alconna.builtins.extensions.telegram import TelegramSlashExtension
|
||||||
|
|
||||||
|
add_global_extension(TelegramSlashExtension)
|
||||||
|
```
|
||||||
|
|
||||||
|
全局的 Extension 可延迟加载 (即若有全局拓展加载于部分 AlconnaMatcher 之后,这部分响应器会被追加拓展)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
例如一个 `LLMExtension` 可以如下实现 (仅举例):
|
例如一个 `LLMExtension` 可以如下实现 (仅举例):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -469,59 +502,121 @@ matcher = on_alconna(
|
|||||||
|
|
||||||
那么添加了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量。
|
那么添加了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量。
|
||||||
|
|
||||||
目前 `Extension` 的功能有:
|
### validate
|
||||||
|
|
||||||
- `validate`: 对于事件的来源适配器或 bot 选择是否接受响应
|
|
||||||
- `output_converter`: 输出信息的自定义转换方法
|
|
||||||
- `message_provider`: 从传入事件中自定义提取消息的方法
|
|
||||||
- `receive_provider`: 对传入的消息 (Message 或 UniMessage) 的额外处理
|
|
||||||
- `context_provider`: 对命令上下文的额外处理
|
|
||||||
- `permission_check`: 命令对消息解析并确认头部匹配(即确认选择响应)时对发送者的权限判断
|
|
||||||
- `parse_wrapper`: 对命令解析结果的额外处理
|
|
||||||
- `send_wrapper`: 对发送的消息 (Message 或 UniMessage) 的额外处理
|
|
||||||
- `before_catch`: 自定义依赖注入的绑定确认函数
|
|
||||||
- `catch`: 自定义依赖注入处理函数
|
|
||||||
- `post_init`: 响应器创建后对命令对象的额外处理
|
|
||||||
|
|
||||||
例如内置的 `DiscordSlashExtension`,其可自动将 Alconna 对象翻译成 slash 指令并注册,且将收到的指令交互事件转为指令供命令解析:
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna import Match, on_alconna
|
def validate(self, bot: Bot, event: Event) -> bool: ...
|
||||||
from nonebot_plugin_alconna.builtins.extensions.discord import DiscordSlashExtension
|
|
||||||
|
|
||||||
|
|
||||||
alc = Alconna(
|
|
||||||
["/"],
|
|
||||||
"permission",
|
|
||||||
Subcommand("add", Args["plugin", str]["priority?", int]),
|
|
||||||
Option("remove", Args["plugin", str]["time?", int]),
|
|
||||||
meta=CommandMeta(description="权限管理"),
|
|
||||||
)
|
|
||||||
|
|
||||||
matcher = on_alconna(alc, extensions=[DiscordSlashExtension()])
|
|
||||||
|
|
||||||
@matcher.assign("add")
|
|
||||||
async def add(plugin: Match[str], priority: Match[int]):
|
|
||||||
await matcher.finish(f"added {plugin.result} with {priority.result if priority.available else 0}")
|
|
||||||
|
|
||||||
@matcher.assign("remove")
|
|
||||||
async def remove(plugin: Match[str], time: Match[int]):
|
|
||||||
await matcher.finish(f"removed {plugin.result} with {time.result if time.available else -1}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
目前插件提供了 4 个内置的 `Extension`,它们在 `nonebot_plugin_alconna.builtins.extensions` 下:
|
默认情况下,`validate` 方法会筛选 `event.get_type()` 为 `message` 的情况,表示接受消息事件。
|
||||||
|
|
||||||
- `ReplyRecordExtension`: 将消息事件中的回复暂存在 extension 中,使得解析用的消息不带回复信息,同时可以在后续的处理中获取回复信息。
|
### output_converter
|
||||||
- `DiscordSlashExtension`: 将 Alconna 的命令自动转换为 Discord 的 Slash Command,并将 Slash Command 的交互事件转换为消息交给 Alconna 处理。
|
|
||||||
- `MarkdownOutputExtension`: 将 Alconna 的自动输出转换为 Markdown 格式
|
|
||||||
- `TelegramSlashExtension`: 将 Alconna 的命令注册在 Telegram 上以获得提示。
|
|
||||||
|
|
||||||
:::tip
|
```python
|
||||||
|
async def output_converter(self, output_type: OutputType, content: str) -> UniMessage: ...
|
||||||
|
```
|
||||||
|
|
||||||
全局的 Extension 可延迟加载 (即若有全局拓展加载于部分 AlconnaMatcher 之后,这部分响应器会被追加拓展)
|
依据输出信息的类型,将字符串转换为消息对象以便发送。
|
||||||
|
|
||||||
|
其中 `OutputType` 为 "help", "shortcut", "completion", "error" 其中之一。
|
||||||
|
|
||||||
|
该方法只会调用一次,即对于多个 Extension,选择优先级靠前且实现了该方法的 Extension。
|
||||||
|
|
||||||
|
### message_provider
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def message_provider(
|
||||||
|
self, event: Event, state: T_State, bot: Bot, use_origin: bool = False
|
||||||
|
) -> UniMessage | None:...
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法用于从事件中提取消息,默认情况下会使用 `event.get_message()` 来获取消息。
|
||||||
|
|
||||||
|
该方法可能会调用多次,即对于多个 Extension,选择优先级靠前且实现了该方法的 Extension,若调用的返回值不为 `None` 则作为结果。
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
该方法的默认实现对结果 (UniMessage) 会进行缓存。`Extension` 的实现也应尽量实现缓存机制。
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### receive_provider
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def receive_provider(self, bot: Bot, event: Event, command: Alconna, receive: UniMessage) -> UniMessage: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法用于对传入的消息 (UniMessage) 进行额外处理,默认情况下会返回原始消息。
|
||||||
|
|
||||||
|
该方法会调用多次,即对于多个 Extension,前一个 Extension 的返回值会作为下一个 Extension 的输入。
|
||||||
|
|
||||||
|
### context_provider
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def context_provider(self, ctx: dict[str, Any], bot: Bot, event: Event, state: T_State) -> dict[str, Any]:
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法用于提取命令上下文,默认情况下会返回 `ctx` 本身。
|
||||||
|
|
||||||
|
该方法会调用多次,即对于多个 Extension,前一个 Extension 的返回值会作为下一个 Extension 的输入。
|
||||||
|
|
||||||
|
### permission_check
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def permission_check(self, bot: Bot, event: Event, command: Alconna) -> bool: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法用于对发送者的权限进行检查,默认情况下会返回 `True`。
|
||||||
|
|
||||||
|
该方法可能会调用多次,即对于多个 Extension,若调用的返回值不为 `True` 则结束判断。
|
||||||
|
|
||||||
|
### parse_wrapper
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def parse_wrapper(self, bot: Bot, state: T_State, event: Event, res: Arparma) -> None: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法用于对命令解析结果进行额外处理。
|
||||||
|
|
||||||
|
该方法会调用多次,即对于多个 Extension,会并发地调用该方法。
|
||||||
|
|
||||||
|
### send_wrapper
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def send_wrapper(self, bot: Bot, event: Event, send: TMessage) -> TMessage: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法用于对 `AlconnaMatcher.send` 或 `UniMessage.send` 发送的消息 (str 或 Message 或 UniMessage) 进行额外处理,默认情况下会返回原始消息。
|
||||||
|
|
||||||
|
该方法会调用多次,即对于多个 Extension,前一个 Extension 的返回值会作为下一个 Extension 的输入。
|
||||||
|
|
||||||
|
由于需要保证输入与输出的类型一致,该方法内需要自行判断类型。
|
||||||
|
|
||||||
|
### before_catch
|
||||||
|
|
||||||
|
```python
|
||||||
|
def before_catch(self, name: str, annotation: type, default: Any) -> bool: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法用于响应函数中某个参数是否需要绑定到该 Extension 上。
|
||||||
|
|
||||||
|
### catch
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def catch(self, interface: Interface) -> Any: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法用于注入经过 `before_catch` 确认的参数。其中 `Interface` 的定义为
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Interface(Generic[TE]):
|
||||||
|
event: TE
|
||||||
|
state: T_State
|
||||||
|
name: str
|
||||||
|
annotation: Any
|
||||||
|
default: Any
|
||||||
|
```
|
||||||
|
|
||||||
## 补全会话
|
## 补全会话
|
||||||
|
|
||||||
补全会话基于 [`半自动补全`](./command.md#半自动补全),用于指令参数缺失或参数错误时给予交互式提示,类似于 `got-reject`:
|
补全会话基于 [`半自动补全`](./command.md#半自动补全),用于指令参数缺失或参数错误时给予交互式提示,类似于 `got-reject`:
|
||||||
@@ -578,30 +673,6 @@ class CompConfig(TypedDict):
|
|||||||
"""禁用的指令"""
|
"""禁用的指令"""
|
||||||
lite: NotRequired[bool]
|
lite: NotRequired[bool]
|
||||||
"""是否使用简洁版本的补全会话(相当于同时配置 disables、hides、hide_tabs)"""
|
"""是否使用简洁版本的补全会话(相当于同时配置 disables、hides、hide_tabs)"""
|
||||||
|
block: NotRequired[bool]
|
||||||
|
"""进行补全会话时是否阻塞响应器"""
|
||||||
```
|
```
|
||||||
|
|
||||||
## 内置插件
|
|
||||||
|
|
||||||
类似于 Nonebot 本身提供的内置插件,`nonebot_plugin_alconna` 提供了两个内置插件:`echo` 和 `help`。
|
|
||||||
|
|
||||||
你可以用本插件的 `load_builtin_plugin(s)` 来加载它们:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna import load_builtin_plugins
|
|
||||||
|
|
||||||
load_builtin_plugins("echo", "help")
|
|
||||||
```
|
|
||||||
|
|
||||||
其中 `help` 仅能列出所有 Alconna 指令。
|
|
||||||
|
|
||||||
<Messenger
|
|
||||||
msgs={[
|
|
||||||
{ position: "right", msg: "/帮助" },
|
|
||||||
{
|
|
||||||
position: "left",
|
|
||||||
msg: "# 当前可用的命令有:\n 0 /echo : echo 指令\n 1 /help : 显示所有命令帮助\n# 输入'命令名 -h|--help' 查看特定命令的语法",
|
|
||||||
},
|
|
||||||
{ position: "right", msg: "/echo [图片]" },
|
|
||||||
{ position: "left", msg: "[图片]" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
121
website/docs/best-practice/alconna/shortcut.md
Normal file
121
website/docs/best-practice/alconna/shortcut.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 6
|
||||||
|
description: 快捷方式
|
||||||
|
---
|
||||||
|
|
||||||
|
# 快捷方式声明
|
||||||
|
|
||||||
|
针对 `Alconna` 编写对于入门开发者来说较为复杂的问题,本插件提供了一些快捷方式来简化开发者的工作。
|
||||||
|
|
||||||
|
## 装饰器构造器
|
||||||
|
|
||||||
|
本插件提供了一个 `funcommand` 装饰器, 其用于将一个接受任意参数, 返回 `str` 或 `Message` 或 `MessageSegment` 的函数转换为命令响应器:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import funcommand
|
||||||
|
|
||||||
|
|
||||||
|
@funcommand()
|
||||||
|
async def echo(msg: str):
|
||||||
|
return msg
|
||||||
|
```
|
||||||
|
|
||||||
|
其等同于:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Args
|
||||||
|
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match
|
||||||
|
|
||||||
|
|
||||||
|
echo = on_alconna(Alconna("echo", Args["msg", str]))
|
||||||
|
|
||||||
|
@echo.handle()
|
||||||
|
async def echo_exit(msg: Match[str] = AlconnaMatch("msg")):
|
||||||
|
await echo.finish(msg.result)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
相比于 `on_alconna`, `funcommand` 增加了三个参数 `name`, `prefixes` 和 `description`。
|
||||||
|
|
||||||
|
## 类 Koishi 构造器
|
||||||
|
|
||||||
|
本插件提供了一个 `Command` 构造器,其基于 `arclet.alconna.tools` 中的 `AlconnaString`, 以类似 `Koishi` 中[注册命令](https://koishi.chat/zh-CN/guide/basic/command.html)的方式来构建一个 **AlconnaMatcher** :
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import Command, Arparma
|
||||||
|
|
||||||
|
|
||||||
|
book = (
|
||||||
|
Command("book", "测试")
|
||||||
|
.option("writer", "-w <id:int>")
|
||||||
|
.option("writer", "--anonymous", {"id": 0})
|
||||||
|
.usage("book [-w <id:int> | --anonymous]")
|
||||||
|
.shortcut("测试", {"args": ["--anonymous"]})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
@book.handle()
|
||||||
|
async def _(arp: Arparma):
|
||||||
|
await book.send(str(arp.options))
|
||||||
|
```
|
||||||
|
|
||||||
|
甚至,你可以设置 `action` 来设定响应行为:
|
||||||
|
|
||||||
|
```python
|
||||||
|
book = (
|
||||||
|
Command("book", "测试")
|
||||||
|
.option("writer", "-w <id:int>")
|
||||||
|
.option("writer", "--anonymous", {"id": 0})
|
||||||
|
.usage("book [-w <id:int> | --anonymous]")
|
||||||
|
.shortcut("测试", {"args": ["--anonymous"]})
|
||||||
|
.action(lambda options: str(options)) # 会自动通过 bot.send 发送
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 参数类型
|
||||||
|
|
||||||
|
`Command` 的参数类型也如 `koishi` 一样,**必选参数** 用尖括号包裹,**可选参数** 用方括号包裹:
|
||||||
|
|
||||||
|
- `foo` 表示参数 `foo`, 类型为 Any
|
||||||
|
- `foo:int` 表示参数 `foo`, 类型为 int
|
||||||
|
- `foo:int=1` 表示参数 `foo`, 类型为 int, 默认值为 1
|
||||||
|
- `...foo` 表示[泛匹配参数](command.md#allparam)
|
||||||
|
- `foo:str+`, `foo:str*` 表示[变长参数](command.md#multivar-与-keywordvar) `foo`, 类型为 str
|
||||||
|
- `foo:+str`, `foo:text` 表示参数 `foo`, 类型为 str, 并且将包含空格 (即将变长参数的结果用空格合并)
|
||||||
|
|
||||||
|
特别的,针对类型部分,本插件拓展了如下内容:
|
||||||
|
|
||||||
|
- `foo:At`, `foo:Image`, ... 表示类型为[通用消息段](./uniseg/segment.md)
|
||||||
|
- `foo:select(Image).first` 表示获取子元素类型
|
||||||
|
- `foo:Dot(Image, 'url')` 表示类型为 `Image`,并且只获取 `url` 属性
|
||||||
|
|
||||||
|
### 从文件加载
|
||||||
|
|
||||||
|
`Command` 支持读取 `json` 或 `yaml` 文件来加载命令:
|
||||||
|
|
||||||
|
```yml title="book.yml"
|
||||||
|
command: book
|
||||||
|
help: 测试
|
||||||
|
options:
|
||||||
|
- name: writer
|
||||||
|
opt: "-w <id:int>"
|
||||||
|
- name: writer
|
||||||
|
opt: "--anonymous"
|
||||||
|
default:
|
||||||
|
id: 1
|
||||||
|
usage: book [-w <id:int> | --anonymous]
|
||||||
|
shortcuts:
|
||||||
|
- key: 测试
|
||||||
|
args: ["--anonymous"]
|
||||||
|
actions:
|
||||||
|
- params: ["options"]
|
||||||
|
code: |
|
||||||
|
return str(options)
|
||||||
|
```
|
||||||
|
|
||||||
|
```python title="加载"
|
||||||
|
from nonebot_plugin_alconna import command_from_yaml
|
||||||
|
|
||||||
|
book = command_from_yaml("book.yml")
|
||||||
|
```
|
203
website/docs/best-practice/alconna/uniseg/README.md
Normal file
203
website/docs/best-practice/alconna/uniseg/README.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# 通用消息组件
|
||||||
|
|
||||||
|
`uniseg` 模块属于 `nonebot-plugin-alconna` 的子插件。
|
||||||
|
|
||||||
|
通用消息组件内容较多,故分为了一个示例以及数个专题。
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### 导入
|
||||||
|
|
||||||
|
一般情况下,你只需要从 `nonebot_plugin_alconna.uniseg` 中导入 `UniMessage` 即可:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
|
```
|
||||||
|
|
||||||
|
### 构建
|
||||||
|
|
||||||
|
你可以通过 `UniMessage` 上的快捷方法来链式构造消息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
message = (
|
||||||
|
UniMessage.text("hello world")
|
||||||
|
.at("1234567890")
|
||||||
|
.image(url="https://example.com/image.png")
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以通过导入通用消息段来构建消息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import Text, At, Image, UniMessage
|
||||||
|
|
||||||
|
message = UniMessage(
|
||||||
|
[
|
||||||
|
Text("hello world"),
|
||||||
|
At("user", "1234567890"),
|
||||||
|
Image(url="https://example.com/image.png"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
更深入一点,比如你想要发送一条包含多个按钮的消息,你可以这样做:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import Button, UniMessage
|
||||||
|
|
||||||
|
message = (
|
||||||
|
UniMessage.text("hello world")
|
||||||
|
.keyboard(
|
||||||
|
Button("link1", url="https://example.com/1"),
|
||||||
|
Button("link2", url="https://example.com/2"),
|
||||||
|
Button("link3", url="https://example.com/3"),
|
||||||
|
row=3,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发送
|
||||||
|
|
||||||
|
你可以通过 `.send` 方法来发送消息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@matcher.handle()
|
||||||
|
async def _():
|
||||||
|
message = UniMessage.text("hello world").image(url="https://example.com/image.png")
|
||||||
|
await message.send()
|
||||||
|
# 类似于 `matcher.finish`
|
||||||
|
await message.finish()
|
||||||
|
```
|
||||||
|
|
||||||
|
你可以通过参数来让消息 @ 发送者:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@matcher.handle()
|
||||||
|
async def _():
|
||||||
|
message = UniMessage.text("hello world").image(url="https://example.com/image.png")
|
||||||
|
await message.send(at_sender=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
或者回复消息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@matcher.handle()
|
||||||
|
async def _():
|
||||||
|
message = UniMessage.text("hello world").image(url="https://example.com/image.png")
|
||||||
|
await message.send(reply_to=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 撤回,编辑,表态
|
||||||
|
|
||||||
|
你可以通过 `message_recall`, `message_edit` 和 `message_reaction` 方法来撤回,编辑和表态消息事件。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import message_recall, message_edit, message_reaction
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _():
|
||||||
|
await message_edit(UniMessage.text("hello world"))
|
||||||
|
await message_reaction("👍")
|
||||||
|
await message_recall()
|
||||||
|
```
|
||||||
|
|
||||||
|
你也可以对你自己发送的消息进行撤回,编辑和表态:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@matcher.handle()
|
||||||
|
async def _():
|
||||||
|
message = UniMessage.text("hello world").image(url="https://example.com/image.png")
|
||||||
|
receipt = await message.send()
|
||||||
|
await receipt.edit(UniMessage.text("hello world!"))
|
||||||
|
await receipt.reaction("👍")
|
||||||
|
await receipt.recall(delay=5) # 5秒后撤回
|
||||||
|
```
|
||||||
|
|
||||||
|
### 处理消息
|
||||||
|
|
||||||
|
通过依赖注入,你可以在事件处理器中获取通用消息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import UniMsg
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(msg: UniMsg):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
然后你可以通过 `UniMessage` 的方法来处理消息.
|
||||||
|
|
||||||
|
例如,你想知道消息中是否包含图片,你可以这样做:
|
||||||
|
|
||||||
|
```python
|
||||||
|
ans1 = Image in message
|
||||||
|
ans2 = message.has(Image)
|
||||||
|
ans3 = message.only(Image)
|
||||||
|
```
|
||||||
|
|
||||||
|
或者,提取所有的图片:
|
||||||
|
|
||||||
|
```python
|
||||||
|
imgs_1 = message[Image]
|
||||||
|
imgs_2 = message.get(Image)
|
||||||
|
imgs_3 = message.include(Image)
|
||||||
|
imgs_4 = message.select(Image)
|
||||||
|
imgs_5 = message.filter(lambda x: x.type == "image")
|
||||||
|
imgs_6 = message.tranform({"image": True})
|
||||||
|
```
|
||||||
|
|
||||||
|
而后,如果你想提取出所有的图片链接,你可以这样做:
|
||||||
|
|
||||||
|
```python
|
||||||
|
urls = imgs.map(lambda x: x.url)
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你想知道消息是否符合某个前缀,你可以这样做:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(msg: UniMsg):
|
||||||
|
if msg.startswith("hello"):
|
||||||
|
await matcher.finish("hello world")
|
||||||
|
else:
|
||||||
|
await matcher.finish("not hello world")
|
||||||
|
```
|
||||||
|
|
||||||
|
或者你想接着去除掉前缀:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(msg: UniMsg):
|
||||||
|
if msg.startswith("hello"):
|
||||||
|
msg = msg.removeprefix("hello")
|
||||||
|
await matcher.finish(msg)
|
||||||
|
else:
|
||||||
|
await matcher.finish("not hello world")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 持久化
|
||||||
|
|
||||||
|
假设你在编写一个词库查询插件,你可以通过 `UniMessage.dump` 方法来将消息序列化为 JSON 格式:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import UniMsg
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(msg: UniMsg):
|
||||||
|
data: list[dict] = msg.dump()
|
||||||
|
# 你可以将 data 存储到数据库或者 JSON 文件中
|
||||||
|
```
|
||||||
|
|
||||||
|
而后你可以通过 `UniMessage.load` 方法来将 JSON 格式的消息反序列化为 `UniMessage` 对象:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import UniMessage
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _():
|
||||||
|
data = [
|
||||||
|
{"type": "text", "text": "hello world"},
|
||||||
|
{"type": "image", "url": "https://example.com/image.png"},
|
||||||
|
]
|
||||||
|
message = UniMessage.load(data)
|
||||||
|
```
|
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"label": "通用消息组件",
|
||||||
|
"position": 5
|
||||||
|
}
|
518
website/docs/best-practice/alconna/uniseg/message.mdx
Normal file
518
website/docs/best-practice/alconna/uniseg/message.mdx
Normal file
@@ -0,0 +1,518 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 3
|
||||||
|
description: 消息序列
|
||||||
|
---
|
||||||
|
|
||||||
|
import Tabs from "@theme/Tabs";
|
||||||
|
import TabItem from "@theme/TabItem";
|
||||||
|
|
||||||
|
# 通用消息序列
|
||||||
|
|
||||||
|
`uniseg` 提供了一个类似于 `Message` 的 `UniMessage` 类型,其元素为[通用消息段](./segment.md)。
|
||||||
|
|
||||||
|
你可以用如下方式获取 `UniMessage`:
|
||||||
|
|
||||||
|
<Tabs groupId="get_unimsg">
|
||||||
|
<TabItem value="depend" label="使用依赖注入">
|
||||||
|
|
||||||
|
通过提供的 `UniversalMessage` 或基于 [`Annotated` 支持](https://github.com/nonebot/nonebot2/pull/1832)的 `UniMsg` 依赖注入器来获取 `UniMessage`。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMsg, At, Text
|
||||||
|
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(msg: UniMsg):
|
||||||
|
text = msg[Text, 0]
|
||||||
|
print(text.text)
|
||||||
|
if msg.has(At):
|
||||||
|
ats = msg.get(At)
|
||||||
|
print(ats)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="method" label="使用 UniMessage.generate">
|
||||||
|
|
||||||
|
注意,`generate` 方法在响应器以外的地方如果不传入 `event` 与 `bot` 则无法处理 reply。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot import Message, EventMessage
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
|
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(message: Message = EventMessage()):
|
||||||
|
msg = await UniMessage.generate(message=message)
|
||||||
|
msg1 = UniMessage.generate_without_reply(message=message)
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## 发送消息
|
||||||
|
|
||||||
|
你还可以通过 `UniMessage` 的 `export` 与 `send` 方法来**跨平台发送消息**。
|
||||||
|
|
||||||
|
`UniMessage.export` 会通过传入的 `bot: Bot` 参数,或上下文中的 `Bot` 对象读取适配器信息,并使用对应的生成方法把通用消息转为适配器对应的消息序列:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot import Bot, on_command
|
||||||
|
from nonebot_plugin_alconna.uniseg import Image, UniMessage
|
||||||
|
|
||||||
|
|
||||||
|
test = on_command("test")
|
||||||
|
|
||||||
|
@test.handle()
|
||||||
|
async def handle_test():
|
||||||
|
await test.send(await UniMessage(Image(path="path/to/img")).export())
|
||||||
|
```
|
||||||
|
|
||||||
|
除此之外 `UniMessage.send`, `.finish` 方法基于 `UniMessage.export` 并调用各适配器下的发送消息方法,返回一个 `Receipt` 对象,用于修改/撤回/表态消息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot import Bot, on_command
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
|
|
||||||
|
|
||||||
|
test = on_command("test")
|
||||||
|
|
||||||
|
@test.handle()
|
||||||
|
async def handle():
|
||||||
|
receipt = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
|
||||||
|
await receipt.recall(delay=1)
|
||||||
|
```
|
||||||
|
|
||||||
|
`UniMessage.send` 的定义如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def send(
|
||||||
|
self,
|
||||||
|
target: Event | Target | None = None,
|
||||||
|
bot: Bot | None = None,
|
||||||
|
fallback: bool | FallbackStrategy = FallbackStrategy.rollback,
|
||||||
|
at_sender: str | bool = False,
|
||||||
|
reply_to: str | bool | Reply | None = False,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> Receipt:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
- `target`: 发送目标,支持事件和[发送对象](./utils.mdx#发送对象),不传入时会尝试从响应器上下文中获取。
|
||||||
|
- `bot`: 发送消息使用的 Bot 对象,若不传入则会尝试从响应器上下文中获取。
|
||||||
|
- `fallback`: [回退策略](#回退策略)。
|
||||||
|
- `at_sender`: 是否提醒发送者,默认为 `False`。当类型为 `str` 时,表示指定用户的 id。
|
||||||
|
- `reply_to`: 是否回复消息,默认为 `False`。
|
||||||
|
- `str` 表示消息 id。
|
||||||
|
- `bool` 表示是否回复当前消息。此时 `target` 不能是[发送对象](./utils.mdx#发送对象)。
|
||||||
|
- `Reply` 表示直接使用回复元素。
|
||||||
|
- `**kwargs`: 各 `Bot.send` 的特定参数。
|
||||||
|
|
||||||
|
而在 `AlconnaMatcher` 下,`got`, `send`, `reject` 等可以发送消息的方法皆支持使用 `UniMessage`,不需要手动调用 export 方法:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Args
|
||||||
|
from nonebot_plugin_alconna import Match, AlconnaMatcher, on_alconna
|
||||||
|
from nonebot_plugin_alconna.uniseg import At, UniMessage
|
||||||
|
|
||||||
|
|
||||||
|
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
|
||||||
|
|
||||||
|
@test_cmd.handle()
|
||||||
|
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
|
||||||
|
if target.available:
|
||||||
|
matcher.set_path_arg("target", target.result)
|
||||||
|
|
||||||
|
@test_cmd.got_path("target", prompt="请输入目标")
|
||||||
|
async def tt(target: At):
|
||||||
|
await test_cmd.send(UniMessage([target, "\ndone."]))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 回退策略
|
||||||
|
|
||||||
|
`send` 方法的 `fallback` 参数用于指定回退策略(即当前适配器不支持的消息段如何处理):
|
||||||
|
|
||||||
|
- `FallbackStrategy.ignore`: 忽略未转换的消息段
|
||||||
|
- `FallbackStrategy.to_text`: 将未转换的消息段转为文本元素
|
||||||
|
- `FallbackStrategy.rollback`: 从未转换消息段的子元素中提取可能的可发送消息段
|
||||||
|
- `FallbackStrategy.forbid`: 抛出异常
|
||||||
|
- `FallbackStrategy.auto`: 插件自动选择策略
|
||||||
|
|
||||||
|
另外 `fallback` 传入 `bool` 时,`True` 等价于 `FallbackStrategy.auto`,`False` 等价于 `FallbackStrategy.forbid`。
|
||||||
|
|
||||||
|
### 主动发送消息
|
||||||
|
|
||||||
|
`UniMessage.send` 也可以用于主动发送消息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage, Target, SupportScope
|
||||||
|
from nonebot import get_driver
|
||||||
|
|
||||||
|
|
||||||
|
driver = get_driver()
|
||||||
|
|
||||||
|
@driver.on_startup
|
||||||
|
async def on_startup():
|
||||||
|
target = Target("xxxx", scope=SupportScope.qq_client)
|
||||||
|
await UniMessage("Hello!").send(target=target)
|
||||||
|
```
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
在响应器以外的地方,除非启用了 `alconna_apply_fetch_targets` 配置项,否则 `bot` 参数必须手动传入。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Receipt 对象
|
||||||
|
|
||||||
|
`send` 方法返回的 `Receipt` 对象可以用于修改/撤回/表态消息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def handle():
|
||||||
|
receipt = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
|
||||||
|
await receipt.recall(delay=1)
|
||||||
|
recept1 = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
|
||||||
|
await recept1.edit("world!")
|
||||||
|
```
|
||||||
|
|
||||||
|
`Receipt` 对象拥有以下方法:
|
||||||
|
|
||||||
|
- `recallable`: 表明是否可以撤回
|
||||||
|
- `recall`: 撤回消息
|
||||||
|
- `editable`: 表明是否可以修改
|
||||||
|
- `edit`: 修改消息
|
||||||
|
- `reactionable`: 表明是否可以表态
|
||||||
|
- `reaction`: 表态消息
|
||||||
|
- `get_reply`: 生成对已经发送的消息的回复元素
|
||||||
|
- `send`, `finish`: 发送消息
|
||||||
|
- `reply`: 回复已经发送的消息
|
||||||
|
|
||||||
|
## 构造
|
||||||
|
|
||||||
|
如同 `Message`, `UniMessage` 可以传入单个字符串/消息段,或可迭代的字符串/消息段:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage, At
|
||||||
|
|
||||||
|
|
||||||
|
msg = UniMessage("Hello")
|
||||||
|
msg1 = UniMessage(At("user", "124"))
|
||||||
|
msg2 = UniMessage(["Hello", At("user", "124")])
|
||||||
|
```
|
||||||
|
|
||||||
|
`UniMessage` 上同时存在便捷方法,令其可以链式地添加消息段:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage, At, Image
|
||||||
|
|
||||||
|
|
||||||
|
msg = UniMessage.text("Hello").at("124").image(path="/path/to/img")
|
||||||
|
assert msg == UniMessage(
|
||||||
|
["Hello", At("user", "124"), Image(path="/path/to/img")]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用消息模板
|
||||||
|
|
||||||
|
`UniMessage.template` 同样类似于 `Message.template`,可以用于格式化消息,大体用法参考 [消息模板](../../../tutorial/message#使用消息模板)。
|
||||||
|
|
||||||
|
这里额外说明 `UniMessage.template` 的拓展控制符
|
||||||
|
|
||||||
|
相比 `Message`,UniMessage 对于 `{:XXX}` 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
|
||||||
|
|
||||||
|
以 At(...) 为例:
|
||||||
|
|
||||||
|
```python title=使用通用消息段的拓展控制符
|
||||||
|
>>> from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
|
>>> UniMessage.template("{:At(user, target)}").format(target="123")
|
||||||
|
UniMessage(At("user", "123"))
|
||||||
|
>>> UniMessage.template("{:At(type=user, target=id)}").format(id="123")
|
||||||
|
UniMessage(At("user", "123"))
|
||||||
|
>>> UniMessage.template("{:At(type=user, target=123)}").format()
|
||||||
|
UniMessage(At("user", "123"))
|
||||||
|
```
|
||||||
|
|
||||||
|
而在 `AlconnaMatcher` 中,`{:XXX}` 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
|
||||||
|
|
||||||
|
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
|
||||||
|
from arclet.alconna import Alconna, Args
|
||||||
|
from nonebot_plugin_alconna import At, Match, UniMessage, AlconnaMatcher, on_alconna
|
||||||
|
|
||||||
|
|
||||||
|
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
|
||||||
|
|
||||||
|
@test_cmd.handle()
|
||||||
|
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
|
||||||
|
if target.available:
|
||||||
|
matcher.set_path_arg("target", target.result)
|
||||||
|
|
||||||
|
@test_cmd.got_path(
|
||||||
|
"target",
|
||||||
|
prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请确认目标")
|
||||||
|
)
|
||||||
|
async def tt():
|
||||||
|
await test_cmd.send(
|
||||||
|
UniMessage.template("{:At(user, $event.get_user_id())} 已确认目标为 {target}")
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
另外也有 `$message_id` 与 `$target` 两个特殊值。
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
注意到上述代码中的 `{target}` 了吗?
|
||||||
|
|
||||||
|
在 `AlconnaMatcher` 中,`UniMessage.template` 的格式化方法会自动将 `Arparma.all_matched_args`、 `state` 中的变量传入到 `format` 方法中,因此你可以直接使用上述变量。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 拼接消息
|
||||||
|
|
||||||
|
`str`、`UniMessage`、`Segment` 对象之间可以直接相加,相加均会返回一个新的 `UniMessage` 对象:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 消息序列与消息段相加
|
||||||
|
UniMessage("text") + Text("text")
|
||||||
|
# 消息序列与字符串相加
|
||||||
|
UniMessage([Text("text")]) + "text"
|
||||||
|
# 消息序列与消息序列相加
|
||||||
|
UniMessage("text") + UniMessage([Text("text")])
|
||||||
|
# 字符串与消息序列相加
|
||||||
|
"text" + UniMessage([Text("text")])
|
||||||
|
# 消息段与消息段相加
|
||||||
|
Text("text") + Text("text")
|
||||||
|
# 消息段与字符串相加
|
||||||
|
Text("text") + "text"
|
||||||
|
# 消息段与消息序列相加
|
||||||
|
Text("text") + UniMessage([Text("text")])
|
||||||
|
# 字符串与消息段相加
|
||||||
|
"text" + Text("text")
|
||||||
|
```
|
||||||
|
|
||||||
|
如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append`、`Message.extend` 方法,或者使用自加:
|
||||||
|
|
||||||
|
```python
|
||||||
|
msg = UniMessage([Text("text")])
|
||||||
|
# 自加
|
||||||
|
msg += "text"
|
||||||
|
msg += Text("text")
|
||||||
|
msg += UniMessage([Text("text")])
|
||||||
|
# 附加
|
||||||
|
msg.append(Text("text"))
|
||||||
|
# 扩展
|
||||||
|
msg.extend([Text("text")])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 操作
|
||||||
|
|
||||||
|
### 检查消息段
|
||||||
|
|
||||||
|
我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 是否存在消息段
|
||||||
|
At("user", "1234") in message
|
||||||
|
# 是否存在指定类型的消息段
|
||||||
|
At in message
|
||||||
|
```
|
||||||
|
|
||||||
|
我们还可以使用 `only` 方法来检查消息中是否仅包含指定的消息段:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 是否都为 "test"
|
||||||
|
message.only("test")
|
||||||
|
# 是否仅包含指定类型的消息段
|
||||||
|
message.only(Text)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取消息纯文本
|
||||||
|
|
||||||
|
类似于 `Message.extract_plain_text()`,用于获取通用消息的纯文本:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 提取消息纯文本字符串
|
||||||
|
assert UniMessage(
|
||||||
|
[At("user", "1234"), "text"]
|
||||||
|
).extract_plain_text() == "text"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 遍历
|
||||||
|
|
||||||
|
通用消息序列继承自 `List[Segment]` ,因此可以使用 `for` 循环遍历消息段:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for segment in message: # type: Segment
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 过滤、索引与切片
|
||||||
|
|
||||||
|
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片:
|
||||||
|
|
||||||
|
```python
|
||||||
|
message = UniMessage(
|
||||||
|
[
|
||||||
|
Reply(...),
|
||||||
|
"text1",
|
||||||
|
At("user", "1234"),
|
||||||
|
"text2"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# 索引
|
||||||
|
message[0] == Reply(...)
|
||||||
|
# 切片
|
||||||
|
message[0:2] == UniMessage([Reply(...), Text("text1")])
|
||||||
|
# 类型过滤
|
||||||
|
message[At] == Message([At("user", "1234")])
|
||||||
|
# 类型索引
|
||||||
|
message[At, 0] == At("user", "1234")
|
||||||
|
# 类型切片
|
||||||
|
message[Text, 0:2] == UniMessage([Text("text1"), Text("text2")])
|
||||||
|
```
|
||||||
|
|
||||||
|
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤:
|
||||||
|
|
||||||
|
```python
|
||||||
|
message.include(Text, At)
|
||||||
|
message.exclude(Reply)
|
||||||
|
```
|
||||||
|
|
||||||
|
或者使用 `filter` 方法:
|
||||||
|
|
||||||
|
```python
|
||||||
|
message.filter(lambda x: isinstance(x, At) and x.flag == "user") # 仅保留 At("user", xxx) 的消息段
|
||||||
|
```
|
||||||
|
|
||||||
|
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 指定类型首个消息段索引
|
||||||
|
message.index(Text) == 1
|
||||||
|
# 指定类型消息段数量
|
||||||
|
message.count(Text) == 2
|
||||||
|
```
|
||||||
|
|
||||||
|
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 获取指定类型指定个数的消息段
|
||||||
|
message.get(Text, 1) == UniMessage([Text("test1")])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 嵌套提取
|
||||||
|
|
||||||
|
消息序列的 `select` 方法可以递归地从消息中选择指定类型的消息段:
|
||||||
|
|
||||||
|
```python
|
||||||
|
message = UniMessage(
|
||||||
|
[
|
||||||
|
Text("text1"),
|
||||||
|
Image(url="url1")(
|
||||||
|
Text("text2"),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert message.select(Text) == UniMessage(
|
||||||
|
[
|
||||||
|
Text("text1"),
|
||||||
|
Text("text2")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 转换
|
||||||
|
|
||||||
|
消息序列的 `map` 方法可以简单地将消息段转换为指定类型的数据:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 转换消息段为另一类型的消息段,此时返回结果仍是 UniMessage
|
||||||
|
message.map(lambda x: Text(x.target)) # 转换为 Text 消息段
|
||||||
|
# 转换消息段为另一类型的数据,此时返回结果为 list[T]
|
||||||
|
message.map(lambda x: x.target) # 转换为 list[str]
|
||||||
|
```
|
||||||
|
|
||||||
|
在此之上,消息序列还提供了 `transform` 和 `transform_async` 方法,允许你传入转换规则,将消息段转换为另一类型的消息段,并返回一个新的消息序列:
|
||||||
|
|
||||||
|
```python
|
||||||
|
rule = {
|
||||||
|
"text": True,
|
||||||
|
"at": lambda attrs, children: Text(attrs["target"])
|
||||||
|
}
|
||||||
|
message.transform(rule)
|
||||||
|
```
|
||||||
|
|
||||||
|
转换规则的类型一般为 `dict[str, Transformer]`,以消息元素类型的名称为键,定义方式如下:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type Fragment = Segment | Segment[];
|
||||||
|
type Render<T> = (attrs: dict, children: Segment[]) => T;
|
||||||
|
type Transformer = boolean | Fragment | Render<boolean | Fragment>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字符串操作
|
||||||
|
|
||||||
|
类似于 `str`,消息序列可以通过如下方法来操作消息内的文本部分:
|
||||||
|
|
||||||
|
- `split`,
|
||||||
|
- `replace`,
|
||||||
|
- `startwith`, `endswith`,
|
||||||
|
- `removeprefix`, `removesuffix`,
|
||||||
|
- `strip`, `lstrip`, `rstrip`,
|
||||||
|
|
||||||
|
```python
|
||||||
|
msg = UniMessage.text("foo bar").at("1234").text("baz qux")
|
||||||
|
# 分割,返回分割结果,类型为 list[UniMessage]
|
||||||
|
parts = msg.split(" ")
|
||||||
|
# 替换,返回替换结果,类型为 UniMessage。新文本可以用 str 或 Text 来替换
|
||||||
|
new_msg = msg.replace("ba", "baaa")
|
||||||
|
# 前缀/后缀检查
|
||||||
|
msg.startswith("foo") # True
|
||||||
|
msg.endswith("qux") # True
|
||||||
|
# 去除前缀/后缀
|
||||||
|
msg1 = msg.removeprefix("foo") # UniMessage([Text(" bar"), At("user", "1234"), Text("baz qux")])
|
||||||
|
msg2 = msg.removesuffix("qux") # UniMessage([Text("foo bar"), At("user", "1234"), Text("baz ")])
|
||||||
|
# 去除空格
|
||||||
|
msg1 = msg1.lstrip() # UniMessage([Text("bar"), At("user", "1234"), Text("baz qux")])
|
||||||
|
msg2 = msg2.rstrip() # UniMessage([Text("foo bar"), At("user", "1234"), Text("baz")])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 持久化
|
||||||
|
|
||||||
|
特别的,`UniMessage` 还支持消息持久化,具体来说为 `dump` 与 `load` 方法:
|
||||||
|
|
||||||
|
```python
|
||||||
|
msg = UniMessage.text("Hello").image(url="url")
|
||||||
|
data = msg.dump() # [{"type": "text", "text": "Hello"}, {"type": "image", "url": "url"}]
|
||||||
|
|
||||||
|
assert UniMessage.load(data) == msg
|
||||||
|
```
|
||||||
|
|
||||||
|
### dump
|
||||||
|
|
||||||
|
`dump` 方法的定义如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def dump(self, media_save_dir: str | Path | bool | None = None, json: bool = False) -> str | list[dict[str, Any]]: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
其中,`media_save_dir` 用于指定持久化的媒体文件存储目录:
|
||||||
|
|
||||||
|
- 若 `media_save_dir` 为 str 或 Path,则会将媒体文件保存到指定目录下。
|
||||||
|
- 若 `media_save_dir` 为 False,则不会保存媒体文件。
|
||||||
|
- 若 `media_save_dir` 为 True,则会将文件数据转为 base64 编码。
|
||||||
|
- 若不指定 `media_save_dir`,则会尝试导入 [`nonebot_plugin_localstore`](../../data-storing.md) 并使用其提供的路径。否则 (即 `localstore` 未安装),将会尝试使用当前工作目录。
|
||||||
|
|
||||||
|
### load
|
||||||
|
|
||||||
|
`load` 方法的定义如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@classmethod
|
||||||
|
def load(cls, data: str | list[dict[str, Any]]) -> UniMessage: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `data` 应符合 JSON 格式。
|
222
website/docs/best-practice/alconna/uniseg/segment.md
Normal file
222
website/docs/best-practice/alconna/uniseg/segment.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 2
|
||||||
|
description: 消息段
|
||||||
|
---
|
||||||
|
|
||||||
|
# 通用消息段
|
||||||
|
|
||||||
|
通用消息段是对各适配器中的消息段的抽象总结。其可用于 Alconna 命令的参数定义,也可用于消息的构建和解析。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import Alconna, Args, Image, on_alconna
|
||||||
|
|
||||||
|
meme = on_alconna(Alconna("make_meme", Args["name", str]["img", Image]))
|
||||||
|
|
||||||
|
@meme.handle()
|
||||||
|
async def _(img: Image):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 模型定义
|
||||||
|
|
||||||
|
> **注意**: 本节的内容经过简化。实际情况以源码为准。
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Segment:
|
||||||
|
"""基类标注"""
|
||||||
|
@property
|
||||||
|
def type(self) -> str: ...
|
||||||
|
@property
|
||||||
|
def data(self) -> [str, Any]: ...
|
||||||
|
@property
|
||||||
|
def children(self) -> list["Segment"]: ...
|
||||||
|
|
||||||
|
class Text(Segment):
|
||||||
|
"""Text对象, 表示一类文本元素"""
|
||||||
|
text: str
|
||||||
|
styles: dict[tuple[int, int], list[str]]
|
||||||
|
|
||||||
|
def cover(self, text: str): ...
|
||||||
|
def mark(self, start: Optional[int] = None, end: Optional[int] = None, *styles: str): ...
|
||||||
|
|
||||||
|
class At(Segment):
|
||||||
|
"""At对象, 表示一类提醒某用户的元素"""
|
||||||
|
flag: Literal["user", "role", "channel"]
|
||||||
|
target: str
|
||||||
|
display: Optional[str]
|
||||||
|
|
||||||
|
class AtAll(Segment):
|
||||||
|
"""AtAll对象, 表示一类提醒所有人的元素"""
|
||||||
|
here: bool
|
||||||
|
|
||||||
|
class Emoji(Segment):
|
||||||
|
"""Emoji对象, 表示一类表情元素"""
|
||||||
|
id: str
|
||||||
|
name: Optional[str]
|
||||||
|
|
||||||
|
class Media(Segment):
|
||||||
|
id: Optional[str]
|
||||||
|
url: Optional[str]
|
||||||
|
path: Optional[Union[str, Path]]
|
||||||
|
raw: Optional[Union[bytes, BytesIO]]
|
||||||
|
mimetype: Optional[str]
|
||||||
|
name: str
|
||||||
|
|
||||||
|
to_url: ClassVar[Optional[MediaToUrl]]
|
||||||
|
|
||||||
|
class Image(Media):
|
||||||
|
"""Image对象, 表示一类图片元素"""
|
||||||
|
width: Optional[int]
|
||||||
|
height: Optional[int]
|
||||||
|
|
||||||
|
class Audio(Media):
|
||||||
|
"""Audio对象, 表示一类音频元素"""
|
||||||
|
duration: Optional[float]
|
||||||
|
|
||||||
|
class Voice(Media):
|
||||||
|
"""Voice对象, 表示一类语音元素"""
|
||||||
|
duration: Optional[float]
|
||||||
|
|
||||||
|
class Video(Media):
|
||||||
|
"""Video对象, 表示一类视频元素"""
|
||||||
|
thumbnail: Optional[Image]
|
||||||
|
duration: Optional[float]
|
||||||
|
|
||||||
|
class File(Media):
|
||||||
|
"""File对象, 表示一类文件元素"""
|
||||||
|
|
||||||
|
class Reply(Segment):
|
||||||
|
"""Reply对象,表示一类回复消息"""
|
||||||
|
id: str
|
||||||
|
"""此处不一定是消息ID,可能是其他ID,如消息序号等"""
|
||||||
|
msg: Optional[Union[Message, str]]
|
||||||
|
origin: Optional[Any]
|
||||||
|
|
||||||
|
class Reference(Segment):
|
||||||
|
"""Reference对象,表示一类引用消息。转发消息 (Forward) 也属于此类"""
|
||||||
|
id: Optional[str]
|
||||||
|
"""此处不一定是消息ID,可能是其他ID,如消息序号等"""
|
||||||
|
children: List[Union[RefNode, CustomNode]]
|
||||||
|
|
||||||
|
class Hyper(Segment):
|
||||||
|
"""Hyper对象,表示一类超级消息。如卡片消息、ark消息、小程序等"""
|
||||||
|
format: Literal["xml", "json"]
|
||||||
|
raw: Optional[str]
|
||||||
|
content: Optional[Union[dict, list]]
|
||||||
|
|
||||||
|
class Reference(Segment):
|
||||||
|
"""Reference对象,表示一类引用消息。转发消息 (Forward) 也属于此类"""
|
||||||
|
id: Optional[str]
|
||||||
|
nodes: Sequence[Union[RefNode, CustomNode]]
|
||||||
|
|
||||||
|
class Button(Segment):
|
||||||
|
"""Button对象,表示一类按钮消息"""
|
||||||
|
flag: Literal["action", "link", "input", "enter"]
|
||||||
|
"""
|
||||||
|
- 点击 action 类型的按钮时会触发一个关于 按钮回调 事件,该事件的 button 资源会包含上述 id
|
||||||
|
- 点击 link 类型的按钮时会打开一个链接或者小程序,该链接的地址为 `url`
|
||||||
|
- 点击 input 类型的按钮时会在用户的输入框中填充 `text`
|
||||||
|
- 点击 enter 类型的按钮时会直接发送 `text`
|
||||||
|
"""
|
||||||
|
label: Union[str, Text]
|
||||||
|
"""按钮上的文字"""
|
||||||
|
clicked_label: Optional[str]
|
||||||
|
"""点击后按钮上的文字"""
|
||||||
|
id: Optional[str]
|
||||||
|
url: Optional[str]
|
||||||
|
text: Optional[str]
|
||||||
|
style: Optional[str]
|
||||||
|
"""
|
||||||
|
仅建议使用下列值:primary, secondary, success, warning, danger, info, link, grey, blue
|
||||||
|
|
||||||
|
此处规定 `grey` 与 `secondary` 等同, `blue` 与 `primary` 等同
|
||||||
|
"""
|
||||||
|
permission: Union[Literal["admin", "all"], list[At]] = "all"
|
||||||
|
"""
|
||||||
|
- admin: 仅管理者可操作
|
||||||
|
- all: 所有人可操作
|
||||||
|
- list[At]: 指定用户/身份组可操作
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Keyboard(Segment):
|
||||||
|
"""Keyboard对象,表示一行按钮元素"""
|
||||||
|
id: Optional[str]
|
||||||
|
"""此处一般用来表示模板id,特殊情况下可能表示例如 bot_appid 等"""
|
||||||
|
buttons: Optional[list[Button]]
|
||||||
|
row: Optional[int]
|
||||||
|
"""当消息中只写有一个 Keyboard 时可根据此参数约定按钮组的列数"""
|
||||||
|
|
||||||
|
class Other(Segment):
|
||||||
|
"""其他 Segment"""
|
||||||
|
origin: MessageSegment
|
||||||
|
|
||||||
|
class I18n(Segment):
|
||||||
|
"""特殊的 Segment,用于 i18n 消息"""
|
||||||
|
item_or_scope: Union[LangItem, str]
|
||||||
|
type_: Optional[str] = None
|
||||||
|
|
||||||
|
def tp(self) -> UniMessageTemplate: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
或许你注意到了 `Segment` 上有一个 `children` 属性。
|
||||||
|
|
||||||
|
这是因为在 [`Satori`](https://satori.js.org/zh-CN/) 协议的规定下,一类元素可以用其子元素来代表一类兼容性消息
|
||||||
|
(例如,qq 的商场表情在某些平台上可以用图片代替)。
|
||||||
|
|
||||||
|
为此,本插件提供了 `select` 方法来表达 "命令中获取子元素" 的方法:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import Args, Image, Alconna, select
|
||||||
|
from nonebot_plugin_alconna.builtins.uniseg.market_face import MarketFace
|
||||||
|
|
||||||
|
# 表示这个指令需要的图片会在目标元素下进行搜索,将所有符合 Image 的元素选出来并将第一个作为结果
|
||||||
|
alc1 = Alconna("make_meme", Args["name", str]["img", select(Image).first]) # 也可以使用 select(Image).nth(0)
|
||||||
|
|
||||||
|
# 表示这个指令需要的图片要么直接是 Image 要么是在 MarketFace 元素内的 Image
|
||||||
|
alc2 = Alconna("make_meme", Args["name", str]["img", [Image, select(Image).from_(MarketFace)]])
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以参考通用消息的 [`嵌套提取`](./message.mdx#嵌套提取)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 自定义消息段
|
||||||
|
|
||||||
|
`uniseg` 提供了部分方法来允许用户自定义 Segment 的序列化和反序列化:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from nonebot.adapters import Bot
|
||||||
|
from nonebot.adapters import MessageSegment as BaseMessageSegment
|
||||||
|
from nonebot.adapters.satori import Custom, Message, MessageSegment
|
||||||
|
|
||||||
|
from nonebot_plugin_alconna.uniseg.builder import MessageBuilder
|
||||||
|
from nonebot_plugin_alconna.uniseg.exporter import MessageExporter
|
||||||
|
from nonebot_plugin_alconna.uniseg import Segment, custom_handler, custom_register
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MarketFace(Segment):
|
||||||
|
tabId: str
|
||||||
|
faceId: str
|
||||||
|
key: str
|
||||||
|
|
||||||
|
|
||||||
|
@custom_register(MarketFace, "chronocat:marketface")
|
||||||
|
def mfbuild(builder: MessageBuilder, seg: BaseMessageSegment):
|
||||||
|
if not isinstance(seg, Custom):
|
||||||
|
raise ValueError("MarketFace can only be built from Satori Message")
|
||||||
|
return MarketFace(**seg.data)(*builder.generate(seg.children))
|
||||||
|
|
||||||
|
|
||||||
|
@custom_handler(MarketFace)
|
||||||
|
async def mfexport(exporter: MessageExporter, seg: MarketFace, bot: Bot, fallback: bool):
|
||||||
|
if exporter.get_message_type() is Message:
|
||||||
|
return MessageSegment("chronocat:marketface", seg.data)(await exporter.export(seg.children, bot, fallback))
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
具体而言,你可以使用 `custom_register` 来增加一个从 MessageSegment 到 Segment 的处理方法;使用 `custom_handler` 来增加一个从 Segment 到 MessageSegment 的处理方法。
|
282
website/docs/best-practice/alconna/uniseg/utils.mdx
Normal file
282
website/docs/best-practice/alconna/uniseg/utils.mdx
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 4
|
||||||
|
description: 辅助模型
|
||||||
|
---
|
||||||
|
|
||||||
|
import Tabs from "@theme/Tabs";
|
||||||
|
import TabItem from "@theme/TabItem";
|
||||||
|
|
||||||
|
# 辅助功能
|
||||||
|
|
||||||
|
`uniseg` 模块同时提供了多种方法以通用消息操作。
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
这些方法中与 `event`, `bot` 相关的参数都会尝试从上下文中获取对象。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 消息事件 ID
|
||||||
|
|
||||||
|
消息事件 ID 是用来标识当前消息事件的唯一 ID,通常用于回复/撤回/编辑/表态当前消息。
|
||||||
|
|
||||||
|
<Tabs groupId="get_msgid">
|
||||||
|
<TabItem value="depend" label="使用依赖注入">
|
||||||
|
|
||||||
|
通过提供的 `MessageId` 或 `MsgId` 依赖注入器来获取消息事件 id。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import MsgId
|
||||||
|
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
asycn def _(msg_id: MsgId):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="method" label="使用获取函数">
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot import Event, Bot
|
||||||
|
from nonebot_plugin_alconna.uniseg import get_message_id
|
||||||
|
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
asycn def _(bot: Bot, event: Event):
|
||||||
|
msg_id: str = get_message_id(event, bot)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
该方法获取的消息事件 ID 不推荐直接用于各适配器的 API 调用中,可能会操作失败。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 发送对象
|
||||||
|
|
||||||
|
消息发送对象是用来描述当前消息事件的可发送对象或者主动发送消息时的目标对象,它包含了以下属性:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Target:
|
||||||
|
id: str
|
||||||
|
"""目标id;若为群聊则为 group_id 或者 channel_id,若为私聊则为 user_id"""
|
||||||
|
parent_id: str
|
||||||
|
"""父级id;若为频道则为 guild_id,其他情况下可能为空字符串(例如 Feishu 下可作为部门 id)"""
|
||||||
|
channel: bool
|
||||||
|
"""是否为频道,仅当目标平台符合频道概念时"""
|
||||||
|
private: bool
|
||||||
|
"""是否为私聊"""
|
||||||
|
source: str
|
||||||
|
"""可能的事件id"""
|
||||||
|
self_id: str | None
|
||||||
|
"""机器人id,若为 None 则 Bot 对象会随机选择"""
|
||||||
|
selector: Callable[[Bot], Awaitable[bool]] | None
|
||||||
|
"""选择器,用于在多个 Bot 对象中选择特定 Bot"""
|
||||||
|
extra: dict[str, Any]
|
||||||
|
"""额外信息,用于适配器扩展"""
|
||||||
|
```
|
||||||
|
|
||||||
|
<Tabs groupId="get_target">
|
||||||
|
<TabItem value="depend" label="使用依赖注入">
|
||||||
|
|
||||||
|
通过提供的 `MessageTarget` 或 `MsgTarget` 依赖注入器来获取消息发送对象。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import MsgTarget
|
||||||
|
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
asycn def _(target: MsgTarget):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="method" label="使用获取函数">
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot import Event, Bot
|
||||||
|
from nonebot_plugin_alconna.uniseg import Target, get_target
|
||||||
|
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
asycn def _(bot: Bot, event: Event):
|
||||||
|
target: Target = get_target(event, bot)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
主动构造一个发送对象时,则需要如下参数:
|
||||||
|
|
||||||
|
- `id`: 目标ID;若为群聊则为 `group_id` 或者 `channel_id`,若为私聊则为 `user_id`
|
||||||
|
- `parent_id`: 父级ID;若为频道则为 `guild_id`,其他情况下可能为空字符串(例如 Feishu 下可作为部门 id)
|
||||||
|
- `channel`: 是否为频道,仅当目标平台符合频道概念时
|
||||||
|
- `private`: 是否为私聊
|
||||||
|
- `source`: 可能的事件ID
|
||||||
|
- `self_id`: 机器人id,若为 None 则 Bot 对象会随机选择
|
||||||
|
- `selector`: 选择器,用于在多个 Bot 对象中选择特定 Bot
|
||||||
|
- `scope`: 平台范围,表示当前发送对象的平台类别
|
||||||
|
- `adapter`: 适配器名称,若为 None 则需要明确指定 Bot 对象
|
||||||
|
- `platform`: 平台名称,仅当目标适配器存在多个平台时使用
|
||||||
|
- `extra`: 额外信息,用于适配器扩展
|
||||||
|
|
||||||
|
通过 `Target` 对象,我们可以在 `UniMessage.send` 中指定发送对象:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage, MsgTarget, Target, SupportScope
|
||||||
|
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(target: MsgTarget):
|
||||||
|
# 将消息发送给当前事件的发送者
|
||||||
|
await UniMessage("Hello!").send(target=target)
|
||||||
|
# 主动发送消息给群号为 12345 的 QQ 群聊
|
||||||
|
target1 = Target("12345", scope=SupportScope.qq_client)
|
||||||
|
await UniMessage("Hello!").send(target=target1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选择器
|
||||||
|
|
||||||
|
一般来说,主动发送消息时,`UniMessage.send` 或 `Target.self_id` 应指定一个 `Bot` 对象。但是这样会加重开发者的负担。
|
||||||
|
|
||||||
|
因此,我们提供了选择器来帮助开发者选择一个 `Bot` 对象。当然,这并非说明一定需要传入 `selector` 参数。
|
||||||
|
|
||||||
|
事实上,构造 `Target` 对象时,`self_id`, `scope`, `adapter` 和 `platform` 都会参与到 `selector` 的构造中。
|
||||||
|
|
||||||
|
::tip
|
||||||
|
|
||||||
|
你其实可以使用 `Target` 来帮你筛选 `Bot` 对象:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def _():
|
||||||
|
target = Target("12345", scope=SupportScope.qq_client)
|
||||||
|
bot = await target.select()
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
若配置了 [`alconna_apply_fetch_targets`](../config.md#alconna_apply_fetch_targets) 选项,则在启动时会主动拉取一次发送对象列表。即对于
|
||||||
|
某一主动构造的 `Target` 对象,插件将其与拉取下来的众多发送对象进行匹配,并选择第一个符合条件的发送对象,以选择对应的 Bot 对象。
|
||||||
|
|
||||||
|
## 撤回消息
|
||||||
|
|
||||||
|
通过 `message_recall` 方法来撤回消息事件。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import message_recall
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(msg_id: MsgId):
|
||||||
|
await message_recall(msg_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
`message_recall` 方法的参数如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def message_recall(
|
||||||
|
message_id: str | None = None,
|
||||||
|
event: Event | None = None,
|
||||||
|
bot: Bot | None = None,
|
||||||
|
adapter: str | None = None
|
||||||
|
): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
当 `message_id` 为 `None` 时,插件会尝试从 `event` 中获取消息事件 ID。
|
||||||
|
|
||||||
|
## 编辑消息
|
||||||
|
|
||||||
|
通过 `message_edit` 方法来编辑消息事件。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage, message_edit
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _():
|
||||||
|
await message_edit(UniMessage.text("1234"))
|
||||||
|
```
|
||||||
|
|
||||||
|
`message_edit` 方法的参数如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def message_edit(
|
||||||
|
msg: UniMessage,
|
||||||
|
message_id: str | None = None,
|
||||||
|
event: Event | None = None,
|
||||||
|
bot: Bot | None = None,
|
||||||
|
adapter: str | None = None,
|
||||||
|
): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
当 `message_id` 为 `None` 时,插件会尝试从 `event` 中获取消息事件 ID。
|
||||||
|
|
||||||
|
## 表态消息
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
该方法属于实验性功能。其接口可能会在未来的版本中发生变化。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
通过 `message_reaction` 方法来表态消息事件。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import message_reaction
|
||||||
|
|
||||||
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _():
|
||||||
|
await message_reaction("👍")
|
||||||
|
```
|
||||||
|
|
||||||
|
`message_reaction` 方法的参数如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def message_reaction(
|
||||||
|
reaction: str | Emoji,
|
||||||
|
message_id: str | None = None,
|
||||||
|
event: Event | None = None,
|
||||||
|
bot: Bot | None = None,
|
||||||
|
adapter: str | None = None,
|
||||||
|
delete: bool = False,
|
||||||
|
): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
当 `message_id` 为 `None` 时,插件会尝试从 `event` 中获取消息事件 ID。
|
||||||
|
|
||||||
|
`delete` 参数表示是否删除**自己的**表态消息,默认为 `False`。
|
||||||
|
|
||||||
|
## 响应规则
|
||||||
|
|
||||||
|
`uniseg` 模块提供了两个响应规则:
|
||||||
|
|
||||||
|
- `at_in`: 是否在消息中 @ 了指定的用户
|
||||||
|
- `at_me`: 是否在消息中 @ 了机器人
|
||||||
|
|
||||||
|
相较于 NoneBot 内置的 `to_me` 规则,`at_me` 规则只会在消息中 @ 机器人时触发。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna.uniseg import at_me
|
||||||
|
|
||||||
|
matcher = on_xxx(..., rule=at_me())
|
||||||
|
```
|
@@ -68,15 +68,27 @@ data = data_file.read_text()
|
|||||||
|
|
||||||
## 配置项
|
## 配置项
|
||||||
|
|
||||||
|
### localstore_use_cwd
|
||||||
|
|
||||||
|
使用当前工作目录作为数据存储目录,以下数据目录配置项默认值将会对应变更
|
||||||
|
|
||||||
|
默认值:`False`
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
LOCALSTORE_USE_CWD=true
|
||||||
|
```
|
||||||
|
|
||||||
### localstore_cache_dir
|
### localstore_cache_dir
|
||||||
|
|
||||||
自定义缓存目录
|
自定义缓存目录
|
||||||
|
|
||||||
默认值:
|
默认值:
|
||||||
|
|
||||||
- macOS: `~/Library/Caches/<AppName>`
|
当 `localstore_use_cwd` 为 `True` 时,缓存目录为 `<current_working_directory>/cache`,否则:
|
||||||
- Unix: `~/.cache/<AppName>` (XDG default)
|
|
||||||
- Windows: `C:\Users\<username>\AppData\Local\<AppName>\Cache`
|
- macOS: `~/Library/Caches/nonebot2`
|
||||||
|
- Unix: `~/.cache/nonebot2` (XDG default)
|
||||||
|
- Windows: `C:\Users\<username>\AppData\Local\nonebot2\Cache`
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
LOCALSTORE_CACHE_DIR=/tmp/cache
|
LOCALSTORE_CACHE_DIR=/tmp/cache
|
||||||
@@ -88,10 +100,12 @@ LOCALSTORE_CACHE_DIR=/tmp/cache
|
|||||||
|
|
||||||
默认值:
|
默认值:
|
||||||
|
|
||||||
- macOS: `~/Library/Application Support/<AppName>`
|
当 `localstore_use_cwd` 为 `True` 时,数据目录为 `<current_working_directory>/data`,否则:
|
||||||
- Unix: `~/.local/share/<AppName>` or in $XDG_DATA_HOME, if defined
|
|
||||||
- Win XP (not roaming): `C:\Documents and Settings\<username>\Application Data\<AppName>`
|
- macOS: `~/Library/Application Support/nonebot2`
|
||||||
- Win 7 (not roaming): `C:\Users\<username>\AppData\Local\<AppName>`
|
- Unix: `~/.local/share/nonebot2` or in $XDG_DATA_HOME, if defined
|
||||||
|
- Win XP (not roaming): `C:\Documents and Settings\<username>\Application Data\nonebot2`
|
||||||
|
- Win 7 (not roaming): `C:\Users\<username>\AppData\Local\nonebot2`
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
LOCALSTORE_DATA_DIR=/tmp/data
|
LOCALSTORE_DATA_DIR=/tmp/data
|
||||||
@@ -103,10 +117,12 @@ LOCALSTORE_DATA_DIR=/tmp/data
|
|||||||
|
|
||||||
默认值:
|
默认值:
|
||||||
|
|
||||||
|
当 `localstore_use_cwd` 为 `True` 时,配置目录为 `<current_working_directory>/config`,否则:
|
||||||
|
|
||||||
- macOS: same as user_data_dir
|
- macOS: same as user_data_dir
|
||||||
- Unix: `~/.config/<AppName>`
|
- Unix: `~/.config/nonebot2`
|
||||||
- Win XP (roaming): `C:\Documents and Settings\<username>\Local Settings\Application Data\<AppName>`
|
- Win XP (roaming): `C:\Documents and Settings\<username>\Local Settings\Application Data\nonebot2`
|
||||||
- Win 7 (roaming): `C:\Users\<username>\AppData\Roaming\<AppName>`
|
- Win 7 (roaming): `C:\Users\<username>\AppData\Roaming\nonebot2`
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
LOCALSTORE_CONFIG_DIR=/tmp/config
|
LOCALSTORE_CONFIG_DIR=/tmp/config
|
||||||
|
@@ -8,13 +8,15 @@ description: 插件跨平台支持
|
|||||||
import Tabs from "@theme/Tabs";
|
import Tabs from "@theme/Tabs";
|
||||||
import TabItem from "@theme/TabItem";
|
import TabItem from "@theme/TabItem";
|
||||||
|
|
||||||
|
## 使用 NoneBot 本身
|
||||||
|
|
||||||
由于不同平台的事件与接口之间,存在着极大的差异性,NoneBot 通过[重载](../appendices/overload.md)的方式,使得插件可以在不同平台上正确响应。但为了减少跨平台的兼容性问题,我们应该尽可能的使用基类方法实现原生跨平台,而不是使用特定平台的方法。当基类方法无法满足需求时,我们可以使用依赖注入的方式,将特定平台的事件或机器人注入到事件处理函数中,实现针对特定平台的处理。
|
由于不同平台的事件与接口之间,存在着极大的差异性,NoneBot 通过[重载](../appendices/overload.md)的方式,使得插件可以在不同平台上正确响应。但为了减少跨平台的兼容性问题,我们应该尽可能的使用基类方法实现原生跨平台,而不是使用特定平台的方法。当基类方法无法满足需求时,我们可以使用依赖注入的方式,将特定平台的事件或机器人注入到事件处理函数中,实现针对特定平台的处理。
|
||||||
|
|
||||||
:::tip 提示
|
:::tip 提示
|
||||||
如果需要在多平台上**使用**跨平台插件,首先应该根据[注册适配器](../advanced/adapter.md#注册适配器)一节,为机器人注册各平台对应的适配器。
|
如果需要在多平台上**使用**跨平台插件,首先应该根据[注册适配器](../advanced/adapter.md#注册适配器)一节,为机器人注册各平台对应的适配器。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 基于基类的跨平台
|
### 基于基类的跨平台
|
||||||
|
|
||||||
在[事件通用信息](../advanced/adapter.md#获取事件通用信息)中,我们了解了事件基类能够提供的通用信息。同时,[事件响应器操作](../appendices/session-control.mdx#更多事件响应器操作)也为我们提供了基本的用户交互方式。使用这些方法,可以让我们的插件运行在任何平台上。例如,一个简单的命令处理插件:
|
在[事件通用信息](../advanced/adapter.md#获取事件通用信息)中,我们了解了事件基类能够提供的通用信息。同时,[事件响应器操作](../appendices/session-control.mdx#更多事件响应器操作)也为我们提供了基本的用户交互方式。使用这些方法,可以让我们的插件运行在任何平台上。例如,一个简单的命令处理插件:
|
||||||
|
|
||||||
@@ -34,11 +36,11 @@ async def handle_function():
|
|||||||
|
|
||||||
由于此插件仅使用了事件通用信息和事件响应器操作的纯文本交互方式,这些方法不使用特定平台的信息或接口,因此是原生跨平台的,并不需要额外处理。但在一些较为复杂的需求下,例如发送图片消息时,并非所有平台都具有统一的接口,因此基类便无能为力,我们需要引入特定平台的适配器了。
|
由于此插件仅使用了事件通用信息和事件响应器操作的纯文本交互方式,这些方法不使用特定平台的信息或接口,因此是原生跨平台的,并不需要额外处理。但在一些较为复杂的需求下,例如发送图片消息时,并非所有平台都具有统一的接口,因此基类便无能为力,我们需要引入特定平台的适配器了。
|
||||||
|
|
||||||
## 基于重载的跨平台
|
### 基于重载的跨平台
|
||||||
|
|
||||||
重载是 NoneBot 跨平台操作的核心,在[事件类型与重载](../appendices/overload.md#重载)一节中,我们初步了解了如何通过类型注解来实现针对不同平台事件的处理方式。在[依赖注入](../advanced/dependency.mdx)一节中,我们又对依赖注入的使用方法进行了详细的介绍。结合这两节内容,我们可以实现更复杂的跨平台操作。
|
重载是 NoneBot 跨平台操作的核心,在[事件类型与重载](../appendices/overload.md#重载)一节中,我们初步了解了如何通过类型注解来实现针对不同平台事件的处理方式。在[依赖注入](../advanced/dependency.mdx)一节中,我们又对依赖注入的使用方法进行了详细的介绍。结合这两节内容,我们可以实现更复杂的跨平台操作。
|
||||||
|
|
||||||
### 处理近似事件
|
#### 处理近似事件
|
||||||
|
|
||||||
对于一系列**差异不大**的事件,我们往往具有相同的处理逻辑。这时,我们不希望将相同的逻辑编写两遍,而应该复用代码,以实现在同一个事件处理函数中处理多个近似事件。我们可以使用[事件重载](../advanced/dependency.mdx#Event)的特性来实现这一功能。例如:
|
对于一系列**差异不大**的事件,我们往往具有相同的处理逻辑。这时,我们不希望将相同的逻辑编写两遍,而应该复用代码,以实现在同一个事件处理函数中处理多个近似事件。我们可以使用[事件重载](../advanced/dependency.mdx#Event)的特性来实现这一功能。例如:
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ async def handle_function(event: Union[OnebotV11MessageEvent, OnebotV12MessageEv
|
|||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
### 在依赖注入中使用重载
|
#### 在依赖注入中使用重载
|
||||||
|
|
||||||
NoneBot 依赖注入系统提供了自定义子依赖的方法,子依赖的类型同样会影响到事件处理函数的重载行为。例如:
|
NoneBot 依赖注入系统提供了自定义子依赖的方法,子依赖的类型同样会影响到事件处理函数的重载行为。例如:
|
||||||
|
|
||||||
@@ -104,7 +106,7 @@ async def handle_function(time: datetime = Depends(get_event_time)):
|
|||||||
|
|
||||||
示例中 ,我们为 `handle_function` 事件处理函数注入了自定义的 `get_event_time` 子依赖,而此子依赖注入参数为 Console 适配器的 `MessageEvent`。因此 `handle_function` 仅会响应 Console 适配器的 `MessageEvent` ,而不能响应其他事件。
|
示例中 ,我们为 `handle_function` 事件处理函数注入了自定义的 `get_event_time` 子依赖,而此子依赖注入参数为 Console 适配器的 `MessageEvent`。因此 `handle_function` 仅会响应 Console 适配器的 `MessageEvent` ,而不能响应其他事件。
|
||||||
|
|
||||||
### 处理多平台事件
|
#### 处理多平台事件
|
||||||
|
|
||||||
不同平台的事件之间,往往存在着极大的差异性。为了满足我们插件的跨平台运行,通常我们需要抽离业务逻辑,以保证代码的复用性。一个合理的做法是,在事件响应器的处理流程中,首先先针对不同平台的事件分别进行处理,提取出核心业务逻辑所需要的信息;然后再将这些信息传递给业务逻辑处理函数;最后将业务逻辑的输出以各平台合适的方式返回给用户。也就是说,与平台绑定的处理部分应该与平台无关部分尽量分离。例如:
|
不同平台的事件之间,往往存在着极大的差异性。为了满足我们插件的跨平台运行,通常我们需要抽离业务逻辑,以保证代码的复用性。一个合理的做法是,在事件响应器的处理流程中,首先先针对不同平台的事件分别进行处理,提取出核心业务逻辑所需要的信息;然后再将这些信息传递给业务逻辑处理函数;最后将业务逻辑的输出以各平台合适的方式返回给用户。也就是说,与平台绑定的处理部分应该与平台无关部分尽量分离。例如:
|
||||||
|
|
||||||
@@ -178,6 +180,29 @@ async def handle_onebot_reply(bot: OnebotBot, state: T_State, location: str = Ar
|
|||||||
await weather.send(f"今天{location}的天气是{state['weather']}")
|
await weather.send(f"今天{location}的天气是{state['weather']}")
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip 提示
|
## 使用插件
|
||||||
NoneBot 社区中有一些插件,例如[all4one](https://github.com/nonepkg/nonebot-plugin-all4one)、[send-anything-anywhere](https://github.com/felinae98/nonebot-plugin-send-anything-anywhere),可以帮助你更好地处理跨平台功能,包括事件处理和消息发送等。
|
|
||||||
:::
|
得益于众多开发者为 NoneBot 社区做出的贡献,我们可以通过一系列插件来完成跨平台插件的开发。
|
||||||
|
|
||||||
|
这些插件可以分为三类:
|
||||||
|
|
||||||
|
### 事件处理
|
||||||
|
|
||||||
|
- [all4one](https://github.com/nonepkg/nonebot-plugin-all4one): 将不同平台的事件转为符合 OneBot V12 协议的插件
|
||||||
|
- 支持的适配器: OneBot V11/V12, Discord, QQ, Telegram
|
||||||
|
|
||||||
|
### 消息处理
|
||||||
|
|
||||||
|
- [alconna](https://github.com/nonebot/plugin-alconna): 对几乎所有适配器中消息的收发、撤回、编辑、表态的统一插件
|
||||||
|
- 支持的适配器: OneBot V11/V12, Telegram, Feishu, Github, QQ, Ding, Console, Kaiheila, Mirai, NtChat, Minecraft, Discord, Satori, Red, Dodo, Kritor, Tailchat, Mail, WXMP, Heybox, Gewechat
|
||||||
|
- [send-anything-anywhere](https://github.com/felinae98/nonebot-plugin-send-anything-anywhere): 帮助处理不同适配器消息的适配和发送的插件
|
||||||
|
- 支持的适配器: OneBot V11/V12, Kaiheila, Telegram, Feishu, Red, DoDo, Satori, QQ, Discord
|
||||||
|
|
||||||
|
### 会话信息提取
|
||||||
|
|
||||||
|
- [uninfo](https://github.com/RF-Tar-Railt/nonebot-plugin-uninfo): 多平台的会话信息(用户、群组、频道)获取插件
|
||||||
|
- 支持的适配器: OneBot V11/V12, Telegram, Feishu, QQ, Console, Kaiheila, Mirai, Minecraft, Discord, Satori, Dodo, Kritor, Mail, WXMP, Gewechat
|
||||||
|
- [session](https://github.com/noneplugin/nonebot-plugin-session): 会话信息提取与会话 id 定义插件
|
||||||
|
- 支持的适配器: OneBot V11/V12, Console, Kaiheila, Telegram, Feishu, Red, DoDo, Satori, QQ, Discord
|
||||||
|
- [userinfo](https://github.com/noneplugin/nonebot-plugin-userinfo: 用户信息获取插件
|
||||||
|
- 支持的适配器: OneBot V11/V12, Console, Kaiheila, Telegram, Feishu, Red, DoDo, Satori, QQ, Discord
|
||||||
|
@@ -48,7 +48,11 @@ NoneBot 插件使用下述命名规范:
|
|||||||
|
|
||||||
#### 第三方项目模板
|
#### 第三方项目模板
|
||||||
|
|
||||||
一些社区用户可能会分享自己制作的项目模板方便大家使用,如:[A-kirami/nonebot-plugin-template](https://github.com/A-kirami/nonebot-plugin-template) 等。
|
一些社区用户可能会分享自己制作的项目模板方便大家使用,如:
|
||||||
|
|
||||||
|
- [A-kirami/nonebot-plugin-template](https://github.com/A-kirami/nonebot-plugin-template)
|
||||||
|
- [RF-Tar-Railt/nonebot-plugin-template](https://github.com/RF-Tar-Railt/nonebot-plugin-template)
|
||||||
|
- [fllesser/nonebot-plugin-template](https://github.com/fllesser/nonebot-plugin-template)
|
||||||
|
|
||||||
:::tip 提示
|
:::tip 提示
|
||||||
本文档**不保证**第三方模板的适用性。
|
本文档**不保证**第三方模板的适用性。
|
||||||
|
81
website/docs/ospp/2025.md
Normal file
81
website/docs/ospp/2025.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 4
|
||||||
|
description: 开源之夏 - 暑期 2025
|
||||||
|
mdx:
|
||||||
|
format: md
|
||||||
|
---
|
||||||
|
|
||||||
|
# 暑期 2025
|
||||||
|
|
||||||
|
**开源之夏 - 暑期 2025** 是**中国科学院软件研究所**发起的**开源软件供应链点亮计划**系列暑期活动,旨在鼓励高校学生积极参与开源软件的开发维护,培养和发掘更多优秀的开发者,促进优秀开源软件社区的蓬勃发展,助力开源软件供应链建设。活动联合各大开源社区,针对重要开源软件的开发与维护提供项目开发任务,并向全球高校学生开放报名。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
||||||
|
|
||||||
|
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
|
||||||
|
|
||||||
|
## NoneBot HTML 图片渲染插件
|
||||||
|
|
||||||
|
文字与图片一直是聊天机器人的两大主流交互方式,而图片的渲染一直是用户开发应用的一大痛点。常见的方式包括 PIL 图片编辑、浏览器渲染 HTML 截图等。PIL 图片编辑依赖人工构建图片布局,容易出现自适应问题,且提升图片特效、美观程度需要极大的开发成本。浏览器渲染方案通过 HTML 与 CSS 能够轻松完成美观自适应能力强的布局,但其部署门槛较高,难以支撑较大规模调用量。而其他轻量化渲染引擎通常不具有完整 HTML/CSS 现代化标准实现,且未提供 Python Binding 直接使用。
|
||||||
|
|
||||||
|
本项目希望调研并实现一种高效、便捷的图片渲染方案。该方案需要在保障跨平台一致性、最大程度保证 HTML 与 CSS 现代化标准的前提下,低成本(资源消耗与吞吐量)将 HTML 渲染为对应图片。
|
||||||
|
|
||||||
|
**难度**:进阶
|
||||||
|
|
||||||
|
**导师**:[@MelodyKnit](https://github.com/MelodyKnit)
|
||||||
|
|
||||||
|
**产出要求**
|
||||||
|
|
||||||
|
- 调研 HTML/CSS 渲染引擎
|
||||||
|
- 调研 litehtml 等渲染引擎 标准支持能力与兼容性
|
||||||
|
- 基于渲染引擎实现 HTML 图片渲染插件
|
||||||
|
- 将渲染引擎通过 binding 等方式集成为 Python 模块
|
||||||
|
- 基于集成模块实现 HTML 图片渲染能力
|
||||||
|
- 编写插件使用文档
|
||||||
|
|
||||||
|
**技术要求**
|
||||||
|
|
||||||
|
- 掌握 Python 及其异步编程
|
||||||
|
- 熟悉 NoneBot 框架及其插件编写
|
||||||
|
- 了解浏览器与 HTML 渲染原理
|
||||||
|
|
||||||
|
**成果仓库**
|
||||||
|
|
||||||
|
- <https://github.com/nonebot/plugin-htmlkit>
|
||||||
|
|
||||||
|
## NB-CLI 命令行工具交互优化
|
||||||
|
|
||||||
|
NB-CLI 作为 NoneBot 生态的核心入门与管理工具,主要负责新手引导项目创建、项目运行以及插件管理几大功能。目前该脚手架工具仍存在几点缺陷:
|
||||||
|
|
||||||
|
- 作为插件管理工具,由于存储数据的局限性,无法很好地展示用户项目当前安装插件状态,并进行卸载等操作;
|
||||||
|
- 当前插件管理高度依赖云端 registry 提供插件信息,在离线情况下完全无法使用;
|
||||||
|
- 由于插件信息繁多,工具未能向用户展示充分的信息,交互复杂 体验较差。
|
||||||
|
|
||||||
|
以上问题对用户使用 NB-CLI 管理项目插件造成了极大的阻碍。
|
||||||
|
本项目希望重点针对插件管理部分,重构工具插件管理模块,完善框架缺陷,并通过缓存等方式确保可用性。其次,调研同类工具方案与 TUI 等相关技术,优化信息展示能力、用户交互方式,提升工具整体交互体验。
|
||||||
|
|
||||||
|
**相关链接**
|
||||||
|
|
||||||
|
- https://github.com/nonebot/nb-cli/issues/138
|
||||||
|
- https://github.com/nonebot/nb-cli/issues/140
|
||||||
|
|
||||||
|
**难度**:基础
|
||||||
|
|
||||||
|
**导师**:[@yanyongyu](https://github.com/yanyongyu)
|
||||||
|
|
||||||
|
**产出要求**
|
||||||
|
|
||||||
|
- 重构 NB-CLI 插件管理模块
|
||||||
|
- 优化项目插件信息存储方式,支持列出、卸载插件等操作
|
||||||
|
- 通过缓存 registry 数据等方式确保离线场景的可用性
|
||||||
|
- 提升 NB-CLI 交互体验
|
||||||
|
- 调研同类工具方案与 TUI 等相关技术
|
||||||
|
- 优化 registry 多字段信息展示能力
|
||||||
|
- 基于 TUI 等技术优化用户交互方式,提升整体交互体验
|
||||||
|
|
||||||
|
**技术要求**
|
||||||
|
|
||||||
|
- 熟练掌握 Python 及其异步编程
|
||||||
|
- 熟悉 NoneBot 框架与 NB-CLI 使用方法
|
||||||
|
- 了解 TUI 等终端交互技术
|
||||||
|
|
||||||
|
**成果仓库**
|
||||||
|
|
||||||
|
- <https://github.com/nonebot/nb-cli>
|
@@ -1,15 +1,8 @@
|
|||||||
|
import { themes } from "prism-react-renderer";
|
||||||
|
|
||||||
import type { Config } from "@docusaurus/types";
|
import type { Config } from "@docusaurus/types";
|
||||||
import type { Options as ChangelogOptions } from "@nullbot/docusaurus-plugin-changelog";
|
import type { Options as ChangelogOptions } from "@nullbot/docusaurus-plugin-changelog";
|
||||||
import type * as Preset from "@nullbot/docusaurus-preset-nonepress";
|
import type * as Preset from "@nullbot/docusaurus-preset-nonepress";
|
||||||
import { themes } from "prism-react-renderer";
|
|
||||||
|
|
||||||
// By default, we use Docusaurus Faster
|
|
||||||
// DOCUSAURUS_SLOWER=true is useful for benchmarking faster against slower
|
|
||||||
// hyperfine --prepare 'yarn clear:website' --runs 3 'DOCUSAURUS_SLOWER=true yarn build:website:fast' 'yarn build:website:fast'
|
|
||||||
const isSlower = process.env.DOCUSAURUS_SLOWER === "true";
|
|
||||||
if (isSlower) {
|
|
||||||
console.log("🐢 Using slower Docusaurus build");
|
|
||||||
}
|
|
||||||
|
|
||||||
// color mode config
|
// color mode config
|
||||||
const colorMode: Preset.ThemeConfig["colorMode"] = {
|
const colorMode: Preset.ThemeConfig["colorMode"] = {
|
||||||
@@ -66,7 +59,7 @@ const navbar: Preset.ThemeConfig["navbar"] = {
|
|||||||
docId: "developer/plugin-publishing",
|
docId: "developer/plugin-publishing",
|
||||||
},
|
},
|
||||||
{ label: "社区", type: "doc", docId: "community/contact" },
|
{ label: "社区", type: "doc", docId: "community/contact" },
|
||||||
{ label: "开源之夏", type: "doc", docId: "ospp/2024" },
|
{ label: "开源之夏", type: "doc", docId: "ospp/2025" },
|
||||||
{ label: "商店", to: "/store/plugins" },
|
{ label: "商店", to: "/store/plugins" },
|
||||||
{ label: "更新日志", to: "/changelog/" },
|
{ label: "更新日志", to: "/changelog/" },
|
||||||
{ label: "论坛", href: "https://discussions.nonebot.dev" },
|
{ label: "论坛", href: "https://discussions.nonebot.dev" },
|
||||||
|
@@ -13,37 +13,39 @@
|
|||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
"start": "docusaurus start --host 0.0.0.0 --port 3000",
|
"start": "docusaurus start --host 0.0.0.0 --port 3000",
|
||||||
"build": "docusaurus build",
|
"build": "docusaurus build",
|
||||||
|
"build:fast": "cross-env BUILD_FAST=true yarn build",
|
||||||
|
"build:fast:rsdoctor": "cross-env BUILD_FAST=true RSDOCTOR=true yarn build",
|
||||||
|
"build:fast:profile": "cross-env BUILD_FAST=true node --cpu-prof --cpu-prof-dir .cpu-prof ./node_modules/.bin/docusaurus build",
|
||||||
"swizzle": "docusaurus swizzle",
|
"swizzle": "docusaurus swizzle",
|
||||||
"deploy": "docusaurus deploy",
|
"deploy": "docusaurus deploy",
|
||||||
"clear": "docusaurus clear",
|
"clear": "docusaurus clear",
|
||||||
"serve": "docusaurus serve",
|
"serve": "docusaurus serve",
|
||||||
"write-translations": "docusaurus write-translations",
|
"write-translations": "docusaurus write-translations",
|
||||||
"write-heading-ids": "docusaurus write-heading-ids",
|
"write-heading-ids": "docusaurus write-heading-ids",
|
||||||
"typecheck": "tsc"
|
"typecheck": "tsc",
|
||||||
|
"prettier": "prettier --config ../.prettierrc --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "^3.6.2",
|
"@docusaurus/core": "^3.7.0",
|
||||||
|
"@docusaurus/eslint-plugin": "3.7.0",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"@nullbot/docusaurus-plugin-changelog": "^3.0.0",
|
"@nullbot/docusaurus-plugin-changelog": "^3.0.0",
|
||||||
"@nullbot/docusaurus-preset-nonepress": "^3.0.0",
|
"@nullbot/docusaurus-preset-nonepress": "^3.0.0",
|
||||||
"@swc/core": "^1.7.26",
|
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"copy-text-to-clipboard": "^3.2.0",
|
"copy-text-to-clipboard": "^3.2.0",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
"raw-loader": "^4.0.2",
|
"react": "^19.0.0",
|
||||||
"react": "^18.0.0",
|
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-use-pagination": "^2.0.1",
|
"react-use-pagination": "^2.0.1"
|
||||||
"swc-loader": "^0.2.6"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/faster": "^3.6.2",
|
"@docusaurus/faster": "^3.7.0",
|
||||||
"@docusaurus/module-type-aliases": "^3.6.2",
|
"@docusaurus/module-type-aliases": "^3.7.0",
|
||||||
"@nullbot/docusaurus-tsconfig": "^3.0.0",
|
"@nullbot/docusaurus-tsconfig": "^3.0.0",
|
||||||
"@types/react-color": "^3.0.10",
|
"@types/react-color": "^3.0.10",
|
||||||
"asciinema-player": "^3.5.0",
|
"asciinema-player": "^3.5.0",
|
||||||
"typescript": "~5.5.2"
|
"typescript": "~5.7.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
@@ -10,9 +10,10 @@
|
|||||||
*/
|
*/
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
|
|
||||||
import { getChangelogItemsSync } from "@nullbot/docusaurus-plugin-changelog";
|
import { getChangelogItemsSync } from "@nullbot/docusaurus-plugin-changelog";
|
||||||
|
|
||||||
|
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
|
||||||
|
|
||||||
const changelogPath = path.join(__dirname, "src/changelog/changelog.md");
|
const changelogPath = path.join(__dirname, "src/changelog/changelog.md");
|
||||||
const changelogItems = getChangelogItemsSync(changelogPath, 10);
|
const changelogItems = getChangelogItemsSync(changelogPath, 10);
|
||||||
|
|
||||||
|
@@ -5,6 +5,174 @@ toc_max_heading_level: 2
|
|||||||
|
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
## 最近更新
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Docs: 增加 `Milky` 适配器说明 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#3492](https://github.com/nonebot/nonebot2/pull/3492))
|
||||||
|
- Docs: 添加 OSPP 2025 项目 [@yanyongyu](https://github.com/yanyongyu) ([#3466](https://github.com/nonebot/nonebot2/pull/3466))
|
||||||
|
- Docs: 更新最佳实践 `Alconna` 章节 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#3447](https://github.com/nonebot/nonebot2/pull/3447))
|
||||||
|
- Docs: 修复移动端侧边栏折叠状态异常 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3414](https://github.com/nonebot/nonebot2/pull/3414))
|
||||||
|
- Docs: 添加 Gewechat 适配器描述 [@Shine-Light](https://github.com/Shine-Light) ([#3372](https://github.com/nonebot/nonebot2/pull/3372))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Develop: 修复 devcontainer feature 配置 [@yanyongyu](https://github.com/yanyongyu) ([#3515](https://github.com/nonebot/nonebot2/pull/3515))
|
||||||
|
- CI: 修复 Ruff 并发组名称 [@KomoriDev](https://github.com/KomoriDev) ([#3434](https://github.com/nonebot/nonebot2/pull/3434))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: 绝区零角色数据获取 [@noneflow](https://github.com/noneflow) ([#3512](https://github.com/nonebot/nonebot2/pull/3512))
|
||||||
|
- Plugin: 命令冷却 [@noneflow](https://github.com/noneflow) ([#3499](https://github.com/nonebot/nonebot2/pull/3499))
|
||||||
|
- Plugin: Hacker News [@noneflow](https://github.com/noneflow) ([#3502](https://github.com/nonebot/nonebot2/pull/3502))
|
||||||
|
- Plugin: nonebot_plugin_df_armor_repair_simulator [@noneflow](https://github.com/noneflow) ([#3506](https://github.com/nonebot/nonebot2/pull/3506))
|
||||||
|
- Plugin: nonebot-plugin-emojilike-automonkey [@noneflow](https://github.com/noneflow) ([#3386](https://github.com/nonebot/nonebot2/pull/3386))
|
||||||
|
- Plugin: bfvplayerlist [@noneflow](https://github.com/noneflow) ([#3450](https://github.com/nonebot/nonebot2/pull/3450))
|
||||||
|
- Plugin: nonebot-plugin-pokemonle [@noneflow](https://github.com/noneflow) ([#3504](https://github.com/nonebot/nonebot2/pull/3504))
|
||||||
|
- Plugin: NoneBotCloversClient [@noneflow](https://github.com/noneflow) ([#3494](https://github.com/nonebot/nonebot2/pull/3494))
|
||||||
|
- Plugin: NYATuringTest [@noneflow](https://github.com/noneflow) ([#3479](https://github.com/nonebot/nonebot2/pull/3479))
|
||||||
|
- Plugin: PicMenu Next [@noneflow](https://github.com/noneflow) ([#3488](https://github.com/nonebot/nonebot2/pull/3488))
|
||||||
|
- Plugin: nonebot-plugin-ehentai [@noneflow](https://github.com/noneflow) ([#3475](https://github.com/nonebot/nonebot2/pull/3475))
|
||||||
|
- Plugin: 真寻农场 [@noneflow](https://github.com/noneflow) ([#3477](https://github.com/nonebot/nonebot2/pull/3477))
|
||||||
|
- Plugin: 修复 QQ 图床 SSL 错误 [@noneflow](https://github.com/noneflow) ([#3482](https://github.com/nonebot/nonebot2/pull/3482))
|
||||||
|
- Plugin: 多源日报 [@noneflow](https://github.com/noneflow) ([#3468](https://github.com/nonebot/nonebot2/pull/3468))
|
||||||
|
- Plugin: QQ账号详细信息查询 [@noneflow](https://github.com/noneflow) ([#3472](https://github.com/nonebot/nonebot2/pull/3472))
|
||||||
|
- Plugin: doro大冒险 [@noneflow](https://github.com/noneflow) ([#3465](https://github.com/nonebot/nonebot2/pull/3465))
|
||||||
|
- Plugin: nonebot_plugin_paper [@noneflow](https://github.com/noneflow) ([#3431](https://github.com/nonebot/nonebot2/pull/3431))
|
||||||
|
- Plugin: Web侧载 [@noneflow](https://github.com/noneflow) ([#3470](https://github.com/nonebot/nonebot2/pull/3470))
|
||||||
|
- Plugin: 群聊广告哒咩 [@noneflow](https://github.com/noneflow) ([#3446](https://github.com/nonebot/nonebot2/pull/3446))
|
||||||
|
- Plugin: 词库语言进阶版 [@noneflow](https://github.com/noneflow) ([#3459](https://github.com/nonebot/nonebot2/pull/3459))
|
||||||
|
- Plugin: nonebot-plugin-mhguesser [@noneflow](https://github.com/noneflow) ([#3464](https://github.com/nonebot/nonebot2/pull/3464))
|
||||||
|
- Plugin: 扑克对决 [@noneflow](https://github.com/noneflow) ([#3409](https://github.com/nonebot/nonebot2/pull/3409))
|
||||||
|
- Plugin: 一言+ [@noneflow](https://github.com/noneflow) ([#3444](https://github.com/nonebot/nonebot2/pull/3444))
|
||||||
|
- Plugin: nonebot-plugin-ban-sticker [@noneflow](https://github.com/noneflow) ([#3429](https://github.com/nonebot/nonebot2/pull/3429))
|
||||||
|
- Plugin: 塔吉多助手 [@noneflow](https://github.com/noneflow) ([#3455](https://github.com/nonebot/nonebot2/pull/3455))
|
||||||
|
- Plugin: nonebot-plugin-jmcomic [@noneflow](https://github.com/noneflow) ([#3391](https://github.com/nonebot/nonebot2/pull/3391))
|
||||||
|
- Plugin: nonebot-plugin-custom-face [@noneflow](https://github.com/noneflow) ([#3449](https://github.com/nonebot/nonebot2/pull/3449))
|
||||||
|
- Plugin: 简易左轮禁言 [@noneflow](https://github.com/noneflow) ([#3453](https://github.com/nonebot/nonebot2/pull/3453))
|
||||||
|
- Plugin: mcmod百科插件 [@noneflow](https://github.com/noneflow) ([#3443](https://github.com/nonebot/nonebot2/pull/3443))
|
||||||
|
- Plugin: LaTeX 在线渲染插件 [@noneflow](https://github.com/noneflow) ([#3314](https://github.com/nonebot/nonebot2/pull/3314))
|
||||||
|
- Plugin: nonebot-plugin-anywhere-llm [@noneflow](https://github.com/noneflow) ([#3393](https://github.com/nonebot/nonebot2/pull/3393))
|
||||||
|
- Plugin: 贴吧监控 [@noneflow](https://github.com/noneflow) ([#3375](https://github.com/nonebot/nonebot2/pull/3375))
|
||||||
|
- Plugin: bfvservermap [@noneflow](https://github.com/noneflow) ([#3451](https://github.com/nonebot/nonebot2/pull/3451))
|
||||||
|
- Plugin: github_release_notifier [@noneflow](https://github.com/noneflow) ([#3388](https://github.com/nonebot/nonebot2/pull/3388))
|
||||||
|
- Plugin: 暗语消息 [@noneflow](https://github.com/noneflow) ([#3433](https://github.com/nonebot/nonebot2/pull/3433))
|
||||||
|
- Plugin: 森空岛 [@noneflow](https://github.com/noneflow) ([#3420](https://github.com/nonebot/nonebot2/pull/3420))
|
||||||
|
- Plugin: asmr100 [@noneflow](https://github.com/noneflow) ([#3418](https://github.com/nonebot/nonebot2/pull/3418))
|
||||||
|
- Plugin: 报错处理器 [@noneflow](https://github.com/noneflow) ([#3411](https://github.com/nonebot/nonebot2/pull/3411))
|
||||||
|
- Plugin: AI 群聊助手 [@noneflow](https://github.com/noneflow) ([#3424](https://github.com/nonebot/nonebot2/pull/3424))
|
||||||
|
- Plugin: nonebot-plugin-furryyunhei [@noneflow](https://github.com/noneflow) ([#3383](https://github.com/nonebot/nonebot2/pull/3383))
|
||||||
|
- Plugin: MC服务器/玩家查询 [@noneflow](https://github.com/noneflow) ([#3422](https://github.com/nonebot/nonebot2/pull/3422))
|
||||||
|
- Plugin: none_plugin_oi_helper [@noneflow](https://github.com/noneflow) ([#3416](https://github.com/nonebot/nonebot2/pull/3416))
|
||||||
|
- Plugin: Gemini Vision [@noneflow](https://github.com/noneflow) ([#3413](https://github.com/nonebot/nonebot2/pull/3413))
|
||||||
|
- Plugin: nonebot-plugin-zssm [@noneflow](https://github.com/noneflow) ([#3403](https://github.com/nonebot/nonebot2/pull/3403))
|
||||||
|
- Plugin: 禁漫下载 [@noneflow](https://github.com/noneflow) ([#3395](https://github.com/nonebot/nonebot2/pull/3395))
|
||||||
|
- Plugin: JMComic插件 [@noneflow](https://github.com/noneflow) ([#3398](https://github.com/nonebot/nonebot2/pull/3398))
|
||||||
|
- Plugin: MoEllm聊天 [@noneflow](https://github.com/noneflow) ([#3351](https://github.com/nonebot/nonebot2/pull/3351))
|
||||||
|
- Plugin: whoasked [@noneflow](https://github.com/noneflow) ([#3367](https://github.com/nonebot/nonebot2/pull/3367))
|
||||||
|
- Plugin: 金价查询 [@noneflow](https://github.com/noneflow) ([#3378](https://github.com/nonebot/nonebot2/pull/3378))
|
||||||
|
- Plugin: 丁真语音生成器 [@noneflow](https://github.com/noneflow) ([#3316](https://github.com/nonebot/nonebot2/pull/3316))
|
||||||
|
- Plugin: LLM调用nonebot插件 [@noneflow](https://github.com/noneflow) ([#3380](https://github.com/nonebot/nonebot2/pull/3380))
|
||||||
|
- Plugin: SuggarChat CloudFlare协议扩展附属插件 [@noneflow](https://github.com/noneflow) ([#3371](https://github.com/nonebot/nonebot2/pull/3371))
|
||||||
|
- Plugin: No Dirty Message [@noneflow](https://github.com/noneflow) ([#3360](https://github.com/nonebot/nonebot2/pull/3360))
|
||||||
|
- Plugin: maimai猜歌小游戏 [@noneflow](https://github.com/noneflow) ([#3318](https://github.com/nonebot/nonebot2/pull/3318))
|
||||||
|
- Plugin: nonebot-plugin-aiochatllm [@noneflow](https://github.com/noneflow) ([#3358](https://github.com/nonebot/nonebot2/pull/3358))
|
||||||
|
- Plugin: 拟人回复bot [@noneflow](https://github.com/noneflow) ([#3353](https://github.com/nonebot/nonebot2/pull/3353))
|
||||||
|
|
||||||
|
### 🍻 机器人发布
|
||||||
|
|
||||||
|
- Bot: Muicebot [@noneflow](https://github.com/noneflow) ([#3523](https://github.com/nonebot/nonebot2/pull/3523))
|
||||||
|
- Bot: LiteBot [@noneflow](https://github.com/noneflow) ([#3514](https://github.com/nonebot/nonebot2/pull/3514))
|
||||||
|
|
||||||
|
### 🍻 适配器发布
|
||||||
|
|
||||||
|
- Adapter: 删除 Gewechat 适配器 [@Shine-Light](https://github.com/Shine-Light) ([#3516](https://github.com/nonebot/nonebot2/pull/3516))
|
||||||
|
- Adapter: nonebot-adapter-milky [@noneflow](https://github.com/noneflow) ([#3491](https://github.com/nonebot/nonebot2/pull/3491))
|
||||||
|
- Adapter: Gewechat [@noneflow](https://github.com/noneflow) ([#3306](https://github.com/nonebot/nonebot2/pull/3306))
|
||||||
|
|
||||||
|
## v2.4.2
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: 添加 pydantic validator 兼容函数 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#3291](https://github.com/nonebot/nonebot2/pull/3291))
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- Fix: shell command 词法解析错误未捕获 [@yanyongyu](https://github.com/yanyongyu) ([#3290](https://github.com/nonebot/nonebot2/pull/3290))
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Docs: 添加第三方插件模版 [@fllesser](https://github.com/fllesser) ([#3361](https://github.com/nonebot/nonebot2/pull/3361))
|
||||||
|
- Docs: 商店头像 skeleton [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3362](https://github.com/nonebot/nonebot2/pull/3362))
|
||||||
|
- Docs: 添加 localstore `use_cwd` 配置项文档 [@yanyongyu](https://github.com/yanyongyu) ([#3345](https://github.com/nonebot/nonebot2/pull/3345))
|
||||||
|
- Docs: 商店插件可用性筛选 \& 更新排序 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3334](https://github.com/nonebot/nonebot2/pull/3334))
|
||||||
|
- Docs: 添加 微信公众平台 适配器描述 [@YangRucheng](https://github.com/YangRucheng) ([#3264](https://github.com/nonebot/nonebot2/pull/3264))
|
||||||
|
- Docs: 添加 黑盒语音 适配器描述 [@lclbm](https://github.com/lclbm) ([#3259](https://github.com/nonebot/nonebot2/pull/3259))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Lint: 修复 async 函数返回值 [@yanyongyu](https://github.com/yanyongyu) ([#3364](https://github.com/nonebot/nonebot2/pull/3364))
|
||||||
|
- Fix: 修复 pyright 类型推导问题 [@yanyongyu](https://github.com/yanyongyu) ([#3347](https://github.com/nonebot/nonebot2/pull/3347))
|
||||||
|
- Fix: 修复 ruff lint 错误 [@yanyongyu](https://github.com/yanyongyu) ([#3346](https://github.com/nonebot/nonebot2/pull/3346))
|
||||||
|
- Plugin: 删除插件 `pjsk` [@lgc2333](https://github.com/lgc2333) ([#3332](https://github.com/nonebot/nonebot2/pull/3332))
|
||||||
|
- CI: 使用官方版本 ruff action [@yanyongyu](https://github.com/yanyongyu) ([#3286](https://github.com/nonebot/nonebot2/pull/3286))
|
||||||
|
- CI: pyright 版本与 pylance 保持一致 [@yanyongyu](https://github.com/yanyongyu) ([#3285](https://github.com/nonebot/nonebot2/pull/3285))
|
||||||
|
- CI: 临时降级 release-drafter [@yanyongyu](https://github.com/yanyongyu) ([#3284](https://github.com/nonebot/nonebot2/pull/3284))
|
||||||
|
- Plugin: 删除 bawiki [@lgc2333](https://github.com/lgc2333) ([#3265](https://github.com/nonebot/nonebot2/pull/3265))
|
||||||
|
- Plugin: 删除插件 nonebot_plugin_clovers [@KarisAya](https://github.com/KarisAya) ([#3254](https://github.com/nonebot/nonebot2/pull/3254))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: ChatGPT (OpenAI API 接口版) [@noneflow](https://github.com/noneflow) ([#3340](https://github.com/nonebot/nonebot2/pull/3340))
|
||||||
|
- Plugin: 卡bin查询 [@noneflow](https://github.com/noneflow) ([#3324](https://github.com/nonebot/nonebot2/pull/3324))
|
||||||
|
- Plugin: 唐菲检测 [@noneflow](https://github.com/noneflow) ([#3338](https://github.com/nonebot/nonebot2/pull/3338))
|
||||||
|
- Plugin: nonebot-plugin-whois [@noneflow](https://github.com/noneflow) ([#3330](https://github.com/nonebot/nonebot2/pull/3330))
|
||||||
|
- Plugin: Meme Stickers [@noneflow](https://github.com/noneflow) ([#3333](https://github.com/nonebot/nonebot2/pull/3333))
|
||||||
|
- Plugin: 群昵称更新 [@noneflow](https://github.com/noneflow) ([#3336](https://github.com/nonebot/nonebot2/pull/3336))
|
||||||
|
- Plugin: 简易AI聊天 [@noneflow](https://github.com/noneflow) ([#3327](https://github.com/nonebot/nonebot2/pull/3327))
|
||||||
|
- Plugin: Vocu 语音插件 [@noneflow](https://github.com/noneflow) ([#3322](https://github.com/nonebot/nonebot2/pull/3322))
|
||||||
|
- Plugin: LuoguLuck|洛谷运势 [@noneflow](https://github.com/noneflow) ([#3320](https://github.com/nonebot/nonebot2/pull/3320))
|
||||||
|
- Plugin: 群聊配置 [@noneflow](https://github.com/noneflow) ([#3311](https://github.com/nonebot/nonebot2/pull/3311))
|
||||||
|
- Plugin: llmchat [@noneflow](https://github.com/noneflow) ([#3309](https://github.com/nonebot/nonebot2/pull/3309))
|
||||||
|
- Plugin: [ 缩写翻译 ] 能不能好好说话 - nbnhhsh [@noneflow](https://github.com/noneflow) ([#3296](https://github.com/nonebot/nonebot2/pull/3296))
|
||||||
|
- Plugin: SuggarChat OpenAI协议聊天插件 [@noneflow](https://github.com/noneflow) ([#3222](https://github.com/nonebot/nonebot2/pull/3222))
|
||||||
|
- Plugin: nonebot-plugin-ACMD [@noneflow](https://github.com/noneflow) ([#3283](https://github.com/nonebot/nonebot2/pull/3283))
|
||||||
|
- Plugin: AI群聊机器人 [@noneflow](https://github.com/noneflow) ([#3258](https://github.com/nonebot/nonebot2/pull/3258))
|
||||||
|
- Plugin: 追番小工具 [@noneflow](https://github.com/noneflow) ([#3279](https://github.com/nonebot/nonebot2/pull/3279))
|
||||||
|
- Plugin: BotTap [@noneflow](https://github.com/noneflow) ([#3288](https://github.com/nonebot/nonebot2/pull/3288))
|
||||||
|
- Plugin: DeepSeek [@noneflow](https://github.com/noneflow) ([#3281](https://github.com/nonebot/nonebot2/pull/3281))
|
||||||
|
- Plugin: 群文件管理 [@noneflow](https://github.com/noneflow) ([#3271](https://github.com/nonebot/nonebot2/pull/3271))
|
||||||
|
- Plugin: 中英文笑话 [@noneflow](https://github.com/noneflow) ([#3277](https://github.com/nonebot/nonebot2/pull/3277))
|
||||||
|
- Plugin: 定时提醒 [@noneflow](https://github.com/noneflow) ([#3275](https://github.com/nonebot/nonebot2/pull/3275))
|
||||||
|
- Plugin: NeuroDraw [@noneflow](https://github.com/noneflow) ([#3269](https://github.com/nonebot/nonebot2/pull/3269))
|
||||||
|
- Plugin: Remove nonebot_plugin_bili_push [@noneflow](https://github.com/noneflow) ([#3260](https://github.com/nonebot/nonebot2/pull/3260))
|
||||||
|
- Plugin: 堡垒之夜游戏插件 [@noneflow](https://github.com/noneflow) ([#3251](https://github.com/nonebot/nonebot2/pull/3251))
|
||||||
|
- Plugin: nonebot-plugin-pictranslator [@noneflow](https://github.com/noneflow) ([#3257](https://github.com/nonebot/nonebot2/pull/3257))
|
||||||
|
- Plugin: 玉! [@noneflow](https://github.com/noneflow) ([#3247](https://github.com/nonebot/nonebot2/pull/3247))
|
||||||
|
- Plugin: nonebot_plugin_palworld [@noneflow](https://github.com/noneflow) ([#3244](https://github.com/nonebot/nonebot2/pull/3244))
|
||||||
|
- Plugin: 群聊总结 [@noneflow](https://github.com/noneflow) ([#3242](https://github.com/nonebot/nonebot2/pull/3242))
|
||||||
|
- Plugin: 恶魔轮盘轻量版 [@noneflow](https://github.com/noneflow) ([#3224](https://github.com/nonebot/nonebot2/pull/3224))
|
||||||
|
- Plugin: 羡慕 koishi [@noneflow](https://github.com/noneflow) ([#3234](https://github.com/nonebot/nonebot2/pull/3234))
|
||||||
|
- Plugin: 每日天文一图 [@noneflow](https://github.com/noneflow) ([#3229](https://github.com/nonebot/nonebot2/pull/3229))
|
||||||
|
- Plugin: 音频文件BPM计算器 [@noneflow](https://github.com/noneflow) ([#3217](https://github.com/nonebot/nonebot2/pull/3217))
|
||||||
|
- Plugin: nonebot_plugin_qbittorrent_manager [@noneflow](https://github.com/noneflow) ([#3208](https://github.com/nonebot/nonebot2/pull/3208))
|
||||||
|
- Plugin: 群成员检测 dev版 [@noneflow](https://github.com/noneflow) ([#3214](https://github.com/nonebot/nonebot2/pull/3214))
|
||||||
|
- Plugin: Liar's Bar [@noneflow](https://github.com/noneflow) ([#3212](https://github.com/nonebot/nonebot2/pull/3212))
|
||||||
|
- Plugin: 他们在聊什么 [@noneflow](https://github.com/noneflow) ([#3210](https://github.com/nonebot/nonebot2/pull/3210))
|
||||||
|
- Plugin: 小真寻的WebUi [@noneflow](https://github.com/noneflow) ([#3206](https://github.com/nonebot/nonebot2/pull/3206))
|
||||||
|
- Plugin: 夸克搜 [@noneflow](https://github.com/noneflow) ([#3203](https://github.com/nonebot/nonebot2/pull/3203))
|
||||||
|
|
||||||
|
### 🍻 机器人发布
|
||||||
|
|
||||||
|
- Bot: PickStarsBot [@noneflow](https://github.com/noneflow) ([#3273](https://github.com/nonebot/nonebot2/pull/3273))
|
||||||
|
- Bot: Nekro Agent Bot [@noneflow](https://github.com/noneflow) ([#3267](https://github.com/nonebot/nonebot2/pull/3267))
|
||||||
|
- Bot: AntiFraudBot [@noneflow](https://github.com/noneflow) ([#3263](https://github.com/nonebot/nonebot2/pull/3263))
|
||||||
|
|
||||||
|
### 🍻 适配器发布
|
||||||
|
|
||||||
|
- Adapter: WXMP [@noneflow](https://github.com/noneflow) ([#3219](https://github.com/nonebot/nonebot2/pull/3219))
|
||||||
|
- Adapter: 黑盒语音 [@noneflow](https://github.com/noneflow) ([#3249](https://github.com/nonebot/nonebot2/pull/3249))
|
||||||
|
|
||||||
## v2.4.1
|
## v2.4.1
|
||||||
|
|
||||||
### 🚀 新功能
|
### 🚀 新功能
|
||||||
|
@@ -25,12 +25,12 @@ export type Props = {
|
|||||||
export default function AsciinemaContainer({
|
export default function AsciinemaContainer({
|
||||||
url,
|
url,
|
||||||
options = {},
|
options = {},
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
AsciinemaPlayer.create(url, ref.current, options);
|
AsciinemaPlayer.create(url, ref.current, options);
|
||||||
}, [url, options]);
|
}, [url, options]);
|
||||||
|
|
||||||
return <div ref={ref} className="not-prose ap-container"></div>;
|
return <div ref={ref} className="not-prose ap-container" />;
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import "asciinema-player/dist/bundle/asciinema-player.css";
|
|
||||||
import BrowserOnly from "@docusaurus/BrowserOnly";
|
import BrowserOnly from "@docusaurus/BrowserOnly";
|
||||||
|
|
||||||
import "./styles.css";
|
import "asciinema-player/dist/bundle/asciinema-player.css";
|
||||||
|
|
||||||
import type { Props } from "./container";
|
import type { Props } from "./container";
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
export type { Props } from "./container";
|
export type { Props } from "./container";
|
||||||
|
|
||||||
export default function Asciinema(props: Props): JSX.Element {
|
export default function Asciinema(props: Props): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<BrowserOnly
|
<BrowserOnly
|
||||||
fallback={
|
fallback={
|
||||||
|
@@ -2,7 +2,7 @@ import React from "react";
|
|||||||
|
|
||||||
import { Form } from ".";
|
import { Form } from ".";
|
||||||
|
|
||||||
export default function AdapterForm(): JSX.Element {
|
export default function AdapterForm(): React.ReactNode {
|
||||||
const formItems = [
|
const formItems = [
|
||||||
{
|
{
|
||||||
name: "基本信息",
|
name: "基本信息",
|
||||||
|
@@ -2,7 +2,7 @@ import React from "react";
|
|||||||
|
|
||||||
import { Form } from ".";
|
import { Form } from ".";
|
||||||
|
|
||||||
export default function BotForm(): JSX.Element {
|
export default function BotForm(): React.ReactNode {
|
||||||
const formItems = [
|
const formItems = [
|
||||||
{
|
{
|
||||||
name: "基本信息",
|
name: "基本信息",
|
||||||
|
@@ -8,7 +8,8 @@ import { ChromePicker, type ColorResult } from "react-color";
|
|||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import TagComponent from "@/components/Tag";
|
import TagComponent from "@/components/Tag";
|
||||||
import { Tag as TagType } from "@/types/tag";
|
|
||||||
|
import type { Tag as TagType } from "@/types/tag";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
allowTags: TagType[];
|
allowTags: TagType[];
|
||||||
@@ -18,7 +19,7 @@ export type Props = {
|
|||||||
export default function TagFormItem({
|
export default function TagFormItem({
|
||||||
allowTags,
|
allowTags,
|
||||||
onTagUpdate,
|
onTagUpdate,
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
const [tags, setTags] = useState<TagType[]>([]);
|
const [tags, setTags] = useState<TagType[]>([]);
|
||||||
const [label, setLabel] = useState<TagType["label"]>("");
|
const [label, setLabel] = useState<TagType["label"]>("");
|
||||||
const [color, setColor] = useState<TagType["color"]>("#ea5252");
|
const [color, setColor] = useState<TagType["color"]>("#ea5252");
|
||||||
@@ -103,7 +104,7 @@ export default function TagFormItem({
|
|||||||
<ChromePicker
|
<ChromePicker
|
||||||
className="my-4 fix-input-color"
|
className="my-4 fix-input-color"
|
||||||
color={color}
|
color={color}
|
||||||
disableAlpha={true}
|
disableAlpha
|
||||||
onChangeComplete={onChangeColor}
|
onChangeComplete={onChangeColor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,7 +2,7 @@ import React from "react";
|
|||||||
|
|
||||||
import { Form } from ".";
|
import { Form } from ".";
|
||||||
|
|
||||||
export default function PluginForm(): JSX.Element {
|
export default function PluginForm(): React.ReactNode {
|
||||||
const formItems = [
|
const formItems = [
|
||||||
{
|
{
|
||||||
name: "包信息",
|
name: "包信息",
|
||||||
|
@@ -4,10 +4,12 @@ import clsx from "clsx";
|
|||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
|
import type { Resource } from "@/libs/store";
|
||||||
|
import { fetchRegistryData } from "@/libs/store";
|
||||||
|
|
||||||
import TagFormItem from "./Items/Tag";
|
import TagFormItem from "./Items/Tag";
|
||||||
|
|
||||||
import { fetchRegistryData, Resource } from "@/libs/store";
|
import type { Tag as TagType } from "@/types/tag";
|
||||||
import { Tag as TagType } from "@/types/tag";
|
|
||||||
|
|
||||||
export type FormItemData = {
|
export type FormItemData = {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -32,7 +34,7 @@ export function Form({
|
|||||||
children,
|
children,
|
||||||
formItems,
|
formItems,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
const [currentStep, setCurrentStep] = useState<number>(0);
|
const [currentStep, setCurrentStep] = useState<number>(0);
|
||||||
const [result, setResult] = useState<Record<string, string>>({});
|
const [result, setResult] = useState<Record<string, string>>({});
|
||||||
const [allowTags, setAllowTags] = useState<TagType[]>([]);
|
const [allowTags, setAllowTags] = useState<TagType[]>([]);
|
||||||
@@ -61,9 +63,10 @@ export function Form({
|
|||||||
const currentStepNames = formItems[currentStep].items.map(
|
const currentStepNames = formItems[currentStep].items.map(
|
||||||
(item) => item.name
|
(item) => item.name
|
||||||
);
|
);
|
||||||
if (currentStepNames.every((name) => result[name]))
|
if (currentStepNames.every((name) => result[name])) {
|
||||||
setCurrentStep(currentStep + 1);
|
setCurrentStep(currentStep + 1);
|
||||||
else return;
|
} else {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const onPrev = () => currentStep > 0 && setCurrentStep(currentStep - 1);
|
const onPrev = () => currentStep > 0 && setCurrentStep(currentStep - 1);
|
||||||
const onNext = () =>
|
const onNext = () =>
|
||||||
@@ -125,7 +128,7 @@ export function FormItem({
|
|||||||
allowTags: TagType[];
|
allowTags: TagType[];
|
||||||
result: Record<string, string>;
|
result: Record<string, string>;
|
||||||
setResult: (key: string, value: string) => void;
|
setResult: (key: string, value: string) => void;
|
||||||
}): JSX.Element {
|
}): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<label className="label">
|
<label className="label">
|
||||||
|
@@ -16,7 +16,7 @@ export function HomeFeature({
|
|||||||
description,
|
description,
|
||||||
annotaion,
|
annotaion,
|
||||||
children,
|
children,
|
||||||
}: Feature): JSX.Element {
|
}: Feature): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center p-4">
|
<div className="flex flex-col items-center justify-center p-4">
|
||||||
<p className="text-sm text-base-content/70 font-medium tracking-wide uppercase">
|
<p className="text-sm text-base-content/70 font-medium tracking-wide uppercase">
|
||||||
@@ -32,7 +32,7 @@ export function HomeFeature({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HomeFeatureSingleColumn(props: Feature): JSX.Element {
|
function HomeFeatureSingleColumn(props: Feature): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 px-4 py-8 md:px-16 mx-auto">
|
<div className="grid grid-cols-1 px-4 py-8 md:px-16 mx-auto">
|
||||||
<HomeFeature {...props} />
|
<HomeFeature {...props} />
|
||||||
@@ -46,7 +46,7 @@ function HomeFeatureDoubleColumn({
|
|||||||
}: {
|
}: {
|
||||||
features: [Feature, Feature];
|
features: [Feature, Feature];
|
||||||
children?: [React.ReactNode, React.ReactNode];
|
children?: [React.ReactNode, React.ReactNode];
|
||||||
}): JSX.Element {
|
}): React.ReactNode {
|
||||||
const [children1, children2] = children ?? [];
|
const [children1, children2] = children ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -57,7 +57,7 @@ function HomeFeatureDoubleColumn({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HomeFeatures(): JSX.Element {
|
function HomeFeatures(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HomeFeatureSingleColumn
|
<HomeFeatureSingleColumn
|
||||||
|
@@ -10,7 +10,7 @@ import copy from "copy-text-to-clipboard";
|
|||||||
import IconCopy from "@theme/Icon/Copy";
|
import IconCopy from "@theme/Icon/Copy";
|
||||||
import IconSuccess from "@theme/Icon/Success";
|
import IconSuccess from "@theme/Icon/Success";
|
||||||
|
|
||||||
function HomeHeroInstallButton(): JSX.Element {
|
function HomeHeroInstallButton(): React.ReactNode {
|
||||||
const code = "pipx run nb-cli create";
|
const code = "pipx run nb-cli create";
|
||||||
|
|
||||||
const [isCopied, setIsCopied] = useState(false);
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
@@ -37,7 +37,7 @@ function HomeHeroInstallButton(): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HomeHero(): JSX.Element {
|
function HomeHero(): React.ReactNode {
|
||||||
const {
|
const {
|
||||||
siteConfig: { tagline },
|
siteConfig: { tagline },
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import "./styles.css";
|
|
||||||
import HomeFeatures from "./Feature";
|
import HomeFeatures from "./Feature";
|
||||||
import HomeHero from "./Hero";
|
import HomeHero from "./Hero";
|
||||||
|
|
||||||
export default function HomeContent(): JSX.Element {
|
import "./styles.css";
|
||||||
|
|
||||||
|
export default function HomeContent(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="home-container">
|
<div className="home-container">
|
||||||
<HomeHero />
|
<HomeHero />
|
||||||
|
@@ -6,9 +6,10 @@ import useBaseUrl from "@docusaurus/useBaseUrl";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useNonepressThemeConfig } from "@nullbot/docusaurus-theme-nonepress/client";
|
import { useNonepressThemeConfig } from "@nullbot/docusaurus-theme-nonepress/client";
|
||||||
|
|
||||||
import "./styles.css";
|
|
||||||
import ThemedImage from "@theme/ThemedImage";
|
import ThemedImage from "@theme/ThemedImage";
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
msg: string;
|
msg: string;
|
||||||
position?: "left" | "right";
|
position?: "left" | "right";
|
||||||
@@ -19,7 +20,7 @@ function MessageBox({
|
|||||||
msg,
|
msg,
|
||||||
position = "left",
|
position = "left",
|
||||||
monospace = false,
|
monospace = false,
|
||||||
}: Message): JSX.Element {
|
}: Message): React.ReactNode {
|
||||||
const {
|
const {
|
||||||
navbar: { logo },
|
navbar: { logo },
|
||||||
} = useNonepressThemeConfig();
|
} = useNonepressThemeConfig();
|
||||||
@@ -54,7 +55,7 @@ function MessageBox({
|
|||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: msg.replace(/\n/g, "<br/>").replace(/ /g, " "),
|
__html: msg.replace(/\n/g, "<br/>").replace(/ /g, " "),
|
||||||
}}
|
}}
|
||||||
></div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -63,7 +64,7 @@ export default function Messenger({
|
|||||||
msgs = [],
|
msgs = [],
|
||||||
}: {
|
}: {
|
||||||
msgs?: Message[];
|
msgs?: Message[];
|
||||||
}): JSX.Element {
|
}): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="messenger-container">
|
<div className="messenger-container">
|
||||||
<header className="messenger-title">
|
<header className="messenger-title">
|
||||||
|
@@ -22,7 +22,7 @@ export default function Modal({
|
|||||||
useCustomTitle,
|
useCustomTitle,
|
||||||
backdropExit,
|
backdropExit,
|
||||||
title,
|
title,
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
const [transitionClass, setTransitionClass] = useState<string>("");
|
const [transitionClass, setTransitionClass] = useState<string>("");
|
||||||
|
|
||||||
const onFadeIn = () => setTransitionClass("fade-in");
|
const onFadeIn = () => setTransitionClass("fade-in");
|
||||||
|
@@ -3,6 +3,7 @@ import React, { useCallback } from "react";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import type { usePagination } from "react-use-pagination";
|
import type { usePagination } from "react-use-pagination";
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
@@ -31,7 +32,7 @@ export default function Paginate({
|
|||||||
setPage,
|
setPage,
|
||||||
previousEnabled,
|
previousEnabled,
|
||||||
nextEnabled,
|
nextEnabled,
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
// const [containerElement, setContainerElement] = useState<HTMLElement | null>(
|
// const [containerElement, setContainerElement] = useState<HTMLElement | null>(
|
||||||
// null
|
// null
|
||||||
// );
|
// );
|
||||||
@@ -66,7 +67,7 @@ export default function Paginate({
|
|||||||
const even = MAX_LENGTH % 2 === 0 ? 1 : 0;
|
const even = MAX_LENGTH % 2 === 0 ? 1 : 0;
|
||||||
const left = Math.floor(MAX_LENGTH / 2);
|
const left = Math.floor(MAX_LENGTH / 2);
|
||||||
const right = totalPages - left + even + 1;
|
const right = totalPages - left + even + 1;
|
||||||
currentPage = currentPage + 1;
|
currentPage += 1;
|
||||||
|
|
||||||
if (totalPages <= MAX_LENGTH) {
|
if (totalPages <= MAX_LENGTH) {
|
||||||
pages.push(...range(1, totalPages));
|
pages.push(...range(1, totalPages));
|
||||||
|
43
website/src/components/Resource/Avatar/index.tsx
Normal file
43
website/src/components/Resource/Avatar/index.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import Link from "@docusaurus/Link";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: string;
|
||||||
|
authorLink: string;
|
||||||
|
authorAvatar: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Avatar({ authorLink, authorAvatar, className }: Props) {
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
const onLoad = () => setLoaded(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="avatar">
|
||||||
|
<div className={className}>
|
||||||
|
<Link href={authorLink}>
|
||||||
|
<div className="relative w-full h-full">
|
||||||
|
{!loaded && (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"absolute inset-0 rounded-full bg-gray-200",
|
||||||
|
"animate-pulse"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<img
|
||||||
|
src={authorAvatar}
|
||||||
|
onLoad={onLoad}
|
||||||
|
className={clsx(
|
||||||
|
"w-full h-full rounded-full object-cover",
|
||||||
|
"transition-opacity duration-300",
|
||||||
|
loaded ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
alt="Avatar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -5,11 +5,13 @@ import clsx from "clsx";
|
|||||||
import Link from "@docusaurus/Link";
|
import Link from "@docusaurus/Link";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import "./styles.css";
|
import Avatar from "@/components/Resource/Avatar";
|
||||||
import Tag from "@/components/Resource/Tag";
|
import Tag from "@/components/Resource/Tag";
|
||||||
import ValidStatus from "@/components/Resource/ValidStatus";
|
import ValidStatus from "@/components/Resource/ValidStatus";
|
||||||
import type { Resource } from "@/libs/store";
|
import type { Resource } from "@/libs/store";
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
resource: Resource;
|
resource: Resource;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@@ -24,7 +26,7 @@ export default function ResourceCard({
|
|||||||
onTagClick,
|
onTagClick,
|
||||||
onAuthorClick,
|
onAuthorClick,
|
||||||
className,
|
className,
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
const isGithub = /^https:\/\/github.com\/[^/]+\/[^/]+/.test(
|
const isGithub = /^https:\/\/github.com\/[^/]+\/[^/]+/.test(
|
||||||
resource.homepage
|
resource.homepage
|
||||||
);
|
);
|
||||||
@@ -73,7 +75,7 @@ export default function ResourceCard({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="divider resource-card-footer-divider"></div>
|
<div className="divider resource-card-footer-divider" />
|
||||||
<div className="resource-card-footer-info">
|
<div className="resource-card-footer-info">
|
||||||
<div className="resource-card-footer-group">
|
<div className="resource-card-footer-group">
|
||||||
<Link href={resource.homepage}>
|
<Link href={resource.homepage}>
|
||||||
@@ -99,13 +101,12 @@ export default function ResourceCard({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="resource-card-footer-group">
|
<div className="resource-card-footer-group">
|
||||||
<div className="avatar">
|
<Avatar
|
||||||
<div className="resource-card-footer-avatar">
|
className="resource-card-footer-avatar"
|
||||||
<Link href={authorLink}>
|
key={resource.author}
|
||||||
<img src={authorAvatar} key={resource.author} />
|
authorAvatar={authorAvatar}
|
||||||
</Link>
|
authorLink={authorLink}
|
||||||
</div>
|
/>
|
||||||
</div>
|
|
||||||
<span
|
<span
|
||||||
className="resource-card-footer-author"
|
className="resource-card-footer-author"
|
||||||
onClick={onAuthorClick}
|
onClick={onAuthorClick}
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
// @ts-expect-error: we need to make package have type: module
|
|
||||||
import copy from "copy-text-to-clipboard";
|
import copy from "copy-text-to-clipboard";
|
||||||
|
|
||||||
import { PyPIData } from "./types";
|
|
||||||
|
|
||||||
import Tag from "@/components/Resource/Tag";
|
import Tag from "@/components/Resource/Tag";
|
||||||
import ValidStatus from "@/components/Resource/ValidStatus";
|
import ValidStatus from "@/components/Resource/ValidStatus";
|
||||||
import type { Resource } from "@/libs/store";
|
import type { Resource } from "@/libs/store";
|
||||||
|
|
||||||
|
import type { PyPIData } from "./types";
|
||||||
|
|
||||||
|
import Avatar from "../Avatar";
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
@@ -72,6 +72,15 @@ export default function ResourceDetailCard({ resource }: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPluginStatusUpdatedTime = (resource: Resource) => {
|
||||||
|
switch (resource.resourceType) {
|
||||||
|
case "plugin":
|
||||||
|
return new Date(resource.time).toLocaleString();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchPypiProject = (projectName: string) =>
|
const fetchPypiProject = (projectName: string) =>
|
||||||
fetch(`https://pypi.org/pypi/${projectName}/json`)
|
fetch(`https://pypi.org/pypi/${projectName}/json`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
@@ -88,27 +97,31 @@ export default function ResourceDetailCard({ resource }: Props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchingTasks: Promise<void>[] = [];
|
const fetchingTasks: Promise<void>[] = [];
|
||||||
if (resource.resourceType === "bot" || resource.resourceType === "driver")
|
if (resource.resourceType === "bot" || resource.resourceType === "driver") {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (resource.project_link)
|
if (resource.project_link) {
|
||||||
fetchingTasks.push(fetchPypiProject(resource.project_link));
|
fetchingTasks.push(fetchPypiProject(resource.project_link));
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all(fetchingTasks);
|
Promise.all(fetchingTasks);
|
||||||
}, [resource]);
|
}, [resource]);
|
||||||
|
|
||||||
const projectLink = getProjectLink(resource) || "无";
|
const projectLink = getProjectLink(resource) || "无";
|
||||||
const moduleName = getModuleName(resource) || "无";
|
const moduleName = getModuleName(resource) || "无";
|
||||||
const homepageLink = getHomepageLink(resource) || undefined;
|
const homepageLink = getHomepageLink(resource);
|
||||||
const pypiProjectLink = getPypiProjectLink(resource) || undefined;
|
const pypiProjectLink = getPypiProjectLink(resource);
|
||||||
|
const updatedTime = getPluginStatusUpdatedTime(resource);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="detail-card-header">
|
<div className="detail-card-header">
|
||||||
<img
|
<Avatar
|
||||||
src={authorAvatar}
|
|
||||||
className="detail-card-avatar"
|
className="detail-card-avatar"
|
||||||
decoding="async"
|
key={resource.author}
|
||||||
|
authorLink={authorLink}
|
||||||
|
authorAvatar={authorAvatar}
|
||||||
/>
|
/>
|
||||||
<div className="detail-card-title">
|
<div className="detail-card-title">
|
||||||
<span className="detail-card-title-main flex items-center gap-x-1">
|
<span className="detail-card-title-main flex items-center gap-x-1">
|
||||||
@@ -142,7 +155,7 @@ export default function ResourceDetailCard({ resource }: Props) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="divider detail-card-header-divider"></div>
|
<div className="divider detail-card-header-divider" />
|
||||||
<div className="detail-card-body">
|
<div className="detail-card-body">
|
||||||
<div className="detail-card-body-left">
|
<div className="detail-card-body-left">
|
||||||
<span className="h-full">{resource.desc}</span>
|
<span className="h-full">{resource.desc}</span>
|
||||||
@@ -183,31 +196,39 @@ export default function ResourceDetailCard({ resource }: Props) {
|
|||||||
{(pypiData && pypiData.info.version) || "无"}
|
{(pypiData && pypiData.info.version) || "无"}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="detail-card-meta-item">
|
{homepageLink && (
|
||||||
<FontAwesomeIcon fixedWidth icon={["fas", "fingerprint"]} />{" "}
|
<div className="detail-card-meta-item">
|
||||||
<a
|
<FontAwesomeIcon fixedWidth icon={["fas", "fingerprint"]} />{" "}
|
||||||
href={homepageLink}
|
<a
|
||||||
target="_blank"
|
href={homepageLink}
|
||||||
rel="noreferrer"
|
target="_blank"
|
||||||
className={homepageLink && "hover:underline hover:text-primary"}
|
rel="noreferrer"
|
||||||
>
|
className="detail-card-meta-item-link"
|
||||||
{moduleName}
|
>
|
||||||
</a>
|
{moduleName}
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{pypiProjectLink && (
|
||||||
|
<div className="detail-card-meta-item">
|
||||||
|
<FontAwesomeIcon fixedWidth icon={["fas", "cubes"]} />{" "}
|
||||||
|
<a
|
||||||
|
href={pypiProjectLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="detail-card-meta-item-link"
|
||||||
|
>
|
||||||
|
{projectLink}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="detail-card-meta-item">
|
<div className="detail-card-meta-item">
|
||||||
<FontAwesomeIcon fixedWidth icon={["fas", "cubes"]} />{" "}
|
<FontAwesomeIcon fixedWidth icon={["fas", "clock-rotate-left"]} />{" "}
|
||||||
<a
|
{updatedTime}
|
||||||
href={pypiProjectLink}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className={
|
|
||||||
pypiProjectLink && "hover:underline hover:text-primary"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{projectLink}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="detail-card-actions">
|
<div className="detail-card-actions">
|
||||||
<ValidStatus
|
<ValidStatus
|
||||||
resource={resource}
|
resource={resource}
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-actions {
|
&-actions {
|
||||||
@apply flex items-center gap-x-2 ml-auto;
|
@apply flex items-center gap-x-2 lg:ml-auto;
|
||||||
|
|
||||||
&-button {
|
&-button {
|
||||||
@apply btn btn-sm;
|
@apply btn btn-sm;
|
||||||
@@ -51,11 +51,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-right {
|
&-right {
|
||||||
@apply flex flex-col justify-start gap-y-2 lg:basis-1/4 max-w-[45%];
|
@apply flex flex-col justify-start gap-y-2 lg:basis-1/4 lg:max-w-[45%];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-meta-item {
|
&-meta-item {
|
||||||
@apply text-sm truncate;
|
@apply text-sm truncate;
|
||||||
|
|
||||||
|
&-link {
|
||||||
|
@apply hover:text-primary hover:transition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,12 @@ import React from "react";
|
|||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import "./styles.css";
|
|
||||||
import { pickTextColor } from "@/libs/color";
|
import { pickTextColor } from "@/libs/color";
|
||||||
|
|
||||||
import type { Tag } from "@/types/tag";
|
import type { Tag } from "@/types/tag";
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
export type Props = Tag & {
|
export type Props = Tag & {
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: React.MouseEventHandler<HTMLSpanElement>;
|
onClick?: React.MouseEventHandler<HTMLSpanElement>;
|
||||||
@@ -16,7 +18,7 @@ export default function ResourceTag({
|
|||||||
color,
|
color,
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={clsx("resource-tag", className)}
|
className={clsx("resource-tag", className)}
|
||||||
|
@@ -2,17 +2,22 @@ import React from "react";
|
|||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import type { IconName } from "@fortawesome/fontawesome-common-types";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { Resource } from "@/libs/store";
|
import type { Resource } from "@/libs/store";
|
||||||
import { ValidStatus } from "@/libs/valid";
|
import { ValidStatus } from "@/libs/valid";
|
||||||
|
|
||||||
|
import type { IconName } from "@fortawesome/fontawesome-common-types";
|
||||||
|
|
||||||
export const getValidStatus = (resource: Resource) => {
|
export const getValidStatus = (resource: Resource) => {
|
||||||
switch (resource.resourceType) {
|
switch (resource.resourceType) {
|
||||||
case "plugin":
|
case "plugin":
|
||||||
if (resource.skip_test) return ValidStatus.SKIP;
|
if (resource.skip_test) {
|
||||||
if (resource.valid) return ValidStatus.VALID;
|
return ValidStatus.SKIP;
|
||||||
|
}
|
||||||
|
if (resource.valid) {
|
||||||
|
return ValidStatus.VALID;
|
||||||
|
}
|
||||||
return ValidStatus.INVALID;
|
return ValidStatus.INVALID;
|
||||||
default:
|
default:
|
||||||
return ValidStatus.MISSING;
|
return ValidStatus.MISSING;
|
||||||
|
@@ -2,10 +2,11 @@ import React, { useRef } from "react";
|
|||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import "./styles.css";
|
|
||||||
import { translate } from "@docusaurus/Translate";
|
import { translate } from "@docusaurus/Translate";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
onSubmit: (value: string) => void;
|
onSubmit: (value: string) => void;
|
||||||
@@ -28,7 +29,7 @@ export default function Searcher({
|
|||||||
className,
|
className,
|
||||||
placeholder,
|
placeholder,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLInputElement>) => {
|
const handleSubmit = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
@@ -85,6 +86,10 @@ export default function Searcher({
|
|||||||
onClick={() => onTagClick(index)}
|
onClick={() => onTagClick(index)}
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
|
<FontAwesomeIcon
|
||||||
|
className="searcher-action-icon close ml-1"
|
||||||
|
icon={["fas", "xmark"]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<input
|
<input
|
||||||
|
@@ -4,7 +4,6 @@ import Translate from "@docusaurus/Translate";
|
|||||||
import { usePagination } from "react-use-pagination";
|
import { usePagination } from "react-use-pagination";
|
||||||
|
|
||||||
import Admonition from "@theme/Admonition";
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
import AdapterForm from "@/components/Form/Adapter";
|
import AdapterForm from "@/components/Form/Adapter";
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
import Paginate from "@/components/Paginate";
|
import Paginate from "@/components/Paginate";
|
||||||
@@ -16,9 +15,10 @@ import { authorFilter, tagFilter } from "@/libs/filter";
|
|||||||
import { useSearchControl } from "@/libs/search";
|
import { useSearchControl } from "@/libs/search";
|
||||||
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
||||||
import { useToolbar } from "@/libs/toolbar";
|
import { useToolbar } from "@/libs/toolbar";
|
||||||
|
|
||||||
import type { Adapter } from "@/types/adapter";
|
import type { Adapter } from "@/types/adapter";
|
||||||
|
|
||||||
export default function AdapterPage(): JSX.Element {
|
export default function AdapterPage(): React.ReactNode {
|
||||||
const [adapters, setAdapters] = useState<Adapter[] | null>(null);
|
const [adapters, setAdapters] = useState<Adapter[] | null>(null);
|
||||||
const adapterCount = adapters?.length ?? 0;
|
const adapterCount = adapters?.length ?? 0;
|
||||||
const loading = adapters === null;
|
const loading = adapters === null;
|
||||||
@@ -146,7 +146,7 @@ export default function AdapterPage(): JSX.Element {
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<p className="store-loading-container">
|
<p className="store-loading-container">
|
||||||
<span className="loading loading-dots loading-lg store-loading"></span>
|
<span className="loading loading-dots loading-lg store-loading" />
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="store-container">
|
<div className="store-container">
|
||||||
|
@@ -4,7 +4,6 @@ import Translate from "@docusaurus/Translate";
|
|||||||
import { usePagination } from "react-use-pagination";
|
import { usePagination } from "react-use-pagination";
|
||||||
|
|
||||||
import Admonition from "@theme/Admonition";
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
import BotForm from "@/components/Form/Bot";
|
import BotForm from "@/components/Form/Bot";
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
import Paginate from "@/components/Paginate";
|
import Paginate from "@/components/Paginate";
|
||||||
@@ -15,9 +14,10 @@ import { authorFilter, tagFilter } from "@/libs/filter";
|
|||||||
import { useSearchControl } from "@/libs/search";
|
import { useSearchControl } from "@/libs/search";
|
||||||
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
||||||
import { useToolbar } from "@/libs/toolbar";
|
import { useToolbar } from "@/libs/toolbar";
|
||||||
|
|
||||||
import type { Bot } from "@/types/bot";
|
import type { Bot } from "@/types/bot";
|
||||||
|
|
||||||
export default function PluginPage(): JSX.Element {
|
export default function PluginPage(): React.ReactNode {
|
||||||
const [bots, setBots] = useState<Bot[] | null>(null);
|
const [bots, setBots] = useState<Bot[] | null>(null);
|
||||||
const botCount = bots?.length ?? 0;
|
const botCount = bots?.length ?? 0;
|
||||||
const loading = bots === null;
|
const loading = bots === null;
|
||||||
@@ -138,7 +138,7 @@ export default function PluginPage(): JSX.Element {
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<p className="store-loading-container">
|
<p className="store-loading-container">
|
||||||
<span className="loading loading-dots loading-lg store-loading"></span>
|
<span className="loading loading-dots loading-lg store-loading" />
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="store-container">
|
<div className="store-container">
|
||||||
|
@@ -4,7 +4,6 @@ import Translate from "@docusaurus/Translate";
|
|||||||
import { usePagination } from "react-use-pagination";
|
import { usePagination } from "react-use-pagination";
|
||||||
|
|
||||||
import Admonition from "@theme/Admonition";
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
import Paginate from "@/components/Paginate";
|
import Paginate from "@/components/Paginate";
|
||||||
import ResourceCard from "@/components/Resource/Card";
|
import ResourceCard from "@/components/Resource/Card";
|
||||||
@@ -13,9 +12,10 @@ import Searcher from "@/components/Searcher";
|
|||||||
import { authorFilter, tagFilter } from "@/libs/filter";
|
import { authorFilter, tagFilter } from "@/libs/filter";
|
||||||
import { useSearchControl } from "@/libs/search";
|
import { useSearchControl } from "@/libs/search";
|
||||||
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
||||||
|
|
||||||
import type { Driver } from "@/types/driver";
|
import type { Driver } from "@/types/driver";
|
||||||
|
|
||||||
export default function DriverPage(): JSX.Element {
|
export default function DriverPage(): React.ReactNode {
|
||||||
const [drivers, setDrivers] = useState<Driver[] | null>(null);
|
const [drivers, setDrivers] = useState<Driver[] | null>(null);
|
||||||
const driverCount = drivers?.length ?? 0;
|
const driverCount = drivers?.length ?? 0;
|
||||||
const loading = drivers === null;
|
const loading = drivers === null;
|
||||||
@@ -123,7 +123,7 @@ export default function DriverPage(): JSX.Element {
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<p className="store-loading-container">
|
<p className="store-loading-container">
|
||||||
<span className="loading loading-dots loading-lg store-loading"></span>
|
<span className="loading loading-dots loading-lg store-loading" />
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="store-container">
|
<div className="store-container">
|
||||||
|
@@ -1,24 +1,28 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import Translate from "@docusaurus/Translate";
|
import Translate, { translate } from "@docusaurus/Translate";
|
||||||
import { usePagination } from "react-use-pagination";
|
import { usePagination } from "react-use-pagination";
|
||||||
|
|
||||||
import Admonition from "@theme/Admonition";
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
import PluginForm from "@/components/Form/Plugin";
|
import PluginForm from "@/components/Form/Plugin";
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
import Paginate from "@/components/Paginate";
|
import Paginate from "@/components/Paginate";
|
||||||
import ResourceCard from "@/components/Resource/Card";
|
import ResourceCard from "@/components/Resource/Card";
|
||||||
import ResourceDetailCard from "@/components/Resource/DetailCard";
|
import ResourceDetailCard from "@/components/Resource/DetailCard";
|
||||||
import Searcher from "@/components/Searcher";
|
import Searcher from "@/components/Searcher";
|
||||||
import StoreToolbar, { type Action } from "@/components/Store/Toolbar";
|
import StoreToolbar, {
|
||||||
|
type Action,
|
||||||
|
type Sorter,
|
||||||
|
} from "@/components/Store/Toolbar";
|
||||||
import { authorFilter, tagFilter } from "@/libs/filter";
|
import { authorFilter, tagFilter } from "@/libs/filter";
|
||||||
import { useSearchControl } from "@/libs/search";
|
import { useSearchControl } from "@/libs/search";
|
||||||
|
import { SortMode } from "@/libs/sorter";
|
||||||
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
import { fetchRegistryData, loadFailedTitle } from "@/libs/store";
|
||||||
import { useToolbar } from "@/libs/toolbar";
|
import { useToolbar } from "@/libs/toolbar";
|
||||||
|
|
||||||
import type { Plugin } from "@/types/plugin";
|
import type { Plugin } from "@/types/plugin";
|
||||||
|
|
||||||
export default function PluginPage(): JSX.Element {
|
export default function PluginPage(): React.ReactNode {
|
||||||
const [plugins, setPlugins] = useState<Plugin[] | null>(null);
|
const [plugins, setPlugins] = useState<Plugin[] | null>(null);
|
||||||
const pluginCount = plugins?.length ?? 0;
|
const pluginCount = plugins?.length ?? 0;
|
||||||
const loading = plugins === null;
|
const loading = plugins === null;
|
||||||
@@ -27,6 +31,38 @@ export default function PluginPage(): JSX.Element {
|
|||||||
const [isOpenModal, setIsOpenModal] = useState<boolean>(false);
|
const [isOpenModal, setIsOpenModal] = useState<boolean>(false);
|
||||||
const [isOpenCardModal, setIsOpenCardModal] = useState<boolean>(false);
|
const [isOpenCardModal, setIsOpenCardModal] = useState<boolean>(false);
|
||||||
const [clickedPlugin, setClickedPlugin] = useState<Plugin | null>(null);
|
const [clickedPlugin, setClickedPlugin] = useState<Plugin | null>(null);
|
||||||
|
const [sortMode, setSortMode] = useState<SortMode>(SortMode.Default);
|
||||||
|
|
||||||
|
const sorterTool: Sorter = {
|
||||||
|
label:
|
||||||
|
sortMode === SortMode.Default
|
||||||
|
? translate({
|
||||||
|
id: "pages.store.sorter.label.default",
|
||||||
|
description: "The label of default sorter",
|
||||||
|
message: "默认顺序",
|
||||||
|
})
|
||||||
|
: translate({
|
||||||
|
id: "pages.store.sorter.label.updateDesc",
|
||||||
|
description: "The label of updateDesc sorter",
|
||||||
|
message: "更新时间倒序",
|
||||||
|
}),
|
||||||
|
icon: ["fas", "sort-amount-down"],
|
||||||
|
active: sortMode === SortMode.UpdateDesc,
|
||||||
|
onClick: () => {
|
||||||
|
setSortMode(
|
||||||
|
sortMode === SortMode.Default ? SortMode.UpdateDesc : SortMode.Default
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSortedPlugins = (plugins: Plugin[]): Plugin[] => {
|
||||||
|
if (sortMode === SortMode.UpdateDesc) {
|
||||||
|
return [...plugins].sort(
|
||||||
|
(a, b) => new Date(b.time).getTime() - new Date(a.time).getTime()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return plugins;
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
filteredResources: filteredPlugins,
|
filteredResources: filteredPlugins,
|
||||||
@@ -37,7 +73,7 @@ export default function PluginPage(): JSX.Element {
|
|||||||
onSearchBackspace,
|
onSearchBackspace,
|
||||||
onSearchClear,
|
onSearchClear,
|
||||||
onSearchTagClick,
|
onSearchTagClick,
|
||||||
} = useSearchControl<Plugin>(plugins ?? []);
|
} = useSearchControl<Plugin>(getSortedPlugins(plugins ?? []));
|
||||||
const filteredPluginCount = filteredPlugins.length;
|
const filteredPluginCount = filteredPlugins.length;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -113,7 +149,7 @@ export default function PluginPage(): JSX.Element {
|
|||||||
<Translate
|
<Translate
|
||||||
id="pages.store.plugin.searchInfo"
|
id="pages.store.plugin.searchInfo"
|
||||||
description="Plugins search info of the plugin store page"
|
description="Plugins search info of the plugin store page"
|
||||||
values={{ pluginCount, filteredPluginCount: filteredPluginCount }}
|
values={{ pluginCount, filteredPluginCount }}
|
||||||
>
|
>
|
||||||
{"当前共有 {filteredPluginCount} / {pluginCount} 个插件"}
|
{"当前共有 {filteredPluginCount} / {pluginCount} 个插件"}
|
||||||
</Translate>
|
</Translate>
|
||||||
@@ -134,6 +170,7 @@ export default function PluginPage(): JSX.Element {
|
|||||||
<StoreToolbar
|
<StoreToolbar
|
||||||
className="not-prose"
|
className="not-prose"
|
||||||
filters={filterTools}
|
filters={filterTools}
|
||||||
|
sorter={sorterTool}
|
||||||
action={actionTool}
|
action={actionTool}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -143,7 +180,7 @@ export default function PluginPage(): JSX.Element {
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<p className="store-loading-container">
|
<p className="store-loading-container">
|
||||||
<span className="loading loading-dots loading-lg store-loading"></span>
|
<span className="loading loading-dots loading-lg store-loading" />
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="store-container">
|
<div className="store-container">
|
||||||
|
@@ -6,6 +6,7 @@ import { useVersionedSidebar } from "@nullbot/docusaurus-plugin-getsidebar/clien
|
|||||||
import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts";
|
import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts";
|
||||||
|
|
||||||
import BackToTopButton from "@theme/BackToTopButton";
|
import BackToTopButton from "@theme/BackToTopButton";
|
||||||
|
import Heading from "@theme/Heading";
|
||||||
import Layout from "@theme/Layout";
|
import Layout from "@theme/Layout";
|
||||||
import Page from "@theme/Page";
|
import Page from "@theme/Page";
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ type Props = {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
function StorePage({ title, children }: Props): JSX.Element {
|
function StorePage({ title, children }: Props): React.ReactNode {
|
||||||
const sidebarItems = useVersionedSidebar(
|
const sidebarItems = useVersionedSidebar(
|
||||||
useDocsVersionCandidates()[0].name,
|
useDocsVersionCandidates()[0].name,
|
||||||
SIDEBAR_ID
|
SIDEBAR_ID
|
||||||
@@ -28,14 +29,19 @@ function StorePage({ title, children }: Props): JSX.Element {
|
|||||||
<Page hideTableOfContents reduceContentWidth={false} sidebarId={SIDEBAR_ID}>
|
<Page hideTableOfContents reduceContentWidth={false} sidebarId={SIDEBAR_ID}>
|
||||||
<SidebarContentFiller items={sidebarItems} />
|
<SidebarContentFiller items={sidebarItems} />
|
||||||
<article className="prose max-w-full">
|
<article className="prose max-w-full">
|
||||||
<h1 className="store-title">{title}</h1>
|
<Heading as="h1" className="store-title">
|
||||||
|
{title}
|
||||||
|
</Heading>
|
||||||
{children}
|
{children}
|
||||||
</article>
|
</article>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StoreLayout({ title, ...props }: Props): JSX.Element {
|
export default function StoreLayout({
|
||||||
|
title,
|
||||||
|
...props
|
||||||
|
}: Props): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageMetadata title={title} />
|
<PageMetadata title={title} />
|
||||||
|
@@ -2,9 +2,10 @@ import React, { useState } from "react";
|
|||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import type { IconProp } from "@fortawesome/fontawesome-svg-core";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import type { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
|
||||||
export type Filter = {
|
export type Filter = {
|
||||||
label: string;
|
label: string;
|
||||||
icon: IconProp;
|
icon: IconProp;
|
||||||
@@ -12,6 +13,13 @@ export type Filter = {
|
|||||||
onSubmit: (query: string) => void;
|
onSubmit: (query: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Sorter = {
|
||||||
|
label: string;
|
||||||
|
icon: IconProp;
|
||||||
|
active: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
export type Action = {
|
export type Action = {
|
||||||
label: string;
|
label: string;
|
||||||
icon: IconProp;
|
icon: IconProp;
|
||||||
@@ -20,6 +28,7 @@ export type Action = {
|
|||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
|
sorter?: Sorter;
|
||||||
action?: Action;
|
action?: Action;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@@ -29,7 +38,7 @@ function ToolbarFilter({
|
|||||||
icon,
|
icon,
|
||||||
choices,
|
choices,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: Filter): JSX.Element {
|
}: Filter): React.ReactNode {
|
||||||
const [query, setQuery] = useState<string>("");
|
const [query, setQuery] = useState<string>("");
|
||||||
|
|
||||||
const filteredChoices = choices
|
const filteredChoices = choices
|
||||||
@@ -65,7 +74,7 @@ function ToolbarFilter({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={icon} />
|
<FontAwesomeIcon icon={icon} />
|
||||||
{label}
|
<span className="hidden sm:block">{label}</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="dropdown-content store-toolbar-dropdown">
|
<div className="dropdown-content store-toolbar-dropdown">
|
||||||
<input
|
<input
|
||||||
@@ -96,33 +105,65 @@ function ToolbarFilter({
|
|||||||
|
|
||||||
export default function StoreToolbar({
|
export default function StoreToolbar({
|
||||||
filters,
|
filters,
|
||||||
|
sorter,
|
||||||
action,
|
action,
|
||||||
className,
|
className,
|
||||||
}: Props): JSX.Element | null {
|
}: Props): React.ReactNode | null {
|
||||||
if (!(filters && filters.length > 0) && !action) {
|
if (!(filters && filters.length > 0) && !action) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx("store-toolbar", className)}>
|
<>
|
||||||
{filters && filters.length > 0 && (
|
<div className={clsx("store-toolbar", className)}>
|
||||||
<div className="store-toolbar-filters">
|
<div className="store-toolbar-filters">
|
||||||
{filters.map((filter, index) => (
|
{filters?.map((filter, index) => (
|
||||||
<ToolbarFilter key={index} {...filter} />
|
<ToolbarFilter key={index} {...filter} />
|
||||||
))}
|
))}
|
||||||
|
{sorter && (
|
||||||
|
<div className="store-toolbar-sorter store-toolbar-sorter-desktop">
|
||||||
|
<button
|
||||||
|
className={clsx(
|
||||||
|
"btn btn-sm btn-primary no-animation mr-2",
|
||||||
|
!sorter.active && "btn-outline"
|
||||||
|
)}
|
||||||
|
onClick={sorter.onClick}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={sorter.icon} />
|
||||||
|
{sorter.label}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{action && (
|
{action && (
|
||||||
<div className="store-toolbar-action">
|
<div className="store-toolbar-action">
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary no-animation"
|
className="btn btn-sm btn-primary no-animation"
|
||||||
onClick={action.onClick}
|
onClick={action.onClick}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={action.icon} />
|
<FontAwesomeIcon icon={action.icon} />
|
||||||
{action.label}
|
{action.label}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className={clsx("store-toolbar store-toolbar-second", className)}>
|
||||||
|
{sorter && (
|
||||||
|
<div className="store-toolbar-sorter">
|
||||||
|
<button
|
||||||
|
className={clsx(
|
||||||
|
"btn btn-sm btn-primary no-animation mr-2",
|
||||||
|
!sorter.active && "btn-outline"
|
||||||
|
)}
|
||||||
|
onClick={sorter.onClick}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={sorter.icon} />
|
||||||
|
{sorter.label}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,18 @@
|
|||||||
&-toolbar {
|
&-toolbar {
|
||||||
@apply flex items-center justify-center my-4;
|
@apply flex items-center justify-center my-4;
|
||||||
|
|
||||||
|
&-second {
|
||||||
|
@apply lg:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-sorter {
|
||||||
|
@apply max-lg:flex-1;
|
||||||
|
|
||||||
|
&-desktop {
|
||||||
|
@apply max-lg:hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-filters {
|
&-filters {
|
||||||
@apply flex grow gap-2;
|
@apply flex grow gap-2;
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,8 @@ import clsx from "clsx";
|
|||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { pickTextColor } from "@/libs/color";
|
import { pickTextColor } from "@/libs/color";
|
||||||
import { Tag as TagType } from "@/types/tag";
|
|
||||||
|
import type { Tag as TagType } from "@/types/tag";
|
||||||
|
|
||||||
export default function Tag({
|
export default function Tag({
|
||||||
label,
|
label,
|
||||||
@@ -15,7 +16,7 @@ export default function Tag({
|
|||||||
}: TagType & {
|
}: TagType & {
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: React.MouseEventHandler<HTMLSpanElement>;
|
onClick?: React.MouseEventHandler<HTMLSpanElement>;
|
||||||
}): JSX.Element {
|
}): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={clsx("tag", className)}
|
className={clsx("tag", className)}
|
||||||
|
@@ -2,15 +2,54 @@ import { useCallback, useState } from "react";
|
|||||||
|
|
||||||
import { translate } from "@docusaurus/Translate";
|
import { translate } from "@docusaurus/Translate";
|
||||||
|
|
||||||
|
import { getValidStatus } from "@/components/Resource/ValidStatus";
|
||||||
|
|
||||||
|
import { ValidStatus } from "./valid";
|
||||||
|
|
||||||
import type { Resource } from "./store";
|
import type { Resource } from "./store";
|
||||||
|
|
||||||
export type Filter<T extends Resource = Resource> = {
|
export type Filter<T extends Resource = Resource> = {
|
||||||
type: string;
|
type: string;
|
||||||
id: string;
|
id: string;
|
||||||
displayName: string;
|
displayName?: string;
|
||||||
filter: (resource: T) => boolean;
|
filter: (resource: T) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validStatusDisplayName = {
|
||||||
|
[ValidStatus.VALID]: translate({
|
||||||
|
id: "pages.store.filter.validateStatusDisplayName.valid",
|
||||||
|
description: "The display name of validateStatus filter",
|
||||||
|
message: "状态: 通过",
|
||||||
|
}),
|
||||||
|
[ValidStatus.INVALID]: translate({
|
||||||
|
id: "pages.store.filter.validateStatusDisplayName.invalid",
|
||||||
|
description: "The display name of validateStatus filter",
|
||||||
|
message: "状态: 未通过",
|
||||||
|
}),
|
||||||
|
[ValidStatus.SKIP]: translate({
|
||||||
|
id: "pages.store.filter.validateStatusDisplayName.skip",
|
||||||
|
description: "The display name of validateStatus filter",
|
||||||
|
message: "状态: 跳过",
|
||||||
|
}),
|
||||||
|
[ValidStatus.MISSING]: translate({
|
||||||
|
id: "pages.store.filter.validateStatusDisplayName.missing",
|
||||||
|
description: "The display name of validateStatus filter",
|
||||||
|
message: "状态: 缺失",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validStatusFilter = <T extends Resource = Resource>(
|
||||||
|
validStatus: ValidStatus
|
||||||
|
): Filter<T> => ({
|
||||||
|
type: "validStatus",
|
||||||
|
id: `validStatus-${validStatus}`,
|
||||||
|
displayName: validStatusDisplayName[validStatus],
|
||||||
|
filter: (resource: Resource): boolean =>
|
||||||
|
resource.resourceType === "plugin"
|
||||||
|
? getValidStatus(resource) === validStatus
|
||||||
|
: true,
|
||||||
|
});
|
||||||
|
|
||||||
export const tagFilter = <T extends Resource = Resource>(
|
export const tagFilter = <T extends Resource = Resource>(
|
||||||
tag: string
|
tag: string
|
||||||
): Filter<T> => ({
|
): Filter<T> => ({
|
||||||
@@ -27,6 +66,7 @@ export const tagFilter = <T extends Resource = Resource>(
|
|||||||
filter: (resource: Resource): boolean =>
|
filter: (resource: Resource): boolean =>
|
||||||
resource.tags.map((tag) => tag.label).includes(tag),
|
resource.tags.map((tag) => tag.label).includes(tag),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const officialFilter = <T extends Resource = Resource>(
|
export const officialFilter = <T extends Resource = Resource>(
|
||||||
official: boolean = true
|
official: boolean = true
|
||||||
): Filter<T> => ({
|
): Filter<T> => ({
|
||||||
@@ -39,6 +79,7 @@ export const officialFilter = <T extends Resource = Resource>(
|
|||||||
}).split("|")[Number(official)],
|
}).split("|")[Number(official)],
|
||||||
filter: (resource: Resource): boolean => resource.is_official === official,
|
filter: (resource: Resource): boolean => resource.is_official === official,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const authorFilter = <T extends Resource = Resource>(
|
export const authorFilter = <T extends Resource = Resource>(
|
||||||
author: string
|
author: string
|
||||||
): Filter<T> => ({
|
): Filter<T> => ({
|
||||||
@@ -54,6 +95,7 @@ export const authorFilter = <T extends Resource = Resource>(
|
|||||||
),
|
),
|
||||||
filter: (resource: Resource): boolean => resource.author === author,
|
filter: (resource: Resource): boolean => resource.author === author,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const queryFilter = <T extends Resource = Resource>(
|
export const queryFilter = <T extends Resource = Resource>(
|
||||||
query: string
|
query: string
|
||||||
): Filter<T> => ({
|
): Filter<T> => ({
|
||||||
@@ -61,7 +103,9 @@ export const queryFilter = <T extends Resource = Resource>(
|
|||||||
id: `query-${query}`,
|
id: `query-${query}`,
|
||||||
displayName: query,
|
displayName: query,
|
||||||
filter: (resource: Resource): boolean => {
|
filter: (resource: Resource): boolean => {
|
||||||
if (!query) return true;
|
if (!query) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const queryLower = query.toLowerCase();
|
const queryLower = query.toLowerCase();
|
||||||
const pluginMatch =
|
const pluginMatch =
|
||||||
resource.resourceType === "plugin" &&
|
resource.resourceType === "plugin" &&
|
||||||
@@ -100,7 +144,9 @@ export function useFilteredResources<T extends Resource>(
|
|||||||
|
|
||||||
const addFilter = useCallback(
|
const addFilter = useCallback(
|
||||||
(filter: Filter<T>) => {
|
(filter: Filter<T>) => {
|
||||||
if (filters.some((f) => f.id === filter.id)) return;
|
if (filters.some((f) => f.id === filter.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setFilters((filters) => [...filters, filter]);
|
setFilters((filters) => [...filters, filter]);
|
||||||
},
|
},
|
||||||
[filters, setFilters]
|
[filters, setFilters]
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { type Filter, useFilteredResources, queryFilter } from "./filter";
|
import { type Filter, useFilteredResources, queryFilter } from "./filter";
|
||||||
|
|
||||||
import type { Resource } from "./store";
|
import type { Resource } from "./store";
|
||||||
|
|
||||||
type useSearchControlReturn<T extends Resource> = {
|
type useSearchControlReturn<T extends Resource> = {
|
||||||
@@ -42,7 +43,9 @@ export function useSearchControl<T extends Resource>(
|
|||||||
|
|
||||||
const newFilter = queryFilter<T>(newQuery);
|
const newFilter = queryFilter<T>(newQuery);
|
||||||
// do nothing if filter is not changed
|
// do nothing if filter is not changed
|
||||||
if (currentFilter?.id === newFilter.id) return;
|
if (currentFilter?.id === newFilter.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// remove old currentFilter
|
// remove old currentFilter
|
||||||
currentFilter && removeFilter(currentFilter);
|
currentFilter && removeFilter(currentFilter);
|
||||||
|
4
website/src/libs/sorter.ts
Normal file
4
website/src/libs/sorter.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum SortMode {
|
||||||
|
Default,
|
||||||
|
UpdateDesc,
|
||||||
|
}
|
@@ -30,10 +30,11 @@ export async function fetchRegistryData<T extends RegistryDataType>(
|
|||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
throw new Error(`Failed to fetch ${dataType}s: ${e}`);
|
throw new Error(`Failed to fetch ${dataType}s: ${e}`);
|
||||||
});
|
});
|
||||||
if (!resp.ok)
|
if (!resp.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to fetch ${dataType}s: ${resp.status} ${resp.statusText}`
|
`Failed to fetch ${dataType}s: ${resp.status} ${resp.statusText}`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
const data = (await resp.json()) as RegistryDataResponseTypes[T];
|
const data = (await resp.json()) as RegistryDataResponseTypes[T];
|
||||||
return data.map(
|
return data.map(
|
||||||
(resource) => ({ ...resource, resourceType: dataType }) as ResourceTypes[T]
|
(resource) => ({ ...resource, resourceType: dataType }) as ResourceTypes[T]
|
||||||
|
@@ -1,8 +1,17 @@
|
|||||||
import { authorFilter, tagFilter, type Filter } from "./filter";
|
import { translate } from "@docusaurus/Translate";
|
||||||
import type { Resource } from "./store";
|
|
||||||
|
|
||||||
import type { Filter as FilterTool } from "@/components/Store/Toolbar";
|
import type { Filter as FilterTool } from "@/components/Store/Toolbar";
|
||||||
|
|
||||||
|
import {
|
||||||
|
authorFilter,
|
||||||
|
tagFilter,
|
||||||
|
validStatusFilter,
|
||||||
|
type Filter,
|
||||||
|
} from "./filter";
|
||||||
|
import { ValidStatus } from "./valid";
|
||||||
|
|
||||||
|
import type { Resource } from "./store";
|
||||||
|
|
||||||
type Props<T extends Resource = Resource> = {
|
type Props<T extends Resource = Resource> = {
|
||||||
resources: T[];
|
resources: T[];
|
||||||
addFilter: (filter: Filter<T>) => void;
|
addFilter: (filter: Filter<T>) => void;
|
||||||
@@ -38,7 +47,43 @@ export function useToolbar<T extends Resource = Resource>({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateStatusFilterMapping: Record<string, ValidStatus> = {
|
||||||
|
[translate({
|
||||||
|
id: "pages.store.filter.validateStatusDisplayName.valid",
|
||||||
|
description: "The display name of validateStatus filter",
|
||||||
|
message: "通过",
|
||||||
|
})]: ValidStatus.VALID,
|
||||||
|
[translate({
|
||||||
|
id: "pages.store.filter.validateStatusDisplayName.invalid",
|
||||||
|
description: "The display name of validateStatus filter",
|
||||||
|
message: "未通过",
|
||||||
|
})]: ValidStatus.INVALID,
|
||||||
|
[translate({
|
||||||
|
id: "pages.store.filter.validateStatusDisplayName.skip",
|
||||||
|
description: "The display name of validateStatus filter",
|
||||||
|
message: "跳过",
|
||||||
|
})]: ValidStatus.SKIP,
|
||||||
|
[translate({
|
||||||
|
id: "pages.store.filter.validateStatusDisplayName.missing",
|
||||||
|
description: "The display name of validateStatus filter",
|
||||||
|
message: "缺失",
|
||||||
|
})]: ValidStatus.MISSING,
|
||||||
|
};
|
||||||
|
|
||||||
|
const validStatusFilterTool: FilterTool = {
|
||||||
|
label: "状态",
|
||||||
|
icon: ["fas", "plug"],
|
||||||
|
choices: Object.keys(validateStatusFilterMapping),
|
||||||
|
onSubmit: (type: string) => {
|
||||||
|
const validStatus = validateStatusFilterMapping[type];
|
||||||
|
if (!validStatus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addFilter(validStatusFilter(validStatus));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: [authorFilterTool, tagFilterTool],
|
filters: [authorFilterTool, tagFilterTool, validStatusFilterTool],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import Layout from "@theme/Layout";
|
import Layout from "@theme/Layout";
|
||||||
|
|
||||||
import HomeContent from "@/components/Home";
|
import HomeContent from "@/components/Home";
|
||||||
|
|
||||||
export default function Homepage(): JSX.Element {
|
export default function Homepage(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<HomeContent />
|
<HomeContent />
|
||||||
|
@@ -5,7 +5,7 @@ import { translate } from "@docusaurus/Translate";
|
|||||||
import AdapterPageContent from "@/components/Store/Content/Adapter";
|
import AdapterPageContent from "@/components/Store/Content/Adapter";
|
||||||
import StoreLayout from "@/components/Store/Layout";
|
import StoreLayout from "@/components/Store/Layout";
|
||||||
|
|
||||||
export default function StoreAdapters(): JSX.Element {
|
export default function StoreAdapters(): React.ReactNode {
|
||||||
const title = translate({
|
const title = translate({
|
||||||
id: "pages.store.adapter.title",
|
id: "pages.store.adapter.title",
|
||||||
message: "适配器商店",
|
message: "适配器商店",
|
||||||
|
@@ -5,7 +5,7 @@ import { translate } from "@docusaurus/Translate";
|
|||||||
import BotPageContent from "@/components/Store/Content/Bot";
|
import BotPageContent from "@/components/Store/Content/Bot";
|
||||||
import StoreLayout from "@/components/Store/Layout";
|
import StoreLayout from "@/components/Store/Layout";
|
||||||
|
|
||||||
export default function StoreBots(): JSX.Element {
|
export default function StoreBots(): React.ReactNode {
|
||||||
const title = translate({
|
const title = translate({
|
||||||
id: "pages.store.bot.title",
|
id: "pages.store.bot.title",
|
||||||
message: "机器人商店",
|
message: "机器人商店",
|
||||||
|
@@ -5,7 +5,7 @@ import { translate } from "@docusaurus/Translate";
|
|||||||
import DriverPageContent from "@/components/Store/Content/Driver";
|
import DriverPageContent from "@/components/Store/Content/Driver";
|
||||||
import StoreLayout from "@/components/Store/Layout";
|
import StoreLayout from "@/components/Store/Layout";
|
||||||
|
|
||||||
export default function StoreDrivers(): JSX.Element {
|
export default function StoreDrivers(): React.ReactNode {
|
||||||
const title = translate({
|
const title = translate({
|
||||||
id: "pages.store.driver.title",
|
id: "pages.store.driver.title",
|
||||||
message: "驱动器商店",
|
message: "驱动器商店",
|
||||||
|
@@ -2,6 +2,6 @@ import React from "react";
|
|||||||
|
|
||||||
import { Redirect } from "@docusaurus/router";
|
import { Redirect } from "@docusaurus/router";
|
||||||
|
|
||||||
export default function Store(): JSX.Element {
|
export default function Store(): React.ReactNode {
|
||||||
return <Redirect to="/store/plugins" />;
|
return <Redirect to="/store/plugins" />;
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import { translate } from "@docusaurus/Translate";
|
|||||||
import PluginPageContent from "@/components/Store/Content/Plugin";
|
import PluginPageContent from "@/components/Store/Content/Plugin";
|
||||||
import StoreLayout from "@/components/Store/Layout";
|
import StoreLayout from "@/components/Store/Layout";
|
||||||
|
|
||||||
export default function StorePlugins(): JSX.Element {
|
export default function StorePlugins(): React.ReactNode {
|
||||||
const title = translate({
|
const title = translate({
|
||||||
id: "pages.store.plugin.title",
|
id: "pages.store.plugin.title",
|
||||||
message: "插件商店",
|
message: "插件商店",
|
||||||
|
@@ -2,7 +2,7 @@ import React, { type ComponentProps } from "react";
|
|||||||
|
|
||||||
export interface Props extends Omit<ComponentProps<"svg">, "viewBox"> {}
|
export interface Props extends Omit<ComponentProps<"svg">, "viewBox"> {}
|
||||||
|
|
||||||
export default function IconCloudflare(props: Props): JSX.Element {
|
export default function IconCloudflare(props: Props): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 651.29 94.76"
|
viewBox="0 0 651.29 94.76"
|
||||||
|
@@ -2,7 +2,7 @@ import React, { type ComponentProps } from "react";
|
|||||||
|
|
||||||
export interface Props extends Omit<ComponentProps<"svg">, "viewBox"> {}
|
export interface Props extends Omit<ComponentProps<"svg">, "viewBox"> {}
|
||||||
|
|
||||||
export default function IconNetlify(props: Props): JSX.Element {
|
export default function IconNetlify(props: Props): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<svg viewBox="0 0 256 105" xmlns="http://www.w3.org/2000/svg" {...props}>
|
<svg viewBox="0 0 256 105" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
<g clipPath="url(#clip0_236_25)">
|
<g clipPath="url(#clip0_236_25)">
|
||||||
|
@@ -4,12 +4,13 @@ import { useWindowSize } from "@nullbot/docusaurus-theme-nonepress/client";
|
|||||||
|
|
||||||
import type { Props } from "@theme/Page/TOC/Container";
|
import type { Props } from "@theme/Page/TOC/Container";
|
||||||
import OriginTOCContainer from "@theme-original/Page/TOC/Container";
|
import OriginTOCContainer from "@theme-original/Page/TOC/Container";
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
export default function TOCContainer({
|
export default function TOCContainer({
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: Props): JSX.Element {
|
}: Props): React.ReactNode {
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const isClient = windowSize !== "ssr";
|
const isClient = windowSize !== "ssr";
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ export default function TOCContainer({
|
|||||||
{children}
|
{children}
|
||||||
{isClient && (
|
{isClient && (
|
||||||
<div className="toc-ads-container">
|
<div className="toc-ads-container">
|
||||||
<div className="wwads-cn wwads-vertical toc-ads" data-id="281"></div>
|
<div className="wwads-cn wwads-vertical toc-ads" data-id="281" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</OriginTOCContainer>
|
</OriginTOCContainer>
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
self.addEventListener("install", function () {
|
self.addEventListener("install", () => {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener("activate", function () {
|
self.addEventListener("activate", () => {
|
||||||
self.registration
|
self.registration
|
||||||
.unregister()
|
.unregister()
|
||||||
.then(function () {
|
.then(() => {
|
||||||
return self.clients.matchAll();
|
return self.clients.matchAll();
|
||||||
})
|
})
|
||||||
.then(function (clients) {
|
.then((clients) => {
|
||||||
clients.forEach((client) => client.navigate(client.url));
|
clients.forEach((client) => client.navigate(client.url));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
if (location.search.includes("?uwu"))
|
if (location.search.includes("?uwu")) {
|
||||||
document.documentElement.setAttribute("data-uwu", "true");
|
document.documentElement.setAttribute("data-uwu", "true");
|
||||||
|
}
|
||||||
|
@@ -11,7 +11,9 @@ function excludeThemeColor(
|
|||||||
): { [key: string]: string } {
|
): { [key: string]: string } {
|
||||||
const newObj: { [key: string]: string } = {};
|
const newObj: { [key: string]: string } = {};
|
||||||
for (const key in theme) {
|
for (const key in theme) {
|
||||||
if (exclude.includes(key)) continue;
|
if (exclude.includes(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
newObj[key] = theme[key]!;
|
newObj[key] = theme[key]!;
|
||||||
}
|
}
|
||||||
return newObj;
|
return newObj;
|
||||||
|
@@ -1,590 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 5
|
|
||||||
description: 通用消息组件
|
|
||||||
---
|
|
||||||
|
|
||||||
import Tabs from "@theme/Tabs";
|
|
||||||
import TabItem from "@theme/TabItem";
|
|
||||||
|
|
||||||
# 通用消息组件
|
|
||||||
|
|
||||||
`uniseg` 模块属于 `nonebot-plugin-alconna` 的子插件,其提供了一套通用的消息组件,用于在 `nonebot-plugin-alconna` 下构建通用消息。
|
|
||||||
|
|
||||||
## 通用消息段
|
|
||||||
|
|
||||||
适配器下的消息段标注会匹配适配器特定的 `MessageSegment`, 而通用消息段与适配器消息段的区别在于:
|
|
||||||
通用消息段会匹配多个适配器中相似类型的消息段,并返回 `uniseg` 模块中定义的 [`Segment` 模型](https://nonebot.dev/docs/next/best-practice/alconna/utils#%E9%80%9A%E7%94%A8%E6%B6%88%E6%81%AF%E6%AE%B5), 以达到**跨平台接收消息**的作用。
|
|
||||||
|
|
||||||
`nonebot-plugin-alconna.uniseg` 提供了类似 `MessageSegment` 的通用消息段,并可在 `Alconna` 下直接标注使用:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Segment:
|
|
||||||
"""基类标注"""
|
|
||||||
children: List["Segment"]
|
|
||||||
|
|
||||||
class Text(Segment):
|
|
||||||
"""Text对象, 表示一类文本元素"""
|
|
||||||
text: str
|
|
||||||
styles: Dict[Tuple[int, int], List[str]]
|
|
||||||
|
|
||||||
class At(Segment):
|
|
||||||
"""At对象, 表示一类提醒某用户的元素"""
|
|
||||||
flag: Literal["user", "role", "channel"]
|
|
||||||
target: str
|
|
||||||
display: Optional[str]
|
|
||||||
|
|
||||||
class AtAll(Segment):
|
|
||||||
"""AtAll对象, 表示一类提醒所有人的元素"""
|
|
||||||
here: bool
|
|
||||||
|
|
||||||
class Emoji(Segment):
|
|
||||||
"""Emoji对象, 表示一类表情元素"""
|
|
||||||
id: str
|
|
||||||
name: Optional[str]
|
|
||||||
|
|
||||||
class Media(Segment):
|
|
||||||
url: Optional[str]
|
|
||||||
id: Optional[str]
|
|
||||||
path: Optional[Union[str, Path]]
|
|
||||||
raw: Optional[Union[bytes, BytesIO]]
|
|
||||||
mimetype: Optional[str]
|
|
||||||
name: str
|
|
||||||
|
|
||||||
to_url: ClassVar[Optional[MediaToUrl]]
|
|
||||||
|
|
||||||
class Image(Media):
|
|
||||||
"""Image对象, 表示一类图片元素"""
|
|
||||||
|
|
||||||
class Audio(Media):
|
|
||||||
"""Audio对象, 表示一类音频元素"""
|
|
||||||
duration: Optional[int]
|
|
||||||
|
|
||||||
class Voice(Media):
|
|
||||||
"""Voice对象, 表示一类语音元素"""
|
|
||||||
duration: Optional[int]
|
|
||||||
|
|
||||||
class Video(Media):
|
|
||||||
"""Video对象, 表示一类视频元素"""
|
|
||||||
|
|
||||||
class File(Segment):
|
|
||||||
"""File对象, 表示一类文件元素"""
|
|
||||||
id: str
|
|
||||||
name: Optional[str]
|
|
||||||
|
|
||||||
class Reply(Segment):
|
|
||||||
"""Reply对象,表示一类回复消息"""
|
|
||||||
id: str
|
|
||||||
"""此处不一定是消息ID,可能是其他ID,如消息序号等"""
|
|
||||||
msg: Optional[Union[Message, str]]
|
|
||||||
origin: Optional[Any]
|
|
||||||
|
|
||||||
class Reference(Segment):
|
|
||||||
"""Reference对象,表示一类引用消息。转发消息 (Forward) 也属于此类"""
|
|
||||||
id: Optional[str]
|
|
||||||
"""此处不一定是消息ID,可能是其他ID,如消息序号等"""
|
|
||||||
children: List[Union[RefNode, CustomNode]]
|
|
||||||
|
|
||||||
class Hyper(Segment):
|
|
||||||
"""Hyper对象,表示一类超级消息。如卡片消息、ark消息、小程序等"""
|
|
||||||
format: Literal["xml", "json"]
|
|
||||||
raw: Optional[str]
|
|
||||||
content: Optional[Union[dict, list]]
|
|
||||||
|
|
||||||
class Other(Segment):
|
|
||||||
"""其他 Segment"""
|
|
||||||
origin: MessageSegment
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
:::tip
|
|
||||||
|
|
||||||
或许你注意到了 `Segment` 上有一个 `children` 属性。
|
|
||||||
|
|
||||||
这是因为在 [`Satori`](https://satori.js.org/zh-CN/) 协议的规定下,一类元素可以用其子元素来代表一类兼容性消息
|
|
||||||
(例如,qq 的商场表情在某些平台上可以用图片代替)。
|
|
||||||
|
|
||||||
为此,本插件提供了两种方式来表达 "获取子元素" 的方法:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.builtins.uniseg.chronocat import MarketFace
|
|
||||||
from nonebot_plugin_alconna import Args, Image, Alconna, select, select_first
|
|
||||||
|
|
||||||
# 表示这个指令需要的图片要么直接是 Image 要么是在 MarketFace 元素内的 Image
|
|
||||||
alc1 = Alconna("make_meme", Args["img", [Image, Image.from_(MarketFace)]])
|
|
||||||
|
|
||||||
# 表示这个指令需要的图片会在目标元素下进行搜索,将所有符合 Image 的元素选出来并将第一个作为结果
|
|
||||||
alc2 = Alconna("make_meme", Args["img", select(Image, index=0)]) # 也可以使用 select_first(Image)
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## 通用消息序列
|
|
||||||
|
|
||||||
`nonebot-plugin-alconna.uniseg` 同时提供了一个类似于 `Message` 的 `UniMessage` 类型,其元素为经过通用标注转换后的通用消息段。
|
|
||||||
|
|
||||||
你可以用如下方式获取 `UniMessage`:
|
|
||||||
|
|
||||||
<Tabs groupId="get_unimsg">
|
|
||||||
<TabItem value="depend" label="使用依赖注入">
|
|
||||||
|
|
||||||
通过提供的 `UniversalMessage` 或 `UniMsg` 依赖注入器来获取 `UniMessage`。
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMsg, At, Reply
|
|
||||||
|
|
||||||
|
|
||||||
matcher = on_xxx(...)
|
|
||||||
|
|
||||||
@matcher.handle()
|
|
||||||
async def _(msg: UniMsg):
|
|
||||||
reply = msg[Reply, 0]
|
|
||||||
print(reply.origin)
|
|
||||||
if msg.has(At):
|
|
||||||
ats = msg.get(At)
|
|
||||||
print(ats)
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="method" label="使用 UniMessage.generate">
|
|
||||||
|
|
||||||
注意,`generate` 方法在响应器以外的地方如果不传入 `event` 与 `bot` 则无法处理 reply。
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import Message, EventMessage
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
|
||||||
|
|
||||||
|
|
||||||
matcher = on_xxx(...)
|
|
||||||
|
|
||||||
@matcher.handle()
|
|
||||||
async def _(message: Message = EventMessage()):
|
|
||||||
msg = await UniMessage.generate(message=message)
|
|
||||||
msg1 = UniMessage.generate_without_reply(message=message)
|
|
||||||
```
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
不仅如此,你还可以通过 `UniMessage` 的 `export` 与 `send` 方法来**跨平台发送消息**。
|
|
||||||
|
|
||||||
`UniMessage.export` 会通过传入的 `bot: Bot` 参数,或上下文中的 `Bot` 对象读取适配器信息,并使用对应的生成方法把通用消息转为适配器对应的消息序列:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import Bot, on_command
|
|
||||||
from nonebot_plugin_alconna.uniseg import Image, UniMessage
|
|
||||||
|
|
||||||
|
|
||||||
test = on_command("test")
|
|
||||||
|
|
||||||
@test.handle()
|
|
||||||
async def handle_test():
|
|
||||||
await test.send(await UniMessage(Image(path="path/to/img")).export())
|
|
||||||
```
|
|
||||||
|
|
||||||
除此之外 `UniMessage.send` 方法基于 `UniMessage.export` 并调用各适配器下的发送消息方法,返回一个 `Receipt` 对象,用于修改/撤回消息:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import Bot, on_command
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
|
||||||
|
|
||||||
|
|
||||||
test = on_command("test")
|
|
||||||
|
|
||||||
@test.handle()
|
|
||||||
async def handle():
|
|
||||||
receipt = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
|
|
||||||
await receipt.recall(delay=1)
|
|
||||||
```
|
|
||||||
|
|
||||||
而在 `AlconnaMatcher` 下,`got`, `send`, `reject` 等可以发送消息的方法皆支持使用 `UniMessage`,不需要手动调用 export 方法:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from arclet.alconna import Alconna, Args
|
|
||||||
from nonebot_plugin_alconna import Match, AlconnaMatcher, on_alconna
|
|
||||||
from nonebot_plugin_alconna.uniseg import At, UniMessage
|
|
||||||
|
|
||||||
|
|
||||||
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
|
|
||||||
|
|
||||||
@test_cmd.handle()
|
|
||||||
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
|
|
||||||
if target.available:
|
|
||||||
matcher.set_path_arg("target", target.result)
|
|
||||||
|
|
||||||
@test_cmd.got_path("target", prompt="请输入目标")
|
|
||||||
async def tt(target: At):
|
|
||||||
await test_cmd.send(UniMessage([target, "\ndone."]))
|
|
||||||
```
|
|
||||||
|
|
||||||
:::caution
|
|
||||||
|
|
||||||
在响应器以外的地方,除非启用了 `alconna_apply_fetch_targets` 配置项,否则 `bot` 参数必须手动传入。
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
### 构造
|
|
||||||
|
|
||||||
如同 `Message`, `UniMessage` 可以传入单个字符串/消息段,或可迭代的字符串/消息段:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, At
|
|
||||||
|
|
||||||
|
|
||||||
msg = UniMessage("Hello")
|
|
||||||
msg1 = UniMessage(At("user", "124"))
|
|
||||||
msg2 = UniMessage(["Hello", At("user", "124")])
|
|
||||||
```
|
|
||||||
|
|
||||||
`UniMessage` 上同时存在便捷方法,令其可以链式地添加消息段:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, At, Image
|
|
||||||
|
|
||||||
|
|
||||||
msg = UniMessage.text("Hello").at("124").image(path="/path/to/img")
|
|
||||||
assert msg == UniMessage(
|
|
||||||
["Hello", At("user", "124"), Image(path="/path/to/img")]
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 拼接消息
|
|
||||||
|
|
||||||
`str`、`UniMessage`、`Segment` 对象之间可以直接相加,相加均会返回一个新的 `UniMessage` 对象:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 消息序列与消息段相加
|
|
||||||
UniMessage("text") + Text("text")
|
|
||||||
# 消息序列与字符串相加
|
|
||||||
UniMessage([Text("text")]) + "text"
|
|
||||||
# 消息序列与消息序列相加
|
|
||||||
UniMessage("text") + UniMessage([Text("text")])
|
|
||||||
# 字符串与消息序列相加
|
|
||||||
"text" + UniMessage([Text("text")])
|
|
||||||
# 消息段与消息段相加
|
|
||||||
Text("text") + Text("text")
|
|
||||||
# 消息段与字符串相加
|
|
||||||
Text("text") + "text"
|
|
||||||
# 消息段与消息序列相加
|
|
||||||
Text("text") + UniMessage([Text("text")])
|
|
||||||
# 字符串与消息段相加
|
|
||||||
"text" + Text("text")
|
|
||||||
```
|
|
||||||
|
|
||||||
如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append`、`Message.extend` 方法,或者使用自加:
|
|
||||||
|
|
||||||
```python
|
|
||||||
msg = UniMessage([Text("text")])
|
|
||||||
# 自加
|
|
||||||
msg += "text"
|
|
||||||
msg += Text("text")
|
|
||||||
msg += UniMessage([Text("text")])
|
|
||||||
# 附加
|
|
||||||
msg.append(Text("text"))
|
|
||||||
# 扩展
|
|
||||||
msg.extend([Text("text")])
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用消息模板
|
|
||||||
|
|
||||||
`UniMessage.template` 同样类似于 `Message.template`,可以用于格式化消息,大体用法参考 [消息模板](../../tutorial/message#使用消息模板)。
|
|
||||||
|
|
||||||
这里额外说明 `UniMessage.template` 的拓展控制符
|
|
||||||
|
|
||||||
相比 `Message`,UniMessage 对于 `{:XXX}` 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
|
|
||||||
|
|
||||||
以 At(...) 为例:
|
|
||||||
|
|
||||||
```python title=使用通用消息段的拓展控制符
|
|
||||||
>>> from nonebot_plugin_alconna.uniseg import UniMessage
|
|
||||||
>>> UniMessage.template("{:At(user, target)}").format(target="123")
|
|
||||||
UniMessage(At("user", "123"))
|
|
||||||
>>> UniMessage.template("{:At(type=user, target=id)}").format(id="123")
|
|
||||||
UniMessage(At("user", "123"))
|
|
||||||
>>> UniMessage.template("{:At(type=user, target=123)}").format()
|
|
||||||
UniMessage(At("user", "123"))
|
|
||||||
```
|
|
||||||
|
|
||||||
而在 `AlconnaMatcher` 中,`{:XXX}` 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
|
|
||||||
|
|
||||||
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
|
|
||||||
from arclet.alconna import Alconna, Args
|
|
||||||
from nonebot_plugin_alconna import At, Match, UniMessage, AlconnaMatcher, on_alconna
|
|
||||||
|
|
||||||
|
|
||||||
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
|
|
||||||
|
|
||||||
@test_cmd.handle()
|
|
||||||
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
|
|
||||||
if target.available:
|
|
||||||
matcher.set_path_arg("target", target.result)
|
|
||||||
|
|
||||||
@test_cmd.got_path(
|
|
||||||
"target",
|
|
||||||
prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请确认目标")
|
|
||||||
)
|
|
||||||
async def tt():
|
|
||||||
await test_cmd.send(
|
|
||||||
UniMessage.template("{:At(user, $event.get_user_id())} 已确认目标为 {target}")
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
另外也有 `$message_id` 与 `$target` 两个特殊值。
|
|
||||||
|
|
||||||
### 检查消息段
|
|
||||||
|
|
||||||
我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 是否存在消息段
|
|
||||||
At("user", "1234") in message
|
|
||||||
# 是否存在指定类型的消息段
|
|
||||||
At in message
|
|
||||||
```
|
|
||||||
|
|
||||||
我们还可以使用 `only` 方法来检查消息中是否仅包含指定的消息段:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 是否都为 "test"
|
|
||||||
message.only("test")
|
|
||||||
# 是否仅包含指定类型的消息段
|
|
||||||
message.only(Text)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 获取消息纯文本
|
|
||||||
|
|
||||||
类似于 `Message.extract_plain_text()`,用于获取通用消息的纯文本:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, At
|
|
||||||
|
|
||||||
|
|
||||||
# 提取消息纯文本字符串
|
|
||||||
assert UniMessage(
|
|
||||||
[At("user", "1234"), "text"]
|
|
||||||
).extract_plain_text() == "text"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 遍历
|
|
||||||
|
|
||||||
通用消息序列继承自 `List[Segment]` ,因此可以使用 `for` 循环遍历消息段:
|
|
||||||
|
|
||||||
```python
|
|
||||||
for segment in message: # type: Segment
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 过滤、索引与切片
|
|
||||||
|
|
||||||
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, At, Text, Reply
|
|
||||||
|
|
||||||
|
|
||||||
message = UniMessage(
|
|
||||||
[
|
|
||||||
Reply(...),
|
|
||||||
"text1",
|
|
||||||
At("user", "1234"),
|
|
||||||
"text2"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
# 索引
|
|
||||||
message[0] == Reply(...)
|
|
||||||
# 切片
|
|
||||||
message[0:2] == UniMessage([Reply(...), Text("text1")])
|
|
||||||
# 类型过滤
|
|
||||||
message[At] == Message([At("user", "1234")])
|
|
||||||
# 类型索引
|
|
||||||
message[At, 0] == At("user", "1234")
|
|
||||||
# 类型切片
|
|
||||||
message[Text, 0:2] == UniMessage([Text("text1"), Text("text2")])
|
|
||||||
```
|
|
||||||
|
|
||||||
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤:
|
|
||||||
|
|
||||||
```python
|
|
||||||
message.include(Text, At)
|
|
||||||
message.exclude(Reply)
|
|
||||||
```
|
|
||||||
|
|
||||||
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 指定类型首个消息段索引
|
|
||||||
message.index(Text) == 1
|
|
||||||
# 指定类型消息段数量
|
|
||||||
message.count(Text) == 2
|
|
||||||
```
|
|
||||||
|
|
||||||
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 获取指定类型指定个数的消息段
|
|
||||||
message.get(Text, 1) == UniMessage([Text("test1")])
|
|
||||||
```
|
|
||||||
|
|
||||||
## 消息发送
|
|
||||||
|
|
||||||
前面提到,通用消息可用 `UniMessage.send` 发送自身:
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def send(
|
|
||||||
self,
|
|
||||||
target: Union[Event, Target, None] = None,
|
|
||||||
bot: Optional[Bot] = None,
|
|
||||||
fallback: bool = True,
|
|
||||||
at_sender: Union[str, bool] = False,
|
|
||||||
reply_to: Union[str, bool] = False,
|
|
||||||
) -> Receipt:
|
|
||||||
```
|
|
||||||
|
|
||||||
实际上,`UniMessage` 同时提供了获取消息事件 id 与消息发送对象的方法:
|
|
||||||
|
|
||||||
<Tabs groupId="get_unimsg">
|
|
||||||
<TabItem value="depend" label="使用依赖注入">
|
|
||||||
|
|
||||||
通过提供的 `MessageTarget`, `MessageId` 或 `MsgTarget`, `MsgId` 依赖注入器来获取消息事件 id 与消息发送对象。
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.uniseg import MessageId, MsgTarget
|
|
||||||
|
|
||||||
|
|
||||||
matcher = on_xxx(...)
|
|
||||||
|
|
||||||
@matcher.handle()
|
|
||||||
asycn def _(target: MsgTarget, msg_id: MessageId):
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="method" label="使用 UniMessage 的方法">
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import Event, Bot
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, Target
|
|
||||||
|
|
||||||
|
|
||||||
matcher = on_xxx(...)
|
|
||||||
|
|
||||||
@matcher.handle()
|
|
||||||
asycn def _(bot: Bot, event: Event):
|
|
||||||
target: Target = UniMessage.get_target(event, bot)
|
|
||||||
msg_id: str = UniMessage.get_message_id(event, bot)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
`send`, `get_target`, `get_message_id` 中与 `event`, `bot` 相关的参数都会尝试从上下文中获取对象。
|
|
||||||
|
|
||||||
### 消息发送对象
|
|
||||||
|
|
||||||
消息发送对象是用来描述响应消息时的发送对象或者主动发送消息时的目标对象的对象,它包含了以下属性:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Target:
|
|
||||||
id: str
|
|
||||||
"""目标id;若为群聊则为group_id或者channel_id,若为私聊则为user_id"""
|
|
||||||
parent_id: str
|
|
||||||
"""父级id;若为频道则为guild_id,其他情况下可能为空字符串(例如 Feishu 下可作为部门 id)"""
|
|
||||||
channel: bool
|
|
||||||
"""是否为频道,仅当目标平台符合频道概念时"""
|
|
||||||
private: bool
|
|
||||||
"""是否为私聊"""
|
|
||||||
source: str
|
|
||||||
"""可能的事件id"""
|
|
||||||
self_id: Union[str, None]
|
|
||||||
"""机器人id,若为 None 则 Bot 对象会随机选择"""
|
|
||||||
selector: Union[Callable[[Bot], Awaitable[bool]], None]
|
|
||||||
"""选择器,用于在多个 Bot 对象中选择特定 Bot"""
|
|
||||||
extra: Dict[str, Any]
|
|
||||||
"""额外信息,用于适配器扩展"""
|
|
||||||
```
|
|
||||||
|
|
||||||
其构造时需要如下参数:
|
|
||||||
|
|
||||||
- `id` 为目标id;若为群聊则为 group_id 或者 channel_id,若为私聊则为user_id
|
|
||||||
- `parent_id` 为父级id;若为频道则为 guild_id,其他情况下可能为空字符串(例如 Feishu 下可作为部门 id)
|
|
||||||
- `channel` 为是否为频道,仅当目标平台符合频道概念时
|
|
||||||
- `private` 为是否为私聊
|
|
||||||
- `source` 为可能的事件id
|
|
||||||
- `self_id` 为机器人id,若为 None 则 Bot 对象会随机选择
|
|
||||||
- `selector` 为选择器,用于在多个 Bot 对象中选择特定 Bot
|
|
||||||
- `scope` 为适配器范围,用于传入内置的特定选择器
|
|
||||||
- `adapter` 为适配器名称,若为 None 则需要明确指定 Bot 对象
|
|
||||||
- `platform` 为平台名称,仅当目标适配器存在多个平台时使用
|
|
||||||
- `extra` 为额外信息,用于适配器扩展
|
|
||||||
|
|
||||||
通过 `Target` 对象,我们可以在 `UniMessage.send` 中指定发送对象:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, MsgTarget, Target, SupportScope
|
|
||||||
|
|
||||||
|
|
||||||
matcher = on_xxx(...)
|
|
||||||
|
|
||||||
@matcher.handle()
|
|
||||||
async def _(target: MsgTarget):
|
|
||||||
await UniMessage("Hello!").send(target=target)
|
|
||||||
target1 = Target("xxxx", scope=SupportScope.qq_client)
|
|
||||||
await UniMessage("Hello!").send(target=target1)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 主动发送消息
|
|
||||||
|
|
||||||
`UniMessage.send` 也可以用于主动发送消息:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, Target, SupportScope
|
|
||||||
from nonebot import get_driver
|
|
||||||
|
|
||||||
|
|
||||||
driver = get_driver()
|
|
||||||
|
|
||||||
@driver.on_startup
|
|
||||||
async def on_startup():
|
|
||||||
target = Target("xxxx", scope=SupportScope.qq_client)
|
|
||||||
await UniMessage("Hello!").send(target=target)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 自定义消息段
|
|
||||||
|
|
||||||
`uniseg` 提供了部分方法来允许用户自定义 Segment 的序列化和反序列化:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
|
||||||
from nonebot.adapters import MessageSegment as BaseMessageSegment
|
|
||||||
from nonebot.adapters.satori import Custom, Message, MessageSegment
|
|
||||||
|
|
||||||
from nonebot_plugin_alconna.uniseg.builder import MessageBuilder
|
|
||||||
from nonebot_plugin_alconna.uniseg.exporter import MessageExporter
|
|
||||||
from nonebot_plugin_alconna.uniseg import Segment, custom_handler, custom_register
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MarketFace(Segment):
|
|
||||||
tabId: str
|
|
||||||
faceId: str
|
|
||||||
key: str
|
|
||||||
|
|
||||||
|
|
||||||
@custom_register(MarketFace, "chronocat:marketface")
|
|
||||||
def mfbuild(builder: MessageBuilder, seg: BaseMessageSegment):
|
|
||||||
if not isinstance(seg, Custom):
|
|
||||||
raise ValueError("MarketFace can only be built from Satori Message")
|
|
||||||
return MarketFace(**seg.data)(*builder.generate(seg.children))
|
|
||||||
|
|
||||||
|
|
||||||
@custom_handler(MarketFace)
|
|
||||||
async def mfexport(exporter: MessageExporter, seg: MarketFace, bot: Bot, fallback: bool):
|
|
||||||
if exporter.get_message_type() is Message:
|
|
||||||
return MessageSegment("chronocat:marketface", seg.data)(await exporter.export(seg.children, bot, fallback))
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
具体而言,你可以使用 `custom_register` 来增加一个从 MessageSegment 到 Segment 的处理方法;使用 `custom_handler` 来增加一个从 Segment 到 MessageSegment 的处理方法。
|
|
@@ -68,15 +68,27 @@ data = data_file.read_text()
|
|||||||
|
|
||||||
## 配置项
|
## 配置项
|
||||||
|
|
||||||
|
### localstore_use_cwd
|
||||||
|
|
||||||
|
使用当前工作目录作为数据存储目录,以下数据目录配置项默认值将会对应变更
|
||||||
|
|
||||||
|
默认值:`False`
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
LOCALSTORE_USE_CWD=true
|
||||||
|
```
|
||||||
|
|
||||||
### localstore_cache_dir
|
### localstore_cache_dir
|
||||||
|
|
||||||
自定义缓存目录
|
自定义缓存目录
|
||||||
|
|
||||||
默认值:
|
默认值:
|
||||||
|
|
||||||
- macOS: `~/Library/Caches/<AppName>`
|
当 `localstore_use_cwd` 为 `True` 时,缓存目录为 `<current_working_directory>/cache`,否则:
|
||||||
- Unix: `~/.cache/<AppName>` (XDG default)
|
|
||||||
- Windows: `C:\Users\<username>\AppData\Local\<AppName>\Cache`
|
- macOS: `~/Library/Caches/nonebot2`
|
||||||
|
- Unix: `~/.cache/nonebot2` (XDG default)
|
||||||
|
- Windows: `C:\Users\<username>\AppData\Local\nonebot2\Cache`
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
LOCALSTORE_CACHE_DIR=/tmp/cache
|
LOCALSTORE_CACHE_DIR=/tmp/cache
|
||||||
@@ -88,10 +100,12 @@ LOCALSTORE_CACHE_DIR=/tmp/cache
|
|||||||
|
|
||||||
默认值:
|
默认值:
|
||||||
|
|
||||||
- macOS: `~/Library/Application Support/<AppName>`
|
当 `localstore_use_cwd` 为 `True` 时,数据目录为 `<current_working_directory>/data`,否则:
|
||||||
- Unix: `~/.local/share/<AppName>` or in $XDG_DATA_HOME, if defined
|
|
||||||
- Win XP (not roaming): `C:\Documents and Settings\<username>\Application Data\<AppName>`
|
- macOS: `~/Library/Application Support/nonebot2`
|
||||||
- Win 7 (not roaming): `C:\Users\<username>\AppData\Local\<AppName>`
|
- Unix: `~/.local/share/nonebot2` or in $XDG_DATA_HOME, if defined
|
||||||
|
- Win XP (not roaming): `C:\Documents and Settings\<username>\Application Data\nonebot2`
|
||||||
|
- Win 7 (not roaming): `C:\Users\<username>\AppData\Local\nonebot2`
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
LOCALSTORE_DATA_DIR=/tmp/data
|
LOCALSTORE_DATA_DIR=/tmp/data
|
||||||
@@ -103,10 +117,12 @@ LOCALSTORE_DATA_DIR=/tmp/data
|
|||||||
|
|
||||||
默认值:
|
默认值:
|
||||||
|
|
||||||
|
当 `localstore_use_cwd` 为 `True` 时,配置目录为 `<current_working_directory>/config`,否则:
|
||||||
|
|
||||||
- macOS: same as user_data_dir
|
- macOS: same as user_data_dir
|
||||||
- Unix: `~/.config/<AppName>`
|
- Unix: `~/.config/nonebot2`
|
||||||
- Win XP (roaming): `C:\Documents and Settings\<username>\Local Settings\Application Data\<AppName>`
|
- Win XP (roaming): `C:\Documents and Settings\<username>\Local Settings\Application Data\nonebot2`
|
||||||
- Win 7 (roaming): `C:\Users\<username>\AppData\Roaming\<AppName>`
|
- Win 7 (roaming): `C:\Users\<username>\AppData\Roaming\nonebot2`
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
LOCALSTORE_CONFIG_DIR=/tmp/config
|
LOCALSTORE_CONFIG_DIR=/tmp/config
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user