mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-08 11:46:43 +00:00
Compare commits
292 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
fa0bfa7346 | ||
|
29720bada8 | ||
|
67e5126f6a | ||
|
0212247694 | ||
|
34aef36bac | ||
|
8ea2d4b81f | ||
|
c9c50fa0eb | ||
|
9dcb734547 | ||
|
577d27ef5f | ||
|
a6687dc887 | ||
|
a27e9b078c | ||
|
d75d29b954 | ||
|
6ad289274f | ||
|
7d87244b3e | ||
|
fd0ca2d883 | ||
|
45c96cad1a | ||
|
8d4822ed21 | ||
|
e5194c715b | ||
|
f532573458 | ||
|
c9c8e4d106 | ||
|
58f3325b2c | ||
|
456f923cd2 | ||
|
66d70b5675 | ||
|
c7e57a070a | ||
|
948f96d0f2 | ||
|
d67238194f | ||
|
3c53260f07 | ||
|
581ba52fcb | ||
|
f7d385be93 | ||
|
f4a31a6404 | ||
|
e7f0a2466a | ||
|
d228c3665c | ||
|
fc6aca5394 | ||
|
8f62306086 | ||
|
10048d401a | ||
|
3043b1e2f2 | ||
|
debdb55955 | ||
|
da7e914c74 | ||
|
e854d0ddf8 | ||
|
04465ea688 | ||
|
24c862843a | ||
|
c85b4bcee3 | ||
|
34c99bd2c0 | ||
|
ddc99eedda | ||
|
4c71b8a2a0 | ||
|
7cb39acab6 | ||
|
933a105348 | ||
|
b52905b8ee | ||
|
9e9c1a27e5 | ||
|
e2463e9e39 | ||
|
9681c92bfc | ||
|
def8c34918 | ||
|
6dc52c9d6b | ||
|
a22b8213ef | ||
|
e2901debfb | ||
|
7e16b034b8 | ||
|
a30bfb76ff | ||
|
9d6ca5c7eb | ||
|
2ac102ea53 | ||
|
9fe86f61d6 | ||
|
47be1aea18 | ||
|
180de00d0b | ||
|
fbec37d089 | ||
|
9b83cf4945 | ||
|
790461d8b7 | ||
|
22b848dfcc | ||
|
9915e89ceb | ||
|
3adc2cc761 | ||
|
38d8701d45 | ||
|
6633d4d2c9 | ||
|
1864269be5 | ||
|
df5b21cb9f | ||
|
460fb59322 | ||
|
0b63ff6e73 | ||
|
d1b9dad8f4 | ||
|
42e4206916 | ||
|
384f99eae6 | ||
|
152af6b458 | ||
|
e9bf5d6931 | ||
|
2b9f8c15f2 | ||
|
ec965fc705 | ||
|
e31f7d8f9f | ||
|
0d8b81614a | ||
|
56f52f2c9f | ||
|
53a4ad1483 | ||
|
2d774174af | ||
|
d960a5d962 | ||
|
3ef7d0c59e | ||
|
dbcd8189c7 | ||
|
be514a03f6 | ||
|
1a059c7f67 | ||
|
5fd27046b8 | ||
|
befc9c61cd | ||
|
c663b23c0c | ||
|
dde9b5aa23 | ||
|
76c2a64ee3 | ||
|
abbcc13f73 | ||
|
8478cc00c5 | ||
|
d6bc43057e | ||
|
c45f956c3d | ||
|
81f899b282 | ||
|
c52e1307f4 | ||
|
87ca2b7cf4 | ||
|
3981d2d6f4 | ||
|
0007515784 | ||
|
7f82056ca3 | ||
|
5f5dd792bd | ||
|
4b1f5541ff | ||
|
fb0387a9ce | ||
|
cf83c27c35 | ||
|
9c6b1334c3 | ||
|
0115cabe96 | ||
|
caa15a1025 | ||
|
45e89a6440 | ||
|
6689ffbbab | ||
|
31c9c1384d | ||
|
08345605d1 | ||
|
4ec9bfb7d7 | ||
|
bc0682af8f | ||
|
38e40bd0de | ||
|
2e04b01402 | ||
|
7045ea2eba | ||
|
c72f7dc76a | ||
|
b6fc736a9b | ||
|
f6ae2b8698 | ||
|
3f24e3dfac | ||
|
01ec1067ea | ||
|
5705ff070b | ||
|
8475078926 | ||
|
4d97ce7e15 | ||
|
9bb3155026 | ||
|
cb21a23e65 | ||
|
fee28edc21 | ||
|
7adbc5e527 | ||
|
b5c7b8340d | ||
|
04459f329a | ||
|
ce77a96cf6 | ||
|
5fa40ebb1a | ||
|
1ac90927c4 | ||
|
73f7a8ecbf | ||
|
7eaf581762 | ||
|
12f3ee56eb | ||
|
e5b17833f3 | ||
|
a06f2de877 | ||
|
82cdaccd18 | ||
|
ee2f0a85b0 | ||
|
539fad0e59 | ||
|
33b06759b6 | ||
|
24dbaf6bdb | ||
|
c75b584b94 | ||
|
608712e780 | ||
|
6d35481209 | ||
|
a0ecd63e7c | ||
|
2185d01534 | ||
|
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 |
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "Default Linux Universal",
|
"name": "Ubuntu",
|
||||||
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
|
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
|
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {},
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {},
|
||||||
|
"ghcr.io/meaningful-ooo/devcontainer-features/fish:2": {}
|
||||||
},
|
},
|
||||||
"postCreateCommand": "./scripts/setup-envs.sh",
|
"postCreateCommand": "./scripts/setup-envs.sh",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
7
.github/ISSUE_TEMPLATE/adapter_publish.yml
vendored
7
.github/ISSUE_TEMPLATE/adapter_publish.yml
vendored
@@ -3,6 +3,13 @@ title: "Adapter: {name}"
|
|||||||
description: 发布适配器到 NoneBot 官方商店
|
description: 发布适配器到 NoneBot 官方商店
|
||||||
labels: ["Adapter", "Publish"]
|
labels: ["Adapter", "Publish"]
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# 发布须知
|
||||||
|
|
||||||
|
非特殊情况下,请通过 [NoneBot 适配器商店](https://nonebot.dev/store/adapters) 的发布表单进行插件发布信息填写。
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: name
|
id: name
|
||||||
attributes:
|
attributes:
|
||||||
|
7
.github/ISSUE_TEMPLATE/bot_publish.yml
vendored
7
.github/ISSUE_TEMPLATE/bot_publish.yml
vendored
@@ -3,6 +3,13 @@ title: "Bot: {name}"
|
|||||||
description: 发布机器人到 NoneBot 官方商店
|
description: 发布机器人到 NoneBot 官方商店
|
||||||
labels: ["Bot", "Publish"]
|
labels: ["Bot", "Publish"]
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# 发布须知
|
||||||
|
|
||||||
|
非特殊情况下,请通过 [NoneBot 机器人商店](https://nonebot.dev/store/bots) 的发布表单进行插件发布信息填写。
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: name
|
id: name
|
||||||
attributes:
|
attributes:
|
||||||
|
14
.github/ISSUE_TEMPLATE/plugin_publish.yml
vendored
14
.github/ISSUE_TEMPLATE/plugin_publish.yml
vendored
@@ -3,6 +3,14 @@ title: "Plugin: {name}"
|
|||||||
description: 发布插件到 NoneBot 官方商店
|
description: 发布插件到 NoneBot 官方商店
|
||||||
labels: ["Plugin", "Publish"]
|
labels: ["Plugin", "Publish"]
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# 发布须知
|
||||||
|
|
||||||
|
非特殊情况下,请通过 [NoneBot 插件商店](https://nonebot.dev/store/plugins) 的发布表单进行插件发布信息填写。
|
||||||
|
在发布前请阅读 [NoneBot 插件发布流程指导](https://nonebot.dev/docs/developer/plugin-publishing) 并确保满足其中所述条件。
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: pypi
|
id: pypi
|
||||||
attributes:
|
attributes:
|
||||||
@@ -15,9 +23,9 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
id: module
|
id: module
|
||||||
attributes:
|
attributes:
|
||||||
label: 插件 import 包名
|
label: 插件模块名
|
||||||
description: 插件 import 包名
|
description: 加载插件时所使用的模块名称
|
||||||
placeholder: e.g. nonebot_plugin_xxx
|
placeholder: e.g. nonebot_plugin_apscheduler
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
7
.github/actions/build-api-doc/action.yml
vendored
7
.github/actions/build-api-doc/action.yml
vendored
@@ -5,10 +5,5 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
poetry run nb-autodoc nonebot \
|
uv run --no-sync bash ./scripts/build-api-docs.sh
|
||||||
-s nonebot.plugins \
|
|
||||||
-u nonebot.internal \
|
|
||||||
-u nonebot.internal.*
|
|
||||||
cp -r ./build/nonebot/* ./website/docs/api/
|
|
||||||
yarn prettier
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
6
.github/actions/setup-node/action.yml
vendored
6
.github/actions/setup-node/action.yml
vendored
@@ -4,10 +4,10 @@ description: Setup Node
|
|||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: lts/*
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
|
|
||||||
- run: yarn install --frozen-lockfile
|
- run: yarn install --frozen-lockfile
|
||||||
shell: bash
|
shell: bash
|
||||||
|
30
.github/actions/setup-python/action.yml
vendored
30
.github/actions/setup-python/action.yml
vendored
@@ -5,36 +5,20 @@ inputs:
|
|||||||
python-version:
|
python-version:
|
||||||
description: Python version
|
description: Python version
|
||||||
required: false
|
required: false
|
||||||
default: "3.10"
|
default: "3.12"
|
||||||
env-dir:
|
env-group:
|
||||||
description: Environment directory
|
description: Environment group
|
||||||
required: false
|
required: false
|
||||||
default: "."
|
default: "pydantic-v2"
|
||||||
no-root:
|
|
||||||
description: Do not install package in the environment
|
|
||||||
required: false
|
|
||||||
default: "false"
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Install poetry
|
- uses: astral-sh/setup-uv@v7
|
||||||
run: pipx install poetry
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
cache: "poetry"
|
cache-suffix: ${{ inputs.env-group }}
|
||||||
cache-dependency-path: |
|
|
||||||
./poetry.lock
|
|
||||||
${{ inputs.env-dir }}/poetry.lock
|
|
||||||
|
|
||||||
- run: |
|
- run: |
|
||||||
cd ${{ inputs.env-dir }}
|
uv sync --all-extras --locked --group ${{ inputs.env-group }}
|
||||||
if [ "${{ inputs.no-root }}" = "true" ]; then
|
|
||||||
poetry install --all-extras --no-root
|
|
||||||
else
|
|
||||||
poetry install --all-extras
|
|
||||||
fi
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
12
.github/workflows/codecov.yml
vendored
12
.github/workflows/codecov.yml
vendored
@@ -13,7 +13,7 @@ on:
|
|||||||
- ".github/actions/setup-python/**"
|
- ".github/actions/setup-python/**"
|
||||||
- ".github/workflows/codecov.yml"
|
- ".github/workflows/codecov.yml"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "poetry.lock"
|
- "uv.lock"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
env: [pydantic-v1, pydantic-v2]
|
env: [pydantic-v1, pydantic-v2]
|
||||||
env:
|
env:
|
||||||
@@ -34,19 +34,17 @@ jobs:
|
|||||||
PYDANTIC_VERSION: ${{ matrix.env }}
|
PYDANTIC_VERSION: ${{ matrix.env }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python environment
|
- name: Setup Python environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
env-dir: ./envs/${{ matrix.env }}
|
env-group: ${{ matrix.env }}
|
||||||
no-root: true
|
|
||||||
|
|
||||||
- name: Run Pytest
|
- name: Run Pytest
|
||||||
run: |
|
run: |
|
||||||
cd ./envs/${{ matrix.env }}
|
uv run --no-sync bash ./scripts/run-tests.sh
|
||||||
poetry run bash "../../scripts/run-tests.sh"
|
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
uses: codecov/test-results-action@v1
|
uses: codecov/test-results-action@v1
|
||||||
|
20
.github/workflows/noneflow.yml
vendored
20
.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:
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
private_key: ${{ secrets.APP_KEY }}
|
private_key: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
9
.github/workflows/pyright.yml
vendored
9
.github/workflows/pyright.yml
vendored
@@ -13,7 +13,7 @@ on:
|
|||||||
- ".github/actions/setup-python/**"
|
- ".github/actions/setup-python/**"
|
||||||
- ".github/workflows/pyright.yml"
|
- ".github/workflows/pyright.yml"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "poetry.lock"
|
- "uv.lock"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pyright:
|
pyright:
|
||||||
@@ -28,16 +28,15 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python environment
|
- name: Setup Python environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
with:
|
with:
|
||||||
env-dir: ./envs/${{ matrix.env }}
|
env-group: ${{ matrix.env }}
|
||||||
no-root: true
|
|
||||||
|
|
||||||
- run: |
|
- run: |
|
||||||
(cd ./envs/${{ matrix.env }} && echo "$(poetry env info --path)/bin" >> $GITHUB_PATH)
|
echo "$(dirname $(uv python find))" >> $GITHUB_PATH
|
||||||
if [ "${{ matrix.env }}" = "pydantic-v1" ]; then
|
if [ "${{ matrix.env }}" = "pydantic-v1" ]; then
|
||||||
sed -i 's/PYDANTIC_V2 = true/PYDANTIC_V2 = false/g' ./pyproject.toml
|
sed -i 's/PYDANTIC_V2 = true/PYDANTIC_V2 = false/g' ./pyproject.toml
|
||||||
fi
|
fi
|
||||||
|
22
.github/workflows/release-drafter.yml
vendored
22
.github/workflows/release-drafter.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
app_id: ${{ secrets.APP_ID }}
|
app_id: ${{ secrets.APP_ID }}
|
||||||
private_key: ${{ secrets.APP_KEY }}
|
private_key: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
app_id: ${{ secrets.APP_ID }}
|
app_id: ${{ secrets.APP_ID }}
|
||||||
private_key: ${{ secrets.APP_KEY }}
|
private_key: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python Environment
|
- name: Setup Python Environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(poetry version -s)" >> $GITHUB_OUTPUT
|
echo "VERSION=$(uv version --short)" >> $GITHUB_OUTPUT
|
||||||
echo "TAG_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
echo "TAG_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||||
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
@@ -102,10 +102,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Package
|
- name: Build Package
|
||||||
run: |
|
run: |
|
||||||
poetry build
|
uv build
|
||||||
|
uv publish
|
||||||
- name: Publish package to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
|
|
||||||
- name: Publish package to GitHub
|
- name: Publish package to GitHub
|
||||||
run: |
|
run: |
|
||||||
@@ -117,13 +115,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
|
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
|
||||||
cd packages/nonebot-plugin-docs/
|
cd packages/nonebot-plugin-docs/
|
||||||
poetry version ${{ steps.version.outputs.VERSION }}
|
uv version ${{ steps.version.outputs.VERSION }}
|
||||||
poetry build
|
uv build
|
||||||
|
uv publish
|
||||||
- name: Publish Doc Package to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
packages-dir: packages/nonebot-plugin-docs/dist/
|
|
||||||
|
|
||||||
- name: Publish Doc Package to GitHub
|
- name: Publish Doc Package to GitHub
|
||||||
run: |
|
run: |
|
||||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
app_id: ${{ secrets.APP_ID }}
|
app_id: ${{ secrets.APP_ID }}
|
||||||
private_key: ${{ secrets.APP_KEY }}
|
private_key: ${{ secrets.APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- name: Build API Doc
|
- name: Build API Doc
|
||||||
uses: ./.github/actions/build-api-doc
|
uses: ./.github/actions/build-api-doc
|
||||||
|
|
||||||
- run: echo "TAG_NAME=v$(poetry version -s)" >> $GITHUB_ENV
|
- run: echo "TAG_NAME=v$(uv version --short)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Archive Changelog
|
- name: Archive Changelog
|
||||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Archive Files
|
- name: Archive Files
|
||||||
run: |
|
run: |
|
||||||
yarn archive $(poetry version -s)
|
yarn archive $(uv version --short)
|
||||||
yarn prettier
|
yarn prettier
|
||||||
|
|
||||||
- name: Push Tag
|
- name: Push Tag
|
||||||
@@ -47,6 +47,6 @@ jobs:
|
|||||||
git config user.name noneflow[bot]
|
git config user.name noneflow[bot]
|
||||||
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
|
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
|
||||||
git add .
|
git add .
|
||||||
git commit -m ":bookmark: Release $(poetry version -s)"
|
git commit -m ":bookmark: Release $(uv version --short)"
|
||||||
git tag ${{ env.TAG_NAME }}
|
git tag ${{ env.TAG_NAME }}
|
||||||
git push && git push --tags
|
git push && git push --tags
|
||||||
|
6
.github/workflows/ruff.yml
vendored
6
.github/workflows/ruff.yml
vendored
@@ -13,18 +13,18 @@ on:
|
|||||||
- ".github/actions/setup-python/**"
|
- ".github/actions/setup-python/**"
|
||||||
- ".github/workflows/ruff.yml"
|
- ".github/workflows/ruff.yml"
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
- "poetry.lock"
|
- "uv.lock"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ruff:
|
ruff:
|
||||||
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@v5
|
||||||
|
|
||||||
- name: Run Ruff Lint
|
- name: Run Ruff Lint
|
||||||
uses: astral-sh/ruff-action@v3
|
uses: astral-sh/ruff-action@v3
|
||||||
|
2
.github/workflows/website-deploy.yml
vendored
2
.github/workflows/website-deploy.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
6
.github/workflows/website-preview-cd.yml
vendored
6
.github/workflows/website-preview-cd.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set Commit Status
|
- name: Set Commit Status
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.repos.createCommitStatus({
|
github.rest.repos.createCommitStatus({
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
})
|
})
|
||||||
|
|
||||||
- name: Download Artifact
|
- name: Download Artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: website-preview
|
name: website-preview
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
:rocket: Deployed to ${{ steps.deploy.outputs.deploy-url }}
|
:rocket: Deployed to ${{ steps.deploy.outputs.deploy-url }}
|
||||||
|
|
||||||
- name: Set Commit Status
|
- name: Set Commit Status
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
|
2
.github/workflows/website-preview-ci.yml
vendored
2
.github/workflows/website-preview-ci.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
@@ -7,9 +7,9 @@ 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.9.10
|
rev: v0.13.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff-check
|
||||||
args: [--fix]
|
args: [--fix]
|
||||||
stages: [pre-commit]
|
stages: [pre-commit]
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
|
@@ -1 +1,2 @@
|
|||||||
.github/**/*.md
|
.github/**/*.md
|
||||||
|
website/docs/tutorial/application.mdx
|
||||||
|
@@ -18,15 +18,15 @@
|
|||||||
|
|
||||||
## Pull Request
|
## Pull Request
|
||||||
|
|
||||||
NoneBot 使用 [poetry](https://python-poetry.org/) 管理项目依赖,由于 pre-commit 也经其管理,所以在此一并说明。
|
NoneBot 使用 [uv](https://docs.astral.sh/uv/) 管理项目依赖,由于 pre-commit 也经其管理,所以在此一并说明。
|
||||||
|
|
||||||
下面的命令能在已安装 poetry 和 yarn 的情况下帮你快速配置开发环境。
|
下面的命令能在已安装 uv 和 yarn 的情况下帮你快速配置开发环境。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 安装 python 依赖
|
# 安装 python 依赖
|
||||||
poetry install
|
uv sync --all-extras
|
||||||
# 安装 pre-commit git hook
|
# 安装 pre-commit git hook
|
||||||
pre-commit install
|
uv run pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用 GitHub Codespaces(Dev Container)
|
### 使用 GitHub Codespaces(Dev Container)
|
||||||
|
10
README.md
10
README.md
@@ -118,17 +118,17 @@ 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)) | ↗️ | 微信协议,由社区贡献 |
|
||||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
|
||||||
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
||||||
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa)) | ❌ | 米游社大别野 Bot 协议,官方已下线 |
|
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa)) | ❌ | 米游社大别野 Bot 协议,官方已下线 |
|
||||||
| Rocket.Chat([仓库](https://github.com/IUnlimit/nonebot-adapter-rocketchat),[协议](https://developer.rocket.chat/)) | ↗️ | Rocket.Chat Bot 协议,由社区贡献 |
|
| Rocket.Chat([仓库](https://github.com/IUnlimit/nonebot-adapter-rocketchat),[协议](https://developer.rocket.chat/)) | ↗️ | Rocket.Chat Bot 协议,由社区贡献 |
|
||||||
@@ -136,6 +136,10 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
| 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/lclbm/adapter-heybox),[协议](https://github.com/QingFengOpen/HeychatDoc)) | ↗️ | 黑盒语音机器人协议,由社区贡献 |
|
||||||
| 微信公众平台([仓库](https://github.com/YangRucheng/nonebot-adapter-wxmp),[协议](https://developers.weixin.qq.com/doc/))| ↗️ | 微信公众平台协议,由社区贡献 |
|
| 微信公众平台([仓库](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不再维护及可用 |
|
||||||
|
| EFChat([仓库](https://github.com/molanp/nonebot_adapter_efchat),[协议](https://irinu-live.melon.fish/efc-help/)) | ↗️ | 恒五聊平台协议,由社区贡献 |
|
||||||
|
| VoceChat ([仓库](https://github.com/5656565566/nonebot-adapter-vocechat),[协议](https://doc.voce.chat/zh-cn/bot/bot-and-webhook)) | ↗️ | VoceChat 平台协议,由社区贡献 |
|
||||||
|
| B站直播间([仓库](https://github.com/MingxuanGame/nonebot-adapter-bilibili-live),[Web API 协议](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/live),[开放平台协议](https://open-live.bilibili.com/document)) | ↗️ | B站直播间(Web API/开放平台)协议,由社区贡献 |
|
||||||
|
|
||||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||||
|
|
||||||
|
@@ -284,4 +284,58 @@
|
|||||||
],
|
],
|
||||||
"is_official": false
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.efchat",
|
||||||
|
"project_link": "nonebot-adapter-efchat",
|
||||||
|
"name": "nonebot-adapter-efchat",
|
||||||
|
"desc": "适用于EFChat(恒五聊)聊天室的nonebot适配器",
|
||||||
|
"author_id": 104612722,
|
||||||
|
"homepage": "https://github.com/molanp/nonebot_adapter_efchat",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "EFChat",
|
||||||
|
"color": "#ac6161"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "恒五聊",
|
||||||
|
"color": "#cc99ff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.vocechat",
|
||||||
|
"project_link": "nonebot-adapter-vocechat",
|
||||||
|
"name": "nonebot-adapter-vocechat",
|
||||||
|
"desc": "Vocechat 协议适配器",
|
||||||
|
"author_id": 56059687,
|
||||||
|
"homepage": "https://github.com/5656565566/nonebot-adapter-vocechat",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.bilibili_live",
|
||||||
|
"project_link": "nonebot-adapter-bilibili-live",
|
||||||
|
"name": "B站直播间",
|
||||||
|
"desc": "B 站直播间协议(Web API/开放平台)支持",
|
||||||
|
"author_id": 68982190,
|
||||||
|
"homepage": "https://github.com/MingxuanGame/nonebot-adapter-bilibili-live",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "bilibili",
|
||||||
|
"color": "#ff6699"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
@@ -705,4 +705,63 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nsybot",
|
||||||
|
"desc": "定时获取推特/bilibili等平台用户文章并推送到QQ群",
|
||||||
|
"author_id": 148176849,
|
||||||
|
"homepage": "https://github.com/AhsokaTano26/nsybot",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Amrita",
|
||||||
|
"desc": "LLM聊天机器人框架",
|
||||||
|
"author_id": 67693593,
|
||||||
|
"homepage": "https://github.com/LiteSuggarDEV/Amrita",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "聊天",
|
||||||
|
"color": "#ea5252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "LLM",
|
||||||
|
"color": "#5c86db"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "快捷部署",
|
||||||
|
"color": "#eebe0b"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
1376
assets/plugins.json5
1376
assets/plugins.json5
File diff suppressed because it is too large
Load Diff
2582
envs/pydantic-v1/poetry.lock
generated
2582
envs/pydantic-v1/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
|||||||
[tool.poetry]
|
|
||||||
name = "nonebot-pydantic-v1"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Private pydantic v1 test env for nonebot"
|
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "^3.9"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
pydantic = "^1.0.0"
|
|
||||||
nonebot-test = { path = "../test/", develop = false }
|
|
||||||
nonebot2 = { path = "../../", extras = ["all"], develop = true }
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
2661
envs/pydantic-v2/poetry.lock
generated
2661
envs/pydantic-v2/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
|||||||
[tool.poetry]
|
|
||||||
name = "nonebot-pydantic-v2"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Private pydantic v2 test env for nonebot"
|
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "^3.9"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
pydantic = "^2.0.0"
|
|
||||||
nonebot-test = { path = "../test/", develop = false }
|
|
||||||
nonebot2 = { path = "../../", extras = ["all"], develop = true }
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
@@ -1 +0,0 @@
|
|||||||
# fake file to make project installable
|
|
1432
envs/test/poetry.lock
generated
1432
envs/test/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
|||||||
[tool.poetry]
|
|
||||||
name = "nonebot-test"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Private test env for nonebot"
|
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
|
||||||
license = "MIT"
|
|
||||||
packages = [{ include = "nonebot-test.py" }]
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "^3.9"
|
|
||||||
trio = "^0.27.0"
|
|
||||||
nonebug = "^0.4.1"
|
|
||||||
wsproto = "^1.2.0"
|
|
||||||
pytest-cov = "^6.0.0"
|
|
||||||
pytest-xdist = "^3.0.2"
|
|
||||||
werkzeug = ">=2.3.6,<4.0.0"
|
|
||||||
coverage-conditional-plugin = "^0.9.0"
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
@@ -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
|
||||||
|
@@ -7,13 +7,14 @@ FrontMatter:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, Callable, ForwardRef
|
from typing import Any, Callable, ForwardRef, cast
|
||||||
|
from typing_extensions import TypeAliasType
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from nonebot.compat import ModelField
|
from nonebot.compat import ModelField
|
||||||
from nonebot.exception import TypeMisMatch
|
from nonebot.exception import TypeMisMatch
|
||||||
from nonebot.typing import evaluate_forwardref
|
from nonebot.typing import evaluate_forwardref, is_type_alias_type
|
||||||
|
|
||||||
|
|
||||||
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||||
@@ -46,6 +47,9 @@ def get_typed_annotation(param: inspect.Parameter, globalns: dict[str, Any]) ->
|
|||||||
f'Unknown ForwardRef["{param.annotation}"] for parameter {param.name}'
|
f'Unknown ForwardRef["{param.annotation}"] for parameter {param.name}'
|
||||||
)
|
)
|
||||||
return inspect.Parameter.empty
|
return inspect.Parameter.empty
|
||||||
|
if is_type_alias_type(annotation):
|
||||||
|
# Python 3.12+ supports PEP 695 TypeAliasType
|
||||||
|
annotation = cast(TypeAliasType, annotation).__value__
|
||||||
return annotation
|
return annotation
|
||||||
|
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@ from nonebot.internal.driver import Request as Request
|
|||||||
from nonebot.internal.driver import Response as Response
|
from nonebot.internal.driver import Response as Response
|
||||||
from nonebot.internal.driver import ReverseDriver as ReverseDriver
|
from nonebot.internal.driver import ReverseDriver as ReverseDriver
|
||||||
from nonebot.internal.driver import ReverseMixin as ReverseMixin
|
from nonebot.internal.driver import ReverseMixin as ReverseMixin
|
||||||
|
from nonebot.internal.driver import Timeout as Timeout
|
||||||
from nonebot.internal.driver import WebSocket as WebSocket
|
from nonebot.internal.driver import WebSocket as WebSocket
|
||||||
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
|
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
|
||||||
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
||||||
@@ -34,6 +35,7 @@ __autodoc__ = {
|
|||||||
"Cookies": True,
|
"Cookies": True,
|
||||||
"Request": True,
|
"Request": True,
|
||||||
"Response": True,
|
"Response": True,
|
||||||
|
"Timeout": True,
|
||||||
"WebSocket": True,
|
"WebSocket": True,
|
||||||
"HTTPVersion": True,
|
"HTTPVersion": True,
|
||||||
"Driver": True,
|
"Driver": True,
|
||||||
|
@@ -37,7 +37,14 @@ from nonebot.drivers import (
|
|||||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
from nonebot.drivers.none import Driver as NoneDriver
|
||||||
from nonebot.exception import WebSocketClosed
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.internal.driver import Cookies, CookieTypes, HeaderTypes, QueryTypes
|
from nonebot.internal.driver import (
|
||||||
|
Cookies,
|
||||||
|
CookieTypes,
|
||||||
|
HeaderTypes,
|
||||||
|
QueryTypes,
|
||||||
|
Timeout,
|
||||||
|
TimeoutTypes,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -56,7 +63,7 @@ class Session(HTTPClientSession):
|
|||||||
headers: HeaderTypes = None,
|
headers: HeaderTypes = None,
|
||||||
cookies: CookieTypes = None,
|
cookies: CookieTypes = None,
|
||||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||||
timeout: Optional[float] = None,
|
timeout: TimeoutTypes = None,
|
||||||
proxy: Optional[str] = None,
|
proxy: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self._client: Optional[aiohttp.ClientSession] = None
|
self._client: Optional[aiohttp.ClientSession] = None
|
||||||
@@ -78,7 +85,15 @@ class Session(HTTPClientSession):
|
|||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Unsupported HTTP version: {version}")
|
raise RuntimeError(f"Unsupported HTTP version: {version}")
|
||||||
|
|
||||||
self._timeout = timeout
|
if isinstance(timeout, Timeout):
|
||||||
|
self._timeout = aiohttp.ClientTimeout(
|
||||||
|
total=timeout.total,
|
||||||
|
connect=timeout.connect,
|
||||||
|
sock_read=timeout.read,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._timeout = aiohttp.ClientTimeout(timeout)
|
||||||
|
|
||||||
self._proxy = proxy
|
self._proxy = proxy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -106,6 +121,13 @@ class Session(HTTPClientSession):
|
|||||||
if cookie.value is not None
|
if cookie.value is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(setup.timeout, Timeout):
|
||||||
|
timeout = aiohttp.ClientTimeout(
|
||||||
|
total=setup.timeout.total,
|
||||||
|
connect=setup.timeout.connect,
|
||||||
|
sock_read=setup.timeout.read,
|
||||||
|
)
|
||||||
|
else:
|
||||||
timeout = aiohttp.ClientTimeout(setup.timeout)
|
timeout = aiohttp.ClientTimeout(setup.timeout)
|
||||||
|
|
||||||
async with await self.client.request(
|
async with await self.client.request(
|
||||||
@@ -125,6 +147,58 @@ class Session(HTTPClientSession):
|
|||||||
request=setup,
|
request=setup,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def stream_request(
|
||||||
|
self,
|
||||||
|
setup: Request,
|
||||||
|
*,
|
||||||
|
chunk_size: int = 1024,
|
||||||
|
) -> AsyncGenerator[Response, None]:
|
||||||
|
if self._params:
|
||||||
|
url = setup.url.with_query({**self._params, **setup.url.query})
|
||||||
|
else:
|
||||||
|
url = setup.url
|
||||||
|
|
||||||
|
data = setup.data
|
||||||
|
if setup.files:
|
||||||
|
data = aiohttp.FormData(data or {}, quote_fields=False)
|
||||||
|
for name, file in setup.files:
|
||||||
|
data.add_field(name, file[1], content_type=file[2], filename=file[0])
|
||||||
|
|
||||||
|
cookies = (
|
||||||
|
(cookie.name, cookie.value)
|
||||||
|
for cookie in setup.cookies
|
||||||
|
if cookie.value is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(setup.timeout, Timeout):
|
||||||
|
timeout = aiohttp.ClientTimeout(
|
||||||
|
total=setup.timeout.total,
|
||||||
|
connect=setup.timeout.connect,
|
||||||
|
sock_read=setup.timeout.read,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
timeout = aiohttp.ClientTimeout(setup.timeout)
|
||||||
|
|
||||||
|
async with self.client.request(
|
||||||
|
setup.method,
|
||||||
|
url,
|
||||||
|
data=setup.content or data,
|
||||||
|
json=setup.json,
|
||||||
|
cookies=cookies,
|
||||||
|
headers=setup.headers,
|
||||||
|
proxy=setup.proxy or self._proxy,
|
||||||
|
timeout=timeout,
|
||||||
|
) as response:
|
||||||
|
response_headers = response.headers.copy()
|
||||||
|
async for chunk in response.content.iter_chunked(chunk_size):
|
||||||
|
yield Response(
|
||||||
|
response.status,
|
||||||
|
headers=response_headers,
|
||||||
|
content=chunk,
|
||||||
|
request=setup,
|
||||||
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def setup(self) -> None:
|
async def setup(self) -> None:
|
||||||
if self._client is not None:
|
if self._client is not None:
|
||||||
@@ -160,6 +234,17 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
|||||||
async with self.get_session() as session:
|
async with self.get_session() as session:
|
||||||
return await session.request(setup)
|
return await session.request(setup)
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def stream_request(
|
||||||
|
self,
|
||||||
|
setup: Request,
|
||||||
|
*,
|
||||||
|
chunk_size: int = 1024,
|
||||||
|
) -> AsyncGenerator[Response, None]:
|
||||||
|
async with self.get_session() as session:
|
||||||
|
async for response in session.stream_request(setup, chunk_size=chunk_size):
|
||||||
|
yield response
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
||||||
@@ -170,6 +255,12 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
|||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
|
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
|
||||||
|
|
||||||
|
if isinstance(setup.timeout, Timeout):
|
||||||
|
timeout = aiohttp.ClientWSTimeout(
|
||||||
|
ws_receive=setup.timeout.read, # type: ignore
|
||||||
|
ws_close=setup.timeout.total, # type: ignore
|
||||||
|
)
|
||||||
|
else:
|
||||||
timeout = aiohttp.ClientWSTimeout(ws_close=setup.timeout or 10.0) # type: ignore
|
timeout = aiohttp.ClientWSTimeout(ws_close=setup.timeout or 10.0) # type: ignore
|
||||||
|
|
||||||
async with aiohttp.ClientSession(version=version, trust_env=True) as session:
|
async with aiohttp.ClientSession(version=version, trust_env=True) as session:
|
||||||
@@ -189,7 +280,7 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
|||||||
headers: HeaderTypes = None,
|
headers: HeaderTypes = None,
|
||||||
cookies: CookieTypes = None,
|
cookies: CookieTypes = None,
|
||||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||||
timeout: Optional[float] = None,
|
timeout: TimeoutTypes = None,
|
||||||
proxy: Optional[str] = None,
|
proxy: Optional[str] = None,
|
||||||
) -> Session:
|
) -> Session:
|
||||||
return Session(
|
return Session(
|
||||||
|
@@ -17,6 +17,7 @@ FrontMatter:
|
|||||||
description: nonebot.drivers.httpx 模块
|
description: nonebot.drivers.httpx 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from collections.abc import AsyncGenerator
|
||||||
from typing import TYPE_CHECKING, Optional, Union
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
@@ -32,7 +33,14 @@ from nonebot.drivers import (
|
|||||||
combine_driver,
|
combine_driver,
|
||||||
)
|
)
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
from nonebot.drivers.none import Driver as NoneDriver
|
||||||
from nonebot.internal.driver import Cookies, CookieTypes, HeaderTypes, QueryTypes
|
from nonebot.internal.driver import (
|
||||||
|
Cookies,
|
||||||
|
CookieTypes,
|
||||||
|
HeaderTypes,
|
||||||
|
QueryTypes,
|
||||||
|
Timeout,
|
||||||
|
TimeoutTypes,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import httpx
|
import httpx
|
||||||
@@ -51,7 +59,7 @@ class Session(HTTPClientSession):
|
|||||||
headers: HeaderTypes = None,
|
headers: HeaderTypes = None,
|
||||||
cookies: CookieTypes = None,
|
cookies: CookieTypes = None,
|
||||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||||
timeout: Optional[float] = None,
|
timeout: TimeoutTypes = None,
|
||||||
proxy: Optional[str] = None,
|
proxy: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self._client: Optional[httpx.AsyncClient] = None
|
self._client: Optional[httpx.AsyncClient] = None
|
||||||
@@ -64,7 +72,16 @@ class Session(HTTPClientSession):
|
|||||||
)
|
)
|
||||||
self._cookies = Cookies(cookies)
|
self._cookies = Cookies(cookies)
|
||||||
self._version = HTTPVersion(version)
|
self._version = HTTPVersion(version)
|
||||||
self._timeout = timeout
|
|
||||||
|
if isinstance(timeout, Timeout):
|
||||||
|
self._timeout = httpx.Timeout(
|
||||||
|
timeout=timeout.total,
|
||||||
|
connect=timeout.connect,
|
||||||
|
read=timeout.read,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._timeout = httpx.Timeout(timeout)
|
||||||
|
|
||||||
self._proxy = proxy
|
self._proxy = proxy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -75,6 +92,15 @@ class Session(HTTPClientSession):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
async def request(self, setup: Request) -> Response:
|
async def request(self, setup: Request) -> Response:
|
||||||
|
if isinstance(setup.timeout, Timeout):
|
||||||
|
timeout = httpx.Timeout(
|
||||||
|
timeout=setup.timeout.total,
|
||||||
|
connect=setup.timeout.connect,
|
||||||
|
read=setup.timeout.read,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
timeout = httpx.Timeout(setup.timeout)
|
||||||
|
|
||||||
response = await self.client.request(
|
response = await self.client.request(
|
||||||
setup.method,
|
setup.method,
|
||||||
str(setup.url),
|
str(setup.url),
|
||||||
@@ -86,7 +112,7 @@ class Session(HTTPClientSession):
|
|||||||
params=setup.url.raw_query_string,
|
params=setup.url.raw_query_string,
|
||||||
headers=tuple(setup.headers.items()),
|
headers=tuple(setup.headers.items()),
|
||||||
cookies=setup.cookies.jar,
|
cookies=setup.cookies.jar,
|
||||||
timeout=setup.timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
return Response(
|
return Response(
|
||||||
response.status_code,
|
response.status_code,
|
||||||
@@ -95,6 +121,44 @@ class Session(HTTPClientSession):
|
|||||||
request=setup,
|
request=setup,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def stream_request(
|
||||||
|
self,
|
||||||
|
setup: Request,
|
||||||
|
*,
|
||||||
|
chunk_size: int = 1024,
|
||||||
|
) -> AsyncGenerator[Response, None]:
|
||||||
|
if isinstance(setup.timeout, Timeout):
|
||||||
|
timeout = httpx.Timeout(
|
||||||
|
timeout=setup.timeout.total,
|
||||||
|
connect=setup.timeout.connect,
|
||||||
|
read=setup.timeout.read,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
timeout = httpx.Timeout(setup.timeout)
|
||||||
|
|
||||||
|
async with self.client.stream(
|
||||||
|
setup.method,
|
||||||
|
str(setup.url),
|
||||||
|
content=setup.content,
|
||||||
|
data=setup.data,
|
||||||
|
files=setup.files,
|
||||||
|
json=setup.json,
|
||||||
|
# ensure the params priority
|
||||||
|
params=setup.url.raw_query_string,
|
||||||
|
headers=tuple(setup.headers.items()),
|
||||||
|
cookies=setup.cookies.jar,
|
||||||
|
timeout=timeout,
|
||||||
|
) as response:
|
||||||
|
response_headers = response.headers.multi_items()
|
||||||
|
async for chunk in response.aiter_bytes(chunk_size=chunk_size):
|
||||||
|
yield Response(
|
||||||
|
response.status_code,
|
||||||
|
headers=response_headers,
|
||||||
|
content=chunk,
|
||||||
|
request=setup,
|
||||||
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def setup(self) -> None:
|
async def setup(self) -> None:
|
||||||
if self._client is not None:
|
if self._client is not None:
|
||||||
@@ -133,6 +197,19 @@ class Mixin(HTTPClientMixin):
|
|||||||
) as session:
|
) as session:
|
||||||
return await session.request(setup)
|
return await session.request(setup)
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def stream_request(
|
||||||
|
self,
|
||||||
|
setup: Request,
|
||||||
|
*,
|
||||||
|
chunk_size: int = 1024,
|
||||||
|
) -> AsyncGenerator[Response, None]:
|
||||||
|
async with self.get_session(
|
||||||
|
version=setup.version, proxy=setup.proxy
|
||||||
|
) as session:
|
||||||
|
async for response in session.stream_request(setup, chunk_size=chunk_size):
|
||||||
|
yield response
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def get_session(
|
def get_session(
|
||||||
self,
|
self,
|
||||||
@@ -140,7 +217,7 @@ class Mixin(HTTPClientMixin):
|
|||||||
headers: HeaderTypes = None,
|
headers: HeaderTypes = None,
|
||||||
cookies: CookieTypes = None,
|
cookies: CookieTypes = None,
|
||||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||||
timeout: Optional[float] = None,
|
timeout: TimeoutTypes = None,
|
||||||
proxy: Optional[str] = None,
|
proxy: Optional[str] = None,
|
||||||
) -> Session:
|
) -> Session:
|
||||||
return Session(
|
return Session(
|
||||||
|
@@ -25,15 +25,14 @@ 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
|
||||||
|
|
||||||
from nonebot.drivers import Request, WebSocketClientMixin, combine_driver
|
from nonebot.drivers import Request, Timeout, WebSocketClientMixin, combine_driver
|
||||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||||
from nonebot.drivers.none import Driver as NoneDriver
|
from nonebot.drivers.none import Driver as NoneDriver
|
||||||
from nonebot.exception import WebSocketClosed
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.log import LoguruHandler
|
from nonebot.log import LoguruHandler
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from websockets.exceptions import ConnectionClosed
|
from websockets import ClientConnection, ConnectionClosed, connect
|
||||||
from websockets.legacy.client import Connect, WebSocketClientProtocol
|
|
||||||
except ModuleNotFoundError as e: # pragma: no cover
|
except ModuleNotFoundError as e: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install websockets first to use this driver. "
|
"Please install websockets first to use this driver. "
|
||||||
@@ -71,12 +70,16 @@ class Mixin(WebSocketClientMixin):
|
|||||||
@override
|
@override
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
||||||
if setup.proxy is not None:
|
if isinstance(setup.timeout, Timeout):
|
||||||
logger.warning("proxy is not supported by websockets driver")
|
timeout = setup.timeout.total or setup.timeout.connect or setup.timeout.read
|
||||||
connection = Connect(
|
else:
|
||||||
|
timeout = setup.timeout
|
||||||
|
|
||||||
|
connection = connect(
|
||||||
str(setup.url),
|
str(setup.url),
|
||||||
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
|
additional_headers={**setup.headers, **setup.cookies.as_header(setup)},
|
||||||
open_timeout=setup.timeout,
|
proxy=setup.proxy if setup.proxy is not None else True,
|
||||||
|
open_timeout=timeout,
|
||||||
)
|
)
|
||||||
async with connection as ws:
|
async with connection as ws:
|
||||||
yield WebSocket(request=setup, websocket=ws)
|
yield WebSocket(request=setup, websocket=ws)
|
||||||
@@ -86,14 +89,14 @@ class WebSocket(BaseWebSocket):
|
|||||||
"""Websockets WebSocket Wrapper"""
|
"""Websockets WebSocket Wrapper"""
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __init__(self, *, request: Request, websocket: WebSocketClientProtocol):
|
def __init__(self, *, request: Request, websocket: ClientConnection):
|
||||||
super().__init__(request=request)
|
super().__init__(request=request)
|
||||||
self.websocket = websocket
|
self.websocket = websocket
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def closed(self) -> bool:
|
def closed(self) -> bool:
|
||||||
return self.websocket.closed
|
return self.websocket.close_code is not None
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def accept(self):
|
async def accept(self):
|
||||||
|
@@ -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:
|
||||||
|
@@ -27,5 +27,7 @@ from .model import RawURL as RawURL
|
|||||||
from .model import Request as Request
|
from .model import Request as Request
|
||||||
from .model import Response as Response
|
from .model import Response as Response
|
||||||
from .model import SimpleQuery as SimpleQuery
|
from .model import SimpleQuery as SimpleQuery
|
||||||
|
from .model import Timeout as Timeout
|
||||||
|
from .model import TimeoutTypes as TimeoutTypes
|
||||||
from .model import WebSocket as WebSocket
|
from .model import WebSocket as WebSocket
|
||||||
from .model import WebSocketServerSetup as WebSocketServerSetup
|
from .model import WebSocketServerSetup as WebSocketServerSetup
|
||||||
|
@@ -30,6 +30,7 @@ from .model import (
|
|||||||
QueryTypes,
|
QueryTypes,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
|
TimeoutTypes,
|
||||||
WebSocket,
|
WebSocket,
|
||||||
WebSocketServerSetup,
|
WebSocketServerSetup,
|
||||||
)
|
)
|
||||||
@@ -245,7 +246,7 @@ class HTTPClientSession(abc.ABC):
|
|||||||
headers: HeaderTypes = None,
|
headers: HeaderTypes = None,
|
||||||
cookies: CookieTypes = None,
|
cookies: CookieTypes = None,
|
||||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||||
timeout: Optional[float] = None,
|
timeout: TimeoutTypes = None,
|
||||||
proxy: Optional[str] = None,
|
proxy: Optional[str] = None,
|
||||||
):
|
):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -255,6 +256,17 @@ class HTTPClientSession(abc.ABC):
|
|||||||
"""发送一个 HTTP 请求"""
|
"""发送一个 HTTP 请求"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
async def stream_request(
|
||||||
|
self,
|
||||||
|
setup: Request,
|
||||||
|
*,
|
||||||
|
chunk_size: int = 1024,
|
||||||
|
) -> AsyncGenerator[Response, None]:
|
||||||
|
"""发送一个 HTTP 流式请求"""
|
||||||
|
raise NotImplementedError
|
||||||
|
yield # used for static type checking's generator detection
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def setup(self) -> None:
|
async def setup(self) -> None:
|
||||||
"""初始化会话"""
|
"""初始化会话"""
|
||||||
@@ -286,6 +298,17 @@ class HTTPClientMixin(ForwardMixin):
|
|||||||
"""发送一个 HTTP 请求"""
|
"""发送一个 HTTP 请求"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
async def stream_request(
|
||||||
|
self,
|
||||||
|
setup: Request,
|
||||||
|
*,
|
||||||
|
chunk_size: int = 1024,
|
||||||
|
) -> AsyncGenerator[Response, None]:
|
||||||
|
"""发送一个 HTTP 流式请求"""
|
||||||
|
raise NotImplementedError
|
||||||
|
yield # used for static type checking's generator detection
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_session(
|
def get_session(
|
||||||
self,
|
self,
|
||||||
@@ -293,7 +316,7 @@ class HTTPClientMixin(ForwardMixin):
|
|||||||
headers: HeaderTypes = None,
|
headers: HeaderTypes = None,
|
||||||
cookies: CookieTypes = None,
|
cookies: CookieTypes = None,
|
||||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||||
timeout: Optional[float] = None,
|
timeout: TimeoutTypes = None,
|
||||||
proxy: Optional[str] = None,
|
proxy: Optional[str] = None,
|
||||||
) -> HTTPClientSession:
|
) -> HTTPClientSession:
|
||||||
"""获取一个 HTTP 会话"""
|
"""获取一个 HTTP 会话"""
|
||||||
|
@@ -42,6 +42,7 @@ FileTypes: TypeAlias = Union[
|
|||||||
FileType,
|
FileType,
|
||||||
]
|
]
|
||||||
FilesTypes: TypeAlias = Union[dict[str, FileTypes], list[tuple[str, FileTypes]], None]
|
FilesTypes: TypeAlias = Union[dict[str, FileTypes], list[tuple[str, FileTypes]], None]
|
||||||
|
TimeoutTypes: TypeAlias = Union[float, "Timeout", None]
|
||||||
|
|
||||||
|
|
||||||
class HTTPVersion(Enum):
|
class HTTPVersion(Enum):
|
||||||
@@ -50,6 +51,15 @@ class HTTPVersion(Enum):
|
|||||||
H2 = "2"
|
H2 = "2"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Timeout:
|
||||||
|
"""Request 超时配置。"""
|
||||||
|
|
||||||
|
total: Optional[float] = None
|
||||||
|
connect: Optional[float] = None
|
||||||
|
read: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
class Request:
|
class Request:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -64,7 +74,7 @@ class Request:
|
|||||||
json: Any = None,
|
json: Any = None,
|
||||||
files: FilesTypes = None,
|
files: FilesTypes = None,
|
||||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||||
timeout: Optional[float] = None,
|
timeout: TimeoutTypes = None,
|
||||||
proxy: Optional[str] = None,
|
proxy: Optional[str] = None,
|
||||||
):
|
):
|
||||||
# method
|
# method
|
||||||
@@ -76,7 +86,7 @@ class Request:
|
|||||||
# http version
|
# http version
|
||||||
self.version: HTTPVersion = HTTPVersion(version)
|
self.version: HTTPVersion = HTTPVersion(version)
|
||||||
# timeout
|
# timeout
|
||||||
self.timeout: Optional[float] = timeout
|
self.timeout: TimeoutTypes = timeout
|
||||||
# proxy
|
# proxy
|
||||||
self.proxy: Optional[str] = proxy
|
self.proxy: Optional[str] = proxy
|
||||||
|
|
||||||
|
@@ -54,6 +54,11 @@ class MatcherManager(MutableMapping[int, list[type["Matcher"]]]):
|
|||||||
@overload
|
@overload
|
||||||
def get(self, key: int) -> Optional[list[type["Matcher"]]]: ...
|
def get(self, key: int) -> Optional[list[type["Matcher"]]]: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get(
|
||||||
|
self, key: int, default: list[type["Matcher"]]
|
||||||
|
) -> list[type["Matcher"]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(self, key: int, default: T) -> Union[list[type["Matcher"]], T]: ...
|
def get(self, key: int, default: T) -> Union[list[type["Matcher"]], T]: ...
|
||||||
|
|
||||||
|
@@ -132,14 +132,16 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
|||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
def _handle_ignored_exception(msg: str) -> Callable[[BaseExceptionGroup], None]:
|
def _handle_ignored_exception(
|
||||||
|
msg: str,
|
||||||
|
) -> Callable[[BaseExceptionGroup[IgnoredException]], None]:
|
||||||
def _handle(exc_group: BaseExceptionGroup[IgnoredException]) -> None:
|
def _handle(exc_group: BaseExceptionGroup[IgnoredException]) -> None:
|
||||||
logger.opt(colors=True).info(msg)
|
logger.opt(colors=True).info(msg)
|
||||||
|
|
||||||
return _handle
|
return _handle
|
||||||
|
|
||||||
|
|
||||||
def _handle_exception(msg: str) -> Callable[[BaseExceptionGroup], None]:
|
def _handle_exception(msg: str) -> Callable[[BaseExceptionGroup[Exception]], None]:
|
||||||
def _handle(exc_group: BaseExceptionGroup[Exception]) -> None:
|
def _handle(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||||
for exc in flatten_exception_group(exc_group):
|
for exc in flatten_exception_group(exc_group):
|
||||||
logger.opt(colors=True, exception=exc).error(msg)
|
logger.opt(colors=True, exception=exc).error(msg)
|
||||||
|
@@ -8,11 +8,13 @@ FrontMatter:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
from itertools import chain
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
from nonebot.log import logger
|
||||||
from nonebot.utils import path_to_module_name
|
from nonebot.utils import path_to_module_name
|
||||||
|
|
||||||
from . import _managers, _module_name_to_plugin_id, get_plugin
|
from . import _managers, _module_name_to_plugin_id, get_plugin
|
||||||
@@ -108,6 +110,19 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> set[Plugin]:
|
|||||||
encoding: 指定 toml 文件编码
|
encoding: 指定 toml 文件编码
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
|
新格式:
|
||||||
|
|
||||||
|
```toml title=pyproject.toml
|
||||||
|
[tool.nonebot]
|
||||||
|
plugin_dirs = ["some_dir"]
|
||||||
|
|
||||||
|
[tool.nonebot.plugins]
|
||||||
|
some-store-plugin = ["some_store_plugin"]
|
||||||
|
"@local" = ["some_local_plugin"]
|
||||||
|
```
|
||||||
|
|
||||||
|
旧格式:
|
||||||
|
|
||||||
```toml title=pyproject.toml
|
```toml title=pyproject.toml
|
||||||
[tool.nonebot]
|
[tool.nonebot]
|
||||||
plugins = ["some_plugin"]
|
plugins = ["some_plugin"]
|
||||||
@@ -126,11 +141,22 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> set[Plugin]:
|
|||||||
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
|
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
|
||||||
if not isinstance(nonebot_data, dict):
|
if not isinstance(nonebot_data, dict):
|
||||||
raise TypeError("'[tool.nonebot]' must be a Table!")
|
raise TypeError("'[tool.nonebot]' must be a Table!")
|
||||||
plugins = nonebot_data.get("plugins", [])
|
plugins = nonebot_data.get("plugins", {})
|
||||||
plugin_dirs = nonebot_data.get("plugin_dirs", [])
|
plugin_dirs = nonebot_data.get("plugin_dirs", [])
|
||||||
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
assert isinstance(plugins, (list, dict)), (
|
||||||
|
"plugins must be a list or a dict of plugin name"
|
||||||
|
)
|
||||||
assert isinstance(plugin_dirs, list), "plugin_dirs must be a list of directories"
|
assert isinstance(plugin_dirs, list), "plugin_dirs must be a list of directories"
|
||||||
return load_all_plugins(plugins, plugin_dirs)
|
if isinstance(plugins, list):
|
||||||
|
logger.warning("Legacy project format found! Upgrade with `nb upgrade-format`.")
|
||||||
|
return load_all_plugins(
|
||||||
|
set(
|
||||||
|
chain.from_iterable(plugins.values())
|
||||||
|
if isinstance(plugins, dict)
|
||||||
|
else plugins
|
||||||
|
),
|
||||||
|
plugin_dirs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_builtin_plugin(name: str) -> Optional[Plugin]:
|
def load_builtin_plugin(name: str) -> Optional[Plugin]:
|
||||||
|
@@ -82,6 +82,7 @@ class Plugin:
|
|||||||
sub_plugins: set["Plugin"] = field(default_factory=set)
|
sub_plugins: set["Plugin"] = field(default_factory=set)
|
||||||
"""子插件集合"""
|
"""子插件集合"""
|
||||||
metadata: Optional[PluginMetadata] = None
|
metadata: Optional[PluginMetadata] = None
|
||||||
|
"""插件元信息"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id_(self) -> str:
|
def id_(self) -> str:
|
||||||
|
@@ -89,7 +89,7 @@ def origin_is_annotated(origin: t.Optional[type[t.Any]]) -> bool:
|
|||||||
return origin is t_ext.Annotated
|
return origin is t_ext.Annotated
|
||||||
|
|
||||||
|
|
||||||
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None]}
|
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None]} # noqa: PYI061
|
||||||
if sys.version_info >= (3, 10):
|
if sys.version_info >= (3, 10):
|
||||||
NONE_TYPES.add(types.NoneType)
|
NONE_TYPES.add(types.NoneType)
|
||||||
|
|
||||||
@@ -99,6 +99,17 @@ def is_none_type(type_: type[t.Any]) -> bool:
|
|||||||
return type_ in NONE_TYPES
|
return type_ in NONE_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 12):
|
||||||
|
|
||||||
|
def is_type_alias_type(type_: type[t.Any]) -> bool:
|
||||||
|
"""判断是否是 TypeAliasType 类型"""
|
||||||
|
return isinstance(type_, t_ext.TypeAliasType)
|
||||||
|
else:
|
||||||
|
|
||||||
|
def is_type_alias_type(type_: type[t.Any]) -> bool:
|
||||||
|
return isinstance(type_, (t.TypeAliasType, t_ext.TypeAliasType))
|
||||||
|
|
||||||
|
|
||||||
def evaluate_forwardref(
|
def evaluate_forwardref(
|
||||||
ref: t.ForwardRef, globalns: dict[str, t.Any], localns: dict[str, t.Any]
|
ref: t.ForwardRef, globalns: dict[str, t.Any], localns: dict[str, t.Any]
|
||||||
) -> t.Any:
|
) -> t.Any:
|
||||||
|
@@ -89,15 +89,28 @@ def generic_check_issubclass(
|
|||||||
|
|
||||||
特别的:
|
特别的:
|
||||||
|
|
||||||
|
- 如果 cls 是 `typing.TypeVar` 类型,
|
||||||
|
则会检查其 `__bound__` 或 `__constraints__`
|
||||||
|
是否是 class_or_tuple 中一个类型的子类或 None。
|
||||||
- 如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
|
- 如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
|
||||||
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
|
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
|
||||||
- 如果 cls 是 `typing.Literal` 类型,
|
- 如果 cls 是 `typing.Literal` 类型,
|
||||||
则会检查其中的所有值是否是 class_or_tuple 中一个类型的实例。
|
则会检查其中的所有值是否是 class_or_tuple 中一个类型的实例。
|
||||||
- 如果 cls 是 `typing.TypeVar` 类型,
|
- 如果 cls 是 `typing.List`、`typing.Dict` 等泛型类型,
|
||||||
则会检查其 `__bound__` 或 `__constraints__`
|
则会检查其原始类型是否是 class_or_tuple 中一个类型的子类。
|
||||||
是否是 class_or_tuple 中一个类型的子类或 None。
|
|
||||||
"""
|
"""
|
||||||
if not type_has_args(cls):
|
# if the target is a TypeVar, we check it first
|
||||||
|
if isinstance(cls, TypeVar):
|
||||||
|
if cls.__constraints__:
|
||||||
|
return all(
|
||||||
|
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
|
||||||
|
for type_ in cls.__constraints__
|
||||||
|
)
|
||||||
|
elif cls.__bound__:
|
||||||
|
return generic_check_issubclass(cls.__bound__, class_or_tuple)
|
||||||
|
return False
|
||||||
|
# elif the target is not a generic type, we check it directly
|
||||||
|
elif not type_has_args(cls):
|
||||||
with contextlib.suppress(TypeError):
|
with contextlib.suppress(TypeError):
|
||||||
return issubclass(cls, class_or_tuple)
|
return issubclass(cls, class_or_tuple)
|
||||||
|
|
||||||
@@ -119,14 +132,6 @@ def generic_check_issubclass(
|
|||||||
return issubclass(origin, class_or_tuple)
|
return issubclass(origin, class_or_tuple)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return False
|
return False
|
||||||
elif isinstance(cls, TypeVar):
|
|
||||||
if cls.__constraints__:
|
|
||||||
return all(
|
|
||||||
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
|
|
||||||
for type_ in cls.__constraints__
|
|
||||||
)
|
|
||||||
elif cls.__bound__:
|
|
||||||
return generic_check_issubclass(cls.__bound__, class_or_tuple)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -279,7 +284,7 @@ def path_to_module_name(path: Path) -> str:
|
|||||||
if rel_path.stem == "__init__":
|
if rel_path.stem == "__init__":
|
||||||
return ".".join(rel_path.parts[:-1])
|
return ".".join(rel_path.parts[:-1])
|
||||||
else:
|
else:
|
||||||
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
|
return ".".join((*rel_path.parts[:-1], rel_path.stem))
|
||||||
|
|
||||||
|
|
||||||
def resolve_dot_notation(
|
def resolve_dot_notation(
|
||||||
|
@@ -1,22 +1,24 @@
|
|||||||
[tool.poetry]
|
[project]
|
||||||
name = "nonebot-plugin-docs"
|
name = "nonebot-plugin-docs"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
description = "View NoneBot2 Docs Locally"
|
description = "View NoneBot2 Docs Locally"
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
authors = [{ name = "yanyongyu", email = "yyy@nonebot.dev" }]
|
||||||
license = "MIT"
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://github.com/nonebot/nonebot2/blob/master/packages/nonebot-plugin-docs"
|
license = "MIT"
|
||||||
repository = "https://github.com/nonebot/nonebot2"
|
|
||||||
keywords = ["nonebot", "nonebot2", "docs"]
|
keywords = ["nonebot", "nonebot2", "docs"]
|
||||||
include = ["nonebot_plugin_docs/dist/**/*"]
|
requires-python = ">=3.9, <4.0"
|
||||||
|
dependencies = ["nonebot2 >=2.0.0, <3.0.0"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/nonebot/nonebot2/blob/master/packages/nonebot-plugin-docs"
|
||||||
|
Repository = "https://github.com/nonebot/nonebot2"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[dependency-groups]
|
||||||
python = "^3.9"
|
dev = []
|
||||||
nonebot2 = "^2.0.0"
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.uv.build-backend]
|
||||||
|
module-root = ""
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["uv_build >=0.8.3, <0.9.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "uv_build"
|
||||||
|
665
packages/nonebot-plugin-docs/uv.lock
generated
Normal file
665
packages/nonebot-plugin-docs/uv.lock
generated
Normal file
@@ -0,0 +1,665 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 2
|
||||||
|
requires-python = ">=3.9, <4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.9.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "sniffio" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exceptiongroup"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loguru"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multidict"
|
||||||
|
version = "6.6.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/67/414933982bce2efce7cbcb3169eaaf901e0f25baec69432b4874dfb1f297/multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817", size = 77017, upload-time = "2025-06-30T15:50:58.931Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/fe/d8a3ee1fad37dc2ef4f75488b0d9d4f25bf204aad8306cbab63d97bff64a/multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140", size = 44897, upload-time = "2025-06-30T15:51:00.999Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/e0/265d89af8c98240265d82b8cbcf35897f83b76cd59ee3ab3879050fd8c45/multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14", size = 44574, upload-time = "2025-06-30T15:51:02.449Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/05/6b759379f7e8e04ccc97cfb2a5dcc5cdbd44a97f072b2272dc51281e6a40/multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a", size = 225729, upload-time = "2025-06-30T15:51:03.794Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/f5/8d5a15488edd9a91fa4aad97228d785df208ed6298580883aa3d9def1959/multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69", size = 242515, upload-time = "2025-06-30T15:51:05.002Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/b5/a8f317d47d0ac5bb746d6d8325885c8967c2a8ce0bb57be5399e3642cccb/multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c", size = 222224, upload-time = "2025-06-30T15:51:06.148Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/88/18b2a0d5e80515fa22716556061189c2853ecf2aa2133081ebbe85ebea38/multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751", size = 253124, upload-time = "2025-06-30T15:51:07.375Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/bf/ebfcfd6b55a1b05ef16d0775ae34c0fe15e8dab570d69ca9941073b969e7/multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8", size = 251529, upload-time = "2025-06-30T15:51:08.691Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/11/780615a98fd3775fc309d0234d563941af69ade2df0bb82c91dda6ddaea1/multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55", size = 241627, upload-time = "2025-06-30T15:51:10.605Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/3d/35f33045e21034b388686213752cabc3a1b9d03e20969e6fa8f1b1d82db1/multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7", size = 239351, upload-time = "2025-06-30T15:51:12.18Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/cc/ff84c03b95b430015d2166d9aae775a3985d757b94f6635010d0038d9241/multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb", size = 233429, upload-time = "2025-06-30T15:51:13.533Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/f0/8cd49a0b37bdea673a4b793c2093f2f4ba8e7c9d6d7c9bd672fd6d38cd11/multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c", size = 243094, upload-time = "2025-06-30T15:51:14.815Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/19/5d9a0cfdafe65d82b616a45ae950975820289069f885328e8185e64283c2/multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c", size = 248957, upload-time = "2025-06-30T15:51:16.076Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/dc/c90066151da87d1e489f147b9b4327927241e65f1876702fafec6729c014/multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61", size = 243590, upload-time = "2025-06-30T15:51:17.413Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/39/458afb0cccbb0ee9164365273be3e039efddcfcb94ef35924b7dbdb05db0/multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b", size = 237487, upload-time = "2025-06-30T15:51:19.039Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/38/0016adac3990426610a081787011177e661875546b434f50a26319dc8372/multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318", size = 41390, upload-time = "2025-06-30T15:51:20.362Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/d2/17897a8f3f2c5363d969b4c635aa40375fe1f09168dc09a7826780bfb2a4/multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485", size = 45954, upload-time = "2025-06-30T15:51:21.383Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/5f/d4a717c1e457fe44072e33fa400d2b93eb0f2819c4d669381f925b7cba1f/multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5", size = 42981, upload-time = "2025-06-30T15:51:22.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/f0/1a39863ced51f639c81a5463fbfa9eb4df59c20d1a8769ab9ef4ca57ae04/multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c", size = 76445, upload-time = "2025-06-30T15:51:24.01Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/0e/a7cfa451c7b0365cd844e90b41e21fab32edaa1e42fc0c9f68461ce44ed7/multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df", size = 44610, upload-time = "2025-06-30T15:51:25.158Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/bb/a14a4efc5ee748cc1904b0748be278c31b9295ce5f4d2ef66526f410b94d/multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d", size = 44267, upload-time = "2025-06-30T15:51:26.326Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/f8/410677d563c2d55e063ef74fe578f9d53fe6b0a51649597a5861f83ffa15/multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539", size = 230004, upload-time = "2025-06-30T15:51:27.491Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/df/2b787f80059314a98e1ec6a4cc7576244986df3e56b3c755e6fc7c99e038/multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462", size = 247196, upload-time = "2025-06-30T15:51:28.762Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/f2/f9117089151b9a8ab39f9019620d10d9718eec2ac89e7ca9d30f3ec78e96/multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9", size = 225337, upload-time = "2025-06-30T15:51:30.025Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/2d/7115300ec5b699faa152c56799b089a53ed69e399c3c2d528251f0aeda1a/multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7", size = 257079, upload-time = "2025-06-30T15:51:31.716Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/ea/ff4bab367623e39c20d3b07637225c7688d79e4f3cc1f3b9f89867677f9a/multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9", size = 255461, upload-time = "2025-06-30T15:51:33.029Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/07/2c9246cda322dfe08be85f1b8739646f2c4c5113a1422d7a407763422ec4/multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821", size = 246611, upload-time = "2025-06-30T15:51:34.47Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/62/279c13d584207d5697a752a66ffc9bb19355a95f7659140cb1b3cf82180e/multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d", size = 243102, upload-time = "2025-06-30T15:51:36.525Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/cc/e06636f48c6d51e724a8bc8d9e1db5f136fe1df066d7cafe37ef4000f86a/multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6", size = 238693, upload-time = "2025-06-30T15:51:38.278Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/a4/66c9d8fb9acf3b226cdd468ed009537ac65b520aebdc1703dd6908b19d33/multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430", size = 246582, upload-time = "2025-06-30T15:51:39.709Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/01/c69e0317be556e46257826d5449feb4e6aa0d18573e567a48a2c14156f1f/multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b", size = 253355, upload-time = "2025-06-30T15:51:41.013Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/da/9cc1da0299762d20e626fe0042e71b5694f9f72d7d3f9678397cbaa71b2b/multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56", size = 247774, upload-time = "2025-06-30T15:51:42.291Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/91/b22756afec99cc31105ddd4a52f95ab32b1a4a58f4d417979c570c4a922e/multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183", size = 242275, upload-time = "2025-06-30T15:51:43.642Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/f1/adcc185b878036a20399d5be5228f3cbe7f823d78985d101d425af35c800/multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5", size = 41290, upload-time = "2025-06-30T15:51:45.264Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/d4/27652c1c6526ea6b4f5ddd397e93f4232ff5de42bea71d339bc6a6cc497f/multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2", size = 45942, upload-time = "2025-06-30T15:51:46.377Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/18/23f4932019804e56d3c2413e237f866444b774b0263bcb81df2fdecaf593/multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb", size = 42880, upload-time = "2025-06-30T15:51:47.561Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843, upload-time = "2025-06-30T15:52:16.155Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053, upload-time = "2025-06-30T15:52:17.429Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273, upload-time = "2025-06-30T15:52:19.346Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124, upload-time = "2025-06-30T15:52:20.773Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892, upload-time = "2025-06-30T15:52:22.242Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547, upload-time = "2025-06-30T15:52:23.736Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223, upload-time = "2025-06-30T15:52:25.185Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262, upload-time = "2025-06-30T15:52:26.969Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345, upload-time = "2025-06-30T15:52:28.467Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248, upload-time = "2025-06-30T15:52:29.938Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115, upload-time = "2025-06-30T15:52:31.416Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649, upload-time = "2025-06-30T15:52:32.996Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203, upload-time = "2025-06-30T15:52:34.521Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051, upload-time = "2025-06-30T15:52:35.999Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601, upload-time = "2025-06-30T15:52:37.473Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683, upload-time = "2025-06-30T15:52:38.927Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811, upload-time = "2025-06-30T15:52:40.207Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056, upload-time = "2025-06-30T15:52:41.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811, upload-time = "2025-06-30T15:52:43.281Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304, upload-time = "2025-06-30T15:52:45.026Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775, upload-time = "2025-06-30T15:52:46.459Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773, upload-time = "2025-06-30T15:52:47.88Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083, upload-time = "2025-06-30T15:52:49.366Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980, upload-time = "2025-06-30T15:52:50.903Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776, upload-time = "2025-06-30T15:52:52.764Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882, upload-time = "2025-06-30T15:52:54.596Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816, upload-time = "2025-06-30T15:52:56.175Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341, upload-time = "2025-06-30T15:52:57.752Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854, upload-time = "2025-06-30T15:52:59.74Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432, upload-time = "2025-06-30T15:53:01.602Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731, upload-time = "2025-06-30T15:53:03.517Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086, upload-time = "2025-06-30T15:53:05.48Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338, upload-time = "2025-06-30T15:53:07.522Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812, upload-time = "2025-06-30T15:53:09.263Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011, upload-time = "2025-06-30T15:53:11.038Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254, upload-time = "2025-06-30T15:53:12.421Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/64/ba29bd6dfc895e592b2f20f92378e692ac306cf25dd0be2f8e0a0f898edb/multidict-6.6.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c8161b5a7778d3137ea2ee7ae8a08cce0010de3b00ac671c5ebddeaa17cefd22", size = 76959, upload-time = "2025-06-30T15:53:13.827Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/cd/872ae4c134257dacebff59834983c1615d6ec863b6e3d360f3203aad8400/multidict-6.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1328201ee930f069961ae707d59c6627ac92e351ed5b92397cf534d1336ce557", size = 44864, upload-time = "2025-06-30T15:53:15.658Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/35/d417d8f62f2886784b76df60522d608aba39dfc83dd53b230ca71f2d4c53/multidict-6.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b1db4d2093d6b235de76932febf9d50766cf49a5692277b2c28a501c9637f616", size = 44540, upload-time = "2025-06-30T15:53:17.208Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/59/25cddf781f12cddb2386baa29744a3fdd160eb705539b48065f0cffd86d5/multidict-6.6.3-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53becb01dd8ebd19d1724bebe369cfa87e4e7f29abbbe5c14c98ce4c383e16cd", size = 224075, upload-time = "2025-06-30T15:53:18.705Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/21/4055b6a527954c572498a8068c26bd3b75f2b959080e17e12104b592273c/multidict-6.6.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41bb9d1d4c303886e2d85bade86e59885112a7f4277af5ad47ab919a2251f306", size = 240535, upload-time = "2025-06-30T15:53:20.359Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/98/17f1f80bdba0b2fef49cf4ba59cebf8a81797f745f547abb5c9a4039df62/multidict-6.6.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:775b464d31dac90f23192af9c291dc9f423101857e33e9ebf0020a10bfcf4144", size = 219361, upload-time = "2025-06-30T15:53:22.371Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/0e/a5e595fdd0820069f0c29911d5dc9dc3a75ec755ae733ce59a4e6962ae42/multidict-6.6.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d04d01f0a913202205a598246cf77826fe3baa5a63e9f6ccf1ab0601cf56eca0", size = 251207, upload-time = "2025-06-30T15:53:24.307Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/9e/0f51e4cffea2daf24c137feabc9ec848ce50f8379c9badcbac00b41ab55e/multidict-6.6.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d25594d3b38a2e6cabfdcafef339f754ca6e81fbbdb6650ad773ea9775af35ab", size = 249749, upload-time = "2025-06-30T15:53:26.056Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/a0/a7cfc13c9a71ceb8c1c55457820733af9ce01e121139271f7b13e30c29d2/multidict-6.6.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35712f1748d409e0707b165bf49f9f17f9e28ae85470c41615778f8d4f7d9609", size = 239202, upload-time = "2025-06-30T15:53:28.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/50/7ae0d1149ac71cab6e20bb7faf2a1868435974994595dadfdb7377f7140f/multidict-6.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c8082e5814b662de8589d6a06c17e77940d5539080cbab9fe6794b5241b76d9", size = 237269, upload-time = "2025-06-30T15:53:30.124Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/ac/2d0bf836c9c63a57360d57b773359043b371115e1c78ff648993bf19abd0/multidict-6.6.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:61af8a4b771f1d4d000b3168c12c3120ccf7284502a94aa58c68a81f5afac090", size = 232961, upload-time = "2025-06-30T15:53:31.766Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/e1/68a65f069df298615591e70e48bfd379c27d4ecb252117c18bf52eebc237/multidict-6.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:448e4a9afccbf297577f2eaa586f07067441e7b63c8362a3540ba5a38dc0f14a", size = 240863, upload-time = "2025-06-30T15:53:33.488Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/ab/702f1baca649f88ea1dc6259fc2aa4509f4ad160ba48c8e61fbdb4a5a365/multidict-6.6.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:233ad16999afc2bbd3e534ad8dbe685ef8ee49a37dbc2cdc9514e57b6d589ced", size = 246800, upload-time = "2025-06-30T15:53:35.21Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/0b/726e690bfbf887985a8710ef2f25f1d6dd184a35bd3b36429814f810a2fc/multidict-6.6.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:bb933c891cd4da6bdcc9733d048e994e22e1883287ff7540c2a0f3b117605092", size = 242034, upload-time = "2025-06-30T15:53:36.913Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/bb/839486b27bcbcc2e0d875fb9d4012b4b6aa99639137343106aa7210e047a/multidict-6.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:37b09ca60998e87734699e88c2363abfd457ed18cfbf88e4009a4e83788e63ed", size = 235377, upload-time = "2025-06-30T15:53:38.618Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/46/574d75ab7b9ae8690fe27e89f5fcd0121633112b438edfb9ed2be8be096b/multidict-6.6.3-cp39-cp39-win32.whl", hash = "sha256:f54cb79d26d0cd420637d184af38f0668558f3c4bbe22ab7ad830e67249f2e0b", size = 41420, upload-time = "2025-06-30T15:53:40.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/c3/8b3bc755508b777868349f4bfa844d3d31832f075ee800a3d6f1807338c5/multidict-6.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:295adc9c0551e5d5214b45cf29ca23dbc28c2d197a9c30d51aed9e037cb7c578", size = 46124, upload-time = "2025-06-30T15:53:41.984Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/30/5a66e7e4550e80975faee5b5dd9e9bd09194d2fd8f62363119b9e46e204b/multidict-6.6.3-cp39-cp39-win_arm64.whl", hash = "sha256:15332783596f227db50fb261c2c251a58ac3873c457f3a550a95d5c0aa3c770d", size = 42973, upload-time = "2025-06-30T15:53:43.505Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nonebot-plugin-docs"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "nonebot2" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "nonebot2", specifier = ">=2.0.0,<3" }]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = []
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nonebot2"
|
||||||
|
version = "2.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "exceptiongroup" },
|
||||||
|
{ name = "loguru" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "pygtrie" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "yarl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1c/06/754d4aea53d1a6622956c9d9a261760b15df03f95d7540666b85ba48d653/nonebot2-2.4.2.tar.gz", hash = "sha256:cf72d5920503ff373ba1d7963f3ddf573db913eb504e3b68ee347efb937db27d", size = 94868, upload-time = "2025-03-12T03:57:45.705Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/a8/18b79572cb6178a18bc2da76a041fc6b9f6308c5d376f5c7cbd84f4acf2d/nonebot2-2.4.2-py3-none-any.whl", hash = "sha256:ed3e970cdb6c885fb23349b65a045c08cf3ac7f43e28564ae0c72d3671ecda74", size = 115183, upload-time = "2025-03-12T03:57:43.988Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "propcache"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/39/8ea9bcfaaff16fd0b0fc901ee522e24c9ec44b4ca0229cfffb8066a06959/propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5", size = 74678, upload-time = "2025-06-09T22:55:41.227Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/85/cab84c86966e1d354cf90cdc4ba52f32f99a5bca92a1529d666d957d7686/propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4", size = 43829, upload-time = "2025-06-09T22:55:42.417Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/f7/9cb719749152d8b26d63801b3220ce2d3931312b2744d2b3a088b0ee9947/propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2", size = 43729, upload-time = "2025-06-09T22:55:43.651Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/a2/0b2b5a210ff311260002a315f6f9531b65a36064dfb804655432b2f7d3e3/propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d", size = 204483, upload-time = "2025-06-09T22:55:45.327Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/e0/7aff5de0c535f783b0c8be5bdb750c305c1961d69fbb136939926e155d98/propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec", size = 217425, upload-time = "2025-06-09T22:55:46.729Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/1d/65fa889eb3b2a7d6e4ed3c2b568a9cb8817547a1450b572de7bf24872800/propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701", size = 214723, upload-time = "2025-06-09T22:55:48.342Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/e2/eecf6989870988dfd731de408a6fa366e853d361a06c2133b5878ce821ad/propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef", size = 200166, upload-time = "2025-06-09T22:55:49.775Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/06/c32be4950967f18f77489268488c7cdc78cbfc65a8ba8101b15e526b83dc/propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1", size = 194004, upload-time = "2025-06-09T22:55:51.335Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/6c/17b521a6b3b7cbe277a4064ff0aa9129dd8c89f425a5a9b6b4dd51cc3ff4/propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886", size = 203075, upload-time = "2025-06-09T22:55:52.681Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/cb/3bdba2b736b3e45bc0e40f4370f745b3e711d439ffbffe3ae416393eece9/propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b", size = 195407, upload-time = "2025-06-09T22:55:54.048Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/bd/760c5c6a60a4a2c55a421bc34a25ba3919d49dee411ddb9d1493bb51d46e/propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb", size = 196045, upload-time = "2025-06-09T22:55:55.485Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/58/ced2757a46f55b8c84358d6ab8de4faf57cba831c51e823654da7144b13a/propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea", size = 208432, upload-time = "2025-06-09T22:55:56.884Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/ec/d98ea8d5a4d8fe0e372033f5254eddf3254344c0c5dc6c49ab84349e4733/propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb", size = 210100, upload-time = "2025-06-09T22:55:58.498Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/56/84/b6d8a7ecf3f62d7dd09d9d10bbf89fad6837970ef868b35b5ffa0d24d9de/propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe", size = 200712, upload-time = "2025-06-09T22:55:59.906Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/32/889f4903ddfe4a9dc61da71ee58b763758cf2d608fe1decede06e6467f8d/propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1", size = 38187, upload-time = "2025-06-09T22:56:01.212Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/74/d666795fb9ba1dc139d30de64f3b6fd1ff9c9d3d96ccfdb992cd715ce5d2/propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9", size = 42025, upload-time = "2025-06-09T22:56:02.875Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.11.7"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.33.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/ea/bbe9095cdd771987d13c82d104a9c8559ae9aec1e29f139e286fd2e9256e/pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", size = 2028677, upload-time = "2025-04-23T18:32:27.227Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/1d/4ac5ed228078737d457a609013e8f7edc64adc37b91d619ea965758369e5/pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", size = 1864735, upload-time = "2025-04-23T18:32:29.019Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/9a/2e70d6388d7cda488ae38f57bc2f7b03ee442fbcf0d75d848304ac7e405b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", size = 1898467, upload-time = "2025-04-23T18:32:31.119Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/2e/1568934feb43370c1ffb78a77f0baaa5a8b6897513e7a91051af707ffdc4/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", size = 1983041, upload-time = "2025-04-23T18:32:33.655Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/1a/1a1118f38ab64eac2f6269eb8c120ab915be30e387bb561e3af904b12499/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", size = 2136503, upload-time = "2025-04-23T18:32:35.519Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/da/44754d1d7ae0f22d6d3ce6c6b1486fc07ac2c524ed8f6eca636e2e1ee49b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", size = 2736079, upload-time = "2025-04-23T18:32:37.659Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/98/f43cd89172220ec5aa86654967b22d862146bc4d736b1350b4c41e7c9c03/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", size = 2006508, upload-time = "2025-04-23T18:32:39.637Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/cc/f77e8e242171d2158309f830f7d5d07e0531b756106f36bc18712dc439df/pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", size = 2113693, upload-time = "2025-04-23T18:32:41.818Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/7a/7be6a7bd43e0a47c147ba7fbf124fe8aaf1200bc587da925509641113b2d/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", size = 2074224, upload-time = "2025-04-23T18:32:44.033Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/07/31cf8fadffbb03be1cb520850e00a8490c0927ec456e8293cafda0726184/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", size = 2245403, upload-time = "2025-04-23T18:32:45.836Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/8d/bbaf4c6721b668d44f01861f297eb01c9b35f612f6b8e14173cb204e6240/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", size = 2242331, upload-time = "2025-04-23T18:32:47.618Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/93/3cc157026bca8f5006250e74515119fcaa6d6858aceee8f67ab6dc548c16/pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", size = 1910571, upload-time = "2025-04-23T18:32:49.401Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/90/7edc3b2a0d9f0dda8806c04e511a67b0b7a41d2187e2003673a996fb4310/pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", size = 1956504, upload-time = "2025-04-23T18:32:51.287Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/98/dbf3fdfabaf81cda5622154fda78ea9965ac467e3239078e0dcd6df159e7/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", size = 2024034, upload-time = "2025-04-23T18:33:32.843Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/99/7810aa9256e7f2ccd492590f86b79d370df1e9292f1f80b000b6a75bd2fb/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", size = 1858578, upload-time = "2025-04-23T18:33:34.912Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/60/bc06fa9027c7006cc6dd21e48dbf39076dc39d9abbaf718a1604973a9670/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", size = 1892858, upload-time = "2025-04-23T18:33:36.933Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/40/9d03997d9518816c68b4dfccb88969756b9146031b61cd37f781c74c9b6a/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", size = 2068498, upload-time = "2025-04-23T18:33:38.997Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/62/d490198d05d2d86672dc269f52579cad7261ced64c2df213d5c16e0aecb1/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", size = 2108428, upload-time = "2025-04-23T18:33:41.18Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/ec/4cd215534fd10b8549015f12ea650a1a973da20ce46430b68fc3185573e8/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", size = 2069854, upload-time = "2025-04-23T18:33:43.446Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/1a/abbd63d47e1d9b0d632fee6bb15785d0889c8a6e0a6c3b5a8e28ac1ec5d2/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", size = 2237859, upload-time = "2025-04-23T18:33:45.56Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/1c/fa883643429908b1c90598fd2642af8839efd1d835b65af1f75fba4d94fe/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", size = 2239059, upload-time = "2025-04-23T18:33:47.735Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661, upload-time = "2025-04-23T18:33:49.995Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygtrie"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b9/13/55deec25bf09383216fa7f1dfcdbfca40a04aa00b6d15a5cbf25af8fce5f/pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2", size = 39266, upload-time = "2022-07-16T14:29:47.459Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/cd/bd196b2cf014afb1009de8b0f05ecd54011d881944e62763f3c1b1e8ef37/pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16", size = 25099, upload-time = "2022-09-23T20:30:05.12Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.14.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-inspection"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "win32-setctime"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yarl"
|
||||||
|
version = "1.20.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "multidict" },
|
||||||
|
{ name = "propcache" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/75/0d37402d208d025afa6b5b8eb80e466d267d3fd1927db8e317d29a94a4cb/yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3", size = 134259, upload-time = "2025-06-10T00:45:29.882Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/84/1fb6c85ae0cf9901046f07d0ac9eb162f7ce6d95db541130aa542ed377e6/yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b", size = 91269, upload-time = "2025-06-10T00:45:32.917Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/9c/eae746b24c4ea29a5accba9a06c197a70fa38a49c7df244e0d3951108861/yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983", size = 89995, upload-time = "2025-06-10T00:45:35.066Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/30/693e71003ec4bc1daf2e4cf7c478c417d0985e0a8e8f00b2230d517876fc/yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805", size = 325253, upload-time = "2025-06-10T00:45:37.052Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/a2/5264dbebf90763139aeb0b0b3154763239398400f754ae19a0518b654117/yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba", size = 320897, upload-time = "2025-06-10T00:45:39.962Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/17/77c7a89b3c05856489777e922f41db79ab4faf58621886df40d812c7facd/yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e", size = 340696, upload-time = "2025-06-10T00:45:41.915Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/55/28409330b8ef5f2f681f5b478150496ec9cf3309b149dab7ec8ab5cfa3f0/yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723", size = 335064, upload-time = "2025-06-10T00:45:43.893Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/58/cb0257cbd4002828ff735f44d3c5b6966c4fd1fc8cc1cd3cd8a143fbc513/yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000", size = 327256, upload-time = "2025-06-10T00:45:46.393Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/f6/c77960370cfa46f6fb3d6a5a79a49d3abfdb9ef92556badc2dcd2748bc2a/yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5", size = 316389, upload-time = "2025-06-10T00:45:48.358Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/ab/be0b10b8e029553c10905b6b00c64ecad3ebc8ace44b02293a62579343f6/yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c", size = 340481, upload-time = "2025-06-10T00:45:50.663Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/c3/3f327bd3905a4916029bf5feb7f86dcf864c7704f099715f62155fb386b2/yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240", size = 336941, upload-time = "2025-06-10T00:45:52.554Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/42/040bdd5d3b3bb02b4a6ace4ed4075e02f85df964d6e6cb321795d2a6496a/yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee", size = 339936, upload-time = "2025-06-10T00:45:54.919Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/1c/911867b8e8c7463b84dfdc275e0d99b04b66ad5132b503f184fe76be8ea4/yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010", size = 360163, upload-time = "2025-06-10T00:45:56.87Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/31/8c389f6c6ca0379b57b2da87f1f126c834777b4931c5ee8427dd65d0ff6b/yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8", size = 359108, upload-time = "2025-06-10T00:45:58.869Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/09/ae4a649fb3964324c70a3e2b61f45e566d9ffc0affd2b974cbf628957673/yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d", size = 351875, upload-time = "2025-06-10T00:46:01.45Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/43/bbb4ed4c34d5bb62b48bf957f68cd43f736f79059d4f85225ab1ef80f4b9/yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06", size = 82293, upload-time = "2025-06-10T00:46:03.763Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/cd/ce185848a7dba68ea69e932674b5c1a42a1852123584bccc5443120f857c/yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00", size = 87385, upload-time = "2025-06-10T00:46:05.655Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" },
|
||||||
|
]
|
2887
poetry.lock
generated
2887
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
117
pyproject.toml
117
pyproject.toml
@@ -1,13 +1,10 @@
|
|||||||
[tool.poetry]
|
[project]
|
||||||
name = "nonebot2"
|
name = "nonebot2"
|
||||||
version = "2.4.2"
|
version = "2.4.3"
|
||||||
description = "An asynchronous python bot framework."
|
description = "An asynchronous python bot framework."
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
authors = [{ name = "yanyongyu", email = "yyy@nonebot.dev" }]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://nonebot.dev/"
|
|
||||||
repository = "https://github.com/nonebot/nonebot2"
|
|
||||||
documentation = "https://nonebot.dev/"
|
|
||||||
keywords = ["bot", "qq", "qqbot", "mirai", "coolq"]
|
keywords = ["bot", "qq", "qqbot", "mirai", "coolq"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
@@ -16,53 +13,68 @@ classifiers = [
|
|||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
]
|
]
|
||||||
packages = [{ include = "nonebot" }]
|
requires-python = ">=3.9, <4.0"
|
||||||
include = ["nonebot/py.typed"]
|
dependencies = [
|
||||||
|
"yarl >=1.7.2, <2.0.0",
|
||||||
|
"anyio >=4.4.0, <5.0.0",
|
||||||
|
"loguru >=0.6.0, <1.0.0",
|
||||||
|
"pygtrie >=2.4.1, <3.0.0",
|
||||||
|
"exceptiongroup >=1.2.2, <2.0.0",
|
||||||
|
"python-dotenv >=0.21.0, <2.0.0",
|
||||||
|
"typing-extensions >=4.6.0, <5.0.0",
|
||||||
|
"tomli >=2.0.1, <3.0.0; python_version < '3.11'",
|
||||||
|
"pydantic >=1.10.0, <3.0.0, !=2.5.0, !=2.5.1, !=2.10.0, !=2.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.poetry.urls]
|
[project.optional-dependencies]
|
||||||
|
websockets = ["websockets >=15.0"]
|
||||||
|
httpx = ["httpx[http2] >=0.26.0, <1.0.0"]
|
||||||
|
aiohttp = ["aiohttp[speedups] >=3.11.0, <4.0.0"]
|
||||||
|
quart = ["Quart >=0.18.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"]
|
||||||
|
all = [
|
||||||
|
"websockets >=15.0",
|
||||||
|
"fastapi >=0.93.0, <1.0.0",
|
||||||
|
"httpx[http2] >=0.26.0, <1.0.0",
|
||||||
|
"aiohttp[speedups] >=3.11.0, <4.0.0",
|
||||||
|
"uvicorn[standard] >=0.20.0, <1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"ruff >=0.12.0, <0.13.0",
|
||||||
|
"nonemoji >=0.1.2, <0.2.0",
|
||||||
|
"pre-commit >=4.0.0, <5.0.0",
|
||||||
|
]
|
||||||
|
test = [
|
||||||
|
"trio >=0.27.0",
|
||||||
|
"nonebug >=0.4.1, <0.5.0",
|
||||||
|
"wsproto >=1.2.0, <2.0.0",
|
||||||
|
"werkzeug >=2.3.6, <4.0.0",
|
||||||
|
"pytest-cov >=6.0.0, <7.0.0",
|
||||||
|
"pytest-xdist >=3.0.2, <4.0.0",
|
||||||
|
"coverage-conditional-plugin >=0.9.0, <0.10.0",
|
||||||
|
]
|
||||||
|
docs = ["nb-autodoc >=1.0.0a5, <2.0.0"]
|
||||||
|
pydantic-v1 = ["pydantic >=1.10.0, <2.0.0"]
|
||||||
|
pydantic-v2 = ["pydantic >=2.0.0, <3.0.0"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://nonebot.dev/"
|
||||||
|
Repository = "https://github.com/nonebot/nonebot2"
|
||||||
|
Documentation = "https://nonebot.dev/"
|
||||||
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
||||||
"Changelog" = "https://nonebot.dev/changelog"
|
Changelog = "https://nonebot.dev/changelog"
|
||||||
"Funding" = "https://afdian.com/@nonebot"
|
Funding = "https://afdian.com/@nonebot"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.uv]
|
||||||
python = "^3.9"
|
required-version = ">=0.8.0"
|
||||||
yarl = "^1.7.2"
|
default-groups = ["dev", "test", "docs"]
|
||||||
anyio = "^4.4.0"
|
conflicts = [[{ group = "pydantic-v1" }, { group = "pydantic-v2" }]]
|
||||||
pygtrie = "^2.4.1"
|
|
||||||
exceptiongroup = "^1.2.2"
|
|
||||||
loguru = ">=0.6.0,<1.0.0"
|
|
||||||
python-dotenv = ">=0.21.0,<2.0.0"
|
|
||||||
typing-extensions = ">=4.4.0,<5.0.0"
|
|
||||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
|
||||||
pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1,!=2.10.0,!=2.10.1"
|
|
||||||
|
|
||||||
websockets = { version = ">=10.0", optional = true }
|
[tool.uv.build-backend]
|
||||||
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
module-name = "nonebot"
|
||||||
fastapi = { version = ">=0.93.0,<1.0.0", optional = true }
|
module-root = ""
|
||||||
aiohttp = { version = "^3.11.0", extras = ["speedups"], optional = true }
|
|
||||||
httpx = { version = ">=0.26.0,<1.0.0", extras = ["http2"], optional = true }
|
|
||||||
uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
|
||||||
"standard",
|
|
||||||
], optional = true }
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
ruff = "^0.9.0"
|
|
||||||
nonemoji = "^0.1.2"
|
|
||||||
pre-commit = "^4.0.0"
|
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
|
||||||
nonebot-test = { path = "./envs/test/", develop = false }
|
|
||||||
|
|
||||||
[tool.poetry.group.docs.dependencies]
|
|
||||||
nb-autodoc = "^1.0.0a5"
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
|
||||||
httpx = ["httpx"]
|
|
||||||
aiohttp = ["aiohttp"]
|
|
||||||
websockets = ["websockets"]
|
|
||||||
quart = ["quart", "uvicorn"]
|
|
||||||
fastapi = ["fastapi", "uvicorn"]
|
|
||||||
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "--cov=nonebot --cov-report=term-missing"
|
addopts = "--cov=nonebot --cov-report=term-missing"
|
||||||
@@ -117,6 +129,9 @@ pythonVersion = "3.9"
|
|||||||
pythonPlatform = "All"
|
pythonPlatform = "All"
|
||||||
defineConstant = { PYDANTIC_V2 = true }
|
defineConstant = { PYDANTIC_V2 = true }
|
||||||
executionEnvironments = [
|
executionEnvironments = [
|
||||||
|
{ root = "./tests/python_3_12", pythonVersion = "3.12", extraPaths = [
|
||||||
|
"./",
|
||||||
|
] },
|
||||||
{ root = "./tests", extraPaths = [
|
{ root = "./tests", extraPaths = [
|
||||||
"./",
|
"./",
|
||||||
] },
|
] },
|
||||||
@@ -128,5 +143,5 @@ reportShadowedImports = false
|
|||||||
disableBytesTypePromotions = true
|
disableBytesTypePromotions = true
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry_core>=1.0.0"]
|
requires = ["uv_build >=0.8.3, <0.9.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "uv_build"
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
#! /usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
# cd to the root of the project
|
# cd to the root of the project
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
poetry run nb-autodoc nonebot \
|
nb-autodoc nonebot \
|
||||||
-s nonebot.plugins \
|
-s nonebot.plugins \
|
||||||
-u nonebot.internal \
|
-u nonebot.internal \
|
||||||
-u nonebot.internal.*
|
-u nonebot.internal.*
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
#! /usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# cd to the root of the tests
|
# cd to the root of the tests
|
||||||
cd "$(dirname "$0")/../tests"
|
cd "$(dirname "$0")/../tests"
|
||||||
|
@@ -1,14 +1,4 @@
|
|||||||
#! /usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# config poetry to install env in project
|
|
||||||
poetry config virtualenvs.in-project true
|
|
||||||
|
|
||||||
# setup dev environment
|
|
||||||
echo "Setting up dev environment"
|
echo "Setting up dev environment"
|
||||||
poetry install --all-extras && poetry run pre-commit install && yarn install
|
uv sync --all-extras && uv run pre-commit install && yarn install
|
||||||
|
|
||||||
# setup pydantic v2 test environment
|
|
||||||
for env in $(find ./envs/ -maxdepth 1 -mindepth 1 -type d -not -name test); do
|
|
||||||
echo "Setting up $env environment"
|
|
||||||
(cd $env && poetry install --no-root)
|
|
||||||
done
|
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
#! /usr/bin/env bash
|
|
||||||
|
|
||||||
# update test env
|
|
||||||
echo "Updating test env..."
|
|
||||||
(cd ./envs/test/ && poetry update --lock)
|
|
||||||
|
|
||||||
# update dev env
|
|
||||||
echo "Updating dev env..."
|
|
||||||
poetry update
|
|
||||||
|
|
||||||
# update other envs
|
|
||||||
for env in $(find ./envs/ -maxdepth 1 -mindepth 1 -type d -not -name test); do
|
|
||||||
echo "Updating $env env..."
|
|
||||||
(cd $env && poetry update)
|
|
||||||
done
|
|
@@ -2,6 +2,7 @@ from collections.abc import Generator
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from typing import TYPE_CHECKING, Callable, TypeVar
|
from typing import TYPE_CHECKING, Callable, TypeVar
|
||||||
from typing_extensions import ParamSpec
|
from typing_extensions import ParamSpec
|
||||||
@@ -67,7 +68,14 @@ def run_once(func: Callable[P, R]) -> Callable[P, R]:
|
|||||||
@run_once
|
@run_once
|
||||||
def load_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
|
def load_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
|
||||||
# preload global plugins
|
# preload global plugins
|
||||||
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
plugins: set["Plugin"] = set()
|
||||||
|
plugins |= nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
||||||
|
if sys.version_info >= (3, 12):
|
||||||
|
# preload python 3.12 plugins
|
||||||
|
plugins |= nonebot.load_plugins(
|
||||||
|
str(Path(__file__).parent / "python_3_12" / "plugins")
|
||||||
|
)
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
3
tests/plugins.legacy.toml
Normal file
3
tests/plugins.legacy.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tool.nonebot]
|
||||||
|
plugins = []
|
||||||
|
plugin_dirs = []
|
@@ -1,3 +1,5 @@
|
|||||||
[tool.nonebot]
|
[tool.nonebot]
|
||||||
plugins = []
|
|
||||||
plugin_dirs = []
|
plugin_dirs = []
|
||||||
|
|
||||||
|
[tool.nonebot.plugins]
|
||||||
|
"@local" = []
|
||||||
|
7
tests/python_3_12/plugins/aliased_param/__init__.py
Normal file
7
tests/python_3_12/plugins/aliased_param/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from nonebot import load_plugins
|
||||||
|
|
||||||
|
_sub_plugins = set()
|
||||||
|
|
||||||
|
_sub_plugins |= load_plugins(str(Path(__file__).parent))
|
10
tests/python_3_12/plugins/aliased_param/param_arg.py
Normal file
10
tests/python_3_12/plugins/aliased_param/param_arg.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from nonebot.adapters import Message
|
||||||
|
from nonebot.params import Arg
|
||||||
|
|
||||||
|
type AliasedArg = Annotated[Message, Arg()]
|
||||||
|
|
||||||
|
|
||||||
|
async def aliased_arg(key: AliasedArg) -> Message:
|
||||||
|
return key
|
7
tests/python_3_12/plugins/aliased_param/param_bot.py
Normal file
7
tests/python_3_12/plugins/aliased_param/param_bot.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from nonebot.adapters import Bot
|
||||||
|
|
||||||
|
type AliasedBot = Bot
|
||||||
|
|
||||||
|
|
||||||
|
async def get_aliased_bot(b: AliasedBot) -> Bot:
|
||||||
|
return b
|
21
tests/python_3_12/plugins/aliased_param/param_depend.py
Normal file
21
tests/python_3_12/plugins/aliased_param/param_depend.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from nonebot import on_message
|
||||||
|
from nonebot.params import Depends
|
||||||
|
|
||||||
|
test_depends = on_message()
|
||||||
|
|
||||||
|
runned = []
|
||||||
|
|
||||||
|
|
||||||
|
def dependency():
|
||||||
|
runned.append(1)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
type AliasedDepends = Annotated[int, Depends(dependency)]
|
||||||
|
|
||||||
|
|
||||||
|
@test_depends.handle()
|
||||||
|
async def aliased_depends(x: AliasedDepends):
|
||||||
|
return x
|
7
tests/python_3_12/plugins/aliased_param/param_event.py
Normal file
7
tests/python_3_12/plugins/aliased_param/param_event.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from nonebot.adapters import Event
|
||||||
|
|
||||||
|
type AliasedEvent = Event
|
||||||
|
|
||||||
|
|
||||||
|
async def aliased_event(e: AliasedEvent) -> Event:
|
||||||
|
return e
|
@@ -0,0 +1,5 @@
|
|||||||
|
type AliasedException = Exception
|
||||||
|
|
||||||
|
|
||||||
|
async def aliased_exc(e: AliasedException) -> Exception:
|
||||||
|
return e
|
7
tests/python_3_12/plugins/aliased_param/param_matcher.py
Normal file
7
tests/python_3_12/plugins/aliased_param/param_matcher.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from nonebot.matcher import Matcher
|
||||||
|
|
||||||
|
type AliasedMatcher = Matcher
|
||||||
|
|
||||||
|
|
||||||
|
async def aliased_matcher(m: AliasedMatcher) -> Matcher:
|
||||||
|
return m
|
7
tests/python_3_12/plugins/aliased_param/param_state.py
Normal file
7
tests/python_3_12/plugins/aliased_param/param_state.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from nonebot.typing import T_State
|
||||||
|
|
||||||
|
type AliasedState = T_State
|
||||||
|
|
||||||
|
|
||||||
|
async def aliased_state(x: AliasedState) -> T_State:
|
||||||
|
return x
|
3
tests/python_3_12/pyproject.toml
Normal file
3
tests/python_3_12/pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tool.ruff]
|
||||||
|
extend = "../pyproject.toml"
|
||||||
|
target-version = "py312"
|
@@ -16,6 +16,7 @@ from nonebot.drivers import (
|
|||||||
HTTPServerSetup,
|
HTTPServerSetup,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
|
Timeout,
|
||||||
WebSocket,
|
WebSocket,
|
||||||
WebSocketClientMixin,
|
WebSocketClientMixin,
|
||||||
WebSocketServerSetup,
|
WebSocketServerSetup,
|
||||||
@@ -235,6 +236,7 @@ async def test_http_client(driver: Driver, server_url: URL):
|
|||||||
headers={"X-Test": "test"},
|
headers={"X-Test": "test"},
|
||||||
cookies={"session": "test"},
|
cookies={"session": "test"},
|
||||||
content="test",
|
content="test",
|
||||||
|
timeout=Timeout(total=4, connect=2, read=2),
|
||||||
)
|
)
|
||||||
response = await driver.request(request)
|
response = await driver.request(request)
|
||||||
assert server_url.host is not None
|
assert server_url.host is not None
|
||||||
@@ -250,6 +252,7 @@ async def test_http_client(driver: Driver, server_url: URL):
|
|||||||
headers={"X-Test": "test"},
|
headers={"X-Test": "test"},
|
||||||
cookies={"session": "test"},
|
cookies={"session": "test"},
|
||||||
content="test",
|
content="test",
|
||||||
|
timeout=Timeout(total=4, connect=2, read=2),
|
||||||
)
|
)
|
||||||
assert request.url == request_raw_url.url, (
|
assert 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"
|
||||||
@@ -304,6 +307,79 @@ async def test_http_client(driver: Driver, server_url: URL):
|
|||||||
"test3": "test",
|
"test3": "test",
|
||||||
}, "file parsing error"
|
}, "file parsing error"
|
||||||
|
|
||||||
|
# post stream request with query, headers, cookies and content
|
||||||
|
request = Request(
|
||||||
|
"POST",
|
||||||
|
server_url,
|
||||||
|
params={"param": "stream"},
|
||||||
|
headers={"X-Test": "stream"},
|
||||||
|
cookies={"session": "stream"},
|
||||||
|
content="stream_test" * 1024,
|
||||||
|
timeout=Timeout(total=4, connect=2, read=2),
|
||||||
|
)
|
||||||
|
chunks = []
|
||||||
|
async for resp in driver.stream_request(request, chunk_size=4):
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.content
|
||||||
|
chunks.append(resp.content)
|
||||||
|
assert all(len(chunk) == 4 for chunk in chunks[:-1])
|
||||||
|
data = json.loads(b"".join(chunks))
|
||||||
|
assert data["method"] == "POST"
|
||||||
|
assert data["args"] == {"param": "stream"}
|
||||||
|
assert data["headers"].get("X-Test") == "stream"
|
||||||
|
assert data["headers"].get("Cookie") == "session=stream"
|
||||||
|
assert data["data"] == "stream_test" * 1024
|
||||||
|
|
||||||
|
# post stream request with data body
|
||||||
|
request = Request("POST", server_url, data={"form": "test"})
|
||||||
|
chunks = []
|
||||||
|
async for resp in driver.stream_request(request, chunk_size=4):
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.content
|
||||||
|
chunks.append(resp.content)
|
||||||
|
assert all(len(chunk) == 4 for chunk in chunks[:-1])
|
||||||
|
data = json.loads(b"".join(chunks))
|
||||||
|
assert data["method"] == "POST"
|
||||||
|
assert data["form"] == {"form": "test"}
|
||||||
|
|
||||||
|
# post stream request with json body
|
||||||
|
request = Request("POST", server_url, json={"json": "test"})
|
||||||
|
chunks = []
|
||||||
|
async for resp in driver.stream_request(request, chunk_size=4):
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.content
|
||||||
|
chunks.append(resp.content)
|
||||||
|
assert all(len(chunk) == 4 for chunk in chunks[:-1])
|
||||||
|
data = json.loads(b"".join(chunks))
|
||||||
|
assert data["method"] == "POST"
|
||||||
|
assert data["json"] == {"json": "test"}
|
||||||
|
|
||||||
|
# post stream request with files and form data
|
||||||
|
request = Request(
|
||||||
|
"POST",
|
||||||
|
server_url,
|
||||||
|
data={"form": "test"},
|
||||||
|
files=[
|
||||||
|
("test1", b"test"),
|
||||||
|
("test2", ("test.txt", b"test")),
|
||||||
|
("test3", ("test.txt", b"test", "text/plain")),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
chunks = []
|
||||||
|
async for resp in driver.stream_request(request, chunk_size=4):
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.content
|
||||||
|
chunks.append(resp.content)
|
||||||
|
assert all(len(chunk) == 4 for chunk in chunks[:-1])
|
||||||
|
data = json.loads(b"".join(chunks))
|
||||||
|
assert data["method"] == "POST"
|
||||||
|
assert data["form"] == {"form": "test"}
|
||||||
|
assert data["files"] == {
|
||||||
|
"test1": "test",
|
||||||
|
"test2": "test",
|
||||||
|
"test3": "test",
|
||||||
|
}, "file parsing error"
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -342,6 +418,7 @@ async def test_http_client_session(driver: Driver, server_url: URL):
|
|||||||
headers={"X-Test": "test"},
|
headers={"X-Test": "test"},
|
||||||
cookies={"cookie": "test"},
|
cookies={"cookie": "test"},
|
||||||
content="test",
|
content="test",
|
||||||
|
timeout=Timeout(total=4, connect=2, read=2),
|
||||||
)
|
)
|
||||||
response = await session.request(request)
|
response = await session.request(request)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -419,6 +496,101 @@ async def test_http_client_session(driver: Driver, server_url: URL):
|
|||||||
"test3": "test",
|
"test3": "test",
|
||||||
}, "file parsing error"
|
}, "file parsing error"
|
||||||
|
|
||||||
|
# post stream request with query, headers, cookies and content
|
||||||
|
request = Request(
|
||||||
|
"POST",
|
||||||
|
server_url,
|
||||||
|
params={"param": "stream"},
|
||||||
|
headers={"X-Test": "stream"},
|
||||||
|
cookies={"cookie": "stream"},
|
||||||
|
content="stream_test" * 1024,
|
||||||
|
timeout=Timeout(total=4, connect=2, read=2),
|
||||||
|
)
|
||||||
|
chunks = []
|
||||||
|
async for resp in session.stream_request(request, chunk_size=4):
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.content
|
||||||
|
chunks.append(resp.content)
|
||||||
|
assert all(len(chunk) == 4 for chunk in chunks[:-1])
|
||||||
|
data = json.loads(b"".join(chunks))
|
||||||
|
assert data["method"] == "POST"
|
||||||
|
assert data["args"] == {"session": "test", "param": "stream"}
|
||||||
|
assert data["headers"].get("X-Session") == "test"
|
||||||
|
assert data["headers"].get("X-Test") == "stream"
|
||||||
|
assert {
|
||||||
|
key: cookie.value
|
||||||
|
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||||
|
} == {"session": "test", "cookie": "stream"}
|
||||||
|
assert data["data"] == "stream_test" * 1024
|
||||||
|
|
||||||
|
# post stream request with data body
|
||||||
|
request = Request("POST", server_url, data={"form": "test"})
|
||||||
|
chunks = []
|
||||||
|
async for resp in session.stream_request(request, chunk_size=4):
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.content
|
||||||
|
chunks.append(resp.content)
|
||||||
|
assert all(len(chunk) == 4 for chunk in chunks[:-1])
|
||||||
|
data = json.loads(b"".join(chunks))
|
||||||
|
assert data["method"] == "POST"
|
||||||
|
assert data["args"] == {"session": "test"}
|
||||||
|
assert data["headers"].get("X-Session") == "test"
|
||||||
|
assert {
|
||||||
|
key: cookie.value
|
||||||
|
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||||
|
} == {"session": "test"}
|
||||||
|
assert data["form"] == {"form": "test"}
|
||||||
|
|
||||||
|
# post stream request with json body
|
||||||
|
request = Request("POST", server_url, json={"json": "test"})
|
||||||
|
chunks = []
|
||||||
|
async for resp in session.stream_request(request, chunk_size=4):
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.content
|
||||||
|
chunks.append(resp.content)
|
||||||
|
assert all(len(chunk) == 4 for chunk in chunks[:-1])
|
||||||
|
data = json.loads(b"".join(chunks))
|
||||||
|
assert data["method"] == "POST"
|
||||||
|
assert data["args"] == {"session": "test"}
|
||||||
|
assert data["headers"].get("X-Session") == "test"
|
||||||
|
assert {
|
||||||
|
key: cookie.value
|
||||||
|
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||||
|
} == {"session": "test"}
|
||||||
|
assert data["json"] == {"json": "test"}
|
||||||
|
|
||||||
|
# post stream request with files and form data
|
||||||
|
request = Request(
|
||||||
|
"POST",
|
||||||
|
server_url,
|
||||||
|
data={"form": "test"},
|
||||||
|
files=[
|
||||||
|
("test1", b"test"),
|
||||||
|
("test2", ("test.txt", b"test")),
|
||||||
|
("test3", ("test.txt", b"test", "text/plain")),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
chunks = []
|
||||||
|
async for resp in session.stream_request(request, chunk_size=4):
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.content
|
||||||
|
chunks.append(resp.content)
|
||||||
|
assert all(len(chunk) == 4 for chunk in chunks[:-1])
|
||||||
|
data = json.loads(b"".join(chunks))
|
||||||
|
assert data["method"] == "POST"
|
||||||
|
assert data["args"] == {"session": "test"}
|
||||||
|
assert data["headers"].get("X-Session") == "test"
|
||||||
|
assert {
|
||||||
|
key: cookie.value
|
||||||
|
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||||
|
} == {"session": "test"}
|
||||||
|
assert data["form"] == {"form": "test"}
|
||||||
|
assert data["files"] == {
|
||||||
|
"test1": "test",
|
||||||
|
"test2": "test",
|
||||||
|
"test3": "test",
|
||||||
|
}, "file parsing error"
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await anyio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
from exceptiongroup import BaseExceptionGroup
|
from exceptiongroup import BaseExceptionGroup
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
@@ -41,6 +42,10 @@ UNKNOWN_PARAM = "Unknown parameter"
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.xfail(
|
||||||
|
(3, 13) <= sys.version_info < (3, 14),
|
||||||
|
reason="CPython Bug, see python/cpython#137317, python/cpython#137862",
|
||||||
|
)
|
||||||
async def test_depend(app: App):
|
async def test_depend(app: App):
|
||||||
from plugins.param.param_depend import (
|
from plugins.param.param_depend import (
|
||||||
ClassDependency,
|
ClassDependency,
|
||||||
@@ -156,6 +161,22 @@ async def test_depend(app: App):
|
|||||||
ctx.should_return(1)
|
ctx.should_return(1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 12), reason="TypeAlias requires Python 3.12 or higher"
|
||||||
|
)
|
||||||
|
async def test_aliased_depend(app: App):
|
||||||
|
from python_3_12.plugins.aliased_param.param_depend import aliased_depends, runned
|
||||||
|
|
||||||
|
async with app.test_dependent(aliased_depends, allow_types=[DependParam]) as ctx:
|
||||||
|
ctx.should_return(1)
|
||||||
|
|
||||||
|
assert len(runned) == 1
|
||||||
|
assert runned[0] == 1
|
||||||
|
|
||||||
|
runned.clear()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_bot(app: App):
|
async def test_bot(app: App):
|
||||||
from plugins.param.param_bot import (
|
from plugins.param.param_bot import (
|
||||||
@@ -221,6 +242,19 @@ async def test_bot(app: App):
|
|||||||
app.test_dependent(not_bot, allow_types=[BotParam])
|
app.test_dependent(not_bot, allow_types=[BotParam])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 12), reason="TypeAlias requires Python 3.12 or higher"
|
||||||
|
)
|
||||||
|
async def test_aliased_bot(app: App):
|
||||||
|
from python_3_12.plugins.aliased_param.param_bot import get_aliased_bot
|
||||||
|
|
||||||
|
async with app.test_dependent(get_aliased_bot, allow_types=[BotParam]) as ctx:
|
||||||
|
bot = ctx.create_bot()
|
||||||
|
ctx.pass_params(bot=bot)
|
||||||
|
ctx.should_return(bot)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_event(app: App):
|
async def test_event(app: App):
|
||||||
from plugins.param.param_event import (
|
from plugins.param.param_event import (
|
||||||
@@ -310,6 +344,21 @@ async def test_event(app: App):
|
|||||||
ctx.should_return(fake_event.is_tome())
|
ctx.should_return(fake_event.is_tome())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 12), reason="TypeAlias requires Python 3.12 or higher"
|
||||||
|
)
|
||||||
|
async def test_aliased_event(app: App):
|
||||||
|
from python_3_12.plugins.aliased_param.param_event import aliased_event
|
||||||
|
|
||||||
|
fake_message = FakeMessage("text")
|
||||||
|
fake_event = make_fake_event(_message=fake_message)()
|
||||||
|
|
||||||
|
async with app.test_dependent(aliased_event, allow_types=[EventParam]) as ctx:
|
||||||
|
ctx.pass_params(event=fake_event)
|
||||||
|
ctx.should_return(fake_event)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_state(app: App):
|
async def test_state(app: App):
|
||||||
from plugins.param.param_state import (
|
from plugins.param.param_state import (
|
||||||
@@ -461,6 +510,37 @@ async def test_state(app: App):
|
|||||||
ctx.should_return(fake_state[KEYWORD_KEY])
|
ctx.should_return(fake_state[KEYWORD_KEY])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 12), reason="TypeAlias requires Python 3.12 or higher"
|
||||||
|
)
|
||||||
|
async def test_aliased_state(app: App):
|
||||||
|
from python_3_12.plugins.aliased_param.param_state import aliased_state
|
||||||
|
|
||||||
|
fake_message = FakeMessage("text")
|
||||||
|
fake_matched = re.match(r"\[cq:(?P<type>.*?),(?P<arg>.*?)\]", "[cq:test,arg=value]")
|
||||||
|
fake_state = {
|
||||||
|
PREFIX_KEY: {
|
||||||
|
CMD_KEY: ("cmd",),
|
||||||
|
RAW_CMD_KEY: "/cmd",
|
||||||
|
CMD_START_KEY: "/",
|
||||||
|
CMD_ARG_KEY: fake_message,
|
||||||
|
CMD_WHITESPACE_KEY: " ",
|
||||||
|
},
|
||||||
|
SHELL_ARGV: ["-h"],
|
||||||
|
SHELL_ARGS: {"help": True},
|
||||||
|
REGEX_MATCHED: fake_matched,
|
||||||
|
STARTSWITH_KEY: "startswith",
|
||||||
|
ENDSWITH_KEY: "endswith",
|
||||||
|
FULLMATCH_KEY: "fullmatch",
|
||||||
|
KEYWORD_KEY: "keyword",
|
||||||
|
}
|
||||||
|
|
||||||
|
async with app.test_dependent(aliased_state, allow_types=[StateParam]) as ctx:
|
||||||
|
ctx.pass_params(state=fake_state)
|
||||||
|
ctx.should_return(fake_state)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_matcher(app: App):
|
async def test_matcher(app: App):
|
||||||
from plugins.param.param_matcher import (
|
from plugins.param.param_matcher import (
|
||||||
@@ -573,6 +653,20 @@ async def test_matcher(app: App):
|
|||||||
ctx.should_return(False)
|
ctx.should_return(False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 12), reason="TypeAlias requires Python 3.12 or higher"
|
||||||
|
)
|
||||||
|
async def test_aliased_matcher(app: App):
|
||||||
|
from python_3_12.plugins.aliased_param.param_matcher import aliased_matcher
|
||||||
|
|
||||||
|
fake_matcher = Matcher()
|
||||||
|
|
||||||
|
async with app.test_dependent(aliased_matcher, allow_types=[MatcherParam]) as ctx:
|
||||||
|
ctx.pass_params(matcher=fake_matcher)
|
||||||
|
ctx.should_return(fake_matcher)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_arg(app: App):
|
async def test_arg(app: App):
|
||||||
from plugins.param.param_arg import (
|
from plugins.param.param_arg import (
|
||||||
@@ -642,11 +736,28 @@ async def test_arg(app: App):
|
|||||||
ctx.should_return(message.extract_plain_text())
|
ctx.should_return(message.extract_plain_text())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 12), reason="TypeAlias requires Python 3.12 or higher"
|
||||||
|
)
|
||||||
|
async def test_aliased_arg(app: App):
|
||||||
|
from python_3_12.plugins.aliased_param.param_arg import aliased_arg
|
||||||
|
|
||||||
|
matcher = Matcher()
|
||||||
|
message = FakeMessage("text")
|
||||||
|
matcher.set_arg("key", message)
|
||||||
|
|
||||||
|
async with app.test_dependent(aliased_arg, allow_types=[ArgParam]) as ctx:
|
||||||
|
ctx.pass_params(matcher=matcher)
|
||||||
|
ctx.should_return(message)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_exception(app: App):
|
async def test_exception(app: App):
|
||||||
from plugins.param.param_exception import exc, legacy_exc
|
from plugins.param.param_exception import exc, legacy_exc
|
||||||
|
|
||||||
exception = ValueError("test")
|
exception = ValueError("test")
|
||||||
|
|
||||||
async with app.test_dependent(exc, allow_types=[ExceptionParam]) as ctx:
|
async with app.test_dependent(exc, allow_types=[ExceptionParam]) as ctx:
|
||||||
ctx.pass_params(exception=exception)
|
ctx.pass_params(exception=exception)
|
||||||
ctx.should_return(exception)
|
ctx.should_return(exception)
|
||||||
@@ -656,6 +767,20 @@ async def test_exception(app: App):
|
|||||||
ctx.should_return(exception)
|
ctx.should_return(exception)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 12), reason="TypeAlias requires Python 3.12 or higher"
|
||||||
|
)
|
||||||
|
async def test_aliased_exception(app: App):
|
||||||
|
from python_3_12.plugins.aliased_param.param_exception import aliased_exc
|
||||||
|
|
||||||
|
exception = ValueError("test")
|
||||||
|
|
||||||
|
async with app.test_dependent(aliased_exc, allow_types=[ExceptionParam]) as ctx:
|
||||||
|
ctx.pass_params(exception=exception)
|
||||||
|
ctx.should_return(exception)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_default(app: App):
|
async def test_default(app: App):
|
||||||
from plugins.param.param_default import default
|
from plugins.param.param_default import default
|
||||||
|
@@ -93,6 +93,8 @@ def test_load_json():
|
|||||||
|
|
||||||
@_recover
|
@_recover
|
||||||
def test_load_toml():
|
def test_load_toml():
|
||||||
|
nonebot.load_from_toml("./plugins.legacy.toml")
|
||||||
|
|
||||||
nonebot.load_from_toml("./plugins.toml")
|
nonebot.load_from_toml("./plugins.toml")
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Cannot find"):
|
with pytest.raises(ValueError, match="Cannot find"):
|
||||||
|
@@ -1,32 +1,53 @@
|
|||||||
---
|
|
||||||
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.59), 本插件已支持 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 |
|
||||||
|
| [Milky](https://github.com/nonebot/adapter-milky) | adapters.milky |
|
||||||
|
| [EFChat](https://github.com/molanp/nonebot_adapter_efchat) | adapters.efchat |
|
||||||
|
|
||||||
## 安装插件
|
## 安装插件
|
||||||
|
|
||||||
@@ -61,14 +82,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 ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## 使用插件
|
## 使用插件
|
||||||
@@ -128,7 +149,7 @@ async def got_location(location: str):
|
|||||||
|
|
||||||
在上面的代码中,我们使用 `Alconna` 来解析命令,`on_alconna` 用来创建响应器,使用 `Match` 来获取解析结果。
|
在上面的代码中,我们使用 `Alconna` 来解析命令,`on_alconna` 用来创建响应器,使用 `Match` 来获取解析结果。
|
||||||
|
|
||||||
关于更多 `Alconna` 的使用方法,可参考 [Alconna 文档](https://arclet.top/docs/tutorial/alconna),
|
关于更多 `Alconna` 的使用方法,可参考 [Alconna 文档](https://arclet.top/tutorial/alconna),
|
||||||
或阅读 [Alconna 基本介绍](./command.md) 一节。
|
或阅读 [Alconna 基本介绍](./command.md) 一节。
|
||||||
|
|
||||||
关于更多 `on_alconna` 的使用方法,可参考 [插件文档](https://github.com/nonebot/plugin-alconna/blob/master/docs.md),
|
关于更多 `on_alconna` 的使用方法,可参考 [插件文档](https://github.com/nonebot/plugin-alconna/blob/master/docs.md),
|
||||||
|
@@ -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,14 +282,13 @@ 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** 承载的解析结果
|
||||||
|
|
||||||
`Arparma` 有如下属性:
|
`Arparma` 有如下属性:
|
||||||
|
|
||||||
- 调试类
|
- 调试类
|
||||||
|
|
||||||
- matched: 是否匹配成功
|
- matched: 是否匹配成功
|
||||||
- error_data: 解析失败时剩余的数据
|
- error_data: 解析失败时剩余的数据
|
||||||
- error_info: 解析失败时的异常内容
|
- error_info: 解析失败时的异常内容
|
||||||
@@ -292,18 +302,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"):
|
|
||||||
group = await create_role_group(result.query[str]("add.name"))
|
@rg.assign("add")
|
||||||
if result.find("add.member"):
|
async def _(name: str, target: Match[tuple[At, ...]]):
|
||||||
ats = result.query[tuple[At, ...]]("add.member.target")
|
group = await create_role_group(name)
|
||||||
|
if target.available:
|
||||||
|
ats: tuple[At, ...] = target.result
|
||||||
group.extend(member.target for member in ats)
|
group.extend(member.target for member in ats)
|
||||||
await rg.finish("添加成功")
|
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")
|
||||||
|
```
|
@@ -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 的处理方法。
|
|
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())
|
||||||
|
```
|
229
website/docs/best-practice/htmlkit-render.md
Normal file
229
website/docs/best-practice/htmlkit-render.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 8
|
||||||
|
description: 轻量化 HTML 绘图
|
||||||
|
---
|
||||||
|
|
||||||
|
# 轻量化 HTML 绘图
|
||||||
|
|
||||||
|
图片是机器人交互中不可或缺的一部分,对于信息展示的直观性、美观性有很大的作用。
|
||||||
|
基于 PIL 直接绘制图片具有良好的性能和存储开销,但是难以调试、维护过程式的绘图代码。
|
||||||
|
使用浏览器渲染类插件可以方便地绘制网页,且能够直接通过 JS 对网页效果进行编程,但是它占用的存储和内存空间相对可观。
|
||||||
|
|
||||||
|
NoneBot 提供的 `nonebot-plugin-htmlkit` 提供了另一种基于 HTML 和 CSS 语法的轻量化绘图选择:它基于 `litehtml` 解析库,无须安装额外的依赖即可使用,没有进程间通信带来的额外开销,且在支持 `webp` `avif` 等丰富图片格式的前提下,安装用的 wheel 文件大小仅有约 10 MB。
|
||||||
|
|
||||||
|
作为粗略的性能参考,在一台 Ryzen 7 9700X 的 Windows 电脑上,渲染 [PEP 7](https://peps.python.org/pep-0007/) 的 HTML 页面(分辨率为 800x5788,大小约 1.4MB,从本地文件系统读取 CSS)大约需要 100ms,每个渲染任务内存最高占用约为 40MB.
|
||||||
|
|
||||||
|
## 安装插件
|
||||||
|
|
||||||
|
在使用前请先安装 `nonebot-plugin-htmlkit` 插件至项目环境中,可参考[获取商店插件](../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如:
|
||||||
|
|
||||||
|
在**项目目录**下执行以下命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nb plugin install nonebot-plugin-htmlkit
|
||||||
|
```
|
||||||
|
|
||||||
|
`nonebot-plugin-htmlkit` 插件目前兼容以下系统架构:
|
||||||
|
|
||||||
|
- Windows x64
|
||||||
|
- macOS arm64(M-系列芯片)
|
||||||
|
- Linux x64 (非 Alpine 等 musl 系发行版)
|
||||||
|
- Linux arm64 (非 Alpine 等 musl 系发行版)
|
||||||
|
|
||||||
|
:::caution 访问网络内容
|
||||||
|
|
||||||
|
如果需要访问网络资源(如 http(s) 网页内容),NoneBot 需要客户端型驱动器(Forward)。内置的驱动器有 `~httpx` 与 `~aiohttp`。
|
||||||
|
|
||||||
|
详见[选择驱动器](../advanced/driver.md)。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 使用插件
|
||||||
|
|
||||||
|
### 加载插件
|
||||||
|
|
||||||
|
在使用本插件前同样需要使用 `require` 方法进行**加载**并**导入**需要使用的方法,可参考 [跨插件访问](../advanced/requiring.md) 一节进行了解,如:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot import require
|
||||||
|
|
||||||
|
require("nonebot_plugin_htmlkit")
|
||||||
|
|
||||||
|
from nonebot_plugin_htmlkit import html_to_pic, md_to_pic, template_to_pic, text_to_pic
|
||||||
|
```
|
||||||
|
|
||||||
|
插件会自动使用[配置中的参数](#配置-fontconfig)初始化 `fontconfig` 以提供字体查找功能。
|
||||||
|
|
||||||
|
### 渲染 API
|
||||||
|
|
||||||
|
`nonebot-plugin-htmlkit` 主要提供以下**异步**渲染函数:
|
||||||
|
|
||||||
|
#### html_to_pic
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def html_to_pic(
|
||||||
|
html: str,
|
||||||
|
*,
|
||||||
|
base_url: str = "",
|
||||||
|
dpi: float = 144.0,
|
||||||
|
max_width: float = 800.0,
|
||||||
|
device_height: float = 600.0,
|
||||||
|
default_font_size: float = 12.0,
|
||||||
|
font_name: str = "sans-serif",
|
||||||
|
allow_refit: bool = True,
|
||||||
|
image_format: Literal["png", "jpeg"] = "png",
|
||||||
|
jpeg_quality: int = 100,
|
||||||
|
lang: str = "zh",
|
||||||
|
culture: str = "CN",
|
||||||
|
img_fetch_fn: ImgFetchFn = combined_img_fetcher,
|
||||||
|
css_fetch_fn: CSSFetchFn = combined_css_fetcher,
|
||||||
|
urljoin_fn: Callable[[str, str], str] = urllib3.parse.urljoin,
|
||||||
|
) -> bytes:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
最核心的渲染函数。
|
||||||
|
|
||||||
|
`base_url` 和 `urljoin_fn` 控制着传入 `image_fetch_fn` 和 `css_fetch_fn` 回调的 url 内容。
|
||||||
|
|
||||||
|
`allow_refit` 如果为真,渲染时会自动缩小产出图片的宽度到最适合的宽度,否则必定产出 `max_width` 宽度的图片。
|
||||||
|
|
||||||
|
`max_width` 与 `device_height` 会在 `@media` 判断中被使用。
|
||||||
|
|
||||||
|
`img_fetch_fn` 预期为一个异步可调用对象(函数),接收图片 url 并返回对应 url 的 jpeg 或 png 二进制数据(`bytes`),可在拒绝加载时返回 `None`.
|
||||||
|
|
||||||
|
`css_fetch_fn` 预期为一个异步可调用对象(函数),接收目标 CSS url 并返回对应 url 的 CSS 文本(`str`),可在拒绝加载时返回 `None`.
|
||||||
|
|
||||||
|
以下为辅助的封装函数,关键字参数若未特殊说明均与 `html_to_pic` 含义相同。
|
||||||
|
|
||||||
|
#### text_to_pic
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def text_to_pic(
|
||||||
|
text: str,
|
||||||
|
css_path: str = "",
|
||||||
|
*,
|
||||||
|
max_width: int = 500,
|
||||||
|
allow_refit: bool = True,
|
||||||
|
image_format: Literal["png", "jpeg"] = "png",
|
||||||
|
jpeg_quality: int = 100,
|
||||||
|
) -> bytes:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
可用于渲染多行文本。
|
||||||
|
|
||||||
|
`text` 会被放置于 `<div id="main" class="main-box"> <div class="text">` 中,可据此编写 CSS 来改变文本表现。
|
||||||
|
|
||||||
|
#### md_to_pic
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def md_to_pic(
|
||||||
|
md: str = "",
|
||||||
|
md_path: str = "",
|
||||||
|
css_path: str = "",
|
||||||
|
*,
|
||||||
|
max_width: int = 500,
|
||||||
|
img_fetch_fn: ImgFetchFn = combined_img_fetcher,
|
||||||
|
allow_refit: bool = True,
|
||||||
|
image_format: Literal["png", "jpeg"] = "png",
|
||||||
|
jpeg_quality: int = 100,
|
||||||
|
) -> bytes:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
可用于渲染 Markdown 文本。默认为 GitHub Markdown Light 风格,支持基于 `pygments` 的代码高亮。
|
||||||
|
|
||||||
|
`md` 和 `md_path` 二选一,前者设置时应为 Markdown 的文本,后者设置时应为指向 Markdown 文本文件的路径。
|
||||||
|
|
||||||
|
#### template_to_pic
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def template_to_pic(
|
||||||
|
template_path: str | PathLike[str] | Sequence[str | PathLike[str]],
|
||||||
|
template_name: str,
|
||||||
|
templates: Mapping[Any, Any],
|
||||||
|
filters: None | Mapping[str, Any] = None,
|
||||||
|
*,
|
||||||
|
max_width: int = 500,
|
||||||
|
device_height: int = 600,
|
||||||
|
base_url: str | None = None,
|
||||||
|
img_fetch_fn: ImgFetchFn = combined_img_fetcher,
|
||||||
|
css_fetch_fn: CSSFetchFn = combined_css_fetcher,
|
||||||
|
allow_refit: bool = True,
|
||||||
|
image_format: Literal["png", "jpeg"] = "png",
|
||||||
|
jpeg_quality: int = 100,
|
||||||
|
) -> bytes:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
渲染 jinja2 模板。
|
||||||
|
|
||||||
|
`template_path` 为 jinja2 环境的路径,`template_name` 是环境中要加载模板的名字,`templates` 为传入模板的参数,`filters` 为过滤器名 -> 自定义过滤器的映射。
|
||||||
|
|
||||||
|
### 控制外部资源获取
|
||||||
|
|
||||||
|
通过传入 `img_fetch_fn` 与 `css_fetch_fn`,我们可以在实际访问资源前进行审查,修改资源的来源,或是对 IO 操作进行缓存。
|
||||||
|
|
||||||
|
`img_fetch_fn` 预期为一个异步可调用对象(函数),接收图片 url 并返回对应 url 的 jpeg 或 png 二进制数据(`bytes`),可在拒绝加载时返回 `None`.
|
||||||
|
|
||||||
|
`css_fetch_fn` 预期为一个异步可调用对象(函数),接收目标 CSS url 并返回对应 url 的 CSS 文本(`str`),可在拒绝加载时返回 `None`.
|
||||||
|
|
||||||
|
如果你想要禁用外部资源加载/只从文件系统加载/只从网络加载,可以使用 `none_fetcher` `filesystem_***_fetcher` `network_***_fetcher`。
|
||||||
|
|
||||||
|
默认的 fetcher 行为(对于 `file://` 从文件系统加载,其余从网络加载)位于 `combined_***_fetcher`,可以通过对其封装实现缓存等操作。
|
||||||
|
|
||||||
|
## 配置项
|
||||||
|
|
||||||
|
### 配置 fontconfig
|
||||||
|
|
||||||
|
`htmlkit` 使用 `fontconfig` 查找字体,请参阅 [`fontconfig 用户手册`](https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-user) 了解环境变量的具体含义、如何通过编写配置文件修改字体配置等。
|
||||||
|
|
||||||
|
#### fontconfig_file
|
||||||
|
|
||||||
|
- **类型**: `str | None`
|
||||||
|
- **默认值**: `None`
|
||||||
|
|
||||||
|
覆盖默认的配置文件路径。
|
||||||
|
|
||||||
|
#### fontconfig_path
|
||||||
|
|
||||||
|
- **类型**: `str | None`
|
||||||
|
- **默认值**: `None`
|
||||||
|
|
||||||
|
覆盖默认的配置目录。
|
||||||
|
|
||||||
|
#### fontconfig_sysroot
|
||||||
|
|
||||||
|
- **类型**: `str | None`
|
||||||
|
- **默认值**: `None`
|
||||||
|
|
||||||
|
覆盖默认的 sysroot。
|
||||||
|
|
||||||
|
#### fc_debug
|
||||||
|
|
||||||
|
- **类型**: `str | None`
|
||||||
|
- **默认值**: `None`
|
||||||
|
|
||||||
|
设置 Fontconfig 的 debug 级别。
|
||||||
|
|
||||||
|
#### fc_dbg_match_filter
|
||||||
|
|
||||||
|
- **类型**: `str | None`
|
||||||
|
- **默认值**: `None`
|
||||||
|
|
||||||
|
当 `FC_DEBUG` 设置为 `MATCH2` 时,过滤 debug 输出。
|
||||||
|
|
||||||
|
#### fc_lang
|
||||||
|
|
||||||
|
- **类型**: `str | None`
|
||||||
|
- **默认值**: `None`
|
||||||
|
|
||||||
|
设置默认语言,否则从 `LOCALE` 环境变量获取。
|
||||||
|
|
||||||
|
#### fontconfig_use_mmap
|
||||||
|
|
||||||
|
- **类型**: `str | None`
|
||||||
|
- **默认值**: `None`
|
||||||
|
|
||||||
|
是否使用 `mmap(2)` 读取字体缓存。
|
@@ -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,13 +36,13 @@ 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)的特性来实现这一功能。例如:
|
||||||
|
|
||||||
<Tabs groupId="python">
|
<Tabs groupId="python">
|
||||||
<TabItem value="3.10" label="Python 3.10+" default>
|
<TabItem value="3.10" label="Python 3.10+" default>
|
||||||
@@ -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
|
||||||
|
@@ -155,7 +155,6 @@ async def test_example(app: App):
|
|||||||
1. `should_call_send`
|
1. `should_call_send`
|
||||||
|
|
||||||
定义事件响应器预期发送的消息,即通过[事件响应器操作 send](../../appendices/session-control.mdx#send)进行的操作。`should_call_send` 有四个参数:
|
定义事件响应器预期发送的消息,即通过[事件响应器操作 send](../../appendices/session-control.mdx#send)进行的操作。`should_call_send` 有四个参数:
|
||||||
|
|
||||||
- `event`:回复的目标事件。
|
- `event`:回复的目标事件。
|
||||||
- `message`:预期的消息对象,可以是 `str`、`Message` 或 `MessageSegment`。
|
- `message`:预期的消息对象,可以是 `str`、`Message` 或 `MessageSegment`。
|
||||||
- `result`:send 的返回值,将会返回给插件。
|
- `result`:send 的返回值,将会返回给插件。
|
||||||
@@ -163,8 +162,7 @@ async def test_example(app: App):
|
|||||||
- `**kwargs`:send 方法的额外参数。
|
- `**kwargs`:send 方法的额外参数。
|
||||||
|
|
||||||
2. `should_call_api`
|
2. `should_call_api`
|
||||||
定义事件响应器预期调用的平台 API 接口,即通过[调用平台 API](../../appendices/api-calling.mdx#调用平台-API)进行的操作。`should_call_api` 有四个参数:
|
定义事件响应器预期调用的平台 API 接口,即通过[调用平台 API](../../appendices/api-calling.mdx#调用平台-api)进行的操作。`should_call_api` 有四个参数:
|
||||||
|
|
||||||
- `api`:API 名称。
|
- `api`:API 名称。
|
||||||
- `data`:预期的请求数据。
|
- `data`:预期的请求数据。
|
||||||
- `result`:call_api 的返回值,将会返回给插件。
|
- `result`:call_api 的返回值,将会返回给插件。
|
||||||
|
@@ -5,7 +5,62 @@ description: 配置编辑器以获得最佳体验
|
|||||||
|
|
||||||
# 编辑器支持
|
# 编辑器支持
|
||||||
|
|
||||||
框架基于 [PEP484](https://www.python.org/dev/peps/pep-0484/)、[PEP 561](https://www.python.org/dev/peps/pep-0561/)、[PEP8](https://www.python.org/dev/peps/pep-0008/) 等规范进行开发并且**拥有完整类型注解**。框架使用 Pyright(Pylance)工具进行类型检查,确保代码可以被编辑器正确解析。
|
框架基于 [PEP 484](https://www.python.org/dev/peps/pep-0484/)、[PEP 561](https://www.python.org/dev/peps/pep-0561/)、[PEP 8](https://www.python.org/dev/peps/pep-0008/) 等规范进行开发并且**拥有完整类型注解**。框架使用 Pyright(Pylance)工具进行类型检查,确保代码可以被编辑器正确解析。
|
||||||
|
|
||||||
|
## CLI 脚手架提供的编辑器工具支持
|
||||||
|
|
||||||
|
在使用 NB-CLI [创建项目](./quick-start.mdx#创建项目)时,如果选择了用于插件开发的 `simple` 模板,其会根据选择的开发工具,**自动配置项目根目录下的 `.vscode/extensions.json` 文件**,以推荐最匹配的 VS Code 插件,同时自动将相应的预设配置项写入 `pyproject.toml` 作为“开箱即用”配置,从而提升开发体验。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[?] 选择一个要使用的模板: simple (插件开发者)
|
||||||
|
...
|
||||||
|
[?] 要使用哪些开发工具?
|
||||||
|
```
|
||||||
|
|
||||||
|
### 支持的开发工具
|
||||||
|
|
||||||
|
1. Pyright (Pylance)
|
||||||
|
|
||||||
|
[VS Code 插件](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) | [项目](https://github.com/microsoft/pyright) | [文档](https://microsoft.github.io/pyright/)
|
||||||
|
|
||||||
|
由微软开发的 Python 静态类型检查器和语言服务器,提供智能感知、跳转定义、查找引用、实时错误检查等强大功能。
|
||||||
|
|
||||||
|
作为 VS Code 官方推荐的 Python 语言服务器,与 Pylance 扩展配合使用,能提供最流畅、最准确的代码补全和类型推断体验,是绝大多数开发者的首选。
|
||||||
|
|
||||||
|
2. Ruff
|
||||||
|
|
||||||
|
[VS Code 插件](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) | [项目](https://github.com/astral-sh/ruff) | [文档](https://docs.astral.sh/ruff/)
|
||||||
|
|
||||||
|
一个用 Rust 编写的超快 Python 代码格式化和 lint 工具,完全兼容 `black`、`isort`、`flake8` 等主流工具的规则。
|
||||||
|
|
||||||
|
速度极快(比 `black` 和 `flake8` 快 100 倍以上),配置简单,能自动格式化代码并检测潜在错误、代码风格问题(尤其是误用同步网络请求库),是提升代码质量和开发效率的必备利器。
|
||||||
|
|
||||||
|
3. MyPy
|
||||||
|
|
||||||
|
[VS Code 插件](https://marketplace.visualstudio.com/items?itemName=matangover.mypy) | [项目](https://github.com/python/mypy) | [文档](https://mypy.readthedocs.io/en/stable/index.html)
|
||||||
|
|
||||||
|
一个官方实现的 Python 静态类型检查器,通过分析代码中的类型注解来发现类型错误。
|
||||||
|
|
||||||
|
4. BasedPyright
|
||||||
|
|
||||||
|
[VS Code 插件](https://marketplace.visualstudio.com/items?itemName=detachhead.basedpyright) | [项目](https://github.com/DetachHead/basedpyright) | [文档](https://docs.basedpyright.com/)
|
||||||
|
|
||||||
|
一个基于 Pyright 的、由社区维护的替代性 Python 语言服务器,旨在提供更优的类型检查支持与接近 Pylance 的更好的使用体验。
|
||||||
|
|
||||||
|
相较于 Pylance,BasedPyright 允许配合 VS Code 之外的其他编辑器使用,同时也复刻了部分 Pylance 限定的功能。
|
||||||
|
|
||||||
|
如果您是高级用户,希望尝试 Pylance 的替代方案,或遇到 Pylance 在特定环境下的兼容性问题,可以考虑使用 BasedPyright。
|
||||||
|
|
||||||
|
:::caution 提示
|
||||||
|
为避免 `Pylance` 和 `BasedPyright` 相互冲突导致配置混乱甚至异常,脚手架默认不允许在创建项目时同时配置这两者。
|
||||||
|
|
||||||
|
如果确实需要同时使用,请在创建项目时选择 Pylance/Pyright 并根据[相关文档](https://docs.basedpyright.com/latest/installation/ides/#vscode-vscodium)进行手动配置。
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 配置效果
|
||||||
|
|
||||||
|
选择上述工具后,NB-CLI 会在您的项目根目录下生成一个 `.vscode/extensions.json` 文件并在 `pyproject.toml` 文件中写入相应的配置项。当您在 VS Code 中打开此项目时,IDE
|
||||||
|
会自动弹出提示,建议您安装这些推荐的扩展,一键即可完成开发环境的初始化,让您可以立即开始编写代码,无需手动搜索和安装插件。
|
||||||
|
|
||||||
## 编辑器推荐配置
|
## 编辑器推荐配置
|
||||||
|
|
||||||
|
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>
|
@@ -81,8 +81,9 @@ nb create
|
|||||||
请注意,多选项使用**空格**选中或取消,**回车**确认。
|
请注意,多选项使用**空格**选中或取消,**回车**确认。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[?] 要使用哪些驱动器? FastAPI (FastAPI 驱动器)
|
|
||||||
[?] 要使用哪些适配器? Console (基于终端的交互式适配器)
|
[?] 要使用哪些适配器? Console (基于终端的交互式适配器)
|
||||||
|
[?] 要使用哪些驱动器? FastAPI (FastAPI 驱动器)
|
||||||
|
[?] 要使用什么本地存储策略? 用户全局 (默认,适用于单用户下单实例)
|
||||||
[?] 立即安装依赖? (Y/n) Yes
|
[?] 立即安装依赖? (Y/n) Yes
|
||||||
[?] 创建虚拟环境? (Y/n) Yes
|
[?] 创建虚拟环境? (Y/n) Yes
|
||||||
```
|
```
|
||||||
|
@@ -10,6 +10,9 @@ options:
|
|||||||
|
|
||||||
# 手动创建项目
|
# 手动创建项目
|
||||||
|
|
||||||
|
import Tabs from "@theme/Tabs";
|
||||||
|
import TabItem from "@theme/TabItem";
|
||||||
|
|
||||||
在[快速上手](../quick-start.mdx)中,我们已经介绍了如何安装和使用 `nb-cli` 创建一个项目。在本章节中,我们将简要介绍如何在不使用 `nb-cli` 的方式创建一个机器人项目的**最小实例**并启动。如果你想要了解 NoneBot 的启动流程,也可以阅读本章节。
|
在[快速上手](../quick-start.mdx)中,我们已经介绍了如何安装和使用 `nb-cli` 创建一个项目。在本章节中,我们将简要介绍如何在不使用 `nb-cli` 的方式创建一个机器人项目的**最小实例**并启动。如果你想要了解 NoneBot 的启动流程,也可以阅读本章节。
|
||||||
|
|
||||||
:::caution 警告
|
:::caution 警告
|
||||||
@@ -30,23 +33,51 @@ options:
|
|||||||
|
|
||||||
1. (可选)创建虚拟环境,以 venv 为例
|
1. (可选)创建虚拟环境,以 venv 为例
|
||||||
|
|
||||||
|
<Tabs groupId="platform">
|
||||||
|
<TabItem value="windows" label="Windows" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 创建虚拟环境
|
||||||
python -m venv .venv --prompt nonebot2
|
python -m venv .venv --prompt nonebot2
|
||||||
# windows
|
# 激活虚拟环境
|
||||||
.venv\Scripts\activate
|
.venv\Scripts\activate
|
||||||
# linux/macOS
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="linux/macos" label="Linux/macOS">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建虚拟环境
|
||||||
|
python -m venv .venv --prompt nonebot2
|
||||||
|
# 激活虚拟环境
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 安装 nonebot2 以及驱动器
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
2. 安装 nonebot2 以及驱动器,以 Fastapi 驱动器为例
|
||||||
|
|
||||||
|
<Tabs groupId="platform">
|
||||||
|
<TabItem value="windows" label="Windows" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install 'nonebot2[fastapi]'
|
pip install "nonebot2[fastapi]"
|
||||||
```
|
```
|
||||||
|
|
||||||
驱动器包名可以在 [驱动器商店](/store/drivers) 中找到。
|
</TabItem>
|
||||||
|
<TabItem value="linux/macos" label="Linux/macOS">
|
||||||
|
|
||||||
3. 安装适配器
|
```bash
|
||||||
|
pip install "nonebot2[fastapi]"
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
驱动器包名可以在 [驱动器商店](/store/drivers) 中找到,请替换上文方括号中的内容。
|
||||||
|
|
||||||
|
3. 安装适配器,以 Console 适配器为例
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install nonebot-adapter-console
|
pip install nonebot-adapter-console
|
||||||
@@ -58,7 +89,7 @@ options:
|
|||||||
|
|
||||||
配置文件用于存放 NoneBot 运行所需要的配置项,使用 [`pydantic`](https://docs.pydantic.dev/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。配置项需符合 dotenv 格式,复杂类型数据需使用 JSON 格式填写。具体可选配置方式以及配置项详情参考[配置](../appendices/config.mdx)。
|
配置文件用于存放 NoneBot 运行所需要的配置项,使用 [`pydantic`](https://docs.pydantic.dev/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。配置项需符合 dotenv 格式,复杂类型数据需使用 JSON 格式填写。具体可选配置方式以及配置项详情参考[配置](../appendices/config.mdx)。
|
||||||
|
|
||||||
在**项目文件夹**中创建一个 `.env` 文本文件,并写入以下内容:
|
在**项目文件夹**中创建一个名为 `.env` 的文件,并写入以下内容:
|
||||||
|
|
||||||
```bash title=.env
|
```bash title=.env
|
||||||
HOST=0.0.0.0 # 配置 NoneBot 监听的 IP / 主机名
|
HOST=0.0.0.0 # 配置 NoneBot 监听的 IP / 主机名
|
||||||
@@ -101,10 +132,29 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
## 运行机器人
|
## 运行机器人
|
||||||
|
|
||||||
在**项目文件夹**中,使用配置好环境的 Python 解释器运行入口文件(如果使用虚拟环境,请先激活虚拟环境):
|
在**项目文件夹**中,使用配置好环境的 Python 解释器运行入口文件:
|
||||||
|
|
||||||
|
<Tabs groupId="platform">
|
||||||
|
<TabItem value="windows" label="Windows" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 激活虚拟环境(未使用虚拟环境时跳过此行)
|
||||||
|
.venv\Scripts\activate
|
||||||
|
# 运行机器人
|
||||||
python bot.py
|
python bot.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="linux/macos" label="Linux/macOS">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 激活虚拟环境(未使用虚拟环境时跳过此行)
|
||||||
|
source .venv/bin/activate
|
||||||
|
# 运行机器人
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
如果你后续使用了 `nb-cli` ,你仍可以使用 `nb run` 命令来运行机器人,`nb-cli` 会自动检测入口文件 `bot.py` 是否存在并运行。同时,你也可以使用 `nb run --reload` 来自动检测代码的更改并自动重新运行入口文件。
|
如果你后续使用了 `nb-cli` ,你仍可以使用 `nb run` 命令来运行机器人,`nb-cli` 会自动检测入口文件 `bot.py` 是否存在并运行。同时,你也可以使用 `nb run --reload` 来自动检测代码的更改并自动重新运行入口文件。
|
@@ -64,7 +64,7 @@ options:
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
:::caution 注意
|
:::caution 注意
|
||||||
如果在之前的[创建项目](./application.md)章节中手动创建了相关文件,那么你需要做出如下修改:
|
如果在之前的[创建项目](./application.mdx)章节中手动创建了相关文件,那么你需要做出如下修改:
|
||||||
|
|
||||||
1. 在项目目录中创建一个两层文件夹 `awesome_bot/plugins`
|
1. 在项目目录中创建一个两层文件夹 `awesome_bot/plugins`
|
||||||
|
|
||||||
@@ -189,12 +189,16 @@ nonebot.load_from_json("plugin_config.json", encoding="utf-8")
|
|||||||
|
|
||||||
### `load_from_toml`
|
### `load_from_toml`
|
||||||
|
|
||||||
通过 TOML 文件加载插件,是 [`load_all_plugins`](#load_all_plugins) 的 TOML 变种。通过读取 TOML 文件中的 `[tool.nonebot]` Table 中的 `plugins` 和 `plugin_dirs` Array 进行加载。例如:
|
通过 TOML 文件加载插件,是 [`load_all_plugins`](#load_all_plugins) 的 TOML 变种。通过读取 TOML 文件中的 `[tool.nonebot]` Table 中的 `plugin_dirs` Array 与
|
||||||
|
`[tool.nonebot.plugins]` Table 中的多个 Array 进行加载。例如:
|
||||||
|
|
||||||
```toml title=plugin_config.toml
|
```toml title=plugin_config.toml
|
||||||
[tool.nonebot]
|
[tool.nonebot]
|
||||||
plugins = ["path.to.your.plugin"]
|
|
||||||
plugin_dirs = ["path/to/your/plugins"]
|
plugin_dirs = ["path/to/your/plugins"]
|
||||||
|
|
||||||
|
[tool.nonebot.plugins]
|
||||||
|
"@local" = ["path.to.your.plugin"] # 本地插件等非插件商店来源的插件
|
||||||
|
"nonebot-plugin-someplugin" = ["nonebot_plugin_someplugin"] # 插件商店来源的插件
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@@ -24,6 +24,9 @@ NoneBot 提供了一个[商店](/store/plugins),商店内容均由社区开发
|
|||||||
|
|
||||||
商店中每个内容的卡片都包含了其名称和简介等信息,点击**卡片右上角**链接图标即可跳转到其主页。
|
商店中每个内容的卡片都包含了其名称和简介等信息,点击**卡片右上角**链接图标即可跳转到其主页。
|
||||||
|
|
||||||
|
与此同时,NB-CLI 也提供了一个 TUI 版本的商店界面,可通过 `nb adapter store`、`nb plugin store`、`nb driver store` 命令或 CLI
|
||||||
|
交互式界面进入。其提供了接近网页商店的体验,同时允许快捷安装到当前项目。
|
||||||
|
|
||||||
## 安装插件
|
## 安装插件
|
||||||
|
|
||||||
<Asciinema
|
<Asciinema
|
||||||
|
@@ -59,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" },
|
||||||
@@ -300,6 +300,7 @@ export default async function createConfigAsync() {
|
|||||||
|
|
||||||
future: {
|
future: {
|
||||||
experimental_faster: true,
|
experimental_faster: true,
|
||||||
|
v4: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@@ -5,6 +5,187 @@ toc_max_heading_level: 2
|
|||||||
|
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
## 最近更新
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: 更新 NB-CLI 新版插件加载格式与文档 [@NCBM](https://github.com/NCBM) ([#3614](https://github.com/nonebot/nonebot2/pull/3614))
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Feature: 更新 NB-CLI 新版插件加载格式与文档 [@NCBM](https://github.com/NCBM) ([#3614](https://github.com/nonebot/nonebot2/pull/3614))
|
||||||
|
- Docs: 添加 htmlkit 文档至最佳实践 [@BlueGlassBlock](https://github.com/BlueGlassBlock) ([#3682](https://github.com/nonebot/nonebot2/pull/3682))
|
||||||
|
- Docs: 修复 userinfo 插件链接 [@XieXiLin2](https://github.com/XieXiLin2) ([#3660](https://github.com/nonebot/nonebot2/pull/3660))
|
||||||
|
- Docs: 升级 docusaurus 3.8.1 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3649](https://github.com/nonebot/nonebot2/pull/3649))
|
||||||
|
- Docs: 更新文档《手动创建项目》 [@Chen-Luan](https://github.com/Chen-Luan) ([#3623](https://github.com/nonebot/nonebot2/pull/3623))
|
||||||
|
- Docs: 增加 B站直播间 适配器说明 [@MingxuanGame](https://github.com/MingxuanGame) ([#3636](https://github.com/nonebot/nonebot2/pull/3636))
|
||||||
|
- Docs: 增加 VoceChat 适配器说明 [@5656565566](https://github.com/5656565566) ([#3627](https://github.com/nonebot/nonebot2/pull/3627))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- CI: 升级文档构建 node 版本 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3668](https://github.com/nonebot/nonebot2/pull/3668))
|
||||||
|
- CI: 测试矩阵加入 Python 3.13 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3605](https://github.com/nonebot/nonebot2/pull/3605))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: 怪物猎人集会码插件 [@noneflow](https://github.com/noneflow) ([#3684](https://github.com/nonebot/nonebot2/pull/3684))
|
||||||
|
- Plugin: nonebot-plugin-htmlkit [@noneflow](https://github.com/noneflow) ([#3695](https://github.com/nonebot/nonebot2/pull/3695))
|
||||||
|
- Plugin: 言令 [@noneflow](https://github.com/noneflow) ([#3675](https://github.com/nonebot/nonebot2/pull/3675))
|
||||||
|
- Plugin: 算法比赛助手 [@noneflow](https://github.com/noneflow) ([#3672](https://github.com/nonebot/nonebot2/pull/3672))
|
||||||
|
- Plugin: 复盘打卡 [@noneflow](https://github.com/noneflow) ([#3681](https://github.com/nonebot/nonebot2/pull/3681))
|
||||||
|
- Plugin: DMP 饥荒管理平台机器人 [@noneflow](https://github.com/noneflow) ([#3616](https://github.com/nonebot/nonebot2/pull/3616))
|
||||||
|
- Plugin: 谁是卧底小游戏 [@noneflow](https://github.com/noneflow) ([#3629](https://github.com/nonebot/nonebot2/pull/3629))
|
||||||
|
- Plugin: 夸克自动转存 [@noneflow](https://github.com/noneflow) ([#3671](https://github.com/nonebot/nonebot2/pull/3671))
|
||||||
|
- Plugin: 禁止复读 [@noneflow](https://github.com/noneflow) ([#3644](https://github.com/nonebot/nonebot2/pull/3644))
|
||||||
|
- Plugin: 蔚蓝档案今日运势 [@noneflow](https://github.com/noneflow) ([#3653](https://github.com/nonebot/nonebot2/pull/3653))
|
||||||
|
- Plugin: 分布式黑名单插件 [@noneflow](https://github.com/noneflow) ([#3655](https://github.com/nonebot/nonebot2/pull/3655))
|
||||||
|
- Plugin: 图片手办化 [@noneflow](https://github.com/noneflow) ([#3662](https://github.com/nonebot/nonebot2/pull/3662))
|
||||||
|
- Plugin: Akash Image Generator [@noneflow](https://github.com/noneflow) ([#3651](https://github.com/nonebot/nonebot2/pull/3651))
|
||||||
|
- Plugin: 让我看看!! [@noneflow](https://github.com/noneflow) ([#3648](https://github.com/nonebot/nonebot2/pull/3648))
|
||||||
|
- Plugin: ImageLibrary [@noneflow](https://github.com/noneflow) ([#3620](https://github.com/nonebot/nonebot2/pull/3620))
|
||||||
|
- Plugin: 抽象 [@noneflow](https://github.com/noneflow) ([#3638](https://github.com/nonebot/nonebot2/pull/3638))
|
||||||
|
- Plugin: 卖若插件 [@noneflow](https://github.com/noneflow) ([#3631](https://github.com/nonebot/nonebot2/pull/3631))
|
||||||
|
- Plugin: HuaEr聊天bot [@noneflow](https://github.com/noneflow) ([#3564](https://github.com/nonebot/nonebot2/pull/3564))
|
||||||
|
- Plugin: Remove nonebot_plugin_cnrail [@noneflow](https://github.com/noneflow) ([#3645](https://github.com/nonebot/nonebot2/pull/3645))
|
||||||
|
- Plugin: Remove nonebot_plugin_pingti [@noneflow](https://github.com/noneflow) ([#3646](https://github.com/nonebot/nonebot2/pull/3646))
|
||||||
|
- Plugin: Anipusher推送机 [@noneflow](https://github.com/noneflow) ([#3582](https://github.com/nonebot/nonebot2/pull/3582))
|
||||||
|
- Plugin: nonebot-plugin-simple-setu [@noneflow](https://github.com/noneflow) ([#3594](https://github.com/nonebot/nonebot2/pull/3594))
|
||||||
|
- Plugin: Alisten [@noneflow](https://github.com/noneflow) ([#3635](https://github.com/nonebot/nonebot2/pull/3635))
|
||||||
|
- Plugin: MC玩家皮肤渲染 [@noneflow](https://github.com/noneflow) ([#3613](https://github.com/nonebot/nonebot2/pull/3613))
|
||||||
|
- Plugin: EconomyValue [@noneflow](https://github.com/noneflow) ([#3566](https://github.com/nonebot/nonebot2/pull/3566))
|
||||||
|
|
||||||
|
### 🍻 机器人发布
|
||||||
|
|
||||||
|
- Bot: Amrita [@noneflow](https://github.com/noneflow) ([#3641](https://github.com/nonebot/nonebot2/pull/3641))
|
||||||
|
|
||||||
|
### 🍻 适配器发布
|
||||||
|
|
||||||
|
- Adapter: B站直播间 [@noneflow](https://github.com/noneflow) ([#3592](https://github.com/nonebot/nonebot2/pull/3592))
|
||||||
|
- Adapter: nonebot-adapter-vocechat [@noneflow](https://github.com/noneflow) ([#3536](https://github.com/nonebot/nonebot2/pull/3536))
|
||||||
|
|
||||||
|
## v2.4.3
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: 支持 PEP 695 类型别名 [@yanyongyu](https://github.com/yanyongyu) ([#3621](https://github.com/nonebot/nonebot2/pull/3621))
|
||||||
|
- Feature: 升级至新版本 websockets client API [@yanyongyu](https://github.com/yanyongyu) ([#3606](https://github.com/nonebot/nonebot2/pull/3606))
|
||||||
|
- Feature: 细化内置驱动器请求参数中的超时控制颗粒度 [@Ailitonia](https://github.com/Ailitonia) ([#3571](https://github.com/nonebot/nonebot2/pull/3571))
|
||||||
|
- Feature: 为 `HTTPClient` 等内置驱动器添加流式请求方法 [@Ailitonia](https://github.com/Ailitonia) ([#3560](https://github.com/nonebot/nonebot2/pull/3560))
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Docs: 更新 Alconna 主页面 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#3598](https://github.com/nonebot/nonebot2/pull/3598))
|
||||||
|
- Docs: 添加插件 metadata 缺失的 docstring [@yanyongyu](https://github.com/yanyongyu) ([#3583](https://github.com/nonebot/nonebot2/pull/3583))
|
||||||
|
- Docs: 修复组织成员提交 issue 时不遵守表单 [@ProgramRipper](https://github.com/ProgramRipper) ([#3558](https://github.com/nonebot/nonebot2/pull/3558))
|
||||||
|
- Docs: 增加 `EFChat` 适配器说明 [@molanp](https://github.com/molanp) ([#3544](https://github.com/nonebot/nonebot2/pull/3544))
|
||||||
|
- Docs: 插件发布表单描述优化 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3520](https://github.com/nonebot/nonebot2/pull/3520))
|
||||||
|
- 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))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Dev: 迁移使用 uv 管理项目依赖 [@yanyongyu](https://github.com/yanyongyu) ([#3607](https://github.com/nonebot/nonebot2/pull/3607))
|
||||||
|
- Fix: `RUF005` tuple 拼接 [@Ailitonia](https://github.com/Ailitonia) ([#3572](https://github.com/nonebot/nonebot2/pull/3572))
|
||||||
|
- Plugin: 更新插件维护情况 [@Agnes4m](https://github.com/Agnes4m) ([#3555](https://github.com/nonebot/nonebot2/pull/3555))
|
||||||
|
- Plugin: 修改 nailong 插件作者 [@superbot-ai445](https://github.com/superbot-ai445) ([#3554](https://github.com/nonebot/nonebot2/pull/3554))
|
||||||
|
- CI: 适配 NoneFlow 4.4.0 [@he0119](https://github.com/he0119) ([#3539](https://github.com/nonebot/nonebot2/pull/3539))
|
||||||
|
- 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: FinalShell 离线激活码 [@noneflow](https://github.com/noneflow) ([#3574](https://github.com/nonebot/nonebot2/pull/3574))
|
||||||
|
- Plugin: 天气查询 [@noneflow](https://github.com/noneflow) ([#3596](https://github.com/nonebot/nonebot2/pull/3596))
|
||||||
|
- Plugin: LazyTea命令拓展 [@noneflow](https://github.com/noneflow) ([#3604](https://github.com/nonebot/nonebot2/pull/3604))
|
||||||
|
- Plugin: AWS Manager [@noneflow](https://github.com/noneflow) ([#3550](https://github.com/nonebot/nonebot2/pull/3550))
|
||||||
|
- Plugin: 三角洲助手 [@noneflow](https://github.com/noneflow) ([#3590](https://github.com/nonebot/nonebot2/pull/3590))
|
||||||
|
- Plugin: 猜猜病 [@noneflow](https://github.com/noneflow) ([#3585](https://github.com/nonebot/nonebot2/pull/3585))
|
||||||
|
- Plugin: LLM-Helper [@noneflow](https://github.com/noneflow) ([#3569](https://github.com/nonebot/nonebot2/pull/3569))
|
||||||
|
- Plugin: 更好的电子钓鱼 [@noneflow](https://github.com/noneflow) ([#3576](https://github.com/nonebot/nonebot2/pull/3576))
|
||||||
|
- Plugin: nonebot-plugin-flomic [@noneflow](https://github.com/noneflow) ([#3529](https://github.com/nonebot/nonebot2/pull/3529))
|
||||||
|
- Plugin: LazyTea [@noneflow](https://github.com/noneflow) ([#3548](https://github.com/nonebot/nonebot2/pull/3548))
|
||||||
|
- Plugin: LitePerm 权限管理插件 [@noneflow](https://github.com/noneflow) ([#3562](https://github.com/nonebot/nonebot2/pull/3562))
|
||||||
|
- Plugin: 三角洲鼠鼠偷吃模拟器 [@noneflow](https://github.com/noneflow) ([#3541](https://github.com/nonebot/nonebot2/pull/3541))
|
||||||
|
- Plugin: 维维表情包搜索器 [@noneflow](https://github.com/noneflow) ([#3546](https://github.com/nonebot/nonebot2/pull/3546))
|
||||||
|
- Plugin: 喜(悲)报生成器 [@noneflow](https://github.com/noneflow) ([#3527](https://github.com/nonebot/nonebot2/pull/3527))
|
||||||
|
- Plugin: 依赖注入扩展 [@noneflow](https://github.com/noneflow) ([#3552](https://github.com/nonebot/nonebot2/pull/3552))
|
||||||
|
- Plugin: nonebot-plugin-orm [@noneflow](https://github.com/noneflow) ([#3557](https://github.com/nonebot/nonebot2/pull/3557))
|
||||||
|
- Plugin: 币安小助手 [@noneflow](https://github.com/noneflow) ([#3525](https://github.com/nonebot/nonebot2/pull/3525))
|
||||||
|
- Plugin: nonebot_plugin_sunset_reminder [@noneflow](https://github.com/noneflow) ([#3538](https://github.com/nonebot/nonebot2/pull/3538))
|
||||||
|
- Plugin: nonebot-plugin-NobleDuel [@noneflow](https://github.com/noneflow) ([#3521](https://github.com/nonebot/nonebot2/pull/3521))
|
||||||
|
- 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: nsybot [@noneflow](https://github.com/noneflow) ([#3610](https://github.com/nonebot/nonebot2/pull/3610))
|
||||||
|
- 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: nonebot-adapter-efchat [@noneflow](https://github.com/noneflow) ([#3496](https://github.com/nonebot/nonebot2/pull/3496))
|
||||||
|
- 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
|
## v2.4.2
|
||||||
|
|
||||||
### 🚀 新功能
|
### 🚀 新功能
|
||||||
|
@@ -35,9 +35,6 @@ export default function AdapterForm(): React.ReactNode {
|
|||||||
const handleSubmit = (result: Record<string, string>) => {
|
const handleSubmit = (result: Record<string, string>) => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({
|
`https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({
|
||||||
assignees: "",
|
|
||||||
labels: "Adapter",
|
|
||||||
projects: "",
|
|
||||||
template: "adapter_publish.yml",
|
template: "adapter_publish.yml",
|
||||||
title: `Adapter: ${result.name}`,
|
title: `Adapter: ${result.name}`,
|
||||||
...result,
|
...result,
|
||||||
|
@@ -29,9 +29,6 @@ export default function BotForm(): React.ReactNode {
|
|||||||
const handleSubmit = (result: Record<string, string>) => {
|
const handleSubmit = (result: Record<string, string>) => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({
|
`https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({
|
||||||
assignees: "",
|
|
||||||
labels: "Bot",
|
|
||||||
projects: "",
|
|
||||||
template: "bot_publish.yml",
|
template: "bot_publish.yml",
|
||||||
title: `Bot: ${result.name}`,
|
title: `Bot: ${result.name}`,
|
||||||
...result,
|
...result,
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import Link from "@docusaurus/Link";
|
||||||
|
|
||||||
import { Form } from ".";
|
import { Form } from ".";
|
||||||
|
|
||||||
export default function PluginForm(): React.ReactNode {
|
export default function PluginForm(): React.ReactNode {
|
||||||
@@ -8,7 +10,7 @@ export default function PluginForm(): React.ReactNode {
|
|||||||
name: "包信息",
|
name: "包信息",
|
||||||
items: [
|
items: [
|
||||||
{ type: "text", name: "pypi", labelText: "PyPI 项目名" },
|
{ type: "text", name: "pypi", labelText: "PyPI 项目名" },
|
||||||
{ type: "text", name: "module", labelText: "插件 import 包名" },
|
{ type: "text", name: "module", labelText: "插件模块名" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -19,9 +21,6 @@ export default function PluginForm(): React.ReactNode {
|
|||||||
const handleSubmit = (result: Record<string, string>) => {
|
const handleSubmit = (result: Record<string, string>) => {
|
||||||
window.open(
|
window.open(
|
||||||
`https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({
|
`https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({
|
||||||
assignees: "",
|
|
||||||
labels: "Plugin",
|
|
||||||
projects: "",
|
|
||||||
template: "plugin_publish.yml",
|
template: "plugin_publish.yml",
|
||||||
title: `Plugin: ${result.pypi}`,
|
title: `Plugin: ${result.pypi}`,
|
||||||
...result,
|
...result,
|
||||||
@@ -29,7 +28,25 @@ export default function PluginForm(): React.ReactNode {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const description = (
|
||||||
|
<p>
|
||||||
|
请在发布前阅读{" "}
|
||||||
|
<Link
|
||||||
|
className="text-primary"
|
||||||
|
href="https://nonebot.dev/docs/developer/plugin-publishing"
|
||||||
|
>
|
||||||
|
NoneBot 插件发布流程指导
|
||||||
|
</Link>
|
||||||
|
,并确保满足其中所述条件。
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form type="plugin" formItems={formItems} handleSubmit={handleSubmit} />
|
<Form
|
||||||
|
type="plugin"
|
||||||
|
formItems={formItems}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
description={description}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ export type FormItemGroup = {
|
|||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
description?: React.ReactNode;
|
||||||
type: Resource["resourceType"];
|
type: Resource["resourceType"];
|
||||||
formItems: FormItemGroup[];
|
formItems: FormItemGroup[];
|
||||||
handleSubmit: (result: Record<string, string>) => void;
|
handleSubmit: (result: Record<string, string>) => void;
|
||||||
@@ -32,6 +33,7 @@ export type Props = {
|
|||||||
export function Form({
|
export function Form({
|
||||||
type,
|
type,
|
||||||
children,
|
children,
|
||||||
|
description,
|
||||||
formItems,
|
formItems,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
}: Props): React.ReactNode {
|
}: Props): React.ReactNode {
|
||||||
@@ -65,7 +67,6 @@ export function Form({
|
|||||||
);
|
);
|
||||||
if (currentStepNames.every((name) => result[name])) {
|
if (currentStepNames.every((name) => result[name])) {
|
||||||
setCurrentStep(currentStep + 1);
|
setCurrentStep(currentStep + 1);
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onPrev = () => currentStep > 0 && setCurrentStep(currentStep - 1);
|
const onPrev = () => currentStep > 0 && setCurrentStep(currentStep - 1);
|
||||||
@@ -86,6 +87,9 @@ export function Form({
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
{description && currentStep === 0 && (
|
||||||
|
<div className="form-description">{description}</div>
|
||||||
|
)}
|
||||||
<div className="form-control w-full min-h-[300px]">
|
<div className="form-control w-full min-h-[300px]">
|
||||||
{children ||
|
{children ||
|
||||||
formItems[currentStep].items.map((item) => (
|
formItems[currentStep].items.map((item) => (
|
||||||
|
@@ -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";
|
||||||
|
|
||||||
@@ -28,7 +29,9 @@ function StorePage({ title, children }: Props): React.ReactNode {
|
|||||||
<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>
|
||||||
|
@@ -74,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
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user