mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 11:16:43 +00:00
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d1601bf2fe | ||
|
2994945c64 | ||
|
c9e3cad738 | ||
|
7c36964812 | ||
|
0e02d13c67 | ||
|
f7aeea2f3d | ||
|
b2da7d4cae | ||
|
239f9769c2 | ||
|
f5947518b1 | ||
|
1a4afa406b | ||
|
412b879f39 | ||
|
a830346545 | ||
|
fbb8320a25 | ||
|
14f4a0f701 | ||
|
e82e2817d5 | ||
|
ffbd1f9aeb | ||
|
5ab418a3cf | ||
|
a58e00b206 | ||
|
a74682bbf6 | ||
|
11142253fb | ||
|
ef7782167f | ||
|
f4a2682e6c | ||
|
35cee22cf6 | ||
|
fbb55228f2 | ||
|
391ac00d81 | ||
|
277b744ca3 | ||
|
a89c67a50e | ||
|
26b30a7b22 | ||
|
4dae23d3bb | ||
|
07e6c3f977 | ||
|
dace63d9d2 | ||
|
2ebf956599 | ||
|
b20793c67a | ||
|
47e9f59cc8 | ||
|
e27cac7fef | ||
|
5bfda6e2bc | ||
|
ef2ab7df48 | ||
|
ac1d9147d2 | ||
|
f2350909d2 | ||
|
f14ef93808 | ||
|
45bd4252bf | ||
|
6b4456bf0e | ||
|
c5e114dc7f | ||
|
30ceea4287 | ||
|
380f9ff013 | ||
|
19ac119714 | ||
|
236f70183c | ||
|
117bc35653 | ||
|
4fcaa8d3d6 | ||
|
536889d3df | ||
|
bbd13c04cc | ||
|
82e4ccb227 | ||
|
626cfa474f | ||
|
18e9a9afd3 | ||
|
41b7d5a3a0 | ||
|
16fcd4c639 | ||
|
ef3641efa6 | ||
|
8d95a32672 | ||
|
3a3a718779 | ||
|
3d1955211a | ||
|
8d87715d6f | ||
|
3c535b8e99 | ||
|
2c6affecea | ||
|
c2d2169a9f | ||
|
1153c5ff17 | ||
|
6c532f5926 | ||
|
7083394bc9 | ||
|
7c58410868 | ||
|
00c3e3b713 | ||
|
9d4a72766d | ||
|
82e16b4438 | ||
|
56353f2d0a | ||
|
4d0eb94a6f | ||
|
e1a494ecbd | ||
|
6b1e34da63 | ||
|
ccf9597102 | ||
|
5a6f4b9e1c | ||
|
9b09b42f97 | ||
|
854345e16f | ||
|
e0ee865b87 | ||
|
dad0c01335 | ||
|
79ef5af19b | ||
|
b349959f93 | ||
|
2e7f9612af | ||
|
8ff2303b22 | ||
|
b681fdd6d6 | ||
|
b65b3b438c | ||
|
580d6bab36 | ||
|
90349ddd7d | ||
|
dcac421bc0 | ||
|
b4f643577f | ||
|
411e7168b3 | ||
|
fef072a62a | ||
|
f529e9cb23 | ||
|
cfa3bfd88c | ||
|
321c99f12b | ||
|
73ad4992ee | ||
|
ddbf37c1be |
@@ -4,7 +4,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
|
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
|
||||||
},
|
},
|
||||||
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && yarn install",
|
"postCreateCommand": "./scripts/setup-envs.sh",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"settings": {
|
"settings": {
|
||||||
|
19
.github/actions/setup-python/action.yml
vendored
19
.github/actions/setup-python/action.yml
vendored
@@ -6,6 +6,14 @@ inputs:
|
|||||||
description: Python version
|
description: Python version
|
||||||
required: false
|
required: false
|
||||||
default: "3.10"
|
default: "3.10"
|
||||||
|
env-dir:
|
||||||
|
description: Environment directory
|
||||||
|
required: false
|
||||||
|
default: "."
|
||||||
|
no-root:
|
||||||
|
description: Do not install package in the environment
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
@@ -19,6 +27,15 @@ runs:
|
|||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
architecture: "x64"
|
architecture: "x64"
|
||||||
cache: "poetry"
|
cache: "poetry"
|
||||||
|
cache-dependency-path: |
|
||||||
|
./poetry.lock
|
||||||
|
${{ inputs.env-dir }}/poetry.lock
|
||||||
|
|
||||||
- run: poetry install -E all
|
- run: |
|
||||||
|
cd ${{ inputs.env-dir }}
|
||||||
|
if [ "${{ inputs.no-root }}" = "true" ]; then
|
||||||
|
poetry install --all-extras --no-root
|
||||||
|
else
|
||||||
|
poetry install --all-extras
|
||||||
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
|
17
.github/workflows/codecov.yml
vendored
17
.github/workflows/codecov.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- "envs/**"
|
||||||
- "nonebot/**"
|
- "nonebot/**"
|
||||||
- "packages/**"
|
- "packages/**"
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
@@ -19,16 +20,18 @@ jobs:
|
|||||||
name: Test Coverage
|
name: Test Coverage
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
concurrency:
|
concurrency:
|
||||||
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}
|
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.env }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
env: [pydantic-v1, pydantic-v2]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
PYDANTIC_VERSION: ${{ matrix.env }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -37,15 +40,19 @@ jobs:
|
|||||||
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 }}
|
||||||
|
no-root: true
|
||||||
|
|
||||||
- name: Run Pytest
|
- name: Run Pytest
|
||||||
run: |
|
run: |
|
||||||
cd tests/
|
cd ./envs/${{ matrix.env }}
|
||||||
poetry run pytest -n auto --cov-report xml
|
poetry run bash "../../scripts/run-tests.sh"
|
||||||
|
|
||||||
- name: Upload coverage report
|
- name: Upload coverage report
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
env_vars: OS,PYTHON_VERSION
|
env_vars: OS,PYTHON_VERSION,PYDANTIC_VERSION
|
||||||
files: ./tests/coverage.xml
|
files: ./tests/coverage.xml
|
||||||
flags: unittests
|
flags: unittests
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
2
.github/workflows/noneflow.yml
vendored
2
.github/workflows/noneflow.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
|||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Cache pre-commit hooks
|
- name: Cache pre-commit hooks
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: .cache/.pre-commit
|
path: .cache/.pre-commit
|
||||||
key: noneflow-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
key: noneflow-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
|
25
.github/workflows/pyright.yml
vendored
25
.github/workflows/pyright.yml
vendored
@@ -6,21 +6,42 @@ on:
|
|||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- "envs/**"
|
||||||
- "nonebot/**"
|
- "nonebot/**"
|
||||||
- "packages/**"
|
- "packages/**"
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
|
- ".github/actions/setup-python/**"
|
||||||
|
- ".github/workflows/pyright.yml"
|
||||||
|
- "pyproject.toml"
|
||||||
|
- "poetry.lock"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pyright:
|
pyright:
|
||||||
name: Pyright Lint
|
name: Pyright Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: pyright-${{ github.ref }}-${{ matrix.env }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
env: [pydantic-v1, pydantic-v2]
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Python environment
|
- name: Setup Python environment
|
||||||
uses: ./.github/actions/setup-python
|
uses: ./.github/actions/setup-python
|
||||||
|
with:
|
||||||
|
env-dir: ./envs/${{ matrix.env }}
|
||||||
|
no-root: true
|
||||||
|
|
||||||
- run: echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
|
- run: |
|
||||||
|
(cd ./envs/${{ matrix.env }} && echo "$(poetry env info --path)/bin" >> $GITHUB_PATH)
|
||||||
|
if [ "${{ matrix.env }}" = "pydantic-v1" ]; then
|
||||||
|
sed -i 's/PYDANTIC_V2 = true/PYDANTIC_V2 = false/g' ./pyproject.toml
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Run Pyright
|
- name: Run Pyright
|
||||||
uses: jakebailey/pyright-action@v1
|
uses: jakebailey/pyright-action@v2
|
||||||
|
4
.github/workflows/release-drafter.yml
vendored
4
.github/workflows/release-drafter.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
- name: Setup Node Environment
|
- name: Setup Node Environment
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
|
|
||||||
- uses: release-drafter/release-drafter@v5
|
- uses: release-drafter/release-drafter@v6
|
||||||
id: release-drafter
|
id: release-drafter
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
|
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
|
||||||
run: exit 1
|
run: exit 1
|
||||||
|
|
||||||
- uses: release-drafter/release-drafter@v5
|
- uses: release-drafter/release-drafter@v6
|
||||||
with:
|
with:
|
||||||
name: Release ${{ steps.version.outputs.TAG_NAME }} 🌈
|
name: Release ${{ steps.version.outputs.TAG_NAME }} 🌈
|
||||||
tag: ${{ steps.version.outputs.TAG_NAME }}
|
tag: ${{ steps.version.outputs.TAG_NAME }}
|
||||||
|
9
.github/workflows/ruff.yml
vendored
9
.github/workflows/ruff.yml
vendored
@@ -6,14 +6,23 @@ on:
|
|||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- "envs/**"
|
||||||
- "nonebot/**"
|
- "nonebot/**"
|
||||||
- "packages/**"
|
- "packages/**"
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
|
- ".github/actions/setup-python/**"
|
||||||
|
- ".github/workflows/ruff.yml"
|
||||||
|
- "pyproject.toml"
|
||||||
|
- "poetry.lock"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ruff:
|
ruff:
|
||||||
name: Ruff Lint
|
name: Ruff Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: pyright-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
@@ -7,26 +7,26 @@ 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.1.6
|
rev: v0.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
stages: [commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 5.12.0
|
rev: 5.13.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
stages: [commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.11.0
|
rev: 24.1.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
stages: [commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v3.0.3
|
rev: v4.0.0-alpha.8
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
||||||
|
14
.prettierrc
14
.prettierrc
@@ -5,5 +5,17 @@
|
|||||||
"arrowParens": "always",
|
"arrowParens": "always",
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"semi": true
|
"semi": true,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/devcontainer.json",
|
||||||
|
"**/tsconfig.json",
|
||||||
|
"**/tsconfig.*.json"
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"parser": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
26
CITATION.cff
Normal file
26
CITATION.cff
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# This CITATION.cff file was generated with cffinit.
|
||||||
|
# Visit https://bit.ly/cffinit to generate yours today!
|
||||||
|
|
||||||
|
cff-version: 1.2.0
|
||||||
|
title: NoneBot
|
||||||
|
message: >-
|
||||||
|
If you use this software, please cite it using the
|
||||||
|
metadata from this file.
|
||||||
|
type: software
|
||||||
|
authors:
|
||||||
|
- given-names: Yongyu
|
||||||
|
family-names: Yan
|
||||||
|
email: yyy@nonebot.dev
|
||||||
|
- name: NoneBot Team
|
||||||
|
email: contact@nonebot.dev
|
||||||
|
website: 'https://github.com/nonebot'
|
||||||
|
repository-code: 'https://github.com/nonebot/nonebot2'
|
||||||
|
url: 'https://nonebot.dev/'
|
||||||
|
abstract: >-
|
||||||
|
NoneBot, an asynchronous multi-platform chatbot framework
|
||||||
|
written in Python
|
||||||
|
keywords:
|
||||||
|
- nonebot
|
||||||
|
- chatbot
|
||||||
|
- pydantic
|
||||||
|
license: MIT
|
48
README.md
48
README.md
@@ -94,7 +94,7 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://asciinema.org/a/569440">
|
<a href="https://asciinema.org/a/569440">
|
||||||
<img src="https://nonebot.dev/img/setup.svg">
|
<img src="https://nonebot.dev/img/setup.svg" alt="setup" >
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -232,10 +232,52 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
### 赞助者
|
### 赞助者
|
||||||
|
|
||||||
|
感谢以下产品对 NoneBot 项目提供的赞助:
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/github-dark.png">
|
||||||
|
<img src="https://assets.nonebot.dev/github-light.png" height="50" alt="GitHub">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.netlify.com/">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/netlify-dark.svg">
|
||||||
|
<img src="https://assets.nonebot.dev/netlify-light.svg" height="50" alt="netlify">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://sentry.io/">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/sentry-dark.svg">
|
||||||
|
<img src="https://assets.nonebot.dev/sentry-light.svg" height="50" alt="sentry">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.docker.com/">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/docker-dark.svg">
|
||||||
|
<img src="https://assets.nonebot.dev/docker-light.svg" height="50" alt="docker">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.algolia.com/">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/algolia-dark.svg">
|
||||||
|
<img src="https://assets.nonebot.dev/algolia-light.svg" height="50" alt="algolia">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.jetbrains.com/">
|
||||||
|
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg" height="80" alt="JetBrains" >
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
感谢以下赞助者对 NoneBot 项目提供的资金支持:
|
感谢以下赞助者对 NoneBot 项目提供的资金支持:
|
||||||
|
|
||||||
<a href="https://assets.nonebot.dev/sponsors.svg">
|
<a href="https://assets.nonebot.dev/sponsors.svg">
|
||||||
<img src='https://assets.nonebot.dev/sponsors.svg'/>
|
<img src="https://assets.nonebot.dev/sponsors.svg" alt="sponsors" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
### 开发者
|
### 开发者
|
||||||
@@ -243,5 +285,5 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
感谢以下开发者对 NoneBot2 作出的贡献:
|
感谢以下开发者对 NoneBot2 作出的贡献:
|
||||||
|
|
||||||
<a href="https://github.com/nonebot/nonebot2/graphs/contributors">
|
<a href="https://github.com/nonebot/nonebot2/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=nonebot/nonebot2&max=1000" />
|
<img src="https://contrib.rocks/image?repo=nonebot/nonebot2&max=1000" alt="contributors" />
|
||||||
</a>
|
</a>
|
||||||
|
@@ -512,30 +512,6 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_gocqhttp",
|
|
||||||
"project_link": "nonebot-plugin-gocqhttp",
|
|
||||||
"author": "mnixry",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "gocqhttp",
|
|
||||||
"color": "#eabd52"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_guild_patch",
|
|
||||||
"project_link": "nonebot-plugin-guild-patch",
|
|
||||||
"author": "mnixry",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "gocqhttp",
|
|
||||||
"color": "#a2ea52"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"module_name": "nonebot_plugin_boardgame",
|
"module_name": "nonebot_plugin_boardgame",
|
||||||
"project_link": "nonebot-plugin-boardgame",
|
"project_link": "nonebot-plugin-boardgame",
|
||||||
@@ -4227,6 +4203,13 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_eitherchoice",
|
||||||
|
"project_link": "nonebot-plugin-eitherchoice",
|
||||||
|
"author": "lgc2333",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot_plugin_poke",
|
"module_name": "nonebot_plugin_poke",
|
||||||
"project_link": "nonebot-plugin-poke",
|
"project_link": "nonebot-plugin-poke",
|
||||||
@@ -5305,8 +5288,8 @@
|
|||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot_plugin_phigros_qq",
|
"module_name": "nonebot_plugin_phigros",
|
||||||
"project_link": "nonebot-plugin-phigros-qq",
|
"project_link": "nonebot-plugin-phigros",
|
||||||
"author": "XTxiaoting14332",
|
"author": "XTxiaoting14332",
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
@@ -5315,5 +5298,259 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_antimonkey",
|
||||||
|
"project_link": "nonebot-plugin-antimonkey",
|
||||||
|
"author": "phquathi",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "猴子",
|
||||||
|
"color": "#ea5252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "群管",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_waiter",
|
||||||
|
"project_link": "nonebot-plugin-waiter",
|
||||||
|
"author": "RF-Tar-Railt",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "waiter",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_imagemaster",
|
||||||
|
"project_link": "nonebot-plugin-imagemaster",
|
||||||
|
"author": "phquathi",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "修图",
|
||||||
|
"color": "#52d4ea"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_nikke",
|
||||||
|
"project_link": "nonebot-plugin-nikke",
|
||||||
|
"author": "Perseus037",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "nikke",
|
||||||
|
"color": "#e111b7"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_mcpic",
|
||||||
|
"project_link": "nonebot-plugin-mcpic",
|
||||||
|
"author": "wlm3201",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "Minecraft",
|
||||||
|
"color": "#3cb371"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "MC",
|
||||||
|
"color": "#3cb371"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "图片",
|
||||||
|
"color": "#3cb371"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_pingti",
|
||||||
|
"project_link": "nonebot-plugin-pingti",
|
||||||
|
"author": "lgc2333",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_wx4",
|
||||||
|
"project_link": "nonebot-plugin-wx4",
|
||||||
|
"author": "Pasumao",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_mypower",
|
||||||
|
"project_link": "nonebot-plugin-mypower",
|
||||||
|
"author": "tianyisama",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_bard",
|
||||||
|
"project_link": "nonebot-plugin-bard",
|
||||||
|
"author": "Alpaca4610",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_nekoimage",
|
||||||
|
"project_link": "nonebot-plugin-nekoimage",
|
||||||
|
"author": "pk5ls20",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_finallines",
|
||||||
|
"project_link": "nonebot-plugin-finallines",
|
||||||
|
"author": "Perseus037",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "最终台词",
|
||||||
|
"color": "#052199"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_gemini",
|
||||||
|
"project_link": "nonebot-plugin-gemini",
|
||||||
|
"author": "zhaomaoniu",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "Gemini",
|
||||||
|
"color": "#3e8ffb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "haruka_bot_red",
|
||||||
|
"project_link": "haruka_bot_red",
|
||||||
|
"author": "boxie123",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "bilibili",
|
||||||
|
"color": "#c83f3f"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_enatfrp",
|
||||||
|
"project_link": "nonebot-plugin-enatfrp",
|
||||||
|
"author": "eya46",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_nezha",
|
||||||
|
"project_link": "nonebot-plugin-nezha",
|
||||||
|
"author": "eya46",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_randpic",
|
||||||
|
"project_link": "nonebot-plugin-randpic",
|
||||||
|
"author": "HuParry",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_BAdrawcard",
|
||||||
|
"project_link": "nonebot-plugin-badrawcard",
|
||||||
|
"author": "lengmianzz",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_gpt",
|
||||||
|
"project_link": "nonebot-plugin-gpt",
|
||||||
|
"author": "nek0us",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "ChatGPT",
|
||||||
|
"color": "#50ec9d"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_easy_blacklist",
|
||||||
|
"project_link": "nonebot-plugin-easy-blacklist",
|
||||||
|
"author": "bingqiu456",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_reminder",
|
||||||
|
"project_link": "nonebot-plugin-reminder",
|
||||||
|
"author": "velor2012",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "scheduler",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_chikari_yinpa",
|
||||||
|
"project_link": "nonebot-plugin-chikari-yinpa",
|
||||||
|
"author": "mrqx0195",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "yinpa",
|
||||||
|
"color": "#ffff00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_splatoon3_nso",
|
||||||
|
"project_link": "nonebot-plugin-splatoon3-nso",
|
||||||
|
"author": "Cypas",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "splatoon3",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_bf1marneserverlist",
|
||||||
|
"project_link": "nonebot-plugin-bf1marneserverlist",
|
||||||
|
"author": "SAFEluren",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "server",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_kawaii_status",
|
||||||
|
"project_link": "nonebot-plugin-kawaii-status",
|
||||||
|
"author": "KomoriDev",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "简约",
|
||||||
|
"color": "#54adff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "可爱",
|
||||||
|
"color": "#ffb3cc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
2166
envs/pydantic-v1/poetry.lock
generated
Normal file
2166
envs/pydantic-v1/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
envs/pydantic-v1/pyproject.toml
Normal file
18
envs/pydantic-v1/pyproject.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[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.8"
|
||||||
|
|
||||||
|
[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"
|
2238
envs/pydantic-v2/poetry.lock
generated
Normal file
2238
envs/pydantic-v2/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
envs/pydantic-v2/pyproject.toml
Normal file
18
envs/pydantic-v2/pyproject.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[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.8"
|
||||||
|
|
||||||
|
[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
envs/test/nonebot-test.py
Normal file
1
envs/test/nonebot-test.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# fake file to make project installable
|
1029
envs/test/poetry.lock
generated
Normal file
1029
envs/test/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
envs/test/pyproject.toml
Normal file
21
envs/test/pyproject.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[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.8"
|
||||||
|
nonebug = "^0.3.0"
|
||||||
|
wsproto = "^1.2.0"
|
||||||
|
pytest-cov = "^4.0.0"
|
||||||
|
pytest-xdist = "^3.0.2"
|
||||||
|
pytest-asyncio = "^0.23.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"
|
@@ -35,6 +35,7 @@
|
|||||||
{ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
|
{ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
|
||||||
- `get_available_plugin_names` =>
|
- `get_available_plugin_names` =>
|
||||||
{ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
|
{ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
|
||||||
|
- `get_plugin_config` => {ref}``get_plugin_config` <nonebot.plugin.get_plugin_config>`
|
||||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
@@ -47,11 +48,11 @@ from importlib.metadata import version
|
|||||||
from typing import Any, Dict, Type, Union, TypeVar, Optional, overload
|
from typing import Any, Dict, Type, Union, TypeVar, Optional, overload
|
||||||
|
|
||||||
import loguru
|
import loguru
|
||||||
from pydantic.env_settings import DotenvType
|
|
||||||
|
|
||||||
from nonebot.config import Env, Config
|
from nonebot.compat import model_dump
|
||||||
from nonebot.log import logger as logger
|
from nonebot.log import logger as logger
|
||||||
from nonebot.adapters import Bot, Adapter
|
from nonebot.adapters import Bot, Adapter
|
||||||
|
from nonebot.config import DOTENV_TYPE, Env, Config
|
||||||
from nonebot.utils import escape_tag, resolve_dot_notation
|
from nonebot.utils import escape_tag, resolve_dot_notation
|
||||||
from nonebot.drivers import Driver, ASGIMixin, combine_driver
|
from nonebot.drivers import Driver, ASGIMixin, combine_driver
|
||||||
|
|
||||||
@@ -273,7 +274,7 @@ def _log_patcher(record: "loguru.Record"):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def init(*, _env_file: Optional[DotenvType] = None, **kwargs: Any) -> None:
|
def init(*, _env_file: Optional[DOTENV_TYPE] = None, **kwargs: Any) -> None:
|
||||||
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
|
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
|
||||||
|
|
||||||
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
|
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
|
||||||
@@ -296,9 +297,11 @@ def init(*, _env_file: Optional[DotenvType] = None, **kwargs: Any) -> None:
|
|||||||
_env_file = _env_file or f".env.{env.environment}"
|
_env_file = _env_file or f".env.{env.environment}"
|
||||||
config = Config(
|
config = Config(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
_env_file=(".env", _env_file)
|
_env_file=(
|
||||||
if isinstance(_env_file, (str, os.PathLike))
|
(".env", _env_file)
|
||||||
else _env_file,
|
if isinstance(_env_file, (str, os.PathLike))
|
||||||
|
else _env_file
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.configure(
|
logger.configure(
|
||||||
@@ -308,7 +311,7 @@ def init(*, _env_file: Optional[DotenvType] = None, **kwargs: Any) -> None:
|
|||||||
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
|
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
|
||||||
)
|
)
|
||||||
logger.opt(colors=True).debug(
|
logger.opt(colors=True).debug(
|
||||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
|
f"Loaded <y><b>Config</b></y>: {escape_tag(str(model_dump(config)))}"
|
||||||
)
|
)
|
||||||
|
|
||||||
DriverClass = _resolve_combine_expr(config.driver)
|
DriverClass = _resolve_combine_expr(config.driver)
|
||||||
@@ -353,10 +356,9 @@ from nonebot.plugin import load_from_json as load_from_json
|
|||||||
from nonebot.plugin import load_from_toml as load_from_toml
|
from nonebot.plugin import load_from_toml as load_from_toml
|
||||||
from nonebot.plugin import load_all_plugins as load_all_plugins
|
from nonebot.plugin import load_all_plugins as load_all_plugins
|
||||||
from nonebot.plugin import on_shell_command as on_shell_command
|
from nonebot.plugin import on_shell_command as on_shell_command
|
||||||
|
from nonebot.plugin import get_plugin_config as get_plugin_config
|
||||||
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
|
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
|
||||||
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
|
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
|
||||||
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
|
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
|
||||||
from nonebot.plugin import get_plugin_by_module_name as get_plugin_by_module_name
|
from nonebot.plugin import get_plugin_by_module_name as get_plugin_by_module_name
|
||||||
from nonebot.plugin import get_available_plugin_names as get_available_plugin_names
|
from nonebot.plugin import get_available_plugin_names as get_available_plugin_names
|
||||||
|
|
||||||
__autodoc__ = {"internal": False}
|
|
||||||
|
383
nonebot/compat.py
Normal file
383
nonebot/compat.py
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
"""本模块为 Pydantic 版本兼容层模块
|
||||||
|
|
||||||
|
为兼容 Pydantic V1 与 V2 版本,定义了一系列兼容函数与类供使用。
|
||||||
|
|
||||||
|
FrontMatter:
|
||||||
|
sidebar_position: 16
|
||||||
|
description: nonebot.compat 模块
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, is_dataclass
|
||||||
|
from typing_extensions import Self, Annotated, get_args, get_origin, is_typeddict
|
||||||
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Set,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Type,
|
||||||
|
Union,
|
||||||
|
TypeVar,
|
||||||
|
Callable,
|
||||||
|
Optional,
|
||||||
|
Protocol,
|
||||||
|
Generator,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pydantic import VERSION, BaseModel
|
||||||
|
|
||||||
|
from nonebot.typing import origin_is_annotated
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
PYDANTIC_V2 = int(VERSION.split(".", 1)[0]) == 2
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
class _CustomValidationClass(Protocol):
|
||||||
|
@classmethod
|
||||||
|
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]: ...
|
||||||
|
|
||||||
|
CVC = TypeVar("CVC", bound=_CustomValidationClass)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"Required",
|
||||||
|
"PydanticUndefined",
|
||||||
|
"PydanticUndefinedType",
|
||||||
|
"ConfigDict",
|
||||||
|
"DEFAULT_CONFIG",
|
||||||
|
"FieldInfo",
|
||||||
|
"ModelField",
|
||||||
|
"extract_field_info",
|
||||||
|
"model_field_validate",
|
||||||
|
"model_fields",
|
||||||
|
"model_config",
|
||||||
|
"model_dump",
|
||||||
|
"type_validate_python",
|
||||||
|
"type_validate_json",
|
||||||
|
"custom_validation",
|
||||||
|
)
|
||||||
|
|
||||||
|
__autodoc__ = {
|
||||||
|
"PydanticUndefined": "Pydantic Undefined object",
|
||||||
|
"PydanticUndefinedType": "Pydantic Undefined type",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||||
|
from pydantic_core import CoreSchema, core_schema
|
||||||
|
from pydantic._internal._repr import display_as_type
|
||||||
|
from pydantic import TypeAdapter, GetCoreSchemaHandler
|
||||||
|
from pydantic.fields import FieldInfo as BaseFieldInfo
|
||||||
|
|
||||||
|
Required = Ellipsis
|
||||||
|
"""Alias of Ellipsis for compatibility with pydantic v1"""
|
||||||
|
|
||||||
|
# Export undefined type
|
||||||
|
from pydantic_core import PydanticUndefined as PydanticUndefined
|
||||||
|
from pydantic_core import PydanticUndefinedType as PydanticUndefinedType
|
||||||
|
|
||||||
|
# isort: split
|
||||||
|
|
||||||
|
# Export model config dict
|
||||||
|
from pydantic import ConfigDict as ConfigDict
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
||||||
|
"""Default config for validations"""
|
||||||
|
|
||||||
|
class FieldInfo(BaseFieldInfo):
|
||||||
|
"""FieldInfo class with extra property for compatibility with pydantic v1"""
|
||||||
|
|
||||||
|
# make default can be positional argument
|
||||||
|
def __init__(self, default: Any = PydanticUndefined, **kwargs: Any) -> None:
|
||||||
|
super().__init__(default=default, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra(self) -> Dict[str, Any]:
|
||||||
|
"""Extra data that is not part of the standard pydantic fields.
|
||||||
|
|
||||||
|
For compatibility with pydantic v1.
|
||||||
|
"""
|
||||||
|
# extract extra data from attributes set except used slots
|
||||||
|
# we need to call super in advance due to
|
||||||
|
# comprehension not inlined in cpython < 3.12
|
||||||
|
# https://peps.python.org/pep-0709/
|
||||||
|
slots = super().__slots__
|
||||||
|
return {k: v for k, v in self._attributes_set.items() if k not in slots}
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ModelField:
|
||||||
|
"""ModelField class for compatibility with pydantic v1"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
"""The name of the field."""
|
||||||
|
annotation: Any
|
||||||
|
"""The annotation of the field."""
|
||||||
|
field_info: FieldInfo
|
||||||
|
"""The FieldInfo of the field."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _construct(cls, name: str, annotation: Any, field_info: FieldInfo) -> Self:
|
||||||
|
return cls(name, annotation, field_info)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def construct(
|
||||||
|
cls, name: str, annotation: Any, field_info: Optional[FieldInfo] = None
|
||||||
|
) -> Self:
|
||||||
|
"""Construct a ModelField from given infos."""
|
||||||
|
return cls._construct(name, annotation, field_info or FieldInfo())
|
||||||
|
|
||||||
|
def _annotation_has_config(self) -> bool:
|
||||||
|
"""Check if the annotation has config.
|
||||||
|
|
||||||
|
TypeAdapter raise error when annotation has config
|
||||||
|
and given config is not None.
|
||||||
|
"""
|
||||||
|
type_is_annotated = origin_is_annotated(get_origin(self.annotation))
|
||||||
|
inner_type = (
|
||||||
|
get_args(self.annotation)[0] if type_is_annotated else self.annotation
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
issubclass(inner_type, BaseModel)
|
||||||
|
or is_dataclass(inner_type)
|
||||||
|
or is_typeddict(inner_type)
|
||||||
|
)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_default(self) -> Any:
|
||||||
|
"""Get the default value of the field."""
|
||||||
|
return self.field_info.get_default(call_default_factory=True)
|
||||||
|
|
||||||
|
def _type_display(self):
|
||||||
|
"""Get the display of the type of the field."""
|
||||||
|
return display_as_type(self.annotation)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
# Each ModelField is unique for our purposes,
|
||||||
|
# to allow store them in a set.
|
||||||
|
return id(self)
|
||||||
|
|
||||||
|
def extract_field_info(field_info: BaseFieldInfo) -> Dict[str, Any]:
|
||||||
|
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
||||||
|
|
||||||
|
kwargs = field_info._attributes_set.copy()
|
||||||
|
kwargs["annotation"] = field_info.rebuild_annotation()
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def model_field_validate(
|
||||||
|
model_field: ModelField, value: Any, config: Optional[ConfigDict] = None
|
||||||
|
) -> Any:
|
||||||
|
"""Validate the value pass to the field."""
|
||||||
|
type: Any = Annotated[model_field.annotation, model_field.field_info]
|
||||||
|
return TypeAdapter(
|
||||||
|
type, config=None if model_field._annotation_has_config() else config
|
||||||
|
).validate_python(value)
|
||||||
|
|
||||||
|
def model_fields(model: Type[BaseModel]) -> List[ModelField]:
|
||||||
|
"""Get field list of a model."""
|
||||||
|
|
||||||
|
return [
|
||||||
|
ModelField._construct(
|
||||||
|
name=name,
|
||||||
|
annotation=field_info.rebuild_annotation(),
|
||||||
|
field_info=FieldInfo(**extract_field_info(field_info)),
|
||||||
|
)
|
||||||
|
for name, field_info in model.model_fields.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
def model_config(model: Type[BaseModel]) -> Any:
|
||||||
|
"""Get config of a model."""
|
||||||
|
return model.model_config
|
||||||
|
|
||||||
|
def model_dump(
|
||||||
|
model: BaseModel,
|
||||||
|
include: Optional[Set[str]] = None,
|
||||||
|
exclude: Optional[Set[str]] = None,
|
||||||
|
by_alias: bool = False,
|
||||||
|
exclude_unset: bool = False,
|
||||||
|
exclude_defaults: bool = False,
|
||||||
|
exclude_none: bool = False,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
return model.model_dump(
|
||||||
|
include=include,
|
||||||
|
exclude=exclude,
|
||||||
|
by_alias=by_alias,
|
||||||
|
exclude_unset=exclude_unset,
|
||||||
|
exclude_defaults=exclude_defaults,
|
||||||
|
exclude_none=exclude_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
def type_validate_python(type_: Type[T], data: Any) -> T:
|
||||||
|
"""Validate data with given type."""
|
||||||
|
return TypeAdapter(type_).validate_python(data)
|
||||||
|
|
||||||
|
def type_validate_json(type_: Type[T], data: Union[str, bytes]) -> T:
|
||||||
|
"""Validate JSON with given type."""
|
||||||
|
return TypeAdapter(type_).validate_json(data)
|
||||||
|
|
||||||
|
def __get_pydantic_core_schema__(
|
||||||
|
cls: Type["_CustomValidationClass"],
|
||||||
|
source_type: Any,
|
||||||
|
handler: GetCoreSchemaHandler,
|
||||||
|
) -> CoreSchema:
|
||||||
|
validators = list(cls.__get_validators__())
|
||||||
|
if len(validators) == 1:
|
||||||
|
return core_schema.no_info_plain_validator_function(validators[0])
|
||||||
|
return core_schema.chain_schema(
|
||||||
|
[core_schema.no_info_plain_validator_function(func) for func in validators]
|
||||||
|
)
|
||||||
|
|
||||||
|
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
|
||||||
|
"""Use pydantic v1 like validator generator in pydantic v2"""
|
||||||
|
|
||||||
|
setattr(
|
||||||
|
class_,
|
||||||
|
"__get_pydantic_core_schema__",
|
||||||
|
classmethod(__get_pydantic_core_schema__),
|
||||||
|
)
|
||||||
|
return class_
|
||||||
|
|
||||||
|
else: # pragma: pydantic-v1
|
||||||
|
from pydantic import Extra
|
||||||
|
from pydantic import parse_obj_as, parse_raw_as
|
||||||
|
from pydantic import BaseConfig as PydanticConfig
|
||||||
|
from pydantic.fields import FieldInfo as BaseFieldInfo
|
||||||
|
from pydantic.fields import ModelField as BaseModelField
|
||||||
|
from pydantic.schema import get_annotation_from_field_info
|
||||||
|
|
||||||
|
# isort: split
|
||||||
|
|
||||||
|
from pydantic.fields import Required as Required
|
||||||
|
|
||||||
|
# isort: split
|
||||||
|
|
||||||
|
from pydantic.fields import Undefined as PydanticUndefined
|
||||||
|
from pydantic.fields import UndefinedType as PydanticUndefinedType
|
||||||
|
|
||||||
|
class ConfigDict(PydanticConfig):
|
||||||
|
"""Config class that allow get value with default value."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, field: str, default: Any = None) -> Any:
|
||||||
|
"""Get a config value."""
|
||||||
|
return getattr(cls, field, default)
|
||||||
|
|
||||||
|
class DEFAULT_CONFIG(ConfigDict):
|
||||||
|
extra = Extra.allow
|
||||||
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
|
class FieldInfo(BaseFieldInfo):
|
||||||
|
def __init__(self, default: Any = PydanticUndefined, **kwargs: Any):
|
||||||
|
# preprocess default value to make it compatible with pydantic v2
|
||||||
|
# when default is Required, set it to PydanticUndefined
|
||||||
|
if default is Required:
|
||||||
|
default = PydanticUndefined
|
||||||
|
super().__init__(default, **kwargs)
|
||||||
|
|
||||||
|
class ModelField(BaseModelField):
|
||||||
|
@classmethod
|
||||||
|
def _construct(cls, name: str, annotation: Any, field_info: FieldInfo) -> Self:
|
||||||
|
return cls(
|
||||||
|
name=name,
|
||||||
|
type_=annotation,
|
||||||
|
class_validators=None,
|
||||||
|
model_config=DEFAULT_CONFIG,
|
||||||
|
default=field_info.default,
|
||||||
|
default_factory=field_info.default_factory,
|
||||||
|
required=(
|
||||||
|
field_info.default is PydanticUndefined
|
||||||
|
and field_info.default_factory is None
|
||||||
|
),
|
||||||
|
field_info=field_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def construct(
|
||||||
|
cls, name: str, annotation: Any, field_info: Optional[FieldInfo] = None
|
||||||
|
) -> Self:
|
||||||
|
"""Construct a ModelField from given infos.
|
||||||
|
|
||||||
|
Field annotation is preprocessed with field_info.
|
||||||
|
"""
|
||||||
|
if field_info is not None:
|
||||||
|
annotation = get_annotation_from_field_info(
|
||||||
|
annotation, field_info, name
|
||||||
|
)
|
||||||
|
return cls._construct(name, annotation, field_info or FieldInfo())
|
||||||
|
|
||||||
|
def extract_field_info(field_info: BaseFieldInfo) -> Dict[str, Any]:
|
||||||
|
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
s: getattr(field_info, s) for s in field_info.__slots__ if s != "extra"
|
||||||
|
}
|
||||||
|
kwargs.update(field_info.extra)
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def model_field_validate(
|
||||||
|
model_field: ModelField, value: Any, config: Optional[Type[ConfigDict]] = None
|
||||||
|
) -> Any:
|
||||||
|
"""Validate the value pass to the field.
|
||||||
|
|
||||||
|
Set config before validate to ensure validate correctly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if model_field.model_config is not config:
|
||||||
|
model_field.set_config(config or ConfigDict)
|
||||||
|
|
||||||
|
v, errs_ = model_field.validate(value, {}, loc=())
|
||||||
|
if errs_:
|
||||||
|
raise ValueError(value, model_field)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def model_fields(model: Type[BaseModel]) -> List[ModelField]:
|
||||||
|
"""Get field list of a model."""
|
||||||
|
|
||||||
|
# construct the model field without preprocess to avoid error
|
||||||
|
return [
|
||||||
|
ModelField._construct(
|
||||||
|
name=model_field.name,
|
||||||
|
annotation=model_field.annotation,
|
||||||
|
field_info=FieldInfo(
|
||||||
|
**extract_field_info(model_field.field_info),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for model_field in model.__fields__.values()
|
||||||
|
]
|
||||||
|
|
||||||
|
def model_config(model: Type[BaseModel]) -> Any:
|
||||||
|
"""Get config of a model."""
|
||||||
|
return model.__config__
|
||||||
|
|
||||||
|
def model_dump(
|
||||||
|
model: BaseModel,
|
||||||
|
include: Optional[Set[str]] = None,
|
||||||
|
exclude: Optional[Set[str]] = None,
|
||||||
|
by_alias: bool = False,
|
||||||
|
exclude_unset: bool = False,
|
||||||
|
exclude_defaults: bool = False,
|
||||||
|
exclude_none: bool = False,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
return model.dict(
|
||||||
|
include=include,
|
||||||
|
exclude=exclude,
|
||||||
|
by_alias=by_alias,
|
||||||
|
exclude_unset=exclude_unset,
|
||||||
|
exclude_defaults=exclude_defaults,
|
||||||
|
exclude_none=exclude_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
def type_validate_python(type_: Type[T], data: Any) -> T:
|
||||||
|
"""Validate data with given type."""
|
||||||
|
return parse_obj_as(type_, data)
|
||||||
|
|
||||||
|
def type_validate_json(type_: Type[T], data: Union[str, bytes]) -> T:
|
||||||
|
"""Validate JSON with given type."""
|
||||||
|
return parse_raw_as(type_, data)
|
||||||
|
|
||||||
|
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
|
||||||
|
"""Do nothing in pydantic v1"""
|
||||||
|
return class_
|
@@ -12,76 +12,256 @@ FrontMatter:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import abc
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
|
from typing_extensions import TypeAlias, get_args, get_origin
|
||||||
|
from typing import (
|
||||||
from pydantic.utils import deep_update
|
TYPE_CHECKING,
|
||||||
from pydantic.fields import Undefined, UndefinedType
|
Any,
|
||||||
from pydantic import Extra, Field, BaseSettings, IPvAnyAddress
|
Set,
|
||||||
from pydantic.env_settings import (
|
Dict,
|
||||||
DotenvType,
|
List,
|
||||||
SettingsError,
|
Type,
|
||||||
EnvSettingsSource,
|
Tuple,
|
||||||
InitSettingsSource,
|
Union,
|
||||||
SettingsSourceCallable,
|
Mapping,
|
||||||
|
Optional,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
from pydantic import Field, BaseModel
|
||||||
|
from pydantic.networks import IPvAnyAddress
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
|
from nonebot.typing import origin_is_union
|
||||||
|
from nonebot.utils import deep_update, type_is_complex, lenient_issubclass
|
||||||
|
from nonebot.compat import (
|
||||||
|
PYDANTIC_V2,
|
||||||
|
ConfigDict,
|
||||||
|
ModelField,
|
||||||
|
PydanticUndefined,
|
||||||
|
PydanticUndefinedType,
|
||||||
|
model_config,
|
||||||
|
model_fields,
|
||||||
|
)
|
||||||
|
|
||||||
|
DOTENV_TYPE: TypeAlias = Union[
|
||||||
|
Path, str, List[Union[Path, str]], Tuple[Union[Path, str], ...]
|
||||||
|
]
|
||||||
|
|
||||||
|
ENV_FILE_SENTINEL = Path("")
|
||||||
|
|
||||||
|
|
||||||
class CustomEnvSettings(EnvSettingsSource):
|
class SettingsError(ValueError): ...
|
||||||
def __call__(self, settings: BaseSettings) -> Dict[str, Any]:
|
|
||||||
|
|
||||||
|
class BaseSettingsSource(abc.ABC):
|
||||||
|
def __init__(self, settings_cls: Type["BaseSettings"]) -> None:
|
||||||
|
self.settings_cls = settings_cls
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config(self) -> "SettingsConfig":
|
||||||
|
return model_config(self.settings_cls)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __call__(self) -> Dict[str, Any]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class InitSettingsSource(BaseSettingsSource):
|
||||||
|
__slots__ = ("init_kwargs",)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, settings_cls: Type["BaseSettings"], init_kwargs: Dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
self.init_kwargs = init_kwargs
|
||||||
|
super().__init__(settings_cls)
|
||||||
|
|
||||||
|
def __call__(self) -> Dict[str, Any]:
|
||||||
|
return self.init_kwargs
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"InitSettingsSource(init_kwargs={self.init_kwargs!r})"
|
||||||
|
|
||||||
|
|
||||||
|
class DotEnvSettingsSource(BaseSettingsSource):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
settings_cls: Type["BaseSettings"],
|
||||||
|
env_file: Optional[DOTENV_TYPE] = ENV_FILE_SENTINEL,
|
||||||
|
env_file_encoding: Optional[str] = None,
|
||||||
|
case_sensitive: Optional[bool] = None,
|
||||||
|
env_nested_delimiter: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(settings_cls)
|
||||||
|
self.env_file = (
|
||||||
|
env_file
|
||||||
|
if env_file is not ENV_FILE_SENTINEL
|
||||||
|
else self.config.get("env_file", (".env",))
|
||||||
|
)
|
||||||
|
self.env_file_encoding = (
|
||||||
|
env_file_encoding
|
||||||
|
if env_file_encoding is not None
|
||||||
|
else self.config.get("env_file_encoding", "utf-8")
|
||||||
|
)
|
||||||
|
self.case_sensitive = (
|
||||||
|
case_sensitive
|
||||||
|
if case_sensitive is not None
|
||||||
|
else self.config.get("case_sensitive", False)
|
||||||
|
)
|
||||||
|
self.env_nested_delimiter = (
|
||||||
|
env_nested_delimiter
|
||||||
|
if env_nested_delimiter is not None
|
||||||
|
else self.config.get("env_nested_delimiter", None)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _apply_case_sensitive(self, var_name: str) -> str:
|
||||||
|
return var_name if self.case_sensitive else var_name.lower()
|
||||||
|
|
||||||
|
def _field_is_complex(self, field: ModelField) -> Tuple[bool, bool]:
|
||||||
|
if type_is_complex(field.annotation):
|
||||||
|
return True, False
|
||||||
|
elif origin_is_union(get_origin(field.annotation)) and any(
|
||||||
|
type_is_complex(arg) for arg in get_args(field.annotation)
|
||||||
|
):
|
||||||
|
return True, True
|
||||||
|
return False, False
|
||||||
|
|
||||||
|
def _parse_env_vars(
|
||||||
|
self, env_vars: Mapping[str, Optional[str]]
|
||||||
|
) -> Dict[str, Optional[str]]:
|
||||||
|
return {
|
||||||
|
self._apply_case_sensitive(key): value for key, value in env_vars.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def _read_env_file(self, file_path: Path) -> Dict[str, Optional[str]]:
|
||||||
|
file_vars = dotenv_values(file_path, encoding=self.env_file_encoding)
|
||||||
|
return self._parse_env_vars(file_vars)
|
||||||
|
|
||||||
|
def _read_env_files(self) -> Dict[str, Optional[str]]:
|
||||||
|
env_files = self.env_file
|
||||||
|
if env_files is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if isinstance(env_files, (str, os.PathLike)):
|
||||||
|
env_files = [env_files]
|
||||||
|
|
||||||
|
dotenv_vars: Dict[str, Optional[str]] = {}
|
||||||
|
for env_file in env_files:
|
||||||
|
env_path = Path(env_file).expanduser()
|
||||||
|
if env_path.is_file():
|
||||||
|
dotenv_vars.update(self._read_env_file(env_path))
|
||||||
|
return dotenv_vars
|
||||||
|
|
||||||
|
def _next_field(
|
||||||
|
self, field: Optional[ModelField], key: str
|
||||||
|
) -> Optional[ModelField]:
|
||||||
|
if not field or origin_is_union(get_origin(field.annotation)):
|
||||||
|
return None
|
||||||
|
elif field.annotation and lenient_issubclass(field.annotation, BaseModel):
|
||||||
|
for field in model_fields(field.annotation):
|
||||||
|
if field.name == key:
|
||||||
|
return field
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _explode_env_vars(
|
||||||
|
self,
|
||||||
|
field: ModelField,
|
||||||
|
env_vars: Dict[str, Optional[str]],
|
||||||
|
env_file_vars: Dict[str, Optional[str]],
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
if self.env_nested_delimiter is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
prefix = f"{field.name}{self.env_nested_delimiter}"
|
||||||
|
result: Dict[str, Any] = {}
|
||||||
|
for env_name, env_val in env_vars.items():
|
||||||
|
if not env_name.startswith(prefix):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# delete from file vars when used
|
||||||
|
if env_name in env_file_vars:
|
||||||
|
del env_file_vars[env_name]
|
||||||
|
|
||||||
|
_, *keys, last_key = env_name.split(self.env_nested_delimiter)
|
||||||
|
env_var = result
|
||||||
|
target_field: Optional[ModelField] = field
|
||||||
|
for key in keys:
|
||||||
|
target_field = self._next_field(target_field, key)
|
||||||
|
env_var = env_var.setdefault(key, {})
|
||||||
|
|
||||||
|
target_field = self._next_field(target_field, last_key)
|
||||||
|
if target_field and env_val:
|
||||||
|
is_complex, allow_parse_failure = self._field_is_complex(target_field)
|
||||||
|
if is_complex:
|
||||||
|
try:
|
||||||
|
env_val = json.loads(env_val)
|
||||||
|
except ValueError as e:
|
||||||
|
if not allow_parse_failure:
|
||||||
|
raise SettingsError(
|
||||||
|
f'error parsing env var "{env_name}"'
|
||||||
|
) from e
|
||||||
|
|
||||||
|
env_var[last_key] = env_val
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __call__(self) -> Dict[str, Any]:
|
||||||
"""从环境变量和 dotenv 配置文件中读取配置项。"""
|
"""从环境变量和 dotenv 配置文件中读取配置项。"""
|
||||||
|
|
||||||
d: Dict[str, Any] = {}
|
d: Dict[str, Any] = {}
|
||||||
|
|
||||||
if settings.__config__.case_sensitive:
|
env_vars = self._parse_env_vars(os.environ)
|
||||||
env_vars: Mapping[str, Optional[str]] = os.environ # pragma: no cover
|
env_file_vars = self._read_env_files()
|
||||||
else:
|
|
||||||
env_vars = {k.lower(): v for k, v in os.environ.items()}
|
|
||||||
|
|
||||||
env_file_vars = self._read_env_files(settings.__config__.case_sensitive)
|
|
||||||
env_vars = {**env_file_vars, **env_vars}
|
env_vars = {**env_file_vars, **env_vars}
|
||||||
|
|
||||||
for field in settings.__fields__.values():
|
for field in model_fields(self.settings_cls):
|
||||||
env_val: Union[str, None, UndefinedType] = Undefined
|
field_name = field.name
|
||||||
for env_name in field.field_info.extra["env_names"]:
|
env_name = self._apply_case_sensitive(field_name)
|
||||||
env_val = env_vars.get(env_name, Undefined)
|
|
||||||
if env_name in env_file_vars:
|
|
||||||
del env_file_vars[env_name]
|
|
||||||
if env_val is not Undefined:
|
|
||||||
break
|
|
||||||
|
|
||||||
is_complex, allow_parse_failure = self.field_is_complex(field)
|
# try get values from env vars
|
||||||
|
env_val = env_vars.get(env_name, PydanticUndefined)
|
||||||
|
# delete from file vars when used
|
||||||
|
if env_name in env_file_vars:
|
||||||
|
del env_file_vars[env_name]
|
||||||
|
|
||||||
|
is_complex, allow_parse_failure = self._field_is_complex(field)
|
||||||
if is_complex:
|
if is_complex:
|
||||||
if isinstance(env_val, UndefinedType):
|
if isinstance(env_val, PydanticUndefinedType):
|
||||||
# field is complex but no value found so far, try explode_env_vars
|
# field is complex but no value found so far, try explode_env_vars
|
||||||
if env_val_built := self.explode_env_vars(field, env_vars):
|
if env_val_built := self._explode_env_vars(
|
||||||
d[field.alias] = env_val_built
|
field, env_vars, env_file_vars
|
||||||
|
):
|
||||||
|
d[field_name] = env_val_built
|
||||||
elif env_val is None:
|
elif env_val is None:
|
||||||
d[field.alias] = env_val
|
d[field_name] = env_val
|
||||||
else:
|
else:
|
||||||
# field is complex and there's a value
|
# field is complex and there's a value
|
||||||
# decode that as JSON, then add explode_env_vars
|
# decode that as JSON, then add explode_env_vars
|
||||||
try:
|
try:
|
||||||
env_val = settings.__config__.parse_env_var(field.name, env_val)
|
env_val = json.loads(env_val)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
if not allow_parse_failure:
|
if not allow_parse_failure:
|
||||||
raise SettingsError(
|
raise SettingsError(
|
||||||
f'error parsing env var "{env_name}"' # type: ignore
|
f'error parsing env var "{env_name}"'
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
if isinstance(env_val, dict):
|
if isinstance(env_val, dict):
|
||||||
d[field.alias] = deep_update(
|
# field value is a dict
|
||||||
env_val, self.explode_env_vars(field, env_vars)
|
# try explode_env_vars to find more sub-values
|
||||||
|
d[field_name] = deep_update(
|
||||||
|
env_val,
|
||||||
|
self._explode_env_vars(field, env_vars, env_file_vars),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
d[field.alias] = env_val
|
d[field_name] = env_val
|
||||||
elif not isinstance(env_val, UndefinedType):
|
elif env_val is not PydanticUndefined:
|
||||||
# simplest case, field is not complex
|
# simplest case, field is not complex
|
||||||
# we only need to add the value if it was found
|
# we only need to add the value if it was found
|
||||||
d[field.alias] = env_val
|
d[field_name] = env_val
|
||||||
|
|
||||||
# remain user custom config
|
# remain user custom config
|
||||||
for env_name in env_file_vars:
|
for env_name in env_file_vars:
|
||||||
@@ -89,7 +269,7 @@ class CustomEnvSettings(EnvSettingsSource):
|
|||||||
if env_val and (val_striped := env_val.strip()):
|
if env_val and (val_striped := env_val.strip()):
|
||||||
# there's a value, decode that as JSON
|
# there's a value, decode that as JSON
|
||||||
try:
|
try:
|
||||||
env_val = settings.__config__.parse_env_var(env_name, val_striped)
|
env_val = json.loads(val_striped)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.trace(
|
logger.trace(
|
||||||
"Error while parsing JSON for "
|
"Error while parsing JSON for "
|
||||||
@@ -113,38 +293,80 @@ class CustomEnvSettings(EnvSettingsSource):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class BaseConfig(BaseSettings):
|
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||||
|
|
||||||
|
class SettingsConfig(ConfigDict, total=False):
|
||||||
|
env_file: Optional[DOTENV_TYPE]
|
||||||
|
env_file_encoding: str
|
||||||
|
case_sensitive: bool
|
||||||
|
env_nested_delimiter: Optional[str]
|
||||||
|
|
||||||
|
else: # pragma: pydantic-v1
|
||||||
|
|
||||||
|
class SettingsConfig(ConfigDict):
|
||||||
|
env_file: Optional[DOTENV_TYPE]
|
||||||
|
env_file_encoding: str
|
||||||
|
case_sensitive: bool
|
||||||
|
env_nested_delimiter: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSettings(BaseModel):
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# dummy getattr for pylance checking, actually not used
|
# dummy getattr for pylance checking, actually not used
|
||||||
def __getattr__(self, name: str) -> Any: # pragma: no cover
|
def __getattr__(self, name: str) -> Any: # pragma: no cover
|
||||||
return self.__dict__.get(name)
|
return self.__dict__.get(name)
|
||||||
|
|
||||||
class Config:
|
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||||
extra = Extra.allow
|
model_config: SettingsConfig = SettingsConfig(
|
||||||
env_nested_delimiter = "__"
|
extra="allow",
|
||||||
|
env_file=".env",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
case_sensitive=False,
|
||||||
|
env_nested_delimiter="__",
|
||||||
|
)
|
||||||
|
else: # pragma: pydantic-v1
|
||||||
|
|
||||||
@classmethod
|
class Config(SettingsConfig):
|
||||||
def customise_sources(
|
extra = "allow" # type: ignore
|
||||||
cls,
|
env_file = ".env"
|
||||||
init_settings: InitSettingsSource,
|
env_file_encoding = "utf-8"
|
||||||
env_settings: EnvSettingsSource,
|
case_sensitive = False
|
||||||
file_secret_settings: SettingsSourceCallable,
|
env_nested_delimiter = "__"
|
||||||
) -> Tuple[SettingsSourceCallable, ...]:
|
|
||||||
common_config = init_settings.init_kwargs.pop("_common_config", {})
|
def __init__(
|
||||||
return (
|
__settings_self__, # pyright: ignore[reportSelfClsParameterName]
|
||||||
init_settings,
|
_env_file: Optional[DOTENV_TYPE] = ENV_FILE_SENTINEL,
|
||||||
CustomEnvSettings(
|
_env_file_encoding: Optional[str] = None,
|
||||||
env_settings.env_file,
|
_env_nested_delimiter: Optional[str] = None,
|
||||||
env_settings.env_file_encoding,
|
**values: Any,
|
||||||
env_settings.env_nested_delimiter,
|
) -> None:
|
||||||
env_settings.env_prefix_len,
|
super().__init__(
|
||||||
),
|
**__settings_self__._settings_build_values(
|
||||||
InitSettingsSource(common_config),
|
values,
|
||||||
file_secret_settings,
|
env_file=_env_file,
|
||||||
|
env_file_encoding=_env_file_encoding,
|
||||||
|
env_nested_delimiter=_env_nested_delimiter,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _settings_build_values(
|
||||||
|
self,
|
||||||
|
init_kwargs: Dict[str, Any],
|
||||||
|
env_file: Optional[DOTENV_TYPE] = None,
|
||||||
|
env_file_encoding: Optional[str] = None,
|
||||||
|
env_nested_delimiter: Optional[str] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
init_settings = InitSettingsSource(self.__class__, init_kwargs=init_kwargs)
|
||||||
|
env_settings = DotEnvSettingsSource(
|
||||||
|
self.__class__,
|
||||||
|
env_file=env_file,
|
||||||
|
env_file_encoding=env_file_encoding,
|
||||||
|
env_nested_delimiter=env_nested_delimiter,
|
||||||
|
)
|
||||||
|
return deep_update(env_settings(), init_settings())
|
||||||
|
|
||||||
|
|
||||||
class Env(BaseConfig):
|
class Env(BaseSettings):
|
||||||
"""运行环境配置。大小写不敏感。
|
"""运行环境配置。大小写不敏感。
|
||||||
|
|
||||||
将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息。
|
将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息。
|
||||||
@@ -156,11 +378,8 @@ class Env(BaseConfig):
|
|||||||
NoneBot 将从 `.env.{environment}` 文件中加载配置。
|
NoneBot 将从 `.env.{environment}` 文件中加载配置。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Config:
|
|
||||||
env_file = ".env"
|
|
||||||
|
|
||||||
|
class Config(BaseSettings):
|
||||||
class Config(BaseConfig):
|
|
||||||
"""NoneBot 主要配置。大小写不敏感。
|
"""NoneBot 主要配置。大小写不敏感。
|
||||||
|
|
||||||
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
|
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
|
||||||
@@ -169,7 +388,8 @@ class Config(BaseConfig):
|
|||||||
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
|
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_env_file: DotenvType = ".env", ".env.prod"
|
if TYPE_CHECKING:
|
||||||
|
_env_file: Optional[DOTENV_TYPE] = ".env", ".env.prod"
|
||||||
|
|
||||||
# nonebot configs
|
# nonebot configs
|
||||||
driver: str = "~fastapi"
|
driver: str = "~fastapi"
|
||||||
@@ -241,9 +461,8 @@ class Config(BaseConfig):
|
|||||||
|
|
||||||
用法:
|
用法:
|
||||||
```conf
|
```conf
|
||||||
SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒
|
SESSION_EXPIRE_TIMEOUT=[-][DD]D[,][HH:MM:]SS[.ffffff]
|
||||||
SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff]
|
SESSION_EXPIRE_TIMEOUT=[±]P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
|
||||||
SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
|
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -254,11 +473,19 @@ class Config(BaseConfig):
|
|||||||
# custom configs can be assigned during nonebot.init
|
# custom configs can be assigned during nonebot.init
|
||||||
# or from env file using json loads
|
# or from env file using json loads
|
||||||
|
|
||||||
class Config:
|
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||||
env_file = ".env", ".env.prod"
|
model_config = SettingsConfig(env_file=(".env", ".env.prod"))
|
||||||
|
else: # pragma: pydantic-v1
|
||||||
|
|
||||||
|
class Config(SettingsConfig):
|
||||||
|
env_file = ".env", ".env.prod"
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"CustomEnvSettings": False,
|
"SettingsError": False,
|
||||||
"BaseConfig": False,
|
"BaseSettingsSource": False,
|
||||||
|
"InitSettingsSource": False,
|
||||||
|
"DotEnvSettingsSource": False,
|
||||||
|
"SettingsConfig": False,
|
||||||
|
"BaseSettings": False,
|
||||||
}
|
}
|
||||||
|
@@ -24,14 +24,11 @@ from typing import (
|
|||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydantic import BaseConfig
|
|
||||||
from pydantic.schema import get_annotation_from_field_info
|
|
||||||
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.typing import _DependentCallable
|
from nonebot.typing import _DependentCallable
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
from nonebot.utils import run_sync, is_coroutine_callable
|
||||||
|
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
|
||||||
|
|
||||||
from .utils import check_field_type, get_typed_signature
|
from .utils import check_field_type, get_typed_signature
|
||||||
|
|
||||||
@@ -69,10 +66,6 @@ class Param(abc.ABC, FieldInfo):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
class CustomConfig(BaseConfig):
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Dependent(Generic[R]):
|
class Dependent(Generic[R]):
|
||||||
"""依赖注入容器
|
"""依赖注入容器
|
||||||
@@ -125,12 +118,8 @@ class Dependent(Generic[R]):
|
|||||||
params = get_typed_signature(call).parameters.values()
|
params = get_typed_signature(call).parameters.values()
|
||||||
|
|
||||||
for param in params:
|
for param in params:
|
||||||
default_value = Required
|
if isinstance(param.default, Param):
|
||||||
if param.default != param.empty:
|
field_info = param.default
|
||||||
default_value = param.default
|
|
||||||
|
|
||||||
if isinstance(default_value, Param):
|
|
||||||
field_info = default_value
|
|
||||||
else:
|
else:
|
||||||
for allow_type in allow_types:
|
for allow_type in allow_types:
|
||||||
if field_info := allow_type._check_param(param, allow_types):
|
if field_info := allow_type._check_param(param, allow_types):
|
||||||
@@ -141,25 +130,13 @@ class Dependent(Generic[R]):
|
|||||||
f"for function {call} with type {param.annotation}"
|
f"for function {call} with type {param.annotation}"
|
||||||
)
|
)
|
||||||
|
|
||||||
default_value = field_info.default
|
|
||||||
|
|
||||||
annotation: Any = Any
|
annotation: Any = Any
|
||||||
required = default_value == Required
|
if param.annotation is not param.empty:
|
||||||
if param.annotation != param.empty:
|
|
||||||
annotation = param.annotation
|
annotation = param.annotation
|
||||||
annotation = get_annotation_from_field_info(
|
|
||||||
annotation, field_info, param.name
|
|
||||||
)
|
|
||||||
|
|
||||||
fields.append(
|
fields.append(
|
||||||
ModelField(
|
ModelField.construct(
|
||||||
name=param.name,
|
name=param.name, annotation=annotation, field_info=field_info
|
||||||
type_=annotation,
|
|
||||||
class_validators=None,
|
|
||||||
model_config=CustomConfig,
|
|
||||||
default=None if required else default_value,
|
|
||||||
required=required,
|
|
||||||
field_info=field_info,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -207,7 +184,7 @@ class Dependent(Generic[R]):
|
|||||||
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
||||||
param = cast(Param, field.field_info)
|
param = cast(Param, field.field_info)
|
||||||
value = await param._solve(**params)
|
value = await param._solve(**params)
|
||||||
if value is Undefined:
|
if value is PydanticUndefined:
|
||||||
value = field.get_default()
|
value = field.get_default()
|
||||||
v = check_field_type(field, value)
|
v = check_field_type(field, value)
|
||||||
return v if param.validate else value
|
return v if param.validate else value
|
||||||
|
@@ -8,10 +8,10 @@ import inspect
|
|||||||
from typing import Any, Dict, Callable, ForwardRef
|
from typing import Any, Dict, Callable, ForwardRef
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic.fields import ModelField
|
|
||||||
from pydantic.typing import evaluate_forwardref
|
|
||||||
|
|
||||||
from nonebot.exception import TypeMisMatch
|
from nonebot.exception import TypeMisMatch
|
||||||
|
from nonebot.typing import evaluate_forwardref
|
||||||
|
from nonebot.compat import DEFAULT_CONFIG, ModelField, model_field_validate
|
||||||
|
|
||||||
|
|
||||||
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||||
@@ -50,7 +50,7 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) ->
|
|||||||
def check_field_type(field: ModelField, value: Any) -> Any:
|
def check_field_type(field: ModelField, value: Any) -> Any:
|
||||||
"""检查字段类型是否匹配"""
|
"""检查字段类型是否匹配"""
|
||||||
|
|
||||||
v, errs_ = field.validate(value, {}, loc=())
|
try:
|
||||||
if errs_:
|
return model_field_validate(field, value, DEFAULT_CONFIG)
|
||||||
|
except ValueError:
|
||||||
raise TypeMisMatch(field, value)
|
raise TypeMisMatch(field, value)
|
||||||
return v
|
|
||||||
|
@@ -179,8 +179,7 @@ class WebSocket(BaseWebSocket):
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
class Driver(Mixin, NoneDriver):
|
class Driver(Mixin, NoneDriver): ...
|
||||||
...
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
Driver = combine_driver(NoneDriver, Mixin)
|
Driver = combine_driver(NoneDriver, Mixin)
|
||||||
|
@@ -15,14 +15,13 @@ FrontMatter:
|
|||||||
description: nonebot.drivers.fastapi 模块
|
description: nonebot.drivers.fastapi 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import contextlib
|
import contextlib
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||||
|
|
||||||
from pydantic import BaseSettings
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.config import Env
|
from nonebot.config import Env
|
||||||
from nonebot.drivers import ASGIMixin
|
from nonebot.drivers import ASGIMixin
|
||||||
@@ -32,6 +31,7 @@ from nonebot.drivers import Driver as BaseDriver
|
|||||||
from nonebot.config import Config as NoneBotConfig
|
from nonebot.config import Config as NoneBotConfig
|
||||||
from nonebot.drivers import Request as BaseRequest
|
from nonebot.drivers import Request as BaseRequest
|
||||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||||
|
from nonebot.compat import model_dump, type_validate_python
|
||||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -59,7 +59,7 @@ def catch_closed(func):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseSettings):
|
class Config(BaseModel):
|
||||||
"""FastAPI 驱动框架设置,详情参考 FastAPI 文档"""
|
"""FastAPI 驱动框架设置,详情参考 FastAPI 文档"""
|
||||||
|
|
||||||
fastapi_openapi_url: Optional[str] = None
|
fastapi_openapi_url: Optional[str] = None
|
||||||
@@ -83,9 +83,6 @@ class Config(BaseSettings):
|
|||||||
fastapi_extra: Dict[str, Any] = {}
|
fastapi_extra: Dict[str, Any] = {}
|
||||||
"""传递给 `FastAPI` 的其他参数。"""
|
"""传递给 `FastAPI` 的其他参数。"""
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra = "ignore"
|
|
||||||
|
|
||||||
|
|
||||||
class Driver(BaseDriver, ASGIMixin):
|
class Driver(BaseDriver, ASGIMixin):
|
||||||
"""FastAPI 驱动框架。"""
|
"""FastAPI 驱动框架。"""
|
||||||
@@ -93,7 +90,7 @@ class Driver(BaseDriver, ASGIMixin):
|
|||||||
def __init__(self, env: Env, config: NoneBotConfig):
|
def __init__(self, env: Env, config: NoneBotConfig):
|
||||||
super().__init__(env, config)
|
super().__init__(env, config)
|
||||||
|
|
||||||
self.fastapi_config: Config = Config(**config.dict())
|
self.fastapi_config: Config = type_validate_python(Config, model_dump(config))
|
||||||
|
|
||||||
self._server_app = FastAPI(
|
self._server_app = FastAPI(
|
||||||
lifespan=self._lifespan_manager,
|
lifespan=self._lifespan_manager,
|
||||||
|
@@ -72,8 +72,7 @@ class Mixin(HTTPClientMixin):
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
class Driver(Mixin, NoneDriver):
|
class Driver(Mixin, NoneDriver): ...
|
||||||
...
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
Driver = combine_driver(NoneDriver, Mixin)
|
Driver = combine_driver(NoneDriver, Mixin)
|
||||||
|
@@ -20,7 +20,7 @@ from functools import wraps
|
|||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from typing import Any, Dict, List, Tuple, Union, Optional, cast
|
from typing import Any, Dict, List, Tuple, Union, Optional, cast
|
||||||
|
|
||||||
from pydantic import BaseSettings
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.config import Env
|
from nonebot.config import Env
|
||||||
from nonebot.drivers import ASGIMixin
|
from nonebot.drivers import ASGIMixin
|
||||||
@@ -30,6 +30,7 @@ from nonebot.drivers import Driver as BaseDriver
|
|||||||
from nonebot.config import Config as NoneBotConfig
|
from nonebot.config import Config as NoneBotConfig
|
||||||
from nonebot.drivers import Request as BaseRequest
|
from nonebot.drivers import Request as BaseRequest
|
||||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||||
|
from nonebot.compat import model_dump, type_validate_python
|
||||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -58,7 +59,7 @@ def catch_closed(func):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseSettings):
|
class Config(BaseModel):
|
||||||
"""Quart 驱动框架设置"""
|
"""Quart 驱动框架设置"""
|
||||||
|
|
||||||
quart_reload: bool = False
|
quart_reload: bool = False
|
||||||
@@ -74,9 +75,6 @@ class Config(BaseSettings):
|
|||||||
quart_extra: Dict[str, Any] = {}
|
quart_extra: Dict[str, Any] = {}
|
||||||
"""传递给 `Quart` 的其他参数。"""
|
"""传递给 `Quart` 的其他参数。"""
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra = "ignore"
|
|
||||||
|
|
||||||
|
|
||||||
class Driver(BaseDriver, ASGIMixin):
|
class Driver(BaseDriver, ASGIMixin):
|
||||||
"""Quart 驱动框架"""
|
"""Quart 驱动框架"""
|
||||||
@@ -84,7 +82,7 @@ class Driver(BaseDriver, ASGIMixin):
|
|||||||
def __init__(self, env: Env, config: NoneBotConfig):
|
def __init__(self, env: Env, config: NoneBotConfig):
|
||||||
super().__init__(env, config)
|
super().__init__(env, config)
|
||||||
|
|
||||||
self.quart_config = Config(**config.dict())
|
self.quart_config = type_validate_python(Config, model_dump(config))
|
||||||
|
|
||||||
self._server_app = Quart(
|
self._server_app = Quart(
|
||||||
self.__class__.__qualname__, **self.quart_config.quart_extra
|
self.__class__.__qualname__, **self.quart_config.quart_extra
|
||||||
|
@@ -50,10 +50,7 @@ def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
|||||||
try:
|
try:
|
||||||
return await func(*args, **kwargs)
|
return await func(*args, **kwargs)
|
||||||
except ConnectionClosed as e:
|
except ConnectionClosed as e:
|
||||||
if e.rcvd_then_sent:
|
raise WebSocketClosed(e.code, e.reason)
|
||||||
raise WebSocketClosed(e.rcvd.code, e.rcvd.reason) # type: ignore
|
|
||||||
else:
|
|
||||||
raise WebSocketClosed(e.sent.code, e.sent.reason) # type: ignore
|
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
@@ -131,8 +128,7 @@ class WebSocket(BaseWebSocket):
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
class Driver(Mixin, NoneDriver):
|
class Driver(Mixin, NoneDriver): ...
|
||||||
...
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
Driver = combine_driver(NoneDriver, Mixin)
|
Driver = combine_driver(NoneDriver, Mixin)
|
||||||
|
@@ -31,7 +31,7 @@ FrontMatter:
|
|||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from pydantic.fields import ModelField
|
from nonebot.compat import ModelField
|
||||||
|
|
||||||
|
|
||||||
class NoneBotException(Exception):
|
class NoneBotException(Exception):
|
||||||
|
@@ -14,8 +14,7 @@ if TYPE_CHECKING:
|
|||||||
from .message import Message, MessageSegment
|
from .message import Message, MessageSegment
|
||||||
|
|
||||||
class _ApiCall(Protocol):
|
class _ApiCall(Protocol):
|
||||||
async def __call__(self, **kwargs: Any) -> Any:
|
async def __call__(self, **kwargs: Any) -> Any: ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class Bot(abc.ABC):
|
class Bot(abc.ABC):
|
||||||
|
@@ -4,6 +4,7 @@ from typing import Any, Type, TypeVar
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.utils import DataclassEncoder
|
from nonebot.utils import DataclassEncoder
|
||||||
|
from nonebot.compat import PYDANTIC_V2, ConfigDict
|
||||||
|
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
|
||||||
@@ -13,15 +14,21 @@ E = TypeVar("E", bound="Event")
|
|||||||
class Event(abc.ABC, BaseModel):
|
class Event(abc.ABC, BaseModel):
|
||||||
"""Event 基类。提供获取关键信息的方法,其余信息可直接获取。"""
|
"""Event 基类。提供获取关键信息的方法,其余信息可直接获取。"""
|
||||||
|
|
||||||
class Config:
|
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||||
extra = "allow"
|
model_config = ConfigDict(extra="allow")
|
||||||
json_encoders = {Message: DataclassEncoder}
|
else: # pragma: pydantic-v1
|
||||||
|
|
||||||
@classmethod
|
class Config(ConfigDict):
|
||||||
def validate(cls: Type["E"], value: Any) -> "E":
|
extra = "allow" # type: ignore
|
||||||
if isinstance(value, Event) and not isinstance(value, cls):
|
json_encoders = {Message: DataclassEncoder}
|
||||||
raise TypeError(f"{value} is incompatible with Event type {cls}")
|
|
||||||
return super().validate(value)
|
if not PYDANTIC_V2: # pragma: pydantic-v1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls: Type["E"], value: Any) -> "E":
|
||||||
|
if isinstance(value, Event) and not isinstance(value, cls):
|
||||||
|
raise TypeError(f"{value} is incompatible with Event type {cls}")
|
||||||
|
return super().validate(value)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_type(self) -> str:
|
def get_type(self) -> str:
|
||||||
|
@@ -17,7 +17,7 @@ from typing import (
|
|||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydantic import parse_obj_as
|
from nonebot.compat import custom_validation, type_validate_python
|
||||||
|
|
||||||
from .template import MessageTemplate
|
from .template import MessageTemplate
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@ TMS = TypeVar("TMS", bound="MessageSegment")
|
|||||||
TM = TypeVar("TM", bound="Message")
|
TM = TypeVar("TM", bound="Message")
|
||||||
|
|
||||||
|
|
||||||
|
@custom_validation
|
||||||
@dataclass
|
@dataclass
|
||||||
class MessageSegment(abc.ABC, Generic[TM]):
|
class MessageSegment(abc.ABC, Generic[TM]):
|
||||||
"""消息段基类"""
|
"""消息段基类"""
|
||||||
@@ -65,6 +66,8 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
|||||||
def _validate(cls, value) -> Self:
|
def _validate(cls, value) -> Self:
|
||||||
if isinstance(value, cls):
|
if isinstance(value, cls):
|
||||||
return value
|
return value
|
||||||
|
if isinstance(value, MessageSegment):
|
||||||
|
raise ValueError(f"Type {type(value)} can not be converted to {cls}")
|
||||||
if not isinstance(value, dict):
|
if not isinstance(value, dict):
|
||||||
raise ValueError(f"Expected dict for MessageSegment, got {type(value)}")
|
raise ValueError(f"Expected dict for MessageSegment, got {type(value)}")
|
||||||
if "type" not in value:
|
if "type" not in value:
|
||||||
@@ -97,6 +100,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@custom_validation
|
||||||
class Message(List[TMS], abc.ABC):
|
class Message(List[TMS], abc.ABC):
|
||||||
"""消息序列
|
"""消息序列
|
||||||
|
|
||||||
@@ -158,9 +162,9 @@ class Message(List[TMS], abc.ABC):
|
|||||||
elif isinstance(value, str):
|
elif isinstance(value, str):
|
||||||
pass
|
pass
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
value = parse_obj_as(cls.get_segment_class(), value)
|
value = type_validate_python(cls.get_segment_class(), value)
|
||||||
elif isinstance(value, Iterable):
|
elif isinstance(value, Iterable):
|
||||||
value = [parse_obj_as(cls.get_segment_class(), v) for v in value]
|
value = [type_validate_python(cls.get_segment_class(), v) for v in value]
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Expected str, dict or iterable for Message, got {type(value)}"
|
f"Expected str, dict or iterable for Message, got {type(value)}"
|
||||||
@@ -281,7 +285,7 @@ class Message(List[TMS], abc.ABC):
|
|||||||
消息内是否存在给定消息段或给定类型的消息段
|
消息内是否存在给定消息段或给定类型的消息段
|
||||||
"""
|
"""
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return bool(next((seg for seg in self if seg.type == value), None))
|
return next((seg for seg in self if seg.type == value), None) is not None
|
||||||
return super().__contains__(value)
|
return super().__contains__(value)
|
||||||
|
|
||||||
def has(self, value: Union[TMS, str]) -> bool:
|
def has(self, value: Union[TMS, str]) -> bool:
|
||||||
|
@@ -20,9 +20,16 @@ from typing import (
|
|||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from _string import formatter_field_name_split # type: ignore
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .message import Message, MessageSegment
|
from .message import Message, MessageSegment
|
||||||
|
|
||||||
|
def formatter_field_name_split( # noqa: F811
|
||||||
|
field_name: str,
|
||||||
|
) -> Tuple[str, List[Tuple[bool, str]]]: ...
|
||||||
|
|
||||||
|
|
||||||
TM = TypeVar("TM", bound="Message")
|
TM = TypeVar("TM", bound="Message")
|
||||||
TF = TypeVar("TF", str, "Message")
|
TF = TypeVar("TF", str, "Message")
|
||||||
|
|
||||||
@@ -36,26 +43,35 @@ class MessageTemplate(Formatter, Generic[TF]):
|
|||||||
参数:
|
参数:
|
||||||
template: 模板
|
template: 模板
|
||||||
factory: 消息类型工厂,默认为 `str`
|
factory: 消息类型工厂,默认为 `str`
|
||||||
|
private_getattr: 是否允许在模板中访问私有属性,默认为 `False`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __init__(
|
def __init__(
|
||||||
self: "MessageTemplate[str]", template: str, factory: Type[str] = str
|
self: "MessageTemplate[str]",
|
||||||
) -> None:
|
template: str,
|
||||||
...
|
factory: Type[str] = str,
|
||||||
|
private_getattr: bool = False,
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __init__(
|
def __init__(
|
||||||
self: "MessageTemplate[TM]", template: Union[str, TM], factory: Type[TM]
|
self: "MessageTemplate[TM]",
|
||||||
) -> None:
|
template: Union[str, TM],
|
||||||
...
|
factory: Type[TM],
|
||||||
|
private_getattr: bool = False,
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, template: Union[str, TM], factory: Union[Type[str], Type[TM]] = str
|
self,
|
||||||
|
template: Union[str, TM],
|
||||||
|
factory: Union[Type[str], Type[TM]] = str,
|
||||||
|
private_getattr: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.template: TF = template # type: ignore
|
self.template: TF = template # type: ignore
|
||||||
self.factory: Type[TF] = factory # type: ignore
|
self.factory: Type[TF] = factory # type: ignore
|
||||||
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
||||||
|
self.private_getattr = private_getattr
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
|
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
|
||||||
@@ -167,6 +183,19 @@ class MessageTemplate(Formatter, Generic[TF]):
|
|||||||
|
|
||||||
return functools.reduce(self._add, results), auto_arg_index
|
return functools.reduce(self._add, results), auto_arg_index
|
||||||
|
|
||||||
|
def get_field(
|
||||||
|
self, field_name: str, args: Sequence[Any], kwargs: Mapping[str, Any]
|
||||||
|
) -> Tuple[Any, Union[int, str]]:
|
||||||
|
first, rest = formatter_field_name_split(field_name)
|
||||||
|
obj = self.get_value(first, args, kwargs)
|
||||||
|
|
||||||
|
for is_attr, value in rest:
|
||||||
|
if not self.private_getattr and value.startswith("_"):
|
||||||
|
raise ValueError("Cannot access private attribute")
|
||||||
|
obj = getattr(obj, value) if is_attr else obj[value]
|
||||||
|
|
||||||
|
return obj, first
|
||||||
|
|
||||||
def format_field(self, value: Any, format_spec: str) -> Any:
|
def format_field(self, value: Any, format_spec: str) -> Any:
|
||||||
formatter: Optional[FormatSpecFunc] = self.format_specs.get(format_spec)
|
formatter: Optional[FormatSpecFunc] = self.format_specs.get(format_spec)
|
||||||
if formatter is None and not issubclass(self.factory, str):
|
if formatter is None and not issubclass(self.factory, str):
|
||||||
|
@@ -6,18 +6,15 @@ D = TypeVar("D", bound="Driver")
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
class CombinedDriver(Driver, Mixin):
|
class CombinedDriver(Driver, Mixin): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def combine_driver(driver: Type[D]) -> Type[D]:
|
def combine_driver(driver: Type[D]) -> Type[D]: ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]:
|
def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
def combine_driver(
|
def combine_driver(
|
||||||
|
@@ -65,12 +65,10 @@ class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
|||||||
return self.provider.items()
|
return self.provider.items()
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get(self, key: int) -> Optional[List[Type["Matcher"]]]:
|
def get(self, key: int) -> Optional[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]: ...
|
||||||
...
|
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
self, key: int, default: Optional[T] = None
|
self, key: int, default: Optional[T] = None
|
||||||
|
@@ -262,16 +262,20 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
"type": type_,
|
"type": type_,
|
||||||
"rule": rule or Rule(),
|
"rule": rule or Rule(),
|
||||||
"permission": permission or Permission(),
|
"permission": permission or Permission(),
|
||||||
"handlers": [
|
"handlers": (
|
||||||
handler
|
[
|
||||||
if isinstance(handler, Dependent)
|
(
|
||||||
else Dependent[Any].parse(
|
handler
|
||||||
call=handler, allow_types=cls.HANDLER_PARAM_TYPES
|
if isinstance(handler, Dependent)
|
||||||
)
|
else Dependent[Any].parse(
|
||||||
for handler in handlers
|
call=handler, allow_types=cls.HANDLER_PARAM_TYPES
|
||||||
]
|
)
|
||||||
if handlers
|
)
|
||||||
else [],
|
for handler in handlers
|
||||||
|
]
|
||||||
|
if handlers
|
||||||
|
else []
|
||||||
|
),
|
||||||
"temp": temp,
|
"temp": temp,
|
||||||
"expire_time": (
|
"expire_time": (
|
||||||
expire_time
|
expire_time
|
||||||
@@ -658,12 +662,10 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
raise SkippedException
|
raise SkippedException
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_receive(self, id: str) -> Union[Event, None]:
|
def get_receive(self, id: str) -> Union[Event, None]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_receive(self, id: str, default: T) -> Union[Event, T]:
|
def get_receive(self, id: str, default: T) -> Union[Event, T]: ...
|
||||||
...
|
|
||||||
|
|
||||||
def get_receive(
|
def get_receive(
|
||||||
self, id: str, default: Optional[T] = None
|
self, id: str, default: Optional[T] = None
|
||||||
@@ -680,12 +682,10 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
self.state[LAST_RECEIVE_KEY] = event
|
self.state[LAST_RECEIVE_KEY] = event
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_last_receive(self) -> Union[Event, None]:
|
def get_last_receive(self) -> Union[Event, None]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_last_receive(self, default: T) -> Union[Event, T]:
|
def get_last_receive(self, default: T) -> Union[Event, T]: ...
|
||||||
...
|
|
||||||
|
|
||||||
def get_last_receive(
|
def get_last_receive(
|
||||||
self, default: Optional[T] = None
|
self, default: Optional[T] = None
|
||||||
@@ -697,12 +697,10 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
return self.state.get(LAST_RECEIVE_KEY, default)
|
return self.state.get(LAST_RECEIVE_KEY, default)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_arg(self, key: str) -> Union[Message, None]:
|
def get_arg(self, key: str) -> Union[Message, None]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_arg(self, key: str, default: T) -> Union[Message, T]:
|
def get_arg(self, key: str, default: T) -> Union[Message, T]: ...
|
||||||
...
|
|
||||||
|
|
||||||
def get_arg(
|
def get_arg(
|
||||||
self, key: str, default: Optional[T] = None
|
self, key: str, default: Optional[T] = None
|
||||||
@@ -724,12 +722,10 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
self.state[REJECT_TARGET] = target
|
self.state[REJECT_TARGET] = target
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_target(self) -> Union[str, None]:
|
def get_target(self) -> Union[str, None]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_target(self, default: T) -> Union[str, T]:
|
def get_target(self, default: T) -> Union[str, T]: ...
|
||||||
...
|
|
||||||
|
|
||||||
def get_target(self, default: Optional[T] = None) -> Optional[Union[str, T]]:
|
def get_target(self, default: Optional[T] = None) -> Optional[Union[str, T]]:
|
||||||
return self.state.get(REJECT_TARGET, default)
|
return self.state.get(REJECT_TARGET, default)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
from typing_extensions import Self, Annotated, override
|
|
||||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||||
|
from typing_extensions import Self, Annotated, get_args, override, get_origin
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
@@ -14,12 +14,12 @@ from typing import (
|
|||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydantic.typing import get_args, get_origin
|
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
||||||
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
|
|
||||||
|
|
||||||
|
from nonebot.dependencies import Param, Dependent
|
||||||
from nonebot.dependencies.utils import check_field_type
|
from nonebot.dependencies.utils import check_field_type
|
||||||
from nonebot.dependencies import Param, Dependent, CustomConfig
|
|
||||||
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
||||||
|
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
|
||||||
from nonebot.utils import (
|
from nonebot.utils import (
|
||||||
get_name,
|
get_name,
|
||||||
run_sync,
|
run_sync,
|
||||||
@@ -34,23 +34,6 @@ if TYPE_CHECKING:
|
|||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot.adapters import Bot, Event
|
||||||
|
|
||||||
EXTRA_FIELD_INFO = (
|
|
||||||
"gt",
|
|
||||||
"lt",
|
|
||||||
"ge",
|
|
||||||
"le",
|
|
||||||
"multiple_of",
|
|
||||||
"allow_inf_nan",
|
|
||||||
"max_digits",
|
|
||||||
"decimal_places",
|
|
||||||
"min_items",
|
|
||||||
"max_items",
|
|
||||||
"unique_items",
|
|
||||||
"min_length",
|
|
||||||
"max_length",
|
|
||||||
"regex",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DependsInner:
|
class DependsInner:
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -58,7 +41,7 @@ class DependsInner:
|
|||||||
dependency: Optional[T_Handler] = None,
|
dependency: Optional[T_Handler] = None,
|
||||||
*,
|
*,
|
||||||
use_cache: bool = True,
|
use_cache: bool = True,
|
||||||
validate: Union[bool, FieldInfo] = False,
|
validate: Union[bool, PydanticFieldInfo] = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.dependency = dependency
|
self.dependency = dependency
|
||||||
self.use_cache = use_cache
|
self.use_cache = use_cache
|
||||||
@@ -75,7 +58,7 @@ def Depends(
|
|||||||
dependency: Optional[T_Handler] = None,
|
dependency: Optional[T_Handler] = None,
|
||||||
*,
|
*,
|
||||||
use_cache: bool = True,
|
use_cache: bool = True,
|
||||||
validate: Union[bool, FieldInfo] = False,
|
validate: Union[bool, PydanticFieldInfo] = False,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""子依赖装饰器
|
"""子依赖装饰器
|
||||||
|
|
||||||
@@ -113,24 +96,32 @@ class DependParam(Param):
|
|||||||
本注入应该具有最高优先级,因此应该在其他参数之前检查。
|
本注入应该具有最高优先级,因此应该在其他参数之前检查。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, *args, dependent: Dependent, use_cache: bool, **kwargs: Any
|
||||||
|
) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.dependent = dependent
|
||||||
|
self.use_cache = use_cache
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Depends({self.extra['dependent']})"
|
return f"Depends({self.dependent}, use_cache={self.use_cache})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_field(
|
def _from_field(
|
||||||
cls, sub_dependent: Dependent, use_cache: bool, validate: Union[bool, FieldInfo]
|
cls,
|
||||||
|
sub_dependent: Dependent,
|
||||||
|
use_cache: bool,
|
||||||
|
validate: Union[bool, PydanticFieldInfo],
|
||||||
) -> Self:
|
) -> Self:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if isinstance(validate, FieldInfo):
|
if isinstance(validate, PydanticFieldInfo):
|
||||||
kwargs.update((k, getattr(validate, k)) for k in EXTRA_FIELD_INFO)
|
kwargs.update(extract_field_info(validate))
|
||||||
|
|
||||||
return cls(
|
kwargs["validate"] = bool(validate)
|
||||||
Required,
|
kwargs["dependent"] = sub_dependent
|
||||||
validate=bool(validate),
|
kwargs["use_cache"] = use_cache
|
||||||
**kwargs,
|
|
||||||
dependent=sub_dependent,
|
return cls(**kwargs)
|
||||||
use_cache=use_cache,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@override
|
@override
|
||||||
@@ -191,10 +182,10 @@ class DependParam(Param):
|
|||||||
dependency_cache: Optional[T_DependencyCache] = None,
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
use_cache: bool = self.extra["use_cache"]
|
use_cache: bool = self.use_cache
|
||||||
dependency_cache = {} if dependency_cache is None else dependency_cache
|
dependency_cache = {} if dependency_cache is None else dependency_cache
|
||||||
|
|
||||||
sub_dependent: Dependent = self.extra["dependent"]
|
sub_dependent: Dependent = self.dependent
|
||||||
call = cast(Callable[..., Any], sub_dependent.call)
|
call = cast(Callable[..., Any], sub_dependent.call)
|
||||||
|
|
||||||
# solve sub dependency with current cache
|
# solve sub dependency with current cache
|
||||||
@@ -231,8 +222,7 @@ class DependParam(Param):
|
|||||||
@override
|
@override
|
||||||
async def _check(self, **kwargs: Any) -> None:
|
async def _check(self, **kwargs: Any) -> None:
|
||||||
# run sub dependent pre-checkers
|
# run sub dependent pre-checkers
|
||||||
sub_dependent: Dependent = self.extra["dependent"]
|
await self.dependent.check(**kwargs)
|
||||||
await sub_dependent.check(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class BotParam(Param):
|
class BotParam(Param):
|
||||||
@@ -243,14 +233,16 @@ class BotParam(Param):
|
|||||||
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
|
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
|
||||||
|
) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.checker = checker
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
"BotParam("
|
"BotParam("
|
||||||
+ (
|
+ (repr(self.checker.annotation) if self.checker is not None else "")
|
||||||
repr(cast(ModelField, checker).type_)
|
|
||||||
if (checker := self.extra.get("checker"))
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
+ ")"
|
+ ")"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -265,18 +257,13 @@ class BotParam(Param):
|
|||||||
if generic_check_issubclass(param.annotation, Bot):
|
if generic_check_issubclass(param.annotation, Bot):
|
||||||
checker: Optional[ModelField] = None
|
checker: Optional[ModelField] = None
|
||||||
if param.annotation is not Bot:
|
if param.annotation is not Bot:
|
||||||
checker = ModelField(
|
checker = ModelField.construct(
|
||||||
name=param.name,
|
name=param.name, annotation=param.annotation, field_info=FieldInfo()
|
||||||
type_=param.annotation,
|
|
||||||
class_validators=None,
|
|
||||||
model_config=CustomConfig,
|
|
||||||
default=None,
|
|
||||||
required=True,
|
|
||||||
)
|
)
|
||||||
return cls(Required, checker=checker)
|
return cls(checker=checker)
|
||||||
# legacy: param is named "bot" and has no type annotation
|
# legacy: param is named "bot" and has no type annotation
|
||||||
elif param.annotation == param.empty and param.name == "bot":
|
elif param.annotation == param.empty and param.name == "bot":
|
||||||
return cls(Required)
|
return cls()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||||
@@ -284,8 +271,8 @@ class BotParam(Param):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
||||||
if checker := self.extra.get("checker"):
|
if self.checker is not None:
|
||||||
check_field_type(checker, bot)
|
check_field_type(self.checker, bot)
|
||||||
|
|
||||||
|
|
||||||
class EventParam(Param):
|
class EventParam(Param):
|
||||||
@@ -296,14 +283,16 @@ class EventParam(Param):
|
|||||||
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
|
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
|
||||||
|
) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.checker = checker
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
"EventParam("
|
"EventParam("
|
||||||
+ (
|
+ (repr(self.checker.annotation) if self.checker is not None else "")
|
||||||
repr(cast(ModelField, checker).type_)
|
|
||||||
if (checker := self.extra.get("checker"))
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
+ ")"
|
+ ")"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -318,18 +307,13 @@ class EventParam(Param):
|
|||||||
if generic_check_issubclass(param.annotation, Event):
|
if generic_check_issubclass(param.annotation, Event):
|
||||||
checker: Optional[ModelField] = None
|
checker: Optional[ModelField] = None
|
||||||
if param.annotation is not Event:
|
if param.annotation is not Event:
|
||||||
checker = ModelField(
|
checker = ModelField.construct(
|
||||||
name=param.name,
|
name=param.name, annotation=param.annotation, field_info=FieldInfo()
|
||||||
type_=param.annotation,
|
|
||||||
class_validators=None,
|
|
||||||
model_config=CustomConfig,
|
|
||||||
default=None,
|
|
||||||
required=True,
|
|
||||||
)
|
)
|
||||||
return cls(Required, checker=checker)
|
return cls(checker=checker)
|
||||||
# legacy: param is named "event" and has no type annotation
|
# legacy: param is named "event" and has no type annotation
|
||||||
elif param.annotation == param.empty and param.name == "event":
|
elif param.annotation == param.empty and param.name == "event":
|
||||||
return cls(Required)
|
return cls()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||||
@@ -337,8 +321,8 @@ class EventParam(Param):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
||||||
if checker := self.extra.get("checker", None):
|
if self.checker is not None:
|
||||||
check_field_type(checker, event)
|
check_field_type(self.checker, event)
|
||||||
|
|
||||||
|
|
||||||
class StateParam(Param):
|
class StateParam(Param):
|
||||||
@@ -359,10 +343,10 @@ class StateParam(Param):
|
|||||||
) -> Optional[Self]:
|
) -> Optional[Self]:
|
||||||
# param type is T_State
|
# param type is T_State
|
||||||
if param.annotation is T_State:
|
if param.annotation is T_State:
|
||||||
return cls(Required)
|
return cls()
|
||||||
# legacy: param is named "state" and has no type annotation
|
# legacy: param is named "state" and has no type annotation
|
||||||
elif param.annotation == param.empty and param.name == "state":
|
elif param.annotation == param.empty and param.name == "state":
|
||||||
return cls(Required)
|
return cls()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||||
@@ -377,8 +361,18 @@ class MatcherParam(Param):
|
|||||||
为保证兼容性,本注入还会解析名为 `matcher` 且没有类型注解的参数。
|
为保证兼容性,本注入还会解析名为 `matcher` 且没有类型注解的参数。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
|
||||||
|
) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.checker = checker
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "MatcherParam()"
|
return (
|
||||||
|
"MatcherParam("
|
||||||
|
+ (repr(self.checker.annotation) if self.checker is not None else "")
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@override
|
@override
|
||||||
@@ -391,18 +385,13 @@ class MatcherParam(Param):
|
|||||||
if generic_check_issubclass(param.annotation, Matcher):
|
if generic_check_issubclass(param.annotation, Matcher):
|
||||||
checker: Optional[ModelField] = None
|
checker: Optional[ModelField] = None
|
||||||
if param.annotation is not Matcher:
|
if param.annotation is not Matcher:
|
||||||
checker = ModelField(
|
checker = ModelField.construct(
|
||||||
name=param.name,
|
name=param.name, annotation=param.annotation, field_info=FieldInfo()
|
||||||
type_=param.annotation,
|
|
||||||
class_validators=None,
|
|
||||||
model_config=CustomConfig,
|
|
||||||
default=None,
|
|
||||||
required=True,
|
|
||||||
)
|
)
|
||||||
return cls(Required, checker=checker)
|
return cls(checker=checker)
|
||||||
# legacy: param is named "matcher" and has no type annotation
|
# legacy: param is named "matcher" and has no type annotation
|
||||||
elif param.annotation == param.empty and param.name == "matcher":
|
elif param.annotation == param.empty and param.name == "matcher":
|
||||||
return cls(Required)
|
return cls()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||||
@@ -410,16 +399,16 @@ class MatcherParam(Param):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
async def _check(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
async def _check(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||||
if checker := self.extra.get("checker", None):
|
if self.checker is not None:
|
||||||
check_field_type(checker, matcher)
|
check_field_type(self.checker, matcher)
|
||||||
|
|
||||||
|
|
||||||
class ArgInner:
|
class ArgInner:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, key: Optional[str], type: Literal["message", "str", "plaintext"]
|
self, key: Optional[str], type: Literal["message", "str", "plaintext"]
|
||||||
) -> None:
|
) -> None:
|
||||||
self.key = key
|
self.key: Optional[str] = key
|
||||||
self.type = type
|
self.type: Literal["message", "str", "plaintext"] = type
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ArgInner(key={self.key!r}, type={self.type!r})"
|
return f"ArgInner(key={self.key!r}, type={self.type!r})"
|
||||||
@@ -449,8 +438,19 @@ class ArgParam(Param):
|
|||||||
留空则会根据参数名称获取。
|
留空则会根据参数名称获取。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args,
|
||||||
|
key: str,
|
||||||
|
type: Literal["message", "str", "plaintext"],
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.key = key
|
||||||
|
self.type = type
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
|
return f"ArgParam(key={self.key!r}, type={self.type!r})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@override
|
@override
|
||||||
@@ -458,22 +458,19 @@ class ArgParam(Param):
|
|||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional[Self]:
|
) -> Optional[Self]:
|
||||||
if isinstance(param.default, ArgInner):
|
if isinstance(param.default, ArgInner):
|
||||||
return cls(
|
return cls(key=param.default.key or param.name, type=param.default.type)
|
||||||
Required, key=param.default.key or param.name, type=param.default.type
|
|
||||||
)
|
|
||||||
elif get_origin(param.annotation) is Annotated:
|
elif get_origin(param.annotation) is Annotated:
|
||||||
for arg in get_args(param.annotation)[:0:-1]:
|
for arg in get_args(param.annotation)[:0:-1]:
|
||||||
if isinstance(arg, ArgInner):
|
if isinstance(arg, ArgInner):
|
||||||
return cls(Required, key=arg.key or param.name, type=arg.type)
|
return cls(key=arg.key or param.name, type=arg.type)
|
||||||
|
|
||||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||||
key: str = self.extra["key"]
|
message = matcher.get_arg(self.key)
|
||||||
message = matcher.get_arg(key)
|
|
||||||
if message is None:
|
if message is None:
|
||||||
return message
|
return message
|
||||||
if self.extra["type"] == "message":
|
if self.type == "message":
|
||||||
return message
|
return message
|
||||||
elif self.extra["type"] == "str":
|
elif self.type == "str":
|
||||||
return str(message)
|
return str(message)
|
||||||
else:
|
else:
|
||||||
return message.extract_plain_text()
|
return message.extract_plain_text()
|
||||||
@@ -497,10 +494,10 @@ class ExceptionParam(Param):
|
|||||||
) -> Optional[Self]:
|
) -> Optional[Self]:
|
||||||
# param type is Exception(s) or subclass(es) of Exception or None
|
# param type is Exception(s) or subclass(es) of Exception or None
|
||||||
if generic_check_issubclass(param.annotation, Exception):
|
if generic_check_issubclass(param.annotation, Exception):
|
||||||
return cls(Required)
|
return cls()
|
||||||
# legacy: param is named "exception" and has no type annotation
|
# legacy: param is named "exception" and has no type annotation
|
||||||
elif param.annotation == param.empty and param.name == "exception":
|
elif param.annotation == param.empty and param.name == "exception":
|
||||||
return cls(Required)
|
return cls()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
||||||
@@ -524,11 +521,11 @@ class DefaultParam(Param):
|
|||||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional[Self]:
|
) -> Optional[Self]:
|
||||||
if param.default != param.empty:
|
if param.default != param.empty:
|
||||||
return cls(param.default)
|
return cls(default=param.default)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _solve(self, **kwargs: Any) -> Any:
|
async def _solve(self, **kwargs: Any) -> Any:
|
||||||
return Undefined
|
return PydanticUndefined
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
|
@@ -39,10 +39,12 @@ class Permission:
|
|||||||
|
|
||||||
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
|
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
|
||||||
self.checkers: Set[Dependent[bool]] = {
|
self.checkers: Set[Dependent[bool]] = {
|
||||||
checker
|
(
|
||||||
if isinstance(checker, Dependent)
|
checker
|
||||||
else Dependent[bool].parse(
|
if isinstance(checker, Dependent)
|
||||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
else Dependent[bool].parse(
|
||||||
|
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||||
|
)
|
||||||
)
|
)
|
||||||
for checker in checkers
|
for checker in checkers
|
||||||
}
|
}
|
||||||
|
@@ -38,10 +38,12 @@ class Rule:
|
|||||||
|
|
||||||
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
|
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
|
||||||
self.checkers: Set[Dependent[bool]] = {
|
self.checkers: Set[Dependent[bool]] = {
|
||||||
checker
|
(
|
||||||
if isinstance(checker, Dependent)
|
checker
|
||||||
else Dependent[bool].parse(
|
if isinstance(checker, Dependent)
|
||||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
else Dependent[bool].parse(
|
||||||
|
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||||
|
)
|
||||||
)
|
)
|
||||||
for checker in checkers
|
for checker in checkers
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,18 @@ FrontMatter:
|
|||||||
description: nonebot.params 模块
|
description: nonebot.params 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, List, Match, Tuple, Union, Optional
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Match,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Literal,
|
||||||
|
Callable,
|
||||||
|
Optional,
|
||||||
|
overload,
|
||||||
|
)
|
||||||
|
|
||||||
from nonebot.typing import T_State
|
from nonebot.typing import T_State
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
@@ -147,13 +158,34 @@ def RegexMatched() -> Match[str]:
|
|||||||
return Depends(_regex_matched, use_cache=False)
|
return Depends(_regex_matched, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
def _regex_str(state: T_State) -> str:
|
def _regex_str(
|
||||||
return _regex_matched(state).group()
|
groups: Tuple[Union[str, int], ...]
|
||||||
|
) -> Callable[[T_State], Union[str, Tuple[Union[str, Any], ...], Any]]:
|
||||||
|
def _regex_str_dependency(
|
||||||
|
state: T_State,
|
||||||
|
) -> Union[str, Tuple[Union[str, Any], ...], Any]:
|
||||||
|
return _regex_matched(state).group(*groups)
|
||||||
|
|
||||||
|
return _regex_str_dependency
|
||||||
|
|
||||||
|
|
||||||
def RegexStr() -> str:
|
@overload
|
||||||
|
def RegexStr(__group: Literal[0] = 0) -> str: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def RegexStr(__group: Union[str, int]) -> Union[str, Any]: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def RegexStr(
|
||||||
|
__group1: Union[str, int], __group2: Union[str, int], *groups: Union[str, int]
|
||||||
|
) -> Tuple[Union[str, Any], ...]: ...
|
||||||
|
|
||||||
|
|
||||||
|
def RegexStr(*groups: Union[str, int]) -> Union[str, Tuple[Union[str, Any], ...], Any]:
|
||||||
"""正则匹配结果文本"""
|
"""正则匹配结果文本"""
|
||||||
return Depends(_regex_str, use_cache=False)
|
return Depends(_regex_str(groups), use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
def _regex_group(state: T_State) -> Tuple[Any, ...]:
|
def _regex_group(state: T_State) -> Tuple[Any, ...]:
|
||||||
|
@@ -39,7 +39,14 @@ FrontMatter:
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from typing import Set, Dict, List, Tuple, Optional
|
from typing import Set, Dict, List, Type, Tuple, TypeVar, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from nonebot import get_driver
|
||||||
|
from nonebot.compat import model_dump, type_validate_python
|
||||||
|
|
||||||
|
C = TypeVar("C", bound=BaseModel)
|
||||||
|
|
||||||
_plugins: Dict[str, "Plugin"] = {}
|
_plugins: Dict[str, "Plugin"] = {}
|
||||||
_managers: List["PluginManager"] = []
|
_managers: List["PluginManager"] = []
|
||||||
@@ -108,6 +115,11 @@ def get_available_plugin_names() -> Set[str]:
|
|||||||
return {*chain.from_iterable(manager.available_plugins for manager in _managers)}
|
return {*chain.from_iterable(manager.available_plugins for manager in _managers)}
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin_config(config: Type[C]) -> C:
|
||||||
|
"""从全局配置获取当前插件需要的配置项。"""
|
||||||
|
return type_validate_python(config, model_dump(get_driver().config))
|
||||||
|
|
||||||
|
|
||||||
from .on import on as on
|
from .on import on as on
|
||||||
from .manager import PluginManager
|
from .manager import PluginManager
|
||||||
from .on import on_type as on_type
|
from .on import on_type as on_type
|
||||||
|
@@ -213,8 +213,10 @@ def inherit_supported_adapters(*names: str) -> Optional[Set[str]]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return final_supported and {
|
return final_supported and {
|
||||||
f"nonebot.adapters.{adapter_name[1:]}"
|
(
|
||||||
if adapter_name.startswith("~")
|
f"nonebot.adapters.{adapter_name[1:]}"
|
||||||
else adapter_name
|
if adapter_name.startswith("~")
|
||||||
|
else adapter_name
|
||||||
|
)
|
||||||
for adapter_name in final_supported
|
for adapter_name in final_supported
|
||||||
}
|
}
|
||||||
|
@@ -19,4 +19,5 @@ echo = on_command("echo", to_me())
|
|||||||
|
|
||||||
@echo.handle()
|
@echo.handle()
|
||||||
async def handle_echo(message: Message = CommandArg()):
|
async def handle_echo(message: Message = CommandArg()):
|
||||||
await echo.send(message=message)
|
if any((not seg.is_text()) or str(seg) for seg in message):
|
||||||
|
await echo.send(message=message)
|
||||||
|
@@ -460,45 +460,38 @@ class ArgumentParser(ArgParser):
|
|||||||
self,
|
self,
|
||||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||||
namespace: None = None,
|
namespace: None = None,
|
||||||
) -> Tuple[Namespace, List[Union[str, MessageSegment]]]:
|
) -> Tuple[Namespace, List[Union[str, MessageSegment]]]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_known_args(
|
def parse_known_args(
|
||||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||||
) -> Tuple[T, List[Union[str, MessageSegment]]]:
|
) -> Tuple[T, List[Union[str, MessageSegment]]]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_known_args(
|
def parse_known_args(
|
||||||
self, *, namespace: T
|
self, *, namespace: T
|
||||||
) -> Tuple[T, List[Union[str, MessageSegment]]]:
|
) -> Tuple[T, List[Union[str, MessageSegment]]]: ...
|
||||||
...
|
|
||||||
|
|
||||||
def parse_known_args(
|
def parse_known_args(
|
||||||
self,
|
self,
|
||||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||||
namespace: Optional[T] = None,
|
namespace: Optional[T] = None,
|
||||||
) -> Tuple[Union[Namespace, T], List[Union[str, MessageSegment]]]:
|
) -> Tuple[Union[Namespace, T], List[Union[str, MessageSegment]]]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_args(
|
def parse_args(
|
||||||
self,
|
self,
|
||||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||||
namespace: None = None,
|
namespace: None = None,
|
||||||
) -> Namespace:
|
) -> Namespace: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_args(
|
def parse_args(
|
||||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||||
) -> T:
|
) -> T: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_args(self, *, namespace: T) -> T:
|
def parse_args(self, *, namespace: T) -> T: ...
|
||||||
...
|
|
||||||
|
|
||||||
def parse_args(
|
def parse_args(
|
||||||
self,
|
self,
|
||||||
|
@@ -10,18 +10,14 @@ FrontMatter:
|
|||||||
description: nonebot.typing 模块
|
description: nonebot.typing 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
from typing_extensions import ParamSpec, TypeAlias, override
|
import contextlib
|
||||||
from typing import (
|
import typing as t
|
||||||
TYPE_CHECKING,
|
import typing_extensions as t_ext
|
||||||
Any,
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
Dict,
|
from typing_extensions import ParamSpec, TypeAlias, get_args, override, get_origin
|
||||||
Union,
|
|
||||||
TypeVar,
|
|
||||||
Callable,
|
|
||||||
Optional,
|
|
||||||
Awaitable,
|
|
||||||
)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from asyncio import Task
|
from asyncio import Task
|
||||||
@@ -32,7 +28,7 @@ if TYPE_CHECKING:
|
|||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
|
|
||||||
T_Wrapped: TypeAlias = Callable[P, T]
|
T_Wrapped: TypeAlias = t.Callable[P, T]
|
||||||
|
|
||||||
|
|
||||||
def overrides(InterfaceClass: object):
|
def overrides(InterfaceClass: object):
|
||||||
@@ -47,14 +43,77 @@ def overrides(InterfaceClass: object):
|
|||||||
return override
|
return override
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
|
||||||
|
def origin_is_union(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||||
|
"""判断是否是 Union 类型"""
|
||||||
|
return origin is t.Union
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def origin_is_union(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||||
|
return origin is t.Union or origin is types.UnionType
|
||||||
|
|
||||||
|
|
||||||
|
def origin_is_literal(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||||
|
"""判断是否是 Literal 类型"""
|
||||||
|
return origin is t.Literal or origin is t_ext.Literal
|
||||||
|
|
||||||
|
|
||||||
|
def _literal_values(type_: t.Type[t.Any]) -> t.Tuple[t.Any, ...]:
|
||||||
|
return get_args(type_)
|
||||||
|
|
||||||
|
|
||||||
|
def all_literal_values(type_: t.Type[t.Any]) -> t.List[t.Any]:
|
||||||
|
"""获取 Literal 类型包含的所有值"""
|
||||||
|
if not origin_is_literal(get_origin(type_)):
|
||||||
|
return [type_]
|
||||||
|
|
||||||
|
return [x for value in _literal_values(type_) for x in all_literal_values(value)]
|
||||||
|
|
||||||
|
|
||||||
|
def origin_is_annotated(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||||
|
"""判断是否是 Annotated 类型"""
|
||||||
|
with contextlib.suppress(TypeError):
|
||||||
|
return origin is not None and issubclass(origin, t_ext.Annotated)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None]}
|
||||||
|
if sys.version_info >= (3, 10):
|
||||||
|
NONE_TYPES.add(types.NoneType)
|
||||||
|
|
||||||
|
|
||||||
|
def is_none_type(type_: t.Type[t.Any]) -> bool:
|
||||||
|
"""判断是否是 None 类型"""
|
||||||
|
return type_ in NONE_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9): # pragma: py-lt-39
|
||||||
|
|
||||||
|
def evaluate_forwardref(
|
||||||
|
ref: t.ForwardRef, globalns: t.Dict[str, t.Any], localns: t.Dict[str, t.Any]
|
||||||
|
) -> t.Any:
|
||||||
|
return ref._evaluate(globalns, localns)
|
||||||
|
|
||||||
|
else: # pragma: py-gte-39
|
||||||
|
|
||||||
|
def evaluate_forwardref(
|
||||||
|
ref: t.ForwardRef, globalns: t.Dict[str, t.Any], localns: t.Dict[str, t.Any]
|
||||||
|
) -> t.Any:
|
||||||
|
return ref._evaluate(globalns, localns, frozenset())
|
||||||
|
|
||||||
|
|
||||||
# state
|
# state
|
||||||
T_State: TypeAlias = Dict[Any, Any]
|
T_State: TypeAlias = t.Dict[t.Any, t.Any]
|
||||||
"""事件处理状态 State 类型"""
|
"""事件处理状态 State 类型"""
|
||||||
|
|
||||||
_DependentCallable: TypeAlias = Union[Callable[..., T], Callable[..., Awaitable[T]]]
|
_DependentCallable: TypeAlias = t.Union[
|
||||||
|
t.Callable[..., T], t.Callable[..., t.Awaitable[T]]
|
||||||
|
]
|
||||||
|
|
||||||
# driver hooks
|
# driver hooks
|
||||||
T_BotConnectionHook: TypeAlias = _DependentCallable[Any]
|
T_BotConnectionHook: TypeAlias = _DependentCallable[t.Any]
|
||||||
"""Bot 连接建立时钩子函数
|
"""Bot 连接建立时钩子函数
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -63,7 +122,7 @@ T_BotConnectionHook: TypeAlias = _DependentCallable[Any]
|
|||||||
- BotParam: Bot 对象
|
- BotParam: Bot 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_BotDisconnectionHook: TypeAlias = _DependentCallable[Any]
|
T_BotDisconnectionHook: TypeAlias = _DependentCallable[t.Any]
|
||||||
"""Bot 连接断开时钩子函数
|
"""Bot 连接断开时钩子函数
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -74,15 +133,17 @@ T_BotDisconnectionHook: TypeAlias = _DependentCallable[Any]
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# api hooks
|
# api hooks
|
||||||
T_CallingAPIHook: TypeAlias = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
|
T_CallingAPIHook: TypeAlias = t.Callable[
|
||||||
|
["Bot", str, t.Dict[str, t.Any]], t.Awaitable[t.Any]
|
||||||
|
]
|
||||||
"""`bot.call_api` 钩子函数"""
|
"""`bot.call_api` 钩子函数"""
|
||||||
T_CalledAPIHook: TypeAlias = Callable[
|
T_CalledAPIHook: TypeAlias = t.Callable[
|
||||||
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[Any]
|
["Bot", t.Optional[Exception], str, t.Dict[str, t.Any], t.Any], t.Awaitable[t.Any]
|
||||||
]
|
]
|
||||||
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
||||||
|
|
||||||
# event hooks
|
# event hooks
|
||||||
T_EventPreProcessor: TypeAlias = _DependentCallable[Any]
|
T_EventPreProcessor: TypeAlias = _DependentCallable[t.Any]
|
||||||
"""事件预处理函数 EventPreProcessor 类型
|
"""事件预处理函数 EventPreProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -93,7 +154,7 @@ T_EventPreProcessor: TypeAlias = _DependentCallable[Any]
|
|||||||
- StateParam: State 对象
|
- StateParam: State 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_EventPostProcessor: TypeAlias = _DependentCallable[Any]
|
T_EventPostProcessor: TypeAlias = _DependentCallable[t.Any]
|
||||||
"""事件后处理函数 EventPostProcessor 类型
|
"""事件后处理函数 EventPostProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -106,7 +167,7 @@ T_EventPostProcessor: TypeAlias = _DependentCallable[Any]
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# matcher run hooks
|
# matcher run hooks
|
||||||
T_RunPreProcessor: TypeAlias = _DependentCallable[Any]
|
T_RunPreProcessor: TypeAlias = _DependentCallable[t.Any]
|
||||||
"""事件响应器运行前预处理函数 RunPreProcessor 类型
|
"""事件响应器运行前预处理函数 RunPreProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -118,7 +179,7 @@ T_RunPreProcessor: TypeAlias = _DependentCallable[Any]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_RunPostProcessor: TypeAlias = _DependentCallable[Any]
|
T_RunPostProcessor: TypeAlias = _DependentCallable[t.Any]
|
||||||
"""事件响应器运行后后处理函数 RunPostProcessor 类型
|
"""事件响应器运行后后处理函数 RunPostProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -155,7 +216,7 @@ T_PermissionChecker: TypeAlias = _DependentCallable[bool]
|
|||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
|
|
||||||
T_Handler: TypeAlias = _DependentCallable[Any]
|
T_Handler: TypeAlias = _DependentCallable[t.Any]
|
||||||
"""Handler 处理函数。"""
|
"""Handler 处理函数。"""
|
||||||
T_TypeUpdater: TypeAlias = _DependentCallable[str]
|
T_TypeUpdater: TypeAlias = _DependentCallable[str]
|
||||||
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。
|
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。
|
||||||
@@ -183,5 +244,5 @@ T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_DependencyCache: TypeAlias = Dict[_DependentCallable[Any], "Task[Any]"]
|
T_DependencyCache: TypeAlias = t.Dict[_DependentCallable[t.Any], "Task[t.Any]"]
|
||||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||||
|
@@ -12,28 +12,38 @@ import inspect
|
|||||||
import importlib
|
import importlib
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from collections import deque
|
||||||
from contextvars import copy_context
|
from contextvars import copy_context
|
||||||
from functools import wraps, partial
|
from functools import wraps, partial
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing_extensions import ParamSpec, get_args, override, get_origin
|
from typing_extensions import ParamSpec, get_args, override, get_origin
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
Dict,
|
||||||
Type,
|
Type,
|
||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
Union,
|
||||||
Generic,
|
Generic,
|
||||||
|
Mapping,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Callable,
|
Callable,
|
||||||
Optional,
|
Optional,
|
||||||
|
Sequence,
|
||||||
Coroutine,
|
Coroutine,
|
||||||
AsyncGenerator,
|
AsyncGenerator,
|
||||||
ContextManager,
|
ContextManager,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydantic.typing import is_union, is_none_type, is_literal_type, all_literal_values
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
|
from nonebot.typing import (
|
||||||
|
is_none_type,
|
||||||
|
origin_is_union,
|
||||||
|
origin_is_literal,
|
||||||
|
all_literal_values,
|
||||||
|
)
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
R = TypeVar("R")
|
R = TypeVar("R")
|
||||||
@@ -53,6 +63,34 @@ def escape_tag(s: str) -> str:
|
|||||||
return re.sub(r"</?((?:[fb]g\s)?[^<>\s]*)>", r"\\\g<0>", s)
|
return re.sub(r"</?((?:[fb]g\s)?[^<>\s]*)>", r"\\\g<0>", s)
|
||||||
|
|
||||||
|
|
||||||
|
def deep_update(
|
||||||
|
mapping: Dict[K, Any], *updating_mappings: Dict[K, Any]
|
||||||
|
) -> Dict[K, Any]:
|
||||||
|
"""深度更新合并字典"""
|
||||||
|
updated_mapping = mapping.copy()
|
||||||
|
for updating_mapping in updating_mappings:
|
||||||
|
for k, v in updating_mapping.items():
|
||||||
|
if (
|
||||||
|
k in updated_mapping
|
||||||
|
and isinstance(updated_mapping[k], dict)
|
||||||
|
and isinstance(v, dict)
|
||||||
|
):
|
||||||
|
updated_mapping[k] = deep_update(updated_mapping[k], v)
|
||||||
|
else:
|
||||||
|
updated_mapping[k] = v
|
||||||
|
return updated_mapping
|
||||||
|
|
||||||
|
|
||||||
|
def lenient_issubclass(
|
||||||
|
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
||||||
|
) -> bool:
|
||||||
|
"""检查 cls 是否是 class_or_tuple 中的一个类型子类并忽略类型错误。"""
|
||||||
|
try:
|
||||||
|
return isinstance(cls, type) and issubclass(cls, class_or_tuple)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def generic_check_issubclass(
|
def generic_check_issubclass(
|
||||||
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -62,6 +100,8 @@ def generic_check_issubclass(
|
|||||||
|
|
||||||
- 如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
|
- 如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
|
||||||
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
|
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
|
||||||
|
- 如果 cls 是 `typing.Literal` 类型,
|
||||||
|
则会检查其中的所有值是否是 class_or_tuple 中一个类型的实例。
|
||||||
- 如果 cls 是 `typing.TypeVar` 类型,
|
- 如果 cls 是 `typing.TypeVar` 类型,
|
||||||
则会检查其 `__bound__` 或 `__constraints__`
|
则会检查其 `__bound__` 或 `__constraints__`
|
||||||
是否是 class_or_tuple 中一个类型的子类或 None。
|
是否是 class_or_tuple 中一个类型的子类或 None。
|
||||||
@@ -70,12 +110,12 @@ def generic_check_issubclass(
|
|||||||
return issubclass(cls, class_or_tuple)
|
return issubclass(cls, class_or_tuple)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
origin = get_origin(cls)
|
origin = get_origin(cls)
|
||||||
if is_union(origin):
|
if origin_is_union(origin):
|
||||||
return all(
|
return all(
|
||||||
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
|
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
|
||||||
for type_ in get_args(cls)
|
for type_ in get_args(cls)
|
||||||
)
|
)
|
||||||
elif is_literal_type(cls):
|
elif origin_is_literal(origin):
|
||||||
return all(
|
return all(
|
||||||
is_none_type(value) or isinstance(value, class_or_tuple)
|
is_none_type(value) or isinstance(value, class_or_tuple)
|
||||||
for value in all_literal_values(cls)
|
for value in all_literal_values(cls)
|
||||||
@@ -99,6 +139,21 @@ def generic_check_issubclass(
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def type_is_complex(type_: Type[Any]) -> bool:
|
||||||
|
"""检查 type_ 是否是复杂类型"""
|
||||||
|
origin = get_origin(type_)
|
||||||
|
return _type_is_complex_inner(type_) or _type_is_complex_inner(origin)
|
||||||
|
|
||||||
|
|
||||||
|
def _type_is_complex_inner(type_: Optional[Type[Any]]) -> bool:
|
||||||
|
if lenient_issubclass(type_, (str, bytes)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return lenient_issubclass(
|
||||||
|
type_, (BaseModel, Mapping, Sequence, tuple, set, frozenset, deque)
|
||||||
|
) or dataclasses.is_dataclass(type_)
|
||||||
|
|
||||||
|
|
||||||
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||||
"""检查 call 是否是一个 callable 协程函数"""
|
"""检查 call 是否是一个 callable 协程函数"""
|
||||||
if inspect.isroutine(call):
|
if inspect.isroutine(call):
|
||||||
@@ -163,8 +218,7 @@ async def run_coro_with_catch(
|
|||||||
coro: Coroutine[Any, Any, T],
|
coro: Coroutine[Any, Any, T],
|
||||||
exc: Tuple[Type[Exception], ...],
|
exc: Tuple[Type[Exception], ...],
|
||||||
return_on_err: None = None,
|
return_on_err: None = None,
|
||||||
) -> Union[T, None]:
|
) -> Union[T, None]: ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@@ -172,8 +226,7 @@ async def run_coro_with_catch(
|
|||||||
coro: Coroutine[Any, Any, T],
|
coro: Coroutine[Any, Any, T],
|
||||||
exc: Tuple[Type[Exception], ...],
|
exc: Tuple[Type[Exception], ...],
|
||||||
return_on_err: R,
|
return_on_err: R,
|
||||||
) -> Union[T, R]:
|
) -> Union[T, R]: ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def run_coro_with_catch(
|
async def run_coro_with_catch(
|
||||||
|
988
poetry.lock
generated
988
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "nonebot2"
|
name = "nonebot2"
|
||||||
version = "2.1.3"
|
version = "2.2.1"
|
||||||
description = "An asynchronous python bot framework."
|
description = "An asynchronous python bot framework."
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -29,9 +29,10 @@ python = "^3.8"
|
|||||||
yarl = "^1.7.2"
|
yarl = "^1.7.2"
|
||||||
pygtrie = "^2.4.1"
|
pygtrie = "^2.4.1"
|
||||||
loguru = ">=0.6.0,<1.0.0"
|
loguru = ">=0.6.0,<1.0.0"
|
||||||
|
python-dotenv = ">=0.21.0,<2.0.0"
|
||||||
typing-extensions = ">=4.4.0,<5.0.0"
|
typing-extensions = ">=4.4.0,<5.0.0"
|
||||||
|
pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1"
|
||||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
tomli = { version = "^2.0.1", python = "<3.11" }
|
||||||
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
|
|
||||||
|
|
||||||
websockets = { version = ">=10.0", optional = true }
|
websockets = { version = ">=10.0", optional = true }
|
||||||
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
||||||
@@ -43,19 +44,14 @@ uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
|||||||
], optional = true }
|
], optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
ruff = "^0.2.0"
|
||||||
isort = "^5.10.1"
|
isort = "^5.10.1"
|
||||||
black = "^23.1.0"
|
black = "^24.0.0"
|
||||||
nonemoji = "^0.1.2"
|
nonemoji = "^0.1.2"
|
||||||
pre-commit = "^3.0.0"
|
pre-commit = "^3.0.0"
|
||||||
ruff = ">=0.0.272,<1.0.0"
|
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
nonebug = "^0.3.0"
|
nonebot-test = { path = "./envs/test/", develop = false }
|
||||||
pytest-cov = "^4.0.0"
|
|
||||||
pytest-xdist = "^3.0.2"
|
|
||||||
pytest-asyncio = "^0.23.2"
|
|
||||||
werkzeug = ">=2.3.6,<4.0.0"
|
|
||||||
coverage-conditional-plugin = "^0.9.0"
|
|
||||||
|
|
||||||
[tool.poetry.group.docs.dependencies]
|
[tool.poetry.group.docs.dependencies]
|
||||||
nb-autodoc = "^1.0.0a5"
|
nb-autodoc = "^1.0.0a5"
|
||||||
@@ -90,19 +86,21 @@ src_paths = ["nonebot", "tests"]
|
|||||||
extra_standard_library = ["typing_extensions"]
|
extra_standard_library = ["typing_extensions"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
|
|
||||||
ignore = ["E402", "C901", "UP037"]
|
|
||||||
|
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = "py38"
|
target-version = "py38"
|
||||||
|
|
||||||
[tool.ruff.flake8-pytest-style]
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
|
||||||
|
ignore = ["E402", "C901", "UP037"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.flake8-pytest-style]
|
||||||
fixture-parentheses = false
|
fixture-parentheses = false
|
||||||
mark-parentheses = false
|
mark-parentheses = false
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
pythonVersion = "3.8"
|
pythonVersion = "3.8"
|
||||||
pythonPlatform = "All"
|
pythonPlatform = "All"
|
||||||
|
defineConstant = { PYDANTIC_V2 = true }
|
||||||
executionEnvironments = [
|
executionEnvironments = [
|
||||||
{ root = "./tests", extraPaths = [
|
{ root = "./tests", extraPaths = [
|
||||||
"./",
|
"./",
|
||||||
|
11
scripts/build-api-docs.sh
Executable file
11
scripts/build-api-docs.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
# cd to the root of the project
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
poetry run nb-autodoc nonebot \
|
||||||
|
-s nonebot.plugins \
|
||||||
|
-u nonebot.internal \
|
||||||
|
-u nonebot.internal.*
|
||||||
|
cp -r ./build/nonebot/* ./website/docs/api/
|
||||||
|
yarn prettier
|
7
scripts/run-tests.sh
Executable file
7
scripts/run-tests.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
# cd to the root of the tests
|
||||||
|
cd "$(dirname "$0")/../tests"
|
||||||
|
|
||||||
|
# Run the tests
|
||||||
|
pytest -n auto --cov-report xml $@
|
14
scripts/setup-envs.sh
Executable file
14
scripts/setup-envs.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#! /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"
|
||||||
|
poetry install --all-extras && poetry 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
|
15
scripts/update-envs.sh
Executable file
15
scripts/update-envs.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#! /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
|
@@ -22,5 +22,8 @@ rules =
|
|||||||
"sys_platform != 'linux'": py-linux
|
"sys_platform != 'linux'": py-linux
|
||||||
"sys_platform != 'darwin'": py-darwin
|
"sys_platform != 'darwin'": py-darwin
|
||||||
"sys_version_info < (3, 9)": py-gte-39
|
"sys_version_info < (3, 9)": py-gte-39
|
||||||
|
"sys_version_info >= (3, 9)": py-lt-39
|
||||||
"sys_version_info < (3, 11)": py-gte-311
|
"sys_version_info < (3, 11)": py-gte-311
|
||||||
"sys_version_info >= (3, 11)": py-lt-311
|
"sys_version_info >= (3, 11)": py-lt-311
|
||||||
|
"package_version('pydantic') < (2,)": pydantic-v2
|
||||||
|
"package_version('pydantic') >= (2,)": pydantic-v1
|
||||||
|
17
tests/.env.example
Normal file
17
tests/.env.example
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
SIMPLE=simple
|
||||||
|
COMPLEX='
|
||||||
|
[1, 2, 3]
|
||||||
|
'
|
||||||
|
COMPLEX_NONE
|
||||||
|
COMPLEX_UNION=[1, 2, 3]
|
||||||
|
NESTED={"a": 1}
|
||||||
|
NESTED__B=2
|
||||||
|
NESTED__C__C=3
|
||||||
|
NESTED__COMPLEX=[1, 2, 3]
|
||||||
|
NESTED_INNER__A=1
|
||||||
|
NESTED_INNER__B=2
|
||||||
|
OTHER_SIMPLE=simple
|
||||||
|
OTHER_NESTED={"a": 1}
|
||||||
|
OTHER_NESTED__B=2
|
||||||
|
OTHER_NESTED_INNER__A=1
|
||||||
|
OTHER_NESTED_INNER__B=2
|
@@ -13,3 +13,4 @@ NESTED_MISSING_DICT__A=1
|
|||||||
NESTED_MISSING_DICT__B__C=2
|
NESTED_MISSING_DICT__B__C=2
|
||||||
NOT_NESTED=some string
|
NOT_NESTED=some string
|
||||||
NOT_NESTED__A=1
|
NOT_NESTED__A=1
|
||||||
|
PLUGIN_CONFIG=1
|
||||||
|
@@ -1,9 +1,15 @@
|
|||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
|
import socket
|
||||||
from typing import Dict, List, Union, TypeVar
|
from typing import Dict, List, Union, TypeVar
|
||||||
|
|
||||||
|
from wsproto.events import Ping
|
||||||
from werkzeug import Request, Response
|
from werkzeug import Request, Response
|
||||||
from werkzeug.datastructures import MultiDict
|
from werkzeug.datastructures import MultiDict
|
||||||
|
from wsproto.frame_protocol import CloseReason
|
||||||
|
from wsproto.events import Request as WSRequest
|
||||||
|
from wsproto import WSConnection, ConnectionType
|
||||||
|
from wsproto.events import TextMessage, BytesMessage, CloseConnection, AcceptConnection
|
||||||
|
|
||||||
K = TypeVar("K")
|
K = TypeVar("K")
|
||||||
V = TypeVar("V")
|
V = TypeVar("V")
|
||||||
@@ -29,8 +35,7 @@ def flattern(d: "MultiDict[K, V]") -> Dict[K, Union[V, List[V]]]:
|
|||||||
return {k: v[0] if len(v) == 1 else v for k, v in d.to_dict(flat=False).items()}
|
return {k: v[0] if len(v) == 1 else v for k, v in d.to_dict(flat=False).items()}
|
||||||
|
|
||||||
|
|
||||||
@Request.application
|
def http_echo(request: Request) -> Response:
|
||||||
def request_handler(request: Request) -> Response:
|
|
||||||
try:
|
try:
|
||||||
_json = json.loads(request.data.decode("utf-8"))
|
_json = json.loads(request.data.decode("utf-8"))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
@@ -67,3 +72,65 @@ def request_handler(request: Request) -> Response:
|
|||||||
status=200,
|
status=200,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def websocket_echo(request: Request) -> Response:
|
||||||
|
stream = request.environ["werkzeug.socket"]
|
||||||
|
|
||||||
|
ws = WSConnection(ConnectionType.SERVER)
|
||||||
|
|
||||||
|
in_data = b"GET %s HTTP/1.1\r\n" % request.path.encode("utf-8")
|
||||||
|
for header, value in request.headers.items():
|
||||||
|
in_data += f"{header}: {value}\r\n".encode()
|
||||||
|
in_data += b"\r\n"
|
||||||
|
|
||||||
|
ws.receive_data(in_data)
|
||||||
|
|
||||||
|
running: bool = True
|
||||||
|
while True:
|
||||||
|
out_data = b""
|
||||||
|
|
||||||
|
for event in ws.events():
|
||||||
|
if isinstance(event, WSRequest):
|
||||||
|
out_data += ws.send(AcceptConnection())
|
||||||
|
elif isinstance(event, CloseConnection):
|
||||||
|
out_data += ws.send(event.response())
|
||||||
|
running = False
|
||||||
|
elif isinstance(event, Ping):
|
||||||
|
out_data += ws.send(event.response())
|
||||||
|
elif isinstance(event, TextMessage):
|
||||||
|
if event.data == "quit":
|
||||||
|
out_data += ws.send(
|
||||||
|
CloseConnection(CloseReason.NORMAL_CLOSURE, "bye")
|
||||||
|
)
|
||||||
|
running = False
|
||||||
|
else:
|
||||||
|
out_data += ws.send(TextMessage(data=event.data))
|
||||||
|
elif isinstance(event, BytesMessage):
|
||||||
|
if event.data == b"quit":
|
||||||
|
out_data += ws.send(
|
||||||
|
CloseConnection(CloseReason.NORMAL_CLOSURE, "bye")
|
||||||
|
)
|
||||||
|
running = False
|
||||||
|
else:
|
||||||
|
out_data += ws.send(BytesMessage(data=event.data))
|
||||||
|
|
||||||
|
if out_data:
|
||||||
|
stream.send(out_data)
|
||||||
|
|
||||||
|
if not running:
|
||||||
|
break
|
||||||
|
|
||||||
|
in_data = stream.recv(4096)
|
||||||
|
ws.receive_data(in_data)
|
||||||
|
|
||||||
|
stream.shutdown(socket.SHUT_RDWR)
|
||||||
|
return Response("", status=204)
|
||||||
|
|
||||||
|
|
||||||
|
@Request.application
|
||||||
|
def request_handler(request: Request) -> Response:
|
||||||
|
if request.headers.get("Connection") == "Upgrade":
|
||||||
|
return websocket_echo(request)
|
||||||
|
else:
|
||||||
|
return http_echo(request)
|
||||||
|
@@ -78,8 +78,7 @@ async def reject_preset(a: str = ArgStr(), b: str = ArgStr()):
|
|||||||
test_overload = on_message()
|
test_overload = on_message()
|
||||||
|
|
||||||
|
|
||||||
class FakeEvent(Event):
|
class FakeEvent(Event): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
@test_overload.got("a")
|
@test_overload.got("a")
|
||||||
|
@@ -8,8 +8,7 @@ class Config(BaseModel):
|
|||||||
custom: str = ""
|
custom: str = ""
|
||||||
|
|
||||||
|
|
||||||
class FakeAdapter(Adapter):
|
class FakeAdapter(Adapter): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
@@ -11,20 +11,17 @@ async def legacy_bot(bot):
|
|||||||
return bot
|
return bot
|
||||||
|
|
||||||
|
|
||||||
async def not_legacy_bot(bot: int):
|
async def not_legacy_bot(bot: int): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class FooBot(Bot):
|
class FooBot(Bot): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def sub_bot(b: FooBot) -> FooBot:
|
async def sub_bot(b: FooBot) -> FooBot:
|
||||||
return b
|
return b
|
||||||
|
|
||||||
|
|
||||||
class BarBot(Bot):
|
class BarBot(Bot): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def union_bot(b: Union[FooBot, BarBot]) -> Union[FooBot, BarBot]:
|
async def union_bot(b: Union[FooBot, BarBot]) -> Union[FooBot, BarBot]:
|
||||||
@@ -45,5 +42,4 @@ async def generic_bot_none(b: CB) -> CB:
|
|||||||
return b
|
return b
|
||||||
|
|
||||||
|
|
||||||
async def not_bot(b: Union[int, Bot]):
|
async def not_bot(b: Union[int, Bot]): ...
|
||||||
...
|
|
||||||
|
@@ -36,8 +36,7 @@ class ClassDependency:
|
|||||||
y: int = Depends(gen_async)
|
y: int = Depends(gen_async)
|
||||||
|
|
||||||
|
|
||||||
class FooBot(Bot):
|
class FooBot(Bot): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def sub_bot(b: FooBot) -> FooBot:
|
async def sub_bot(b: FooBot) -> FooBot:
|
||||||
|
@@ -12,20 +12,17 @@ async def legacy_event(event):
|
|||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
async def not_legacy_event(event: int):
|
async def not_legacy_event(event: int): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class FooEvent(Event):
|
class FooEvent(Event): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def sub_event(e: FooEvent) -> FooEvent:
|
async def sub_event(e: FooEvent) -> FooEvent:
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
class BarEvent(Event):
|
class BarEvent(Event): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def union_event(e: Union[FooEvent, BarEvent]) -> Union[FooEvent, BarEvent]:
|
async def union_event(e: Union[FooEvent, BarEvent]) -> Union[FooEvent, BarEvent]:
|
||||||
@@ -46,8 +43,7 @@ async def generic_event_none(e: CE) -> CE:
|
|||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
async def not_event(e: Union[int, Event]):
|
async def not_event(e: Union[int, Event]): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def event_type(t: str = EventType()) -> str:
|
async def event_type(t: str = EventType()) -> str:
|
||||||
|
@@ -4,3 +4,7 @@ from typing import Union
|
|||||||
async def exc(e: Exception, x: Union[ValueError, TypeError]) -> Exception:
|
async def exc(e: Exception, x: Union[ValueError, TypeError]) -> Exception:
|
||||||
assert e == x
|
assert e == x
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
async def legacy_exc(exception) -> Exception:
|
||||||
|
return exception
|
||||||
|
@@ -13,20 +13,17 @@ async def legacy_matcher(matcher):
|
|||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
|
|
||||||
async def not_legacy_matcher(matcher: int):
|
async def not_legacy_matcher(matcher: int): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class FooMatcher(Matcher):
|
class FooMatcher(Matcher): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def sub_matcher(m: FooMatcher) -> FooMatcher:
|
async def sub_matcher(m: FooMatcher) -> FooMatcher:
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
class BarMatcher(Matcher):
|
class BarMatcher(Matcher): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def union_matcher(
|
async def union_matcher(
|
||||||
@@ -49,8 +46,7 @@ async def generic_matcher_none(m: CM) -> CM:
|
|||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
async def not_matcher(m: Union[int, Matcher]):
|
async def not_matcher(m: Union[int, Matcher]): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def receive(e: Event = Received("test")) -> Event:
|
async def receive(e: Event = Received("test")) -> Event:
|
||||||
|
@@ -29,8 +29,7 @@ async def legacy_state(state):
|
|||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
async def not_legacy_state(state: int):
|
async def not_legacy_state(state: int): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def command(cmd: Tuple[str, ...] = Command()) -> Tuple[str, ...]:
|
async def command(cmd: Tuple[str, ...] = Command()) -> Tuple[str, ...]:
|
||||||
@@ -77,8 +76,13 @@ async def regex_matched(regex_matched: Match[str] = RegexMatched()) -> Match[str
|
|||||||
return regex_matched
|
return regex_matched
|
||||||
|
|
||||||
|
|
||||||
async def regex_str(regex_str: str = RegexStr()) -> str:
|
async def regex_str(
|
||||||
return regex_str
|
entire: str = RegexStr(),
|
||||||
|
type_: str = RegexStr("type"),
|
||||||
|
second: str = RegexStr(2),
|
||||||
|
groups: Tuple[str, ...] = RegexStr(1, "arg"),
|
||||||
|
) -> Tuple[str, str, str, Tuple[str, ...]]:
|
||||||
|
return entire, type_, second, groups
|
||||||
|
|
||||||
|
|
||||||
async def startswith(startswith: str = Startswith()) -> str:
|
async def startswith(startswith: str = Startswith()) -> str:
|
||||||
|
@@ -19,5 +19,4 @@ async def complex_priority(
|
|||||||
arg: Message = Arg(),
|
arg: Message = Arg(),
|
||||||
exception: Optional[Exception] = None,
|
exception: Optional[Exception] = None,
|
||||||
default: int = 1,
|
default: int = 1,
|
||||||
):
|
): ...
|
||||||
...
|
|
||||||
|
@@ -202,8 +202,7 @@ matcher_on_regex = on_regex(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestEvent(Event):
|
class TestEvent(Event): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
matcher_on_type = on_type(
|
matcher_on_type = on_type(
|
||||||
|
@@ -99,8 +99,7 @@ async def test_adapter_server(driver: Driver):
|
|||||||
async def handle_http(request: Request):
|
async def handle_http(request: Request):
|
||||||
return Response(200, content="test")
|
return Response(200, content="test")
|
||||||
|
|
||||||
async def handle_ws(ws: WebSocket):
|
async def handle_ws(ws: WebSocket): ...
|
||||||
...
|
|
||||||
|
|
||||||
adapter = FakeAdapter(driver)
|
adapter = FakeAdapter(driver)
|
||||||
|
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from pydantic import ValidationError, parse_obj_as
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from nonebot.adapters import Message
|
from nonebot.compat import type_validate_python
|
||||||
from utils import FakeMessage, FakeMessageSegment
|
from utils import FakeMessage, FakeMessageSegment
|
||||||
|
from nonebot.adapters import Message, MessageSegment
|
||||||
|
|
||||||
|
|
||||||
def test_segment_data():
|
def test_segment_data():
|
||||||
@@ -47,16 +48,21 @@ def test_segment_add():
|
|||||||
|
|
||||||
|
|
||||||
def test_segment_validate():
|
def test_segment_validate():
|
||||||
assert parse_obj_as(
|
assert type_validate_python(
|
||||||
FakeMessageSegment,
|
FakeMessageSegment,
|
||||||
{"type": "text", "data": {"text": "text"}, "extra": "should be ignored"},
|
{"type": "text", "data": {"text": "text"}, "extra": "should be ignored"},
|
||||||
) == FakeMessageSegment.text("text")
|
) == FakeMessageSegment.text("text")
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
type_validate_python(
|
||||||
|
type("FakeMessageSegment2", (MessageSegment,), {}),
|
||||||
|
FakeMessageSegment.text("text"),
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
parse_obj_as(FakeMessageSegment, "some str")
|
type_validate_python(FakeMessageSegment, "some str")
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
parse_obj_as(FakeMessageSegment, {"data": {}})
|
type_validate_python(FakeMessageSegment, {"data": {}})
|
||||||
|
|
||||||
|
|
||||||
def test_segment_join():
|
def test_segment_join():
|
||||||
@@ -144,26 +150,26 @@ def test_message_getitem():
|
|||||||
|
|
||||||
|
|
||||||
def test_message_validate():
|
def test_message_validate():
|
||||||
assert parse_obj_as(FakeMessage, FakeMessage([])) == FakeMessage([])
|
assert type_validate_python(FakeMessage, FakeMessage([])) == FakeMessage([])
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
parse_obj_as(type("FakeMessage2", (Message,), {}), FakeMessage([]))
|
type_validate_python(type("FakeMessage2", (Message,), {}), FakeMessage([]))
|
||||||
|
|
||||||
assert parse_obj_as(FakeMessage, "text") == FakeMessage(
|
assert type_validate_python(FakeMessage, "text") == FakeMessage(
|
||||||
[FakeMessageSegment.text("text")]
|
[FakeMessageSegment.text("text")]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert parse_obj_as(
|
assert type_validate_python(
|
||||||
FakeMessage, {"type": "text", "data": {"text": "text"}}
|
FakeMessage, {"type": "text", "data": {"text": "text"}}
|
||||||
) == FakeMessage([FakeMessageSegment.text("text")])
|
) == FakeMessage([FakeMessageSegment.text("text")])
|
||||||
|
|
||||||
assert parse_obj_as(
|
assert type_validate_python(
|
||||||
FakeMessage,
|
FakeMessage,
|
||||||
[FakeMessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
|
[FakeMessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
|
||||||
) == FakeMessage([FakeMessageSegment.text("text"), FakeMessageSegment.text("text")])
|
) == FakeMessage([FakeMessageSegment.text("text"), FakeMessageSegment.text("text")])
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
parse_obj_as(FakeMessage, object())
|
type_validate_python(FakeMessage, object())
|
||||||
|
|
||||||
|
|
||||||
def test_message_contains():
|
def test_message_contains():
|
||||||
@@ -186,6 +192,11 @@ def test_message_contains():
|
|||||||
assert message.has("foo") is False
|
assert message.has("foo") is False
|
||||||
assert "foo" not in message
|
assert "foo" not in message
|
||||||
|
|
||||||
|
assert not bool(FakeMessageSegment.text(""))
|
||||||
|
msg_with_empty_seg = FakeMessage([FakeMessageSegment.text("")])
|
||||||
|
assert msg_with_empty_seg.has("text") is True
|
||||||
|
assert "text" in msg_with_empty_seg
|
||||||
|
|
||||||
|
|
||||||
def test_message_only():
|
def test_message_only():
|
||||||
message = FakeMessage(
|
message = FakeMessage(
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from nonebot.adapters import MessageTemplate
|
from nonebot.adapters import MessageTemplate
|
||||||
from utils import FakeMessage, FakeMessageSegment, escape_text
|
from utils import FakeMessage, FakeMessageSegment, escape_text
|
||||||
|
|
||||||
@@ -15,12 +17,8 @@ def test_template_message():
|
|||||||
def custom(input: str) -> str:
|
def custom(input: str) -> str:
|
||||||
return f"{input}-custom!"
|
return f"{input}-custom!"
|
||||||
|
|
||||||
try:
|
with pytest.raises(ValueError, match="already exists"):
|
||||||
template.add_format_spec(custom)
|
template.add_format_spec(custom)
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise AssertionError("Should raise ValueError")
|
|
||||||
|
|
||||||
format_args = {
|
format_args = {
|
||||||
"a": "custom",
|
"a": "custom",
|
||||||
@@ -57,3 +55,22 @@ def test_message_injection():
|
|||||||
message = template.format(name="[fake:image]")
|
message = template.format(name="[fake:image]")
|
||||||
|
|
||||||
assert message.extract_plain_text() == escape_text("[fake:image]Is Bad")
|
assert message.extract_plain_text() == escape_text("[fake:image]Is Bad")
|
||||||
|
|
||||||
|
|
||||||
|
def test_malformed_template():
|
||||||
|
positive_template = FakeMessage.template("{a}{b}")
|
||||||
|
message = positive_template.format(a="a", b="b")
|
||||||
|
assert message.extract_plain_text() == "ab"
|
||||||
|
|
||||||
|
malformed_template = FakeMessage.template("{a.__init__}")
|
||||||
|
with pytest.raises(ValueError, match="private attribute"):
|
||||||
|
message = malformed_template.format(a="a")
|
||||||
|
|
||||||
|
malformed_template = FakeMessage.template("{a[__builtins__]}")
|
||||||
|
with pytest.raises(ValueError, match="private attribute"):
|
||||||
|
message = malformed_template.format(a=globals())
|
||||||
|
|
||||||
|
malformed_template = MessageTemplate(
|
||||||
|
"{a[__builtins__][__import__]}{b.__init__}", private_getattr=True
|
||||||
|
)
|
||||||
|
message = malformed_template.format(a=globals(), b="b")
|
||||||
|
92
tests/test_compat.py
Normal file
92
tests/test_compat.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
from typing import Any, Optional
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from nonebot.compat import (
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
Required,
|
||||||
|
FieldInfo,
|
||||||
|
PydanticUndefined,
|
||||||
|
model_dump,
|
||||||
|
custom_validation,
|
||||||
|
type_validate_json,
|
||||||
|
type_validate_python,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_default_config():
|
||||||
|
assert DEFAULT_CONFIG.get("extra") == "allow"
|
||||||
|
assert DEFAULT_CONFIG.get("arbitrary_types_allowed") is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_field_info():
|
||||||
|
# required should be convert to PydanticUndefined
|
||||||
|
assert FieldInfo(Required).default is PydanticUndefined
|
||||||
|
|
||||||
|
# field info should allow extra attributes
|
||||||
|
assert FieldInfo(test="test").extra["test"] == "test"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_model_dump():
|
||||||
|
class TestModel(BaseModel):
|
||||||
|
test1: int
|
||||||
|
test2: int
|
||||||
|
|
||||||
|
assert model_dump(TestModel(test1=1, test2=2), include={"test1"}) == {"test1": 1}
|
||||||
|
assert model_dump(TestModel(test1=1, test2=2), exclude={"test1"}) == {"test2": 2}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_custom_validation():
|
||||||
|
called = []
|
||||||
|
|
||||||
|
@custom_validation
|
||||||
|
@dataclass
|
||||||
|
class TestModel:
|
||||||
|
test: int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __get_validators__(cls):
|
||||||
|
yield cls._validate_1
|
||||||
|
yield cls._validate_2
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_1(cls, v: Any) -> Any:
|
||||||
|
called.append(1)
|
||||||
|
return v
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_2(cls, v: Any) -> Any:
|
||||||
|
called.append(2)
|
||||||
|
return cls(test=v["test"])
|
||||||
|
|
||||||
|
assert type_validate_python(TestModel, {"test": 1}) == TestModel(test=1)
|
||||||
|
assert called == [1, 2]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_json():
|
||||||
|
class TestModel(BaseModel):
|
||||||
|
test1: int
|
||||||
|
test2: str
|
||||||
|
test3: bool
|
||||||
|
test4: dict
|
||||||
|
test5: list
|
||||||
|
test6: Optional[int]
|
||||||
|
|
||||||
|
assert type_validate_json(
|
||||||
|
TestModel,
|
||||||
|
"{"
|
||||||
|
' "test1": 1,'
|
||||||
|
' "test2": "2",'
|
||||||
|
' "test3": true,'
|
||||||
|
' "test4": {},'
|
||||||
|
' "test5": [],'
|
||||||
|
' "test6": null'
|
||||||
|
"}",
|
||||||
|
) == TestModel(test1=1, test2="2", test3=True, test4={}, test5=[], test6=None)
|
118
tests/test_config.py
Normal file
118
tests/test_config.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
from typing import List, Union, Optional
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from nonebot.config import DOTENV_TYPE, BaseSettings, SettingsError
|
||||||
|
|
||||||
|
|
||||||
|
class Simple(BaseModel):
|
||||||
|
a: int = 0
|
||||||
|
b: int = 0
|
||||||
|
c: dict = {}
|
||||||
|
complex: list = []
|
||||||
|
|
||||||
|
|
||||||
|
class Example(BaseSettings):
|
||||||
|
_env_file: Optional[DOTENV_TYPE] = ".env", ".env.example"
|
||||||
|
_env_nested_delimiter: Optional[str] = "__"
|
||||||
|
|
||||||
|
simple: str = ""
|
||||||
|
complex: List[int] = [1]
|
||||||
|
complex_none: Optional[List[int]] = None
|
||||||
|
complex_union: Union[int, List[int]] = 1
|
||||||
|
nested: Simple = Simple()
|
||||||
|
nested_inner: Simple = Simple()
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env", ".env.example"
|
||||||
|
env_nested_delimiter = "__"
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleWithoutDelimiter(Example):
|
||||||
|
class Config:
|
||||||
|
env_nested_delimiter = None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_no_env():
|
||||||
|
config = Example(_env_file=None)
|
||||||
|
assert config.simple == ""
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.common_config
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_with_env():
|
||||||
|
config = Example(_env_file=(".env", ".env.example"))
|
||||||
|
assert config.simple == "simple"
|
||||||
|
|
||||||
|
assert config.complex == [1, 2, 3]
|
||||||
|
|
||||||
|
assert config.complex_none is None
|
||||||
|
|
||||||
|
assert config.complex_union == [1, 2, 3]
|
||||||
|
|
||||||
|
assert config.nested.a == 1
|
||||||
|
assert config.nested.b == 2
|
||||||
|
assert config.nested.c == {"c": "3"}
|
||||||
|
assert config.nested.complex == [1, 2, 3]
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.nested__b
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.nested__c__c
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.nested__complex
|
||||||
|
|
||||||
|
assert config.nested_inner.a == 1
|
||||||
|
assert config.nested_inner.b == 2
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.nested_inner__a
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.nested_inner__b
|
||||||
|
|
||||||
|
assert config.common_config == "common"
|
||||||
|
|
||||||
|
assert config.other_simple == "simple"
|
||||||
|
|
||||||
|
assert config.other_nested == {"a": 1, "b": 2}
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.other_nested__b
|
||||||
|
|
||||||
|
assert config.other_nested_inner == {"a": 1, "b": 2}
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.other_nested_inner__a
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.other_nested_inner__b
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_error_env():
|
||||||
|
with pytest.MonkeyPatch().context() as m:
|
||||||
|
m.setenv("COMPLEX", "not json")
|
||||||
|
|
||||||
|
with pytest.raises(SettingsError):
|
||||||
|
Example(_env_file=(".env", ".env.example"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_without_delimiter():
|
||||||
|
config = ExampleWithoutDelimiter()
|
||||||
|
assert config.nested.a == 1
|
||||||
|
assert config.nested.b == 0
|
||||||
|
assert config.nested__b == 2
|
||||||
|
assert config.nested.c == {}
|
||||||
|
assert config.nested__c__c == 3
|
||||||
|
assert config.nested.complex == []
|
||||||
|
assert config.nested__complex == [1, 2, 3]
|
||||||
|
|
||||||
|
assert config.nested_inner.a == 0
|
||||||
|
assert config.nested_inner.b == 0
|
||||||
|
|
||||||
|
assert config.other_nested == {"a": 1}
|
||||||
|
assert config.other_nested__b == 2
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
config.other_nested_inner
|
||||||
|
assert config.other_nested_inner__a == 1
|
||||||
|
assert config.other_nested_inner__b == 2
|
@@ -131,7 +131,7 @@ async def test_websocket_server(app: App, driver: Driver):
|
|||||||
assert data == b"ping"
|
assert data == b"ping"
|
||||||
await ws.send(b"pong")
|
await ws.send(b"pong")
|
||||||
|
|
||||||
with pytest.raises(WebSocketClosed):
|
with pytest.raises(WebSocketClosed, match=r"code=1000"):
|
||||||
await ws.receive()
|
await ws.receive()
|
||||||
|
|
||||||
ws_setup = WebSocketServerSetup(URL("/ws_test"), "ws_test", _handle_ws)
|
ws_setup = WebSocketServerSetup(URL("/ws_test"), "ws_test", _handle_ws)
|
||||||
@@ -152,7 +152,7 @@ async def test_websocket_server(app: App, driver: Driver):
|
|||||||
await ws.send_bytes(b"ping")
|
await ws.send_bytes(b"ping")
|
||||||
assert await ws.receive_bytes() == b"pong"
|
assert await ws.receive_bytes() == b"pong"
|
||||||
|
|
||||||
await ws.close()
|
await ws.close(code=1000)
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
@@ -315,9 +315,29 @@ async def test_http_client(driver: Driver, server_url: URL):
|
|||||||
],
|
],
|
||||||
indirect=True,
|
indirect=True,
|
||||||
)
|
)
|
||||||
async def test_websocket_client(driver: Driver):
|
async def test_websocket_client(driver: Driver, server_url: URL):
|
||||||
assert isinstance(driver, WebSocketClientMixin)
|
assert isinstance(driver, WebSocketClientMixin)
|
||||||
|
|
||||||
|
request = Request("GET", server_url.with_scheme("ws"))
|
||||||
|
async with driver.websocket(request) as ws:
|
||||||
|
await ws.send("test")
|
||||||
|
assert await ws.receive() == "test"
|
||||||
|
|
||||||
|
await ws.send(b"test")
|
||||||
|
assert await ws.receive() == b"test"
|
||||||
|
|
||||||
|
await ws.send_text("test")
|
||||||
|
assert await ws.receive_text() == "test"
|
||||||
|
|
||||||
|
await ws.send_bytes(b"test")
|
||||||
|
assert await ws.receive_bytes() == b"test"
|
||||||
|
|
||||||
|
await ws.send("quit")
|
||||||
|
with pytest.raises(WebSocketClosed, match=r"code=1000"):
|
||||||
|
await ws.receive()
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
33
tests/test_echo.py
Normal file
33
tests/test_echo.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import pytest
|
||||||
|
from nonebug import App
|
||||||
|
|
||||||
|
from utils import FakeMessage, FakeMessageSegment, make_fake_event
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_echo(app: App):
|
||||||
|
from nonebot.plugins.echo import echo
|
||||||
|
|
||||||
|
async with app.test_matcher(echo) as ctx:
|
||||||
|
bot = ctx.create_bot()
|
||||||
|
|
||||||
|
message = FakeMessage("/echo 123")
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
ctx.receive_event(bot, event)
|
||||||
|
ctx.should_call_send(event, FakeMessage("123"), True, bot=bot)
|
||||||
|
|
||||||
|
message = FakeMessageSegment.text("/echo 123") + FakeMessageSegment.image(
|
||||||
|
"test"
|
||||||
|
)
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
ctx.receive_event(bot, event)
|
||||||
|
ctx.should_call_send(
|
||||||
|
event,
|
||||||
|
FakeMessageSegment.text("123") + FakeMessageSegment.image("test"),
|
||||||
|
True,
|
||||||
|
bot=bot,
|
||||||
|
)
|
||||||
|
|
||||||
|
message = FakeMessage("/echo")
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
ctx.receive_event(bot, event)
|
@@ -218,7 +218,7 @@ async def test_event(app: App):
|
|||||||
|
|
||||||
async with app.test_dependent(union_event, allow_types=[EventParam]) as ctx:
|
async with app.test_dependent(union_event, allow_types=[EventParam]) as ctx:
|
||||||
ctx.pass_params(event=fake_fooevent)
|
ctx.pass_params(event=fake_fooevent)
|
||||||
ctx.should_return(fake_event)
|
ctx.should_return(fake_fooevent)
|
||||||
|
|
||||||
async with app.test_dependent(generic_event, allow_types=[EventParam]) as ctx:
|
async with app.test_dependent(generic_event, allow_types=[EventParam]) as ctx:
|
||||||
ctx.pass_params(event=fake_event)
|
ctx.pass_params(event=fake_event)
|
||||||
@@ -361,7 +361,9 @@ async def test_state(app: App):
|
|||||||
regex_str, allow_types=[StateParam, DependParam]
|
regex_str, allow_types=[StateParam, DependParam]
|
||||||
) as ctx:
|
) as ctx:
|
||||||
ctx.pass_params(state=fake_state)
|
ctx.pass_params(state=fake_state)
|
||||||
ctx.should_return("[cq:test,arg=value]")
|
ctx.should_return(
|
||||||
|
("[cq:test,arg=value]", "test", "arg=value", ("test", "arg=value"))
|
||||||
|
)
|
||||||
|
|
||||||
async with app.test_dependent(
|
async with app.test_dependent(
|
||||||
regex_group, allow_types=[StateParam, DependParam]
|
regex_group, allow_types=[StateParam, DependParam]
|
||||||
@@ -527,13 +529,17 @@ async def test_arg(app: App):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_exception(app: App):
|
async def test_exception(app: App):
|
||||||
from plugins.param.param_exception import 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)
|
||||||
|
|
||||||
|
async with app.test_dependent(legacy_exc, allow_types=[ExceptionParam]) as ctx:
|
||||||
|
ctx.pass_params(exception=exception)
|
||||||
|
ctx.should_return(exception)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_default(app: App):
|
async def test_default(app: App):
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.plugin import PluginManager, _managers
|
from nonebot.plugin import PluginManager, _managers
|
||||||
@@ -35,3 +36,14 @@ async def test_get_available_plugin():
|
|||||||
finally:
|
finally:
|
||||||
_managers.clear()
|
_managers.clear()
|
||||||
_managers.extend(old_managers)
|
_managers.extend(old_managers)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_plugin_config():
|
||||||
|
class Config(BaseModel):
|
||||||
|
plugin_config: int
|
||||||
|
|
||||||
|
# check get plugin config
|
||||||
|
config = nonebot.get_plugin_config(Config)
|
||||||
|
assert isinstance(config, Config)
|
||||||
|
assert config.plugin_config == 1
|
||||||
|
@@ -34,19 +34,15 @@ def test_generic_check_issubclass():
|
|||||||
|
|
||||||
|
|
||||||
def test_is_coroutine_callable():
|
def test_is_coroutine_callable():
|
||||||
async def test1():
|
async def test1(): ...
|
||||||
...
|
|
||||||
|
|
||||||
def test2():
|
def test2(): ...
|
||||||
...
|
|
||||||
|
|
||||||
class TestClass1:
|
class TestClass1:
|
||||||
async def __call__(self):
|
async def __call__(self): ...
|
||||||
...
|
|
||||||
|
|
||||||
class TestClass2:
|
class TestClass2:
|
||||||
def __call__(self):
|
def __call__(self): ...
|
||||||
...
|
|
||||||
|
|
||||||
assert is_coroutine_callable(test1)
|
assert is_coroutine_callable(test1)
|
||||||
assert not is_coroutine_callable(test2)
|
assert not is_coroutine_callable(test2)
|
||||||
@@ -62,8 +58,7 @@ def test_is_gen_callable():
|
|||||||
async def test2():
|
async def test2():
|
||||||
yield
|
yield
|
||||||
|
|
||||||
def test3():
|
def test3(): ...
|
||||||
...
|
|
||||||
|
|
||||||
class TestClass1:
|
class TestClass1:
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
@@ -74,8 +69,7 @@ def test_is_gen_callable():
|
|||||||
yield
|
yield
|
||||||
|
|
||||||
class TestClass3:
|
class TestClass3:
|
||||||
def __call__(self):
|
def __call__(self): ...
|
||||||
...
|
|
||||||
|
|
||||||
assert is_gen_callable(test1)
|
assert is_gen_callable(test1)
|
||||||
assert not is_gen_callable(test2)
|
assert not is_gen_callable(test2)
|
||||||
@@ -92,8 +86,7 @@ def test_is_async_gen_callable():
|
|||||||
def test2():
|
def test2():
|
||||||
yield
|
yield
|
||||||
|
|
||||||
async def test3():
|
async def test3(): ...
|
||||||
...
|
|
||||||
|
|
||||||
class TestClass1:
|
class TestClass1:
|
||||||
async def __call__(self):
|
async def __call__(self):
|
||||||
@@ -104,8 +97,7 @@ def test_is_async_gen_callable():
|
|||||||
yield
|
yield
|
||||||
|
|
||||||
class TestClass3:
|
class TestClass3:
|
||||||
async def __call__(self):
|
async def __call__(self): ...
|
||||||
...
|
|
||||||
|
|
||||||
assert is_async_gen_callable(test1)
|
assert is_async_gen_callable(test1)
|
||||||
assert not is_async_gen_callable(test2)
|
assert not is_async_gen_callable(test2)
|
||||||
|
@@ -19,6 +19,25 @@ NoneBot 使用 [`pydantic`](https://docs.pydantic.dev/) 以及 [`python-dotenv`]
|
|||||||
|
|
||||||
NoneBot 内置的配置项列表及含义可以在[内置配置项](#内置配置项)中查看。
|
NoneBot 内置的配置项列表及含义可以在[内置配置项](#内置配置项)中查看。
|
||||||
|
|
||||||
|
:::caution 注意
|
||||||
|
|
||||||
|
NoneBot 自 2.2.0 起兼容了 Pydantic v1 与 v2 版本,以下文档中 Pydantic 相关示例均采用 v2 版本用法。
|
||||||
|
|
||||||
|
如果在使用商店或其他第三方插件的过程中遇到 Pydantic 相关警告或报错,例如:
|
||||||
|
|
||||||
|
```python
|
||||||
|
pydantic_core._pydantic_core.ValidationError: 1 validation error for Config
|
||||||
|
Input should be a valid dictionary or instance of Config [type=model_type, input_value=Config(...), input_type=Config]
|
||||||
|
```
|
||||||
|
|
||||||
|
请考虑降级 Pydantic 至 v1 版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install --force-reinstall 'pydantic~=1.10'
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## 配置项的加载
|
## 配置项的加载
|
||||||
|
|
||||||
在 NoneBot 中,我们可以把配置途径分为 **直接传入**、**系统环境变量**、**dotenv 配置文件** 三种,其加载优先级依次由高到低。
|
在 NoneBot 中,我们可以把配置途径分为 **直接传入**、**系统环境变量**、**dotenv 配置文件** 三种,其加载优先级依次由高到低。
|
||||||
@@ -182,18 +201,19 @@ superusers = config.superusers
|
|||||||
在 NoneBot 中,我们使用强大高效的 `pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在 `weather` 插件目录中新建 `config.py` 来定义一个模型:
|
在 NoneBot 中,我们使用强大高效的 `pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在 `weather` 插件目录中新建 `config.py` 来定义一个模型:
|
||||||
|
|
||||||
```python title=weather/config.py
|
```python title=weather/config.py
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel, field_validator
|
||||||
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
weather_api_key: str
|
weather_api_key: str
|
||||||
weather_command_priority: int = 10
|
weather_command_priority: int = 10
|
||||||
weather_plugin_enabled: bool = True
|
weather_plugin_enabled: bool = True
|
||||||
|
|
||||||
@validator("weather_command_priority")
|
@field_validator("weather_command_priority")
|
||||||
def check_priority(cls, v):
|
@classmethod
|
||||||
if isinstance(v, int) and v >= 1:
|
def check_priority(cls, v: int) -> int:
|
||||||
|
if v >= 1:
|
||||||
return v
|
return v
|
||||||
raise ValueError("weather command priority must be an integer and greater than 1")
|
raise ValueError("weather command priority must greater than 1")
|
||||||
```
|
```
|
||||||
|
|
||||||
在 `config.py` 中,我们定义了一个 `Config` 类,它继承自 `pydantic.BaseModel`,并定义了一些配置项。在 `Config` 类中,我们还定义了一个 `check_priority` 方法,它用于检查 `weather_command_priority` 配置项的合法性。更多关于 `pydantic` 的编写方式,可以参考 [pydantic 官方文档](https://docs.pydantic.dev/)。
|
在 `config.py` 中,我们定义了一个 `Config` 类,它继承自 `pydantic.BaseModel`,并定义了一些配置项。在 `Config` 类中,我们还定义了一个 `check_priority` 方法,它用于检查 `weather_command_priority` 配置项的合法性。更多关于 `pydantic` 的编写方式,可以参考 [pydantic 官方文档](https://docs.pydantic.dev/)。
|
||||||
@@ -201,11 +221,11 @@ class Config(BaseModel):
|
|||||||
在定义好配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型并使用:
|
在定义好配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型并使用:
|
||||||
|
|
||||||
```python {5,11} title=weather/__init__.py
|
```python {5,11} title=weather/__init__.py
|
||||||
from nonebot import get_driver
|
from nonebot import get_plugin_config
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
|
||||||
plugin_config = Config.parse_obj(get_driver().config)
|
plugin_config = get_plugin_config(Config)
|
||||||
|
|
||||||
weather = on_command(
|
weather = on_command(
|
||||||
"天气",
|
"天气",
|
||||||
@@ -239,11 +259,11 @@ class Config(BaseModel):
|
|||||||
```
|
```
|
||||||
|
|
||||||
```python title=weather/__init__.py
|
```python title=weather/__init__.py
|
||||||
from nonebot import get_driver
|
from nonebot import get_plugin_config
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
|
||||||
plugin_config = Config.parse_obj(get_driver().config).weather
|
plugin_config = get_plugin_config(Config).weather
|
||||||
```
|
```
|
||||||
|
|
||||||
这样我们就可以省略插件配置项名称中的前缀 `weather_` 了。但需要注意的是,如果我们使用了 scope 配置,那么在配置文件中也需要使用 [`env_nested_delimiter` 格式](#配置项解析),例如:
|
这样我们就可以省略插件配置项名称中的前缀 `weather_` 了。但需要注意的是,如果我们使用了 scope 配置,那么在配置文件中也需要使用 [`env_nested_delimiter` 格式](#配置项解析),例如:
|
||||||
@@ -559,13 +579,13 @@ nonebot.init(command_start={"/", ""}, command_sep={".", " "})
|
|||||||
- **类型**: `timedelta`
|
- **类型**: `timedelta`
|
||||||
- **默认值**: `timedelta(minutes=2)`
|
- **默认值**: `timedelta(minutes=2)`
|
||||||
|
|
||||||
用户会话超时时间,配置格式参考 [Datetime Types](https://docs.pydantic.dev/usage/types/#datetime-types),可以为单位为秒的 `int | float` 等。
|
用户会话超时时间,配置格式参考 [Datetime Types](https://docs.pydantic.dev/latest/api/standard_library_types/#datetimetimedelta)。
|
||||||
|
|
||||||
<Tabs groupId="configMethod">
|
<Tabs groupId="configMethod">
|
||||||
<TabItem value="dotenv" label="dotenv" default>
|
<TabItem value="dotenv" label="dotenv" default>
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
SESSION_EXPIRE_TIMEOUT=120
|
SESSION_EXPIRE_TIMEOUT=00:02:00
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
@@ -573,9 +593,9 @@ SESSION_EXPIRE_TIMEOUT=120
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# windows
|
# windows
|
||||||
set SESSION_EXPIRE_TIMEOUT '120'
|
set SESSION_EXPIRE_TIMEOUT '00:02:00'
|
||||||
# linux/macOS
|
# linux/macOS
|
||||||
export SESSION_EXPIRE_TIMEOUT='120'
|
export SESSION_EXPIRE_TIMEOUT='00:02:00'
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
@@ -21,7 +21,7 @@ options:
|
|||||||
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
||||||
|
|
||||||
```python {3,4} title=weather/__init__.py
|
```python {3,4} title=weather/__init__.py
|
||||||
plugin_config = Config.parse_obj(get_driver().config)
|
plugin_config = get_plugin_config(Config)
|
||||||
|
|
||||||
async def is_enable() -> bool:
|
async def is_enable() -> bool:
|
||||||
return plugin_config.weather_plugin_enabled
|
return plugin_config.weather_plugin_enabled
|
||||||
@@ -57,7 +57,7 @@ weather = on_command("天气", rule=rule)
|
|||||||
```python {10} title=weather/__init__.py
|
```python {10} title=weather/__init__.py
|
||||||
from nonebot.rule import to_me
|
from nonebot.rule import to_me
|
||||||
|
|
||||||
plugin_config = Config.parse_obj(get_driver().config)
|
plugin_config = get_plugin_config(Config)
|
||||||
|
|
||||||
async def is_enable() -> bool:
|
async def is_enable() -> bool:
|
||||||
return plugin_config.weather_plugin_enabled
|
return plugin_config.weather_plugin_enabled
|
||||||
|
@@ -3,114 +3,42 @@ sidebar_position: 2
|
|||||||
description: Alconna 基本介绍
|
description: Alconna 基本介绍
|
||||||
---
|
---
|
||||||
|
|
||||||
# Alconna 命令解析
|
# Alconna 本体
|
||||||
|
|
||||||
[Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
|
[`Alconna`](https://github.com/ArcletProject/Alconna) 隶属于 `ArcletProject`,是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||||
是一个简单、灵活、高效的命令参数解析器,并且不局限于解析命令式字符串。
|
|
||||||
|
|
||||||
特点包括:
|
我们通过一个例子来讲解 **Alconna** 的核心 —— `Args`, `Subcommand`, `Option`:
|
||||||
|
|
||||||
- 高效
|
|
||||||
- 直观的命令组件创建方式
|
|
||||||
- 强大的类型解析与类型转换功能
|
|
||||||
- 自定义的帮助信息格式
|
|
||||||
- 多语言支持
|
|
||||||
- 易用的快捷命令创建与使用
|
|
||||||
- 可创建命令补全会话,以实现多轮连续的补全提示
|
|
||||||
- 可嵌套的多级子命令
|
|
||||||
- 正则匹配支持
|
|
||||||
|
|
||||||
## 命令示范
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import sys
|
from arclet.alconna import Alconna, Args, Subcommand, Option
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
from arclet.alconna import Alconna, Args, Field, Option, CommandMeta, MultiVar, Arparma
|
|
||||||
from nepattern import AnyString
|
|
||||||
|
|
||||||
alc = Alconna(
|
alc = Alconna(
|
||||||
"exec",
|
"pip",
|
||||||
Args["code", MultiVar(AnyString), Field(completion=lambda: "print(1+1)")] / "\n",
|
Subcommand(
|
||||||
Option("纯文本"),
|
"install",
|
||||||
Option("无输出"),
|
Args["package", str],
|
||||||
Option("目标", Args["name", str, "res"]),
|
Option("-r|--requirement", Args["file", str]),
|
||||||
meta=CommandMeta("exec python code", example="exec\\nprint(1+1)"),
|
Option("-i|--index-url", Args["url", str]),
|
||||||
)
|
|
||||||
|
|
||||||
alc.shortcut(
|
|
||||||
"echo",
|
|
||||||
{"command": "exec 纯文本\nprint(\\'{*}\\')"},
|
|
||||||
)
|
|
||||||
|
|
||||||
alc.shortcut(
|
|
||||||
"sin(\d+)",
|
|
||||||
{"command": "exec 纯文本\nimport math\nprint(math.sin({0}*math.pi/180))"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def exec_code(result: Arparma):
|
|
||||||
if result.find("纯文本"):
|
|
||||||
codes = list(result.code)
|
|
||||||
else:
|
|
||||||
codes = str(result.origin).split("\n")[1:]
|
|
||||||
output = result.query[str]("目标.name", "res")
|
|
||||||
if not codes:
|
|
||||||
return ""
|
|
||||||
lcs = {}
|
|
||||||
_stdout = StringIO()
|
|
||||||
_to = sys.stdout
|
|
||||||
sys.stdout = _stdout
|
|
||||||
try:
|
|
||||||
exec(
|
|
||||||
"def rc(__out: str):\n "
|
|
||||||
+ " ".join(_code + "\n" for _code in codes)
|
|
||||||
+ " return locals().get(__out)",
|
|
||||||
{**globals(), **locals()},
|
|
||||||
lcs,
|
|
||||||
)
|
|
||||||
code_res = lcs["rc"](output)
|
|
||||||
sys.stdout = _to
|
|
||||||
if result.find("无输出"):
|
|
||||||
return ""
|
|
||||||
if code_res is not None:
|
|
||||||
return f"{output}: {code_res}"
|
|
||||||
_out = _stdout.getvalue()
|
|
||||||
return f"输出: {_out}"
|
|
||||||
except Exception as e:
|
|
||||||
sys.stdout = _to
|
|
||||||
return str(e)
|
|
||||||
finally:
|
|
||||||
sys.stdout = _to
|
|
||||||
|
|
||||||
print(exec_code(alc.parse("echo 1234")))
|
|
||||||
print(exec_code(alc.parse("sin30")))
|
|
||||||
print(
|
|
||||||
exec_code(
|
|
||||||
alc.parse(
|
|
||||||
"""\
|
|
||||||
exec
|
|
||||||
print(
|
|
||||||
exec_code(
|
|
||||||
alc.parse(
|
|
||||||
"exec\\n"
|
|
||||||
"import sys;print(sys.version)"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
res = alc.parse("pip install nonebot2 -i URL")
|
||||||
|
|
||||||
|
print(res)
|
||||||
|
# matched=True, header_match=(origin='pip' result='pip' matched=True groups={}), subcommands={'install': (value=Ellipsis args={'package': 'nonebot2'} options={'index-url': (value=None args={'url': 'URL'})} subcommands={})}, other_args={'package': 'nonebot2', 'url': 'URL'}
|
||||||
|
|
||||||
|
print(res.all_matched_args)
|
||||||
|
# {'package': 'nonebot2', 'url': 'URL'}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 命令编写
|
这段代码通过`Alconna`创捷了一个接受主命令名为`pip`, 子命令为`install`且子命令接受一个 **Args** 参数`package`和二个 **Option** 参数`-r`和`-i`的命令参数解析器, 通过`parse`方法返回解析结果 **Arparma** 的实例。
|
||||||
|
|
||||||
|
## 组成
|
||||||
|
|
||||||
### 命令头
|
### 命令头
|
||||||
|
|
||||||
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 `!help` 中的 `!` 与 `help`。
|
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 !help 中的 ! 与 help。
|
||||||
|
|
||||||
在 Alconna 中,你可以传入多种类型的命令头,例如:
|
|
||||||
|
|
||||||
| 前缀 | 命令名 | 匹配内容 | 说明 |
|
| 前缀 | 命令名 | 匹配内容 | 说明 |
|
||||||
| :--------------------------: | :--------: | :---------------------------------------------------------: | :--------------: |
|
| :--------------------------: | :--------: | :---------------------------------------------------------: | :--------------: |
|
||||||
@@ -127,23 +55,20 @@ print(
|
|||||||
| [123, "foo"] | "bar" | `[123, "bar"]` 或 `"foobar"` 或 `["foo", "bar"]` | 混合头 |
|
| [123, "foo"] | "bar" | `[123, "bar"]` 或 `"foobar"` 或 `["foo", "bar"]` | 混合头 |
|
||||||
| [(int, "foo"), (456, "bar")] | "baz" | `[123, "foobaz"]` 或 `[456, "foobaz"]` 或 `[456, "barbaz"]` | 对头 |
|
| [(int, "foo"), (456, "bar")] | "baz" | `[123, "foobaz"]` 或 `[456, "foobaz"]` 或 `[456, "barbaz"]` | 对头 |
|
||||||
|
|
||||||
其中
|
无前缀的类型头:此时会将传入的值尝试转为 BasePattern,例如 `int` 会转为 `nepattern.INTEGER`。此时命令头会匹配对应的类型, 例如 `int` 会匹配 `123` 或 `"456"`,但不会匹配 `"foo"`。同时,Alconna 会将命令头匹配到的值转为对应的类型,例如 `int` 会将 `"123"` 转为 `123`。
|
||||||
|
|
||||||
- 元素头:只会匹配对应的值,例如 `[123, 456]` 只会匹配 `123` 或 `456`,不会匹配 `789`。
|
:::tip
|
||||||
- 纯文字头:只会匹配对应的字符串,例如 `["foo", "bar"]` 只会匹配 `"foo"` 或 `"bar"`,不会匹配 `"baz"`。
|
|
||||||
- 正则头:`re:xxx` 会将 `xxx` 转为正则表达式,然后匹配对应的字符串,例如 `re:\d{2}` 只会匹配 `"12"` 或 `"34"`,不会匹配 `"foo"`。
|
|
||||||
**正则只在命令名上生效,命令前缀中的正则会被转义**。
|
|
||||||
- 类型头:只会匹配对应的类型,例如 `[int, bool]` 只会匹配 `123` 或 `True`,不会匹配 `"foo"`。
|
|
||||||
- 无前缀的类型头:此时会将传入的值尝试转为 BasePattern,例如 `int` 会转为 `nepattern.INTEGER`。此时命令头会匹配对应的类型,
|
|
||||||
例如 `int` 会匹配 `123` 或 `"456"`,但不会匹配 `"foo"`。同时,Alconna 会将命令头匹配到的值转为对应的类型,例如 `int` 会将 `"123"` 转为 `123`。
|
|
||||||
- 表达式头:只会匹配对应的表达式,例如 `[nepattern.NUMBER]` 只会匹配 `123` 或 `123.456`,不会匹配 `"foo"`。
|
|
||||||
- 混合头:
|
|
||||||
|
|
||||||
除了通过传入 `re:xxx` 来使用正则表达式外,Alconna 还提供了一种更加简洁的方式来使用正则表达式,那就是 Bracket Header。
|
**正则只在命令名上生效,命令前缀中的正则会被转义**
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
除了通过传入 `re:xxx` 来使用正则表达式外,Alconna 还提供了一种更加简洁的方式来使用正则表达式,那就是 Bracket Header:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from alconna import Alconna
|
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
|
||||||
```
|
```
|
||||||
@@ -152,362 +77,185 @@ Bracket Header 类似 python 里的 f-string 写法,通过 "{}" 声明匹配
|
|||||||
|
|
||||||
"{}" 中的内容为 "name:type or pat":
|
"{}" 中的内容为 "name:type or pat":
|
||||||
|
|
||||||
- "{}", "{:}": 占位符,等价于 "(.+)"
|
- "{}", "{:}" ⇔ "(.+)", 占位符
|
||||||
- "{foo}": 等价于 "(?P<foo>.+)"
|
- "{foo}" ⇔ "(?P<foo>.+)"
|
||||||
- "{:\d+}": 等价于 "(\d+)"
|
- "{:\d+}" ⇔ "(\d+)"
|
||||||
- "{foo:int}": 等价于 "(?P<foo>\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
|
- "{foo:int}" ⇔ "(?P<foo>\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
|
||||||
|
|
||||||
### 组件
|
### 参数声明(Args)
|
||||||
|
|
||||||
我们可以看到主要的两大组件:`Option` 与 `Subcommand`。
|
`Args` 是用于声明命令参数的组件, 可以通过以下几种方式构造 **Args** :
|
||||||
|
|
||||||
`Option` 可以传入一组 `alias`,如 `Option("--foo|-F|FOO|f")` 或 `Option("--foo", alias=["-F"])`
|
- `Args[key, var, default][key1, var1, default1][...]`
|
||||||
|
- `Args[(key, var, default)]`
|
||||||
|
- `Args.key[var, default]`
|
||||||
|
|
||||||
传入别名后,Option 会选择其中长度最长的作为选项名称。若传入为 "--foo|-f",则命令名称为 "--foo"。
|
其中,key **一定**是字符串,而 var 一般为参数的类型,default 为具体的值或者 **arclet.alconna.args.Field**
|
||||||
|
|
||||||
:::tip 特别提醒!!!
|
其与函数签名类似,但是允许含有默认值的参数在前;同时支持 keyword-only 参数不依照构造顺序传入 (但是仍需要在非 keyword-only 参数之后)。
|
||||||
|
|
||||||
在 Alconna 中 Option 的名字或别名**没有要求**必须在前面写上 `-`
|
#### key
|
||||||
|
|
||||||
:::
|
`key` 的作用是用以标记解析出来的参数并存放于 **Arparma** 中,以方便用户调用。
|
||||||
|
|
||||||
`Subcommand` 则可以传入自己的 **Option** 与 **Subcommand**。
|
其有三种为 Args 注解的标识符: `?`、`/`、 `!`, 标识符与 key 之间建议以 `;` 分隔:
|
||||||
|
|
||||||
```python
|
- `!` 标识符表示该处传入的参数应**不是**规定的类型,或**不在**指定的值中。
|
||||||
from arclet.alconna import Alconna, Option, Subcommand
|
- `?` 标识符表示该参数为**可选**参数,会在无参数匹配时跳过。
|
||||||
|
- `/` 标识符表示该参数的类型注解需要隐藏。
|
||||||
|
|
||||||
alc = Alconna(
|
另外,对于参数的注释也可以标记在 `key` 中,其与 key 或者标识符 以 `#` 分割:
|
||||||
"command_name",
|
`foo#这是注释;?` 或 `foo?#这是注释`
|
||||||
Option("opt1"),
|
|
||||||
Option("--opt2"),
|
|
||||||
Subcommand(
|
|
||||||
"sub1",
|
|
||||||
Option("sub1_opt1"),
|
|
||||||
Option("SO2"),
|
|
||||||
Subcommand(
|
|
||||||
"sub1_sub1"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"sub2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
他们拥有如下共同参数:
|
|
||||||
|
|
||||||
- `help_text`: 传入该组件的帮助信息
|
|
||||||
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
|
|
||||||
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
|
|
||||||
对于命令 `test foo bar baz qux <a:int>` 来讲,因为`foo bar baz` 仅需要判断是否相等,所以可以这么编写:
|
|
||||||
|
|
||||||
```python
|
|
||||||
Alconna("test", Option("qux", Args["a", int], requires=["foo", "bar", "baz"]))
|
|
||||||
```
|
|
||||||
|
|
||||||
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
|
|
||||||
|
|
||||||
特别的,使用 `OptionResult` 或 `SubcomanndResult` 可以设置包括参数字典在内的默认值:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from arclet.alconna import Option, OptionResult
|
|
||||||
|
|
||||||
opt1 = Option("--foo", default=False)
|
|
||||||
opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
|
||||||
```
|
|
||||||
|
|
||||||
### 选项操作
|
|
||||||
|
|
||||||
`Option` 可以特别设置传入一类 `Action`,作为解析操作
|
|
||||||
|
|
||||||
`Action` 分为三类:
|
|
||||||
|
|
||||||
- `store`: 无 Args 时, 仅存储一个值, 默认为 Ellipsis; 有 Args 时, 后续的解析结果会覆盖之前的值
|
|
||||||
- `append`: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis; 有 Args 时, 每个解析结果会追加到列表中
|
|
||||||
|
|
||||||
当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性
|
|
||||||
|
|
||||||
- `count`: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同
|
|
||||||
|
|
||||||
当存在默认值并且不为数字时, 会自动将默认值变成 1, 以保证计数器的正确性。
|
|
||||||
|
|
||||||
`Alconna` 提供了预制的几类 `action`:
|
|
||||||
|
|
||||||
- `store`,`store_value`,`store_true`,`store_false`
|
|
||||||
- `append`,`append_value`
|
|
||||||
- `count`
|
|
||||||
|
|
||||||
### 参数声明
|
|
||||||
|
|
||||||
`Args` 是用于声明命令参数的组件。
|
|
||||||
|
|
||||||
`Args` 是参数解析的基础组件,构造方法形如 `Args["foo", str]["bar", int]["baz", bool, False]`,
|
|
||||||
与函数签名类似,但是允许含有默认值的参数在前;同时支持 keyword-only 参数不依照构造顺序传入 (但是仍需要在非 keyword-only 参数之后)。
|
|
||||||
|
|
||||||
`Args` 中的 `name` 是用以标记解析出来的参数并存放于 **Arparma** 中,以方便用户调用。
|
|
||||||
|
|
||||||
其有三种为 Args 注解的标识符: `?`、`/` 与 `!`。标识符与 key 之间建议以 `;` 分隔:
|
|
||||||
|
|
||||||
- `!` 标识符表示该处传入的参数应不是规定的类型,或不在指定的值中。
|
|
||||||
- `?` 标识符表示该参数为可选参数,会在无参数匹配时跳过。
|
|
||||||
- `/` 标识符表示该参数的类型注解需要隐藏。
|
|
||||||
|
|
||||||
另外,对于参数的注释也可以标记在 `name` 中,其与 name 或者标识符 以 `#` 分割:
|
|
||||||
|
|
||||||
`foo#这是注释;?` 或 `foo?#这是注释`
|
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|
||||||
`Args` 中的 `name` 在实际命令中并不需要传入(keyword 参数除外):
|
`Args` 中的 `key` 在实际命令中并不需要传入(keyword 参数除外):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from arclet.alconna import Alconna, Args
|
from arclet.alconna import Alconna, Args
|
||||||
|
|
||||||
|
|
||||||
alc = Alconna("test", Args["foo", str])
|
alc = Alconna("test", Args["foo", str])
|
||||||
alc.parse("test --foo abc") # 错误
|
alc.parse("test --foo abc") # 错误
|
||||||
alc.parse("test abc") # 正确
|
alc.parse("test abc") # 正确
|
||||||
```
|
```
|
||||||
|
|
||||||
若需要 `test --foo abc`,你应该使用 `Option`:
|
若需要 `test --foo abc`,你应该使用 `Option`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from arclet.alconna import Alconna, Args, Option
|
from arclet.alconna import Alconna, Args, Option
|
||||||
|
|
||||||
|
|
||||||
alc = Alconna("test", Option("--foo", Args["foo", str]))
|
alc = Alconna("test", Option("--foo", Args["foo", str]))
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
`Args` 的参数类型表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例。
|
#### var
|
||||||
|
|
||||||
|
var 负责命令参数的**类型检查**与**类型转化**
|
||||||
|
|
||||||
|
`Args` 的`var`表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from arclet.alconna import Args
|
from arclet.alconna import Args
|
||||||
from nepattern import BasePattern
|
from nepattern import BasePattern
|
||||||
|
|
||||||
|
|
||||||
# 表示 foo 参数需要匹配一个 @number 样式的字符串
|
# 表示 foo 参数需要匹配一个 @number 样式的字符串
|
||||||
args = Args["foo", BasePattern("@\d+")]
|
args = Args["foo", BasePattern("@\d+")]
|
||||||
```
|
```
|
||||||
|
|
||||||
示例中传入的 `str` 是因为 `str` 已经注册在了 `nepattern.global_patterns` 中,因此会替换为 `nepattern.global_patterns[str]`。
|
示例中可以传入 `str` 是因为 `str` 已经注册在了 `nepattern.global_patterns` 中,因此会替换为 `nepattern.global_patterns[str]`
|
||||||
|
|
||||||
默认支持的类型有:
|
`nepattern.global_patterns`默认支持的类型有:
|
||||||
|
|
||||||
- `str`: 匹配任意字符串
|
- `str`: 匹配任意字符串
|
||||||
- `int`: 匹配整数
|
- `int`: 匹配整数
|
||||||
- `float`: 匹配浮点数
|
- `float`: 匹配浮点数
|
||||||
- `bool`: 匹配 `True` 与 `False` 以及他们小写形式
|
- `bool`: 匹配 `True` 与 `False` 以及他们小写形式
|
||||||
- `hex`: 匹配 `0x` 开头的十六进制字符串
|
- `hex`: 匹配 `0x` 开头的十六进制字符串
|
||||||
- `url`: 匹配网址
|
- `url`: 匹配网址
|
||||||
- `email`: 匹配 `xxxx@xxx` 的字符串
|
- `email`: 匹配 `xxxx@xxx` 的字符串
|
||||||
- `ipv4`: 匹配 `xxx.xxx.xxx.xxx` 的字符串
|
- `ipv4`: 匹配 `xxx.xxx.xxx.xxx` 的字符串
|
||||||
- `list`: 匹配类似 `["foo","bar","baz"]` 的字符串
|
- `list`: 匹配类似 `["foo","bar","baz"]` 的字符串
|
||||||
- `dict`: 匹配类似 `{"foo":"bar","baz":"qux"}` 的字符串
|
- `dict`: 匹配类似 `{"foo":"bar","baz":"qux"}` 的字符串
|
||||||
- `datetime`: 传入一个 `datetime` 支持的格式字符串,或时间戳
|
- `datetime`: 传入一个 `datetime` 支持的格式字符串,或时间戳
|
||||||
- `Any`: 匹配任意类型
|
- `Any`: 匹配任意类型
|
||||||
- `AnyString`: 匹配任意类型,转为 `str`
|
- `AnyString`: 匹配任意类型,转为 `str`
|
||||||
- `Number`: 匹配 `int` 与 `float`,转为 `int`
|
- `Number`: 匹配 `int` 与 `float`,转为 `int`
|
||||||
|
|
||||||
同时可以使用 typing 中的类型:
|
同时可以使用 typing 中的类型:
|
||||||
|
|
||||||
- `Literal[X]`: 匹配其中的任意一个值
|
- `Literal[X]`: 匹配其中的任意一个值
|
||||||
- `Union[X, Y]`: 匹配其中的任意一个类型
|
- `Union[X, Y]`: 匹配其中的任意一个类型
|
||||||
- `Optional[xxx]`: 会自动将默认值设为 `None`,并在解析失败时使用默认值
|
- `Optional[xxx]`: 会自动将默认值设为 `None`,并在解析失败时使用默认值
|
||||||
- `List[X]`: 匹配一个列表,其中的元素为 `X` 类型
|
- `List[X]`: 匹配一个列表,其中的元素为 `X` 类型
|
||||||
- `Dict[X, Y]`: 匹配一个字典,其中的 key 为 `X` 类型,value 为 `Y` 类型
|
- `Dict[X, Y]`: 匹配一个字典,其中的 key 为 `X` 类型,value 为 `Y` 类型
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|
||||||
几类特殊的传入标记:
|
几类特殊的传入标记:
|
||||||
|
|
||||||
- `"foo"`: 匹配字符串 "foo" (若没有某个 `BasePattern` 与之关联)
|
- `"foo"`: 匹配字符串 "foo" (若没有某个 `BasePattern` 与之关联)
|
||||||
- `RawStr("foo")`: 匹配字符串 "foo" (不会被 `BasePattern` 替换)
|
- `RawStr("foo")`: 匹配字符串 "foo" (不会被 `BasePattern` 替换)
|
||||||
- `"foo|bar|baz"`: 匹配 "foo" 或 "bar" 或 "baz"
|
- `"foo|bar|baz"`: 匹配 "foo" 或 "bar" 或 "baz"
|
||||||
- `[foo, bar, Baz, ...]`: 匹配其中的任意一个值或类型
|
- `[foo, bar, Baz, ...]`: 匹配其中的任意一个值或类型
|
||||||
- `Callable[[X], Y]`: 匹配一个参数为 `X` 类型的值,并返回通过该函数调用得到的 `Y` 类型的值
|
- `Callable[[X], Y]`: 匹配一个参数为 `X` 类型的值,并返回通过该函数调用得到的 `Y` 类型的值
|
||||||
- `"re:xxx"`: 匹配一个正则表达式 `xxx`,会返回 Match[0]
|
- `"re:xxx"`: 匹配一个正则表达式 `xxx`,会返回 Match[0]
|
||||||
- `"rep:xxx"`: 匹配一个正则表达式 `xxx`,会返回 `re.Match` 对象
|
- `"rep:xxx"`: 匹配一个正则表达式 `xxx`,会返回 `re.Match` 对象
|
||||||
- `{foo: bar, baz: qux}`: 匹配字典中的任意一个键, 并返回对应的值 (特殊的键 ... 会匹配任意的值)
|
- `{foo: bar, baz: qux}`: 匹配字典中的任意一个键, 并返回对应的值 (特殊的键 ... 会匹配任意的值)
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
`MultiVar` 则是一个特殊的标注,用于告知解析器该参数可以接受多个值,其构造方法形如 `MultiVar(str)`。
|
`MultiVar` 则是一个特殊的标注,用于告知解析器该参数可以接受多个值,其构造方法形如 `MultiVar(str)`。 同样的还有 `KeyWordVar`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数。
|
||||||
同样的还有 `KeyWordVar`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数。
|
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|
||||||
`MultiVar` 与 `KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,其构造方法形如 `MultiVar(KeyWordVar(str))`
|
`MultiVar` 与 `KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,其构造方法形如 `MultiVar(KeyWordVar(str))`
|
||||||
|
|
||||||
`MultiVar` 与 `KeyWordVar` 也可以传入 `default` 参数,用于指定默认值。
|
`MultiVar` 与 `KeyWordVar` 也可以传入 `default` 参数,用于指定默认值
|
||||||
|
|
||||||
`MultiVar` 不能在 `KeyWordVar` 之后传入。
|
`MultiVar` 不能在 `KeyWordVar` 之后传入
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### 紧凑命令
|
### Option 和 Subcommand
|
||||||
|
|
||||||
`Alconna`,`Option` 可以设置 `compact=True` 使得解析命令时允许名称与后随参数之间没有分隔:
|
`Option` 可以传入一组 `alias`,如 `Option("--foo|-F|--FOO|-f")` 或 `Option("--foo", alias=["-F"]`
|
||||||
|
|
||||||
|
传入别名后,`option` 会选择其中长度最长的作为选项名称。若传入为 "--foo|-f",则命令名称为 "--foo"
|
||||||
|
|
||||||
|
:::tip 特别提醒!!!
|
||||||
|
|
||||||
|
在 Alconna 中 Option 的名字或别名**没有要求**必须在前面写上 `-`
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
`Subcommand` 可以传入自己的 **Option** 与 **Subcommand**
|
||||||
|
|
||||||
|
他们拥有如下共同参数:
|
||||||
|
|
||||||
|
- `help_text`: 传入该组件的帮助信息
|
||||||
|
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
|
||||||
|
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
|
||||||
|
对于命令 `test foo bar baz qux <a:int>` 来讲,因为`foo bar baz` 仅需要判断是否相等, 所以可以这么编写:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from arclet.alconna import Alconna, Option, CommandMeta, Args
|
Alconna("test", Option("qux", Args.a[int], requires=["foo", "bar", "baz"]))
|
||||||
|
|
||||||
alc = Alconna("test", Args["foo", int], Option("BAR", Args["baz", str], compact=True), meta=CommandMeta(compact=True))
|
|
||||||
|
|
||||||
assert alc.parse("test123 BARabc").matched
|
|
||||||
```
|
```
|
||||||
|
|
||||||
这使得我们可以实现如下命令:
|
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
|
||||||
|
特别的,使用 `OptionResult` 或 `SubcomanndResult` 可以设置包括参数字典在内的默认值:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> from arclet.alconna import Alconna, Option, Args, append
|
from arclet.alconna import Option, OptionResult
|
||||||
>>> alc = Alconna("gcc", Option("--flag|-F", Args["content", str], action=append, compact=True))
|
|
||||||
>>> alc.parse("gcc -Fabc -Fdef -Fxyz").query[list[str]]("flag.content")
|
opt1 = Option("--foo", default=False)
|
||||||
['abc', 'def', 'xyz']
|
opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
||||||
```
|
```
|
||||||
|
|
||||||
当 `Option` 的 `action` 为 `count` 时,其自动支持 `compact` 特性:
|
`Option` 可以特别设置传入一类 `Action`,作为解析操作
|
||||||
|
|
||||||
```python
|
`Action` 分为三类:
|
||||||
>>> from arclet.alconna import Alconna, Option, Args, count
|
|
||||||
>>> alc = Alconna("pp", Option("--verbose|-v", action=count, default=0))
|
|
||||||
>>> alc.parse("pp -vvv").query[int]("verbose.value")
|
|
||||||
3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 命令特性
|
- `store`: 无 Args 时, 仅存储一个值, 默认为 Ellipsis; 有 Args 时, 后续的解析结果会覆盖之前的值
|
||||||
|
- `append`: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis; 有 Args 时, 每个解析结果会追加到列表中, 当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性
|
||||||
|
- `count`: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同, 当存在默认值并且不为数字时, 会自动将默认值变成 1, 以保证计数器的正确性。
|
||||||
|
|
||||||
### 配置
|
`Alconna` 提供了预制的几类 `Action`:
|
||||||
|
|
||||||
`arclet.alconna.Namespace` 表示某一命名空间下的默认配置:
|
- `store`(默认),`store_value`,`store_true`,`store_false`
|
||||||
|
- `append`,`append_value`
|
||||||
|
- `count`
|
||||||
|
|
||||||
```python
|
### Arparma
|
||||||
from arclet.alconna import config, namespace, Namespace
|
|
||||||
from arclet.alconna.tools import ShellTextFormatter
|
|
||||||
|
|
||||||
|
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果
|
||||||
|
|
||||||
np = Namespace("foo", prefixes=["/"]) # 创建 Namespace 对象,并进行初始配置
|
`Arparma` 会有如下参数:
|
||||||
|
|
||||||
with namespace("bar") as np1:
|
|
||||||
np1.prefixes = ["!"] # 以上下文管理器方式配置命名空间,此时配置会自动注入上下文内创建的命令
|
|
||||||
np1.formatter_type = ShellTextFormatter # 设置此命名空间下的命令的 formatter 默认为 ShellTextFormatter
|
|
||||||
np1.builtin_option_name["help"] = {"帮助", "-h"} # 设置此命名空间下的命令的帮助选项名称
|
|
||||||
|
|
||||||
config.namespaces["foo"] = np # 将命名空间挂载到 config 上
|
|
||||||
```
|
|
||||||
|
|
||||||
同时也提供了默认命名空间配置与修改方法:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from arclet.alconna import config, namespace, Namespace
|
|
||||||
|
|
||||||
|
|
||||||
config.default_namespace.prefixes = [...] # 直接修改默认配置
|
|
||||||
|
|
||||||
np = Namespace("xxx", prefixes=[...])
|
|
||||||
config.default_namespace = np # 更换默认的命名空间
|
|
||||||
|
|
||||||
with namespace(config.default_namespace.name) as np:
|
|
||||||
np.prefixes = [...]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 半自动补全
|
|
||||||
|
|
||||||
半自动补全为用户提供了推荐后续输入的功能。
|
|
||||||
|
|
||||||
补全默认通过 `--comp` 或 `-cp` 或 `?` 触发:(命名空间配置可修改名称)
|
|
||||||
|
|
||||||
```python
|
|
||||||
from arclet.alconna import Alconna, Args, Option
|
|
||||||
|
|
||||||
alc = Alconna("test", Args["abc", int]) + Option("foo") + Option("bar")
|
|
||||||
alc.parse("test ?")
|
|
||||||
|
|
||||||
'''
|
|
||||||
output
|
|
||||||
|
|
||||||
以下是建议的输入:
|
|
||||||
* <abc: int>
|
|
||||||
* --help
|
|
||||||
* -h
|
|
||||||
* -sct
|
|
||||||
* --shortcut
|
|
||||||
* foo
|
|
||||||
* bar
|
|
||||||
'''
|
|
||||||
```
|
|
||||||
|
|
||||||
### 快捷指令
|
|
||||||
|
|
||||||
快捷指令顾名思义,可以为基础指令创建便捷的触发方式
|
|
||||||
|
|
||||||
一般情况下你可以通过 `Alconna.shortcut` 进行快捷指令操作 (创建,删除);
|
|
||||||
|
|
||||||
```python
|
|
||||||
>>> from arclet.alconna import Alconna, Args
|
|
||||||
>>> alc = Alconna("setu", Args["count", int])
|
|
||||||
>>> alc.shortcut("涩图(\d+)张", {"args": ["{0}"]})
|
|
||||||
'Alconna::setu 的快捷指令: "涩图(\\d+)张" 添加成功'
|
|
||||||
>>> alc.parse("涩图3张").query("count")
|
|
||||||
3
|
|
||||||
```
|
|
||||||
|
|
||||||
`shortcut` 的第一个参数为快捷指令名称,第二个参数为 `ShortcutArgs`,作为快捷指令的配置
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ShortcutArgs(TypedDict):
|
|
||||||
"""快捷指令参数"""
|
|
||||||
|
|
||||||
command: NotRequired[DataCollection[Any]]
|
|
||||||
"""快捷指令的命令"""
|
|
||||||
args: NotRequired[list[Any]]
|
|
||||||
"""快捷指令的附带参数"""
|
|
||||||
fuzzy: NotRequired[bool]
|
|
||||||
"""是否允许命令后随参数"""
|
|
||||||
prefix: NotRequired[bool]
|
|
||||||
"""是否调用时保留指令前缀"""
|
|
||||||
```
|
|
||||||
|
|
||||||
当 `fuzzy` 为 False 时,传入 `"涩图1张 abc"` 之类的快捷指令将视为解析失败
|
|
||||||
|
|
||||||
快捷指令允许三类特殊的 placeholder:
|
|
||||||
|
|
||||||
- `{%X}`: 如 `setu {%0}`,表示此处必须填入快捷指令后随的第 X 个参数。
|
|
||||||
|
|
||||||
例如,若快捷指令为 `涩图`,配置为 `{"command": "setu {%0}"}`,则指令 `涩图 1` 相当于 `setu 1`
|
|
||||||
|
|
||||||
- `{*}`: 表示此处填入所有后随参数,并且可以通过 `{*X}` 的方式指定组合参数之间的分隔符。
|
|
||||||
- `{X}`: 表示此处填入可能的正则匹配的组:
|
|
||||||
- 若 `command` 中存在匹配组 `(xxx)`,则 `{X}` 表示第 X 个匹配组的内容
|
|
||||||
- 若 `command` 中存储匹配组 `(?P<xxx>...)`,则 `{X}` 表示名字为 X 的匹配结果
|
|
||||||
|
|
||||||
除此之外,通过内置选项 `--shortcut` 可以动态操作快捷指令。
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- `cmd --shortcut <key> <cmd>` 来增加一个快捷指令
|
|
||||||
- `cmd --shortcut list` 来列出当前指令的所有快捷指令
|
|
||||||
- `cmd --shortcut delete key` 来删除一个快捷指令
|
|
||||||
|
|
||||||
### 使用模糊匹配
|
|
||||||
|
|
||||||
模糊匹配通过在 Alconna 中设置其 CommandMeta 开启。
|
|
||||||
|
|
||||||
模糊匹配会应用在任意需要进行名称判断的地方,如**命令名称**,**选项名称**和**参数名称**(如指定需要传入参数名称)。
|
|
||||||
|
|
||||||
```python
|
|
||||||
from arclet.alconna import Alconna, CommandMeta
|
|
||||||
|
|
||||||
alc = Alconna("test_fuzzy", meta=CommandMeta(fuzzy_match=True))
|
|
||||||
alc.parse("test_fuzy")
|
|
||||||
# output: test_fuzy is not matched. Do you mean "test_fuzzy"?
|
|
||||||
```
|
|
||||||
|
|
||||||
## 解析结果
|
|
||||||
|
|
||||||
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果。
|
|
||||||
|
|
||||||
`Arpamar` 会有如下参数:
|
|
||||||
|
|
||||||
- 调试类
|
- 调试类
|
||||||
|
|
||||||
@@ -524,47 +272,286 @@ alc.parse("test_fuzy")
|
|||||||
- 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`键对应的值
|
- `main_args.xxx`, `options.xxx`, ...: 返回字典中 `xxx`键对应的值
|
||||||
- `args.xxx`: 返回 all_matched_args 中 `xxx`键对应的值
|
- `args.xxx`: 返回 all_matched_args 中 `xxx`键对应的值
|
||||||
- `options.foo`,`foo`: 返回选项 `foo` 的解析结果 (OptionResult)
|
- `options.foo`, `foo`: 返回选项 `foo` 的解析结果 (OptionResult)
|
||||||
- `options.foo.value`,`foo.value`: 返回选项 `foo` 的解析值
|
- `options.foo.value`, `foo.value`: 返回选项 `foo` 的解析值
|
||||||
- `options.foo.args`,`foo.args`: 返回选项 `foo` 的解析参数字典
|
- `options.foo.args`, `foo.args`: 返回选项 `foo` 的解析参数字典
|
||||||
- `options.foo.args.bar`,`foo.bar`: 返回选项 `foo` 的参数字典中 `bar` 键对应的值
|
- `options.foo.args.bar`, `foo.bar`: 返回选项 `foo` 的参数字典中 `bar` 键对应的值 ...
|
||||||
...
|
|
||||||
|
|
||||||
同样,`Arparma["foo.bar"]` 的表现与 `query()` 一致
|
## 命名空间配置
|
||||||
|
|
||||||
|
命名空间配置 (以下简称命名空间) 相当于`Alconna`的设置,`Alconna`默认使用“Alconna”命名空间,命名空间有以下几个属性:
|
||||||
|
|
||||||
|
- name: 命名空间名称
|
||||||
|
- prefixes: 默认前缀配置
|
||||||
|
- separators: 默认分隔符配置
|
||||||
|
- formatter_type: 默认格式化器类型
|
||||||
|
- fuzzy_match: 默认是否开启模糊匹配
|
||||||
|
- raise_exception: 默认是否抛出异常
|
||||||
|
- builtin_option_name: 默认的内置选项名称(--help, --shortcut, --comp)
|
||||||
|
- enable_message_cache: 默认是否启用消息缓存
|
||||||
|
- compact: 默认是否开启紧凑模式
|
||||||
|
- strict: 命令是否严格匹配
|
||||||
|
- ...
|
||||||
|
|
||||||
|
### 新建命名空间并替换
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, namespace, Namespace, Subcommand, Args, config
|
||||||
|
|
||||||
|
|
||||||
|
ns = Namespace("foo", prefixes=["/"]) # 创建 "foo"命名空间配置, 它要求创建的Alconna的主命令前缀必须是/
|
||||||
|
|
||||||
|
alc = Alconna("pip", Subcommand("install", Args["package", str]), namespace=ns) # 在创建Alconna时候传入命名空间以替换默认命名空间
|
||||||
|
|
||||||
|
# 可以通过with方式创建命名空间
|
||||||
|
with namespace("bar") as np1:
|
||||||
|
np1.prefixes = ["!"] # 以上下文管理器方式配置命名空间,此时配置会自动注入上下文内创建的命令
|
||||||
|
np1.formatter_type = ShellTextFormatter # 设置此命名空间下的命令的 formatter 默认为 ShellTextFormatter
|
||||||
|
np1.builtin_option_name["help"] = {"帮助", "-h"} # 设置此命名空间下的命令的帮助选项名称
|
||||||
|
|
||||||
|
# 你还可以使用config来管理所有命名空间并切换至任意命名空间
|
||||||
|
config.namespaces["foo"] = ns # 将命名空间挂载到 config 上
|
||||||
|
|
||||||
|
alc = Alconna("pip", Subcommand("install", Args["package", str]), namespace=config.namespaces["foo"]) # 也是同样可以切换到"foo"命名空间
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改默认的命名空间
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import config, namespace, Namespace
|
||||||
|
|
||||||
|
|
||||||
|
config.default_namespace.prefixes = [...] # 直接修改默认配置
|
||||||
|
|
||||||
|
np = Namespace("xxx", prefixes=[...])
|
||||||
|
config.default_namespace = np # 更换默认的命名空间
|
||||||
|
|
||||||
|
with namespace(config.default_namespace.name) as np:
|
||||||
|
np.prefixes = [...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快捷指令
|
||||||
|
|
||||||
|
快捷命令可以做到标识一段命令, 并且传递参数给原命令
|
||||||
|
|
||||||
|
一般情况下你可以通过 `Alconna.shortcut` 进行快捷指令操作 (创建,删除)
|
||||||
|
|
||||||
|
`shortcut` 的第一个参数为快捷指令名称,第二个参数为 `ShortcutArgs`,作为快捷指令的配置:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ShortcutArgs(TypedDict):
|
||||||
|
"""快捷指令参数"""
|
||||||
|
|
||||||
|
command: NotRequired[DataCollection[Any]]
|
||||||
|
"""快捷指令的命令"""
|
||||||
|
args: NotRequired[list[Any]]
|
||||||
|
"""快捷指令的附带参数"""
|
||||||
|
fuzzy: NotRequired[bool]
|
||||||
|
"""是否允许命令后随参数"""
|
||||||
|
prefix: NotRequired[bool]
|
||||||
|
"""是否调用时保留指令前缀"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### args的使用
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Args
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna("setu", Args["count", int])
|
||||||
|
|
||||||
|
alc.shortcut("涩图(\d+)张", {"args": ["{0}"]})
|
||||||
|
# 'Alconna::setu 的快捷指令: "涩图(\\d+)张" 添加成功'
|
||||||
|
|
||||||
|
alc.parse("涩图3张").query("count")
|
||||||
|
# 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### command的使用
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Args
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna("eval", Args["content", str])
|
||||||
|
|
||||||
|
alc.shortcut("echo", {"command": "eval print(\\'{*}\\')"})
|
||||||
|
# 'Alconna::eval 的快捷指令: "echo" 添加成功'
|
||||||
|
|
||||||
|
alc.shortcut("echo", delete=True) # 删除快捷指令
|
||||||
|
# 'Alconna::eval 的快捷指令: "echo" 删除成功'
|
||||||
|
|
||||||
|
@alc.bind() # 绑定一个命令执行器, 若匹配成功则会传入参数, 自动执行命令执行器
|
||||||
|
def cb(content: str):
|
||||||
|
eval(content, {}, {})
|
||||||
|
|
||||||
|
alc.parse('eval print(\\"hello world\\")')
|
||||||
|
# hello world
|
||||||
|
|
||||||
|
alc.parse("echo hello world!")
|
||||||
|
# hello world!
|
||||||
|
```
|
||||||
|
|
||||||
|
当 `fuzzy` 为 False 时,第一个例子中传入 `"涩图1张 abc"` 之类的快捷指令将视为解析失败
|
||||||
|
|
||||||
|
快捷指令允许三类特殊的 placeholder:
|
||||||
|
|
||||||
|
- `{%X}`: 如 `setu {%0}`,表示此处填入快捷指令后随的第 X 个参数。
|
||||||
|
|
||||||
|
例如,若快捷指令为 `涩图`, 配置为 `{"command": "setu {%0}"}`, 则指令 `涩图 1` 相当于 `setu 1`
|
||||||
|
|
||||||
|
- `{*}`: 表示此处填入所有后随参数,并且可以通过 `{*X}` 的方式指定组合参数之间的分隔符。
|
||||||
|
|
||||||
|
- `{X}`: 表示此处填入可能的正则匹配的组:
|
||||||
|
|
||||||
|
- 若 `command` 中存在匹配组 `(xxx)`,则 `{X}` 表示第 X 个匹配组的内容
|
||||||
|
- 若 `command` 中存储匹配组 `(?P<xxx>...)`, 则 `{X}` 表示 **名字** 为 X 的匹配结果
|
||||||
|
|
||||||
|
除此之外, 通过 **Alconna** 内置选项 `--shortcut` 可以动态操作快捷指令
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- `cmd --shortcut <key> <cmd>` 来增加一个快捷指令
|
||||||
|
- `cmd --shortcut list` 来列出当前指令的所有快捷指令
|
||||||
|
- `cmd --shortcut delete key` 来删除一个快捷指令
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Args
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna("eval", Args["content", str])
|
||||||
|
|
||||||
|
alc.shortcut("echo", {"command": "eval print(\\'{*}\\')"})
|
||||||
|
|
||||||
|
alc.parse("eval --shortcut list")
|
||||||
|
# 'echo'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 紧凑命令
|
||||||
|
|
||||||
|
`Alconna`, `Option` 与 `Subcommand` 可以设置 `compact=True` 使得解析命令时允许名称与后随参数之间没有分隔:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Option, CommandMeta, Args
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna("test", Args["foo", int], Option("BAR", Args["baz", str], compact=True), meta=CommandMeta(compact=True))
|
||||||
|
|
||||||
|
assert alc.parse("test123 BARabc").matched
|
||||||
|
```
|
||||||
|
|
||||||
|
这使得我们可以实现如下命令:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Option, Args, append
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna("gcc", Option("--flag|-F", Args["content", str], action=append, compact=True))
|
||||||
|
print(alc.parse("gcc -Fabc -Fdef -Fxyz").query[list]("flag.content"))
|
||||||
|
# ['abc', 'def', 'xyz']
|
||||||
|
```
|
||||||
|
|
||||||
|
当 `Option` 的 `action` 为 `count` 时,其自动支持 `compact` 特性:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Option, count
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna("pp", Option("--verbose|-v", action=count, default=0))
|
||||||
|
print(alc.parse("pp -vvv").query[int]("verbose.value"))
|
||||||
|
# 3
|
||||||
|
```
|
||||||
|
|
||||||
|
## 模糊匹配
|
||||||
|
|
||||||
|
模糊匹配通过在 Alconna 中设置其 CommandMeta 开启
|
||||||
|
|
||||||
|
模糊匹配会应用在任意需要进行名称判断的地方,如 **命令名称**,**选项名称** 和 **参数名称** (如指定需要传入参数名称)。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, CommandMeta
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna("test_fuzzy", meta=CommandMeta(fuzzy_match=True))
|
||||||
|
|
||||||
|
alc.parse("test_fuzy")
|
||||||
|
# test_fuzy is not matched. Do you mean "test_fuzzy"?
|
||||||
|
```
|
||||||
|
|
||||||
|
## 半自动补全
|
||||||
|
|
||||||
|
半自动补全为用户提供了推荐后续输入的功能
|
||||||
|
|
||||||
|
补全默认通过 `--comp` 或 `-cp` 或 `?` 触发:(命名空间配置可修改名称)
|
||||||
|
|
||||||
|
```python
|
||||||
|
from arclet.alconna import Alconna, Args, Option
|
||||||
|
|
||||||
|
|
||||||
|
alc = Alconna("test", Args["abc", int]) + Option("foo") + Option("bar")
|
||||||
|
alc.parse("test --comp")
|
||||||
|
|
||||||
|
'''
|
||||||
|
output
|
||||||
|
|
||||||
|
以下是建议的输入:
|
||||||
|
* <abc: int>
|
||||||
|
* --help
|
||||||
|
* -h
|
||||||
|
* -sct
|
||||||
|
* --shortcut
|
||||||
|
* foo
|
||||||
|
* bar
|
||||||
|
'''
|
||||||
|
```
|
||||||
|
|
||||||
## Duplication
|
## Duplication
|
||||||
|
|
||||||
**Duplication** 用来提供更好的自动补全,类似于 **ArgParse** 的 **Namespace**,经测试表现良好(好耶)。
|
**Duplication** 用来提供更好的自动补全,类似于 **ArgParse** 的 **Namespace**
|
||||||
|
|
||||||
普通情况下使用,需要利用到 **ArgsStub**、**OptionStub** 和 **SubcommandStub** 三个部分,
|
普通情况下使用,需要利用到 **ArgsStub**、**OptionStub** 和 **SubcommandStub** 三个部分
|
||||||
|
|
||||||
以 pip 为例,其对应的 Duplication 应如下构造:
|
以pip为例,其对应的 Duplication 应如下构造:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from arclet.alconna import OptionResult, Duplication, SubcommandStub
|
from arclet.alconna import Alconna, Args, Option, OptionResult, Duplication, SubcommandStub, Subcommand, count
|
||||||
|
|
||||||
|
|
||||||
class MyDup(Duplication):
|
class MyDup(Duplication):
|
||||||
verbose: OptionResult
|
verbose: OptionResult
|
||||||
install: SubcommandStub # 选项与子命令对应的stub的变量名必须与其名字相同
|
install: SubcommandStub
|
||||||
```
|
|
||||||
|
|
||||||
并在解析时传入 Duplication:
|
|
||||||
|
|
||||||
```python
|
alc = Alconna(
|
||||||
|
"pip",
|
||||||
|
Subcommand(
|
||||||
|
"install",
|
||||||
|
Args["package", str],
|
||||||
|
Option("-r|--requirement", Args["file", str]),
|
||||||
|
Option("-i|--index-url", Args["url", str]),
|
||||||
|
),
|
||||||
|
Option("-v|--version"),
|
||||||
|
Option("-v|--verbose", action=count),
|
||||||
|
)
|
||||||
|
|
||||||
|
res = alc.parse("pip -v install ...") # 不使用duplication获得的提示较少
|
||||||
|
print(res.query("install"))
|
||||||
|
# (value=Ellipsis args={'package': '...'} options={} subcommands={})
|
||||||
|
|
||||||
result = alc.parse("pip -v install ...", duplication=MyDup)
|
result = alc.parse("pip -v install ...", duplication=MyDup)
|
||||||
>>> type(result)
|
print(result.install)
|
||||||
<class MyDup>
|
# SubcommandStub(_origin=Subcommand('install', args=Args('package': str)), _value=Ellipsis, available=True, args=ArgsStub(_origin=Args('package': str), _value={'package': '...'}, available=True), dest='install', options=[OptionStub(_origin=Option('requirement', args=Args('file': str)), _value=None, available=False, args=ArgsStub(_origin=Args('file': str), _value={}, available=False), dest='requirement', aliases=['r', 'requirement'], name='requirement'), OptionStub(_origin=Option('index-url', args=Args('url': str)), _value=None, available=False, args=ArgsStub(_origin=Args('url': str), _value={}, available=False), dest='index-url', aliases=['index-url', 'i'], name='index-url')], subcommands=[], name='install')
|
||||||
```
|
```
|
||||||
|
|
||||||
**Duplication** 也可以如 **Namespace** 一样直接标明参数名称和类型:
|
**Duplication** 也可以如 **Namespace** 一样直接标明参数名称和类型:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@@ -10,7 +10,7 @@ description: 配置项
|
|||||||
- **类型**: `bool`
|
- **类型**: `bool`
|
||||||
- **默认值**: `False`
|
- **默认值**: `False`
|
||||||
|
|
||||||
是否全局启用输出信息自动发送,不启用则会在触特殊内置选项后仍然将解析结果传递至响应器。
|
是否全局启用输出信息自动发送,不启用则会在触发特殊内置选项后仍然将解析结果传递至响应器。
|
||||||
|
|
||||||
## alconna_use_command_start
|
## alconna_use_command_start
|
||||||
|
|
||||||
@@ -38,11 +38,11 @@ description: 配置项
|
|||||||
- **类型**: `bool`
|
- **类型**: `bool`
|
||||||
- **默认值**: `False`
|
- **默认值**: `False`
|
||||||
|
|
||||||
是否读取 Nonebot 的配置项 `COMMAND_SEP` 来作为全局的 Alconna 命令分隔符
|
是否读取 Nonebot 的配置项 `COMMAND_SEP` 来作为全局的 Alconna 命令分隔符。
|
||||||
|
|
||||||
## alconna_global_extensions
|
## alconna_global_extensions
|
||||||
|
|
||||||
- **类型**: `List[str]`
|
- **类型**: `List[str]`
|
||||||
- **默认值**: `[]`
|
- **默认值**: `[]`
|
||||||
|
|
||||||
全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`
|
全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`。
|
||||||
|
@@ -3,15 +3,16 @@ sidebar_position: 3
|
|||||||
description: 响应规则的使用
|
description: 响应规则的使用
|
||||||
---
|
---
|
||||||
|
|
||||||
# Alconna 响应规则
|
# Alconna 插件
|
||||||
|
|
||||||
以下为一个使用示例:
|
展示:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna.adapters.onebot12 import Image
|
from nonebot_plugin_alconna.adapters.onebot12 import Image
|
||||||
from nonebot_plugin_alconna import At, on_alconna
|
from nonebot_plugin_alconna import At, on_alconna
|
||||||
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
|
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
|
||||||
|
|
||||||
|
|
||||||
alc = Alconna(
|
alc = Alconna(
|
||||||
["/", "!"],
|
["/", "!"],
|
||||||
"role-group",
|
"role-group",
|
||||||
@@ -41,64 +42,103 @@ async def _(result: Arparma):
|
|||||||
|
|
||||||
## 响应器使用
|
## 响应器使用
|
||||||
|
|
||||||
`on_alconna` 的所有参数如下:
|
本插件基于 **Alconna**,为 **Nonebot** 提供了一类新的事件响应器辅助函数 `on_alconna`:
|
||||||
|
|
||||||
- `command: Alconna | str`: Alconna 命令
|
|
||||||
- `skip_for_unmatch: bool = True`: 是否在命令不匹配时跳过该响应
|
|
||||||
- `auto_send_output: bool = False`: 是否自动发送输出信息并跳过响应
|
|
||||||
- `aliases: set[str | tuple[str, ...]] | None = None`: 命令别名,作用类似于 `on_command` 中的 aliases
|
|
||||||
- `comp_config: CompConfig | None = None`: 补全会话配置,不传入则不启用补全会话
|
|
||||||
- `extensions: list[type[Extension] | Extension] | None = None`: 需要加载的匹配扩展,可以是扩展类或扩展实例
|
|
||||||
- `exclude_ext: list[type[Extension] | str] | None = None`: 需要排除的匹配扩展,可以是扩展类或扩展的 id
|
|
||||||
- `use_origin: bool = False`: 是否使用未经 to_me 等处理过的消息
|
|
||||||
- `use_cmd_start: bool = False`: 是否使用 COMMAND_START 作为命令前缀
|
|
||||||
- `use_cmd_sep: bool = False`: 是否使用 COMMAND_SEP 作为命令分隔符
|
|
||||||
|
|
||||||
`on_alconna` 返回的是 `Matcher` 的子类 `AlconnaMatcher`,其拓展了如下方法:
|
|
||||||
|
|
||||||
- `.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`
|
|
||||||
- `.got`, `send`, `reject`, ...: 拓展了 prompt 类型,即支持使用 `UniMessage` 作为 prompt
|
|
||||||
|
|
||||||
用例:
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
def on_alconna(
|
||||||
|
command: Alconna | str,
|
||||||
|
skip_for_unmatch: bool = True,
|
||||||
|
auto_send_output: bool = False,
|
||||||
|
aliases: set[str | tuple[str, ...]] | None = None,
|
||||||
|
comp_config: CompConfig | None = None,
|
||||||
|
extensions: list[type[Extension] | Extension] | None = None,
|
||||||
|
exclude_ext: list[type[Extension] | str] | None = None,
|
||||||
|
use_origin: bool = False,
|
||||||
|
use_cmd_start: bool = False,
|
||||||
|
use_cmd_sep: bool = False,
|
||||||
|
**kwargs,
|
||||||
|
...,
|
||||||
|
):
|
||||||
|
```
|
||||||
|
|
||||||
|
- `command`: Alconna 命令或字符串,字符串将通过 `AlconnaFormat` 转换为 Alconna 命令
|
||||||
|
- `skip_for_unmatch`: 是否在命令不匹配时跳过该响应
|
||||||
|
- `auto_send_output`: 是否自动发送输出信息并跳过响应
|
||||||
|
- `aliases`: 命令别名, 作用类似于 `on_command` 中的 aliases
|
||||||
|
- `comp_config`: 补全会话配置, 不传入则不启用补全会话
|
||||||
|
- `extensions`: 需要加载的匹配扩展, 可以是扩展类或扩展实例
|
||||||
|
- `exclude_ext`: 需要排除的匹配扩展, 可以是扩展类或扩展的id
|
||||||
|
- `use_origin`: 是否使用未经 to_me 等处理过的消息
|
||||||
|
- `use_cmd_start`: 是否使用 COMMAND_START 作为命令前缀
|
||||||
|
- `use_cmd_sep`: 是否使用 COMMAND_SEP 作为命令分隔符
|
||||||
|
|
||||||
|
`on_alconna` 返回的是 `Matcher` 的子类 `AlconnaMatcher` ,其拓展了如下方法:
|
||||||
|
|
||||||
|
- `.assign(path, value, or_not)`: 用于对包含多个选项/子命令的命令的分派处理(具体请看[条件控制](./matcher.md#条件控制))
|
||||||
|
- `.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`
|
||||||
|
- `.got`, `send`, `reject`, ... : 拓展了 prompt 类型,即支持使用 `UniMessage` 作为 prompt
|
||||||
|
|
||||||
|
实例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot import require
|
||||||
|
require("nonebot_plugin_alconna")
|
||||||
|
|
||||||
from arclet.alconna import Alconna, Option, Args
|
from arclet.alconna import Alconna, Option, Args
|
||||||
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match, UniMessage
|
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match, UniMessage
|
||||||
|
|
||||||
login = on_alconna(Alconna(["/"], "login", Args["password?", str], Option("-r|--recall")))
|
|
||||||
|
|
||||||
|
login = on_alconna(Alconna(["/"], "login", Args["password?", str], Option("-r|--recall"))) # 这里["/"]指命令前缀必须是/
|
||||||
|
|
||||||
|
# /login -r 触发
|
||||||
@login.assign("recall")
|
@login.assign("recall")
|
||||||
async def login_exit():
|
async def login_exit():
|
||||||
await login.finish("已退出")
|
await login.finish("已退出")
|
||||||
|
|
||||||
|
# /login xxx 触发
|
||||||
@login.assign("password")
|
@login.assign("password")
|
||||||
async def login_handle(pw: Match[str] = AlconnaMatch("password")):
|
async def login_handle(pw: Match[str] = AlconnaMatch("password")):
|
||||||
if pw.available:
|
if pw.available:
|
||||||
login.set_path_arg("password", pw.result)
|
login.set_path_arg("password", pw.result)
|
||||||
|
|
||||||
|
# /login 触发
|
||||||
@login.got_path("password", prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请输入密码"))
|
@login.got_path("password", prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请输入密码"))
|
||||||
async def login_got(password: str):
|
async def login_got(password: str):
|
||||||
assert password
|
assert password
|
||||||
await login.send("登录成功")
|
await login.send("登录成功")
|
||||||
```
|
```
|
||||||
|
|
||||||
## 依赖注入
|
## 依赖注入
|
||||||
|
|
||||||
`Alconna` 的解析结果会放入 `Arparma` 类中,或用户指定的 `Duplication` 类。
|
本插件提供了一系列依赖注入函数,便于在响应函数中获取解析结果:
|
||||||
|
|
||||||
而 `AlconnaMatcher` 在原有 Matcher 的基础上拓展了允许的依赖注入:
|
- `AlconnaResult`: `CommandResult` 类型的依赖注入函数
|
||||||
|
- `AlconnaMatches`: `Arparma` 类型的依赖注入函数
|
||||||
|
- `AlconnaDuplication`: `Duplication` 类型的依赖注入函数
|
||||||
|
- `AlconnaMatch`: `Match` 类型的依赖注入函数
|
||||||
|
- `AlconnaQuery`: `Query` 类型的依赖注入函数
|
||||||
|
|
||||||
|
同时,基于 [`Annotated` 支持](https://github.com/nonebot/nonebot2/pull/1832),添加了两类注解:
|
||||||
|
|
||||||
|
- `AlcMatches`:同 `AlconnaMatches`
|
||||||
|
- `AlcResult`:同 `AlconnaResult`
|
||||||
|
|
||||||
|
可以看到,本插件提供了几类额外的模型:
|
||||||
|
|
||||||
|
- `CommandResult`: 解析结果,包括了源命令 `source: Alconna` ,解析结果 `result: Arparma`,以及可能的输出信息 `output: str | None` 字段
|
||||||
|
- `Match`: 匹配项,表示参数是否存在于 `all_matched_args` 内,可用 `Match.available` 判断是否匹配,`Match.result` 获取匹配的值
|
||||||
|
- `Query`: 查询项,表示参数是否可由 `Arparma.query` 查询并获得结果,可用 `Query.available` 判断是否查询成功,`Query.result` 获取查询结果
|
||||||
|
|
||||||
|
**Alconna** 默认依赖注入的目标参数皆不需要使用依赖注入函数, 该效果对于 `AlconnaMatcher.got_path` 下的 Arg 同样有效:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@cmd.handle()
|
|
||||||
async def handle(
|
async def handle(
|
||||||
result: CommandResult,
|
result: CommandResult,
|
||||||
arp: Arparma,
|
arp: Arparma,
|
||||||
dup: Duplication, # 基类或子类都可以
|
dup: Duplication,
|
||||||
ext: Extension,
|
|
||||||
source: Alconna,
|
source: Alconna,
|
||||||
abc: str, # 类似 Match, 但是若匹配结果不存在对应字段则跳过该 handler
|
abc: str, # 类似 Match, 但是若匹配结果不存在对应字段则跳过该 handler
|
||||||
foo: Match[str],
|
foo: Match[str],
|
||||||
@@ -107,12 +147,6 @@ async def handle(
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
可以看到,本插件提供了几类额外的模型:
|
|
||||||
|
|
||||||
- `CommandResult`: 解析结果,包括了源命令 `source: Alconna` ,解析结果 `result: Arparma`,以及可能的输出信息 `output: str | None` 字段
|
|
||||||
- `Match`: 匹配项,表示参数是否存在于 `all_matched_args` 内,可用 `Match.available` 判断是否匹配,`Match.result` 获取匹配的值
|
|
||||||
- `Query`: 查询项,表示参数是否可由 `Arparma.query` 查询并获得结果,可用 `Query.available` 判断是否查询成功,`Query.result` 获取查询结果
|
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
|
|
||||||
如果你更喜欢 Depends 式的依赖注入,`nonebot_plugin_alconna` 同时提供了一系列的依赖注入函数,他们包括:
|
如果你更喜欢 Depends 式的依赖注入,`nonebot_plugin_alconna` 同时提供了一系列的依赖注入函数,他们包括:
|
||||||
@@ -130,14 +164,19 @@ 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 on_alconna, Match, Query, AlconnaQuery
|
from nonebot_plugin_alconna import (
|
||||||
|
on_alconna,
|
||||||
|
Match,
|
||||||
|
Query,
|
||||||
|
AlconnaMatch,
|
||||||
|
AlcResult
|
||||||
|
)
|
||||||
from arclet.alconna import Alconna, Args, Option, Arparma
|
from arclet.alconna import Alconna, Args, Option, Arparma
|
||||||
|
|
||||||
|
|
||||||
test = on_alconna(
|
test = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"test",
|
"test",
|
||||||
@@ -147,41 +186,34 @@ test = on_alconna(
|
|||||||
auto_send_output=True
|
auto_send_output=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@test.handle()
|
@test.handle()
|
||||||
async def handle_test1(result: AlcResult):
|
async def handle_test1(result: AlcResult):
|
||||||
await test.send(f"matched: {result.matched}")
|
await test.send(f"matched: {result.matched}")
|
||||||
await test.send(f"maybe output: {result.output}")
|
await test.send(f"maybe output: {result.output}")
|
||||||
|
|
||||||
@test.handle()
|
@test.handle()
|
||||||
async def handle_test2(bar: Match[int]):
|
async def handle_test2(result: Arparma):
|
||||||
|
await test.send(f"head result: {result.header_result}")
|
||||||
|
await test.send(f"args: {result.all_matched_args}")
|
||||||
|
|
||||||
|
@test.handle()
|
||||||
|
async def handle_test3(bar: Match[int] = AlconnaMatch("bar")):
|
||||||
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_test3(qux: Query[bool] = AlconnaQuery("baz.qux", False)):
|
async def handle_test4(qux: Query[bool] = Query("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` 适配器下的标注。
|
本插件提供了通用消息段标注, 通用消息段序列, 使插件使用者可以忽略平台之间字段的差异
|
||||||
|
|
||||||
适配器下的消息段标注会匹配特定的 `MessageSegment`:
|
响应器使用示例中使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
|
||||||
|
|
||||||
而通用标注与适配器标注的区别在于,通用标注会匹配多个适配器中相似类型的消息段,并返回
|
具体介绍和使用请查看 [通用信息组件](./uniseg.mdx#通用消息段)
|
||||||
`nonebot_plugin_alconna.uniseg` 中定义的 [`Segment` 模型](./utils.md#通用消息段)
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
```python
|
|
||||||
...
|
|
||||||
ats = result.query[tuple[At, ...]]("add.member.target")
|
|
||||||
group.extend(member.target for member in ats)
|
|
||||||
```
|
|
||||||
|
|
||||||
这样插件使用者就不用考虑平台之间字段的差异
|
|
||||||
|
|
||||||
本插件为以下适配器提供了专门的适配器标注:
|
本插件为以下适配器提供了专门的适配器标注:
|
||||||
|
|
||||||
@@ -219,6 +251,7 @@ require("nonebot_plugin_alconna")
|
|||||||
from arclet.alconna import Alconna, Subcommand, Option, Args
|
from arclet.alconna import Alconna, Subcommand, Option, Args
|
||||||
from nonebot_plugin_alconna import on_alconna, CommandResult
|
from nonebot_plugin_alconna import on_alconna, CommandResult
|
||||||
|
|
||||||
|
|
||||||
pip = Alconna(
|
pip = Alconna(
|
||||||
"pip",
|
"pip",
|
||||||
Subcommand(
|
Subcommand(
|
||||||
@@ -262,6 +295,7 @@ async def update(arp: CommandResult):
|
|||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna import At, Match, UniMessage, on_alconna
|
from nonebot_plugin_alconna import At, Match, UniMessage, on_alconna
|
||||||
|
|
||||||
|
|
||||||
test_cmd = on_alconna(Alconna("test", Args["target?", Union[str, At]]))
|
test_cmd = on_alconna(Alconna("test", Args["target?", Union[str, At]]))
|
||||||
|
|
||||||
@test_cmd.handle()
|
@test_cmd.handle()
|
||||||
@@ -305,15 +339,101 @@ async def tt(target: Union[str, At]):
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## 响应器创建装饰
|
||||||
|
|
||||||
|
本插件提供了一个 `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)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 类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()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 返回值回调
|
||||||
|
|
||||||
|
在 `AlconnaMatch`,`AlconnaQuery` 或 `got_path` 中,你可以使用 `middleware` 参数来传入一个对返回值进行处理的函数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from nonebot_plugin_alconna import image_fetch
|
||||||
|
|
||||||
|
|
||||||
|
mask_cmd = on_alconna(
|
||||||
|
Alconna("search", Args["img?", Image]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mask_cmd.handle()
|
||||||
|
async def mask_h(matcher: AlconnaMatcher, img: Match[bytes] = AlconnaMatch("img", image_fetch)):
|
||||||
|
result = await search_img(img.result)
|
||||||
|
await matcher.send(result.content)
|
||||||
|
```
|
||||||
|
|
||||||
|
其中,`image_fetch` 是一个中间件,其接受一个 `Image` 对象,并提取图片的二进制数据返回。
|
||||||
|
|
||||||
## 匹配拓展
|
## 匹配拓展
|
||||||
|
|
||||||
本插件提供了一个 `Extension` 类,其用于自定义 AlconnaMatcher 的部分行为。
|
本插件提供了一个 `Extension` 类,其用于自定义 AlconnaMatcher 的部分行为
|
||||||
|
|
||||||
例如 `LLMExtension` (仅举例):
|
例如 `LLMExtension` (仅举例):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna import Extension, Alconna, on_alconna, Interface
|
from nonebot_plugin_alconna import Extension, Alconna, on_alconna, Interface
|
||||||
|
|
||||||
|
|
||||||
class LLMExtension(Extension):
|
class LLMExtension(Extension):
|
||||||
@property
|
@property
|
||||||
def priority(self) -> int:
|
def priority(self) -> int:
|
||||||
@@ -347,9 +467,9 @@ matcher = on_alconna(
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
那么使用了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量
|
那么添加了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量。
|
||||||
|
|
||||||
目前 `Extension` 的功能有:
|
目前 `Extension` 的功能有:
|
||||||
|
|
||||||
- `validate`: 对于事件的来源适配器或 bot 选择是否接受响应
|
- `validate`: 对于事件的来源适配器或 bot 选择是否接受响应
|
||||||
- `output_converter`: 输出信息的自定义转换方法
|
- `output_converter`: 输出信息的自定义转换方法
|
||||||
@@ -360,15 +480,15 @@ matcher = on_alconna(
|
|||||||
- `send_wrapper`: 对发送的消息 (Message 或 UniMessage) 的额外处理
|
- `send_wrapper`: 对发送的消息 (Message 或 UniMessage) 的额外处理
|
||||||
- `before_catch`: 自定义依赖注入的绑定确认函数
|
- `before_catch`: 自定义依赖注入的绑定确认函数
|
||||||
- `catch`: 自定义依赖注入处理函数
|
- `catch`: 自定义依赖注入处理函数
|
||||||
- `post_init`: 响应器创建后对命令对象的额外除了
|
- `post_init`: 响应器创建后对命令对象的额外处理
|
||||||
|
|
||||||
例如内置的 `DiscordSlashExtension`,其可自动将 Alconna 对象翻译成 slash 指令并注册,且将收到的指令交互事件转为指令供命令解析:
|
例如内置的 `DiscordSlashExtension`,其可自动将 Alconna 对象翻译成 slash 指令并注册,且将收到的指令交互事件转为指令供命令解析:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna import Match, on_alconna
|
from nonebot_plugin_alconna import Match, on_alconna
|
||||||
|
|
||||||
from nonebot_plugin_alconna.adapters.discord import DiscordSlashExtension
|
from nonebot_plugin_alconna.adapters.discord import DiscordSlashExtension
|
||||||
|
|
||||||
|
|
||||||
alc = Alconna(
|
alc = Alconna(
|
||||||
["/"],
|
["/"],
|
||||||
"permission",
|
"permission",
|
||||||
|
@@ -12,6 +12,9 @@ import TabItem from "@theme/TabItem";
|
|||||||
|
|
||||||
## 通用消息段
|
## 通用消息段
|
||||||
|
|
||||||
|
适配器下的消息段标注会匹配适配器特定的 `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` 下直接标注使用:
|
`nonebot-plugin-alconna.uniseg` 提供了类似 `MessageSegment` 的通用消息段,并可在 `Alconna` 下直接标注使用:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -80,13 +83,13 @@ class Other(Segment):
|
|||||||
"""其他 Segment"""
|
"""其他 Segment"""
|
||||||
```
|
```
|
||||||
|
|
||||||
来自各自适配器的消息序列都会经过这些通用消息段对应的标注转换,以达到跨平台接收消息的作用
|
此类消息段通过 `UniMessage.export` 可以转为特定的 `MessageSegment`
|
||||||
|
|
||||||
## 通用消息序列
|
## 通用消息序列
|
||||||
|
|
||||||
`nonebot-plugin-alconna.uniseg` 同时提供了一个类似于 `Message` 的 `UniMessage` 类型,其元素为经过通用标注转换后的通用消息段。
|
`nonebot-plugin-alconna.uniseg` 同时提供了一个类似于 `Message` 的 `UniMessage` 类型,其元素为经过通用标注转换后的通用消息段。
|
||||||
|
|
||||||
你可以用如下方式获取 `UniMessage` :
|
你可以用如下方式获取 `UniMessage`:
|
||||||
|
|
||||||
<Tabs groupId="get_unimsg">
|
<Tabs groupId="get_unimsg">
|
||||||
<TabItem value="depend" label="使用依赖注入">
|
<TabItem value="depend" label="使用依赖注入">
|
||||||
@@ -96,6 +99,7 @@ class Other(Segment):
|
|||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna.uniseg import UniMsg, At, Reply
|
from nonebot_plugin_alconna.uniseg import UniMsg, At, Reply
|
||||||
|
|
||||||
|
|
||||||
matcher = on_xxx(...)
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
@matcher.handle()
|
@matcher.handle()
|
||||||
@@ -117,6 +121,7 @@ async def _(msg: UniMsg):
|
|||||||
from nonebot import Message, EventMessage
|
from nonebot import Message, EventMessage
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
|
|
||||||
|
|
||||||
matcher = on_xxx(...)
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
@matcher.handle()
|
@matcher.handle()
|
||||||
@@ -129,12 +134,13 @@ async def _(message: Message = EventMessage()):
|
|||||||
|
|
||||||
不仅如此,你还可以通过 `UniMessage` 的 `export` 与 `send` 方法来**跨平台发送消息**。
|
不仅如此,你还可以通过 `UniMessage` 的 `export` 与 `send` 方法来**跨平台发送消息**。
|
||||||
|
|
||||||
`UniMessage.export` 会通过传入的 `bot: Bot` 参数,或上下文中的 `Bot` 对象读取适配器信息,并使用对应的生成方法把通用消息转为适配器对应的消息序列
|
`UniMessage.export` 会通过传入的 `bot: Bot` 参数,或上下文中的 `Bot` 对象读取适配器信息,并使用对应的生成方法把通用消息转为适配器对应的消息序列:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot import Bot, on_command
|
from nonebot import Bot, on_command
|
||||||
from nonebot_plugin_alconna.uniseg import Image, UniMessage
|
from nonebot_plugin_alconna.uniseg import Image, UniMessage
|
||||||
|
|
||||||
|
|
||||||
test = on_command("test")
|
test = on_command("test")
|
||||||
|
|
||||||
@test.handle()
|
@test.handle()
|
||||||
@@ -149,6 +155,7 @@ from arclet.alconna import Alconna, Args
|
|||||||
from nonebot_plugin_alconna import Match, AlconnaMatcher, on_alconna
|
from nonebot_plugin_alconna import Match, AlconnaMatcher, on_alconna
|
||||||
from nonebot_plugin_alconna.uniseg import At, UniMessage
|
from nonebot_plugin_alconna.uniseg import At, UniMessage
|
||||||
|
|
||||||
|
|
||||||
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
|
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
|
||||||
|
|
||||||
@test_cmd.handle()
|
@test_cmd.handle()
|
||||||
@@ -167,6 +174,7 @@ async def tt(target: At):
|
|||||||
from nonebot import Bot, on_command
|
from nonebot import Bot, on_command
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
|
|
||||||
|
|
||||||
test = on_command("test")
|
test = on_command("test")
|
||||||
|
|
||||||
@test.handle()
|
@test.handle()
|
||||||
@@ -188,6 +196,7 @@ async def handle():
|
|||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, At
|
from nonebot_plugin_alconna.uniseg import UniMessage, At
|
||||||
|
|
||||||
|
|
||||||
msg = UniMessage("Hello")
|
msg = UniMessage("Hello")
|
||||||
msg1 = UniMessage(At("user", "124"))
|
msg1 = UniMessage(At("user", "124"))
|
||||||
msg2 = UniMessage(["Hello", At("user", "124")])
|
msg2 = UniMessage(["Hello", At("user", "124")])
|
||||||
@@ -198,33 +207,96 @@ msg2 = UniMessage(["Hello", At("user", "124")])
|
|||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, At, Image
|
from nonebot_plugin_alconna.uniseg import UniMessage, At, Image
|
||||||
|
|
||||||
|
|
||||||
msg = UniMessage.text("Hello").at("124").image(path="/path/to/img")
|
msg = UniMessage.text("Hello").at("124").image(path="/path/to/img")
|
||||||
assert msg == UniMessage(
|
assert msg == UniMessage(
|
||||||
["Hello", At("user", "124"), Image(path="/path/to/img")]
|
["Hello", At("user", "124"), Image(path="/path/to/img")]
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 获取消息纯文本
|
### 拼接消息
|
||||||
|
|
||||||
类似于 `Message.extract_plain_text()`,用于获取通用消息的纯文本。
|
`str`、`UniMessage`、`Segment` 对象之间可以直接相加,相加均会返回一个新的 `UniMessage` 对象:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, At
|
# 消息序列与消息段相加
|
||||||
# 提取消息纯文本字符串
|
UniMessage("text") + Text("text")
|
||||||
assert UniMessage(
|
# 消息序列与字符串相加
|
||||||
[At("user", "1234"), "text"]
|
UniMessage([Text("text")]) + "text"
|
||||||
).extract_plain_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` 方法,或者使用自加:
|
||||||
|
|
||||||
通用消息序列继承自 `List[Segment]` ,因此可以使用 `for` 循环遍历消息段。
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
for segment in message: # type: Segment
|
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` 方法来:
|
我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
|
||||||
@@ -236,7 +308,7 @@ At("user", "1234") in message
|
|||||||
At in message
|
At in message
|
||||||
```
|
```
|
||||||
|
|
||||||
我们还可以使用 `only` 方法来检查消息中是否仅包含指定的消息段。
|
我们还可以使用 `only` 方法来检查消息中是否仅包含指定的消息段:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 是否都为 "test"
|
# 是否都为 "test"
|
||||||
@@ -245,13 +317,37 @@ message.only("test")
|
|||||||
message.only(Text)
|
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` 过滤索引与切片。
|
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, At, Text, Reply
|
from nonebot_plugin_alconna.uniseg import UniMessage, At, Text, Reply
|
||||||
|
|
||||||
|
|
||||||
message = UniMessage(
|
message = UniMessage(
|
||||||
[
|
[
|
||||||
Reply(...),
|
Reply(...),
|
||||||
@@ -272,14 +368,14 @@ message[At, 0] == At("user", "1234")
|
|||||||
message[Text, 0:2] == UniMessage([Text("text1"), Text("text2")])
|
message[Text, 0:2] == UniMessage([Text("text1"), Text("text2")])
|
||||||
```
|
```
|
||||||
|
|
||||||
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤。
|
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
message.include(Text, At)
|
message.include(Text, At)
|
||||||
message.exclude(Reply)
|
message.exclude(Reply)
|
||||||
```
|
```
|
||||||
|
|
||||||
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。
|
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 指定类型首个消息段索引
|
# 指定类型首个消息段索引
|
||||||
@@ -288,96 +384,14 @@ message.index(Text) == 1
|
|||||||
message.count(Text) == 2
|
message.count(Text) == 2
|
||||||
```
|
```
|
||||||
|
|
||||||
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。
|
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 获取指定类型指定个数的消息段
|
# 获取指定类型指定个数的消息段
|
||||||
message.get(Text, 1) == UniMessage([Text("test1")])
|
message.get(Text, 1) == UniMessage([Text("test1")])
|
||||||
```
|
```
|
||||||
|
|
||||||
### 拼接消息
|
### 消息发送
|
||||||
|
|
||||||
`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` 两个特殊值。
|
|
||||||
|
|
||||||
## 消息发送
|
|
||||||
|
|
||||||
前面提到,通用消息可用 `UniMessage.send` 发送自身:
|
前面提到,通用消息可用 `UniMessage.send` 发送自身:
|
||||||
|
|
||||||
@@ -398,6 +412,7 @@ async def send(
|
|||||||
from nonebot import Event, Bot
|
from nonebot import Event, Bot
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage, Target
|
from nonebot_plugin_alconna.uniseg import UniMessage, Target
|
||||||
|
|
||||||
|
|
||||||
matcher = on_xxx(...)
|
matcher = on_xxx(...)
|
||||||
|
|
||||||
@matcher.handle()
|
@matcher.handle()
|
||||||
|
@@ -1,88 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 6
|
|
||||||
description: 杂项
|
|
||||||
---
|
|
||||||
|
|
||||||
# 杂项
|
|
||||||
|
|
||||||
## 特殊装饰器
|
|
||||||
|
|
||||||
`nonebot_plugin_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)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 特殊构造器
|
|
||||||
|
|
||||||
`nonebot_plugin_alconna` 提供了一个 `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()
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 中间件
|
|
||||||
|
|
||||||
在 `AlconnaMatch`, `AlconnaQuery` 或 `got_path` 中,你可以使用 `middleware` 参数来传入一个对返回值进行处理的函数,
|
|
||||||
|
|
||||||
```python {1, 9}
|
|
||||||
from nonebot_plugin_alconna import image_fetch
|
|
||||||
|
|
||||||
mask_cmd = on_alconna(
|
|
||||||
Alconna("search", Args["img?", Image]),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@mask_cmd.handle()
|
|
||||||
async def mask_h(matcher: AlconnaMatcher, img: Match[bytes] = AlconnaMatch("img", image_fetch)):
|
|
||||||
result = await search_img(img.result)
|
|
||||||
await matcher.send(result.content)
|
|
||||||
```
|
|
||||||
|
|
||||||
其中,`image_fetch` 是一个中间件,其接受一个 `Image` 对象,并提取图片的二进制数据返回。
|
|
@@ -79,9 +79,9 @@ except Exception as e:
|
|||||||
通常适配器需要一些配置项,例如平台连接密钥等。适配器的配置方法与[插件配置](../appendices/config#%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE)类似,例如:
|
通常适配器需要一些配置项,例如平台连接密钥等。适配器的配置方法与[插件配置](../appendices/config#%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE)类似,例如:
|
||||||
|
|
||||||
```python title=config.py
|
```python title=config.py
|
||||||
from pydantic import BaseModel, Extra
|
from pydantic import BaseModel
|
||||||
|
|
||||||
class Config(BaseModel, extra=Extra.ignore):
|
class Config(BaseModel):
|
||||||
xxx_id: str
|
xxx_id: str
|
||||||
xxx_token: str
|
xxx_token: str
|
||||||
```
|
```
|
||||||
@@ -97,6 +97,7 @@ from typing import Any
|
|||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
from nonebot.drivers import Driver
|
from nonebot.drivers import Driver
|
||||||
|
from nonebot import get_plugin_config
|
||||||
from nonebot.adapters import Adapter as BaseAdapter
|
from nonebot.adapters import Adapter as BaseAdapter
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
@@ -106,7 +107,7 @@ class Adapter(BaseAdapter):
|
|||||||
def __init__(self, driver: Driver, **kwargs: Any):
|
def __init__(self, driver: Driver, **kwargs: Any):
|
||||||
super().__init__(driver, **kwargs)
|
super().__init__(driver, **kwargs)
|
||||||
# 读取适配器所需的配置项
|
# 读取适配器所需的配置项
|
||||||
self.adapter_config: Config = Config(**self.config.dict())
|
self.adapter_config: Config = get_plugin_config(Config)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@override
|
@override
|
||||||
@@ -125,6 +126,7 @@ NoneBot 提供了多种 [Driver](../advanced/driver) 来帮助适配器进行网
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
|
from nonebot import get_plugin_config
|
||||||
from nonebot.exception import WebSocketClosed
|
from nonebot.exception import WebSocketClosed
|
||||||
from nonebot.drivers import Request, WebSocketClientMixin
|
from nonebot.drivers import Request, WebSocketClientMixin
|
||||||
|
|
||||||
@@ -132,7 +134,7 @@ class Adapter(BaseAdapter):
|
|||||||
@override
|
@override
|
||||||
def __init__(self, driver: Driver, **kwargs: Any):
|
def __init__(self, driver: Driver, **kwargs: Any):
|
||||||
super().__init__(driver, **kwargs)
|
super().__init__(driver, **kwargs)
|
||||||
self.adapter_config: Config = Config(**self.config.dict())
|
self.adapter_config: Config = get_plugin_config(Config)
|
||||||
self.task: Optional[asyncio.Task] = None # 存储 ws 任务
|
self.task: Optional[asyncio.Task] = None # 存储 ws 任务
|
||||||
self.setup()
|
self.setup()
|
||||||
|
|
||||||
@@ -200,6 +202,7 @@ class Adapter(BaseAdapter):
|
|||||||
##### 服务端通信方式
|
##### 服务端通信方式
|
||||||
|
|
||||||
```python {30,38} title=adapter.py
|
```python {30,38} title=adapter.py
|
||||||
|
from nonebot import get_plugin_config
|
||||||
from nonebot.drivers import (
|
from nonebot.drivers import (
|
||||||
Request,
|
Request,
|
||||||
ASGIMixin,
|
ASGIMixin,
|
||||||
@@ -212,7 +215,7 @@ class Adapter(BaseAdapter):
|
|||||||
@override
|
@override
|
||||||
def __init__(self, driver: Driver, **kwargs: Any):
|
def __init__(self, driver: Driver, **kwargs: Any):
|
||||||
super().__init__(driver, **kwargs)
|
super().__init__(driver, **kwargs)
|
||||||
self.adapter_config: Config = Config(**self.config.dict())
|
self.adapter_config: Config = get_plugin_config(Config)
|
||||||
self.setup()
|
self.setup()
|
||||||
|
|
||||||
def setup(self) -> None:
|
def setup(self) -> None:
|
||||||
@@ -286,6 +289,8 @@ class Adapter(BaseAdapter):
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from nonebot.compat import type_validate_python
|
||||||
|
|
||||||
from .bot import Bot
|
from .bot import Bot
|
||||||
from .event import Event
|
from .event import Event
|
||||||
from .log import log
|
from .log import log
|
||||||
@@ -301,7 +306,7 @@ class Adapter(BaseAdapter):
|
|||||||
|
|
||||||
# 做一层异常处理,以应对平台事件数据的变更
|
# 做一层异常处理,以应对平台事件数据的变更
|
||||||
try:
|
try:
|
||||||
return your_event_class.parse_obj(payload)
|
return type_validate_python(your_event_class, payload)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 无法正常解析为具体 Event 时,给出日志提示
|
# 无法正常解析为具体 Event 时,给出日志提示
|
||||||
log(
|
log(
|
||||||
@@ -309,7 +314,7 @@ class Adapter(BaseAdapter):
|
|||||||
f"Parse event error: {str(payload)}",
|
f"Parse event error: {str(payload)}",
|
||||||
)
|
)
|
||||||
# 也可以尝试转为基础 Event 进行处理
|
# 也可以尝试转为基础 Event 进行处理
|
||||||
return Event.parse_obj(payload)
|
return type_validate_python(Event, payload)
|
||||||
|
|
||||||
|
|
||||||
async def _forward(self, bot: Bot):
|
async def _forward(self, bot: Bot):
|
||||||
@@ -440,6 +445,7 @@ Event 是 NoneBot 中的事件主体对象,所有平台消息在进入处理
|
|||||||
```python {5,8,13,18,23,28,33} title=event.py
|
```python {5,8,13,18,23,28,33} title=event.py
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
|
from nonebot.compat import model_dump
|
||||||
from nonebot.adapters import Event as BaseEvent
|
from nonebot.adapters import Event as BaseEvent
|
||||||
|
|
||||||
class Event(BaseEvent):
|
class Event(BaseEvent):
|
||||||
@@ -452,7 +458,7 @@ class Event(BaseEvent):
|
|||||||
@override
|
@override
|
||||||
def get_event_description(self) -> str:
|
def get_event_description(self) -> str:
|
||||||
# 返回事件的描述,用于日志打印,请注意转义 loguru tag
|
# 返回事件的描述,用于日志打印,请注意转义 loguru tag
|
||||||
return escape_tag(repr(self.dict()))
|
return escape_tag(repr(model_dump(self)))
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def get_message(self):
|
def get_message(self):
|
||||||
@@ -574,6 +580,27 @@ class Message(BaseMessage[MessageSegment]):
|
|||||||
- [QQGuild](https://github.com/nonebot/adapter-qqguild/blob/master/nonebot/adapters/qqguild/message.py#L22-L150)
|
- [QQGuild](https://github.com/nonebot/adapter-qqguild/blob/master/nonebot/adapters/qqguild/message.py#L22-L150)
|
||||||
- [Telegram](https://github.com/nonebot/adapter-telegram/blob/beta/nonebot/adapters/telegram/message.py#L43-L250)
|
- [Telegram](https://github.com/nonebot/adapter-telegram/blob/beta/nonebot/adapters/telegram/message.py#L43-L250)
|
||||||
|
|
||||||
|
## 适配器测试
|
||||||
|
|
||||||
|
关于适配器测试相关内容在这里不再展开,开发者可以根据需要进行合适的测试。这里为开发者提供几个常见问题的解决方法:
|
||||||
|
|
||||||
|
1. 在测试中无法导入 editable 模式安装的适配器代码。在 pytest 的 `conftest.py` 内添加如下代码:
|
||||||
|
|
||||||
|
```python title=tests/conftest.py
|
||||||
|
from pathlib import Path
|
||||||
|
import nonebot.adapters
|
||||||
|
nonebot.adapters.__path__.append( # type: ignore
|
||||||
|
str((Path(__file__).parent.parent / "nonebot" / "adapters").resolve())
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 需要计算适配器测试覆盖率,请在 `pyproject.toml` 中添加 pytest 配置:
|
||||||
|
|
||||||
|
```toml title=pyproject.toml
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "--cov nonebot/adapters/{adapter-name} --cov-report term-missing"
|
||||||
|
```
|
||||||
|
|
||||||
## 后续工作
|
## 后续工作
|
||||||
|
|
||||||
在完成适配器代码的编写后,如果想要将适配器发布到 NoneBot 商店,我们需要将适配器发布到 PyPI 中,然后前往[商店](/store/adapters)页面,切换到适配器页签,点击**发布适配器**按钮,填写适配器相关信息并提交。
|
在完成适配器代码的编写后,如果想要将适配器发布到 NoneBot 商店,我们需要将适配器发布到 PyPI 中,然后前往[商店](/store/adapters)页面,切换到适配器页签,点击**发布适配器**按钮,填写适配器相关信息并提交。
|
||||||
|
@@ -56,7 +56,7 @@ options:
|
|||||||
|
|
||||||
## 创建配置文件
|
## 创建配置文件
|
||||||
|
|
||||||
配置文件用于存放 NoneBot 运行所需要的配置项,使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`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` 文本文件,并写入以下内容:
|
||||||
|
|
||||||
|
@@ -82,20 +82,19 @@ Message([MessageSegment.text("Hello, world!")])
|
|||||||
|
|
||||||
#### 从字典数组构造
|
#### 从字典数组构造
|
||||||
|
|
||||||
`Message` 对象支持 Pydantic 自定义类型构造,可以使用 Pydantic 的 `parse_obj_as` 方法进行构造。
|
`Message` 对象支持 Pydantic 自定义类型构造,可以使用 Pydantic 的 `TypeAdapter` 方法进行构造。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from pydantic import parse_obj_as
|
from pydantic import TypeAdapter
|
||||||
from nonebot.adapters.console import Message, MessageSegment
|
from nonebot.adapters.console import Message, MessageSegment
|
||||||
|
|
||||||
# 由字典构造消息段
|
# 由字典构造消息段
|
||||||
parse_obj_as(
|
TypeAdapter(MessageSegment).validate_python(
|
||||||
MessageSegment, {"type": "text", "data": {"text": "text"}}
|
{"type": "text", "data": {"text": "text"}}
|
||||||
) == MessageSegment.text("text")
|
) == MessageSegment.text("text")
|
||||||
|
|
||||||
# 由字典数组构造消息序列
|
# 由字典数组构造消息序列
|
||||||
parse_obj_as(
|
TypeAdapter(Message).validate_python(
|
||||||
Message,
|
|
||||||
[MessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
|
[MessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
|
||||||
) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
|
) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
|
||||||
```
|
```
|
||||||
|
@@ -39,13 +39,15 @@ export default function TagFormItem({
|
|||||||
}
|
}
|
||||||
if (validateTag()) {
|
if (validateTag()) {
|
||||||
const tag: TagType = { label, color };
|
const tag: TagType = { label, color };
|
||||||
setTags([...tags, tag]);
|
const newTags = [...tags, tag];
|
||||||
onTagUpdate(tags);
|
setTags(newTags);
|
||||||
|
onTagUpdate(newTags);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const delTag = (index: number) => {
|
const delTag = (index: number) => {
|
||||||
setTags(tags.filter((_, i) => i !== index));
|
const newTags = tags.filter((_, i) => i !== index);
|
||||||
onTagUpdate(tags);
|
setTags(newTags);
|
||||||
|
onTagUpdate(newTags);
|
||||||
};
|
};
|
||||||
const onChangeColor = (color: ColorResult) => {
|
const onChangeColor = (color: ColorResult) => {
|
||||||
setColor(color.hex as TagType["color"]);
|
setColor(color.hex as TagType["color"]);
|
||||||
|
@@ -5,6 +5,84 @@ toc_max_heading_level: 2
|
|||||||
|
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
## v2.2.1
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: 优化 pydantic 兼容函数 `model_dump` 和 `type_validate_json` [@MingxuanGame](https://github.com/MingxuanGame) ([#2579](https://github.com/nonebot/nonebot2/pull/2579))
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- Fix: 修改遗漏的过时 Pydantic 方法 [@yanyongyu](https://github.com/yanyongyu) ([#2577](https://github.com/nonebot/nonebot2/pull/2577))
|
||||||
|
- Fix: `Message.__contains__()` 未考虑 `bool(MessageSegment)` 存在 False 情况导致的异常结果 [@lgc2333](https://github.com/lgc2333) ([#2572](https://github.com/nonebot/nonebot2/pull/2572))
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Docs: 更新 Session Expire Timeout 文档 [@MingxuanGame](https://github.com/MingxuanGame) ([#2585](https://github.com/nonebot/nonebot2/pull/2585))
|
||||||
|
- Docs: 添加适配器测试注意事项 [@yanyongyu](https://github.com/yanyongyu) ([#2570](https://github.com/nonebot/nonebot2/pull/2570))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Plugin: 修改 phigros 相关内容 [@XTxiaoting14332](https://github.com/XTxiaoting14332) ([#2578](https://github.com/nonebot/nonebot2/pull/2578))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: 运行状态 [@noneflow](https://github.com/noneflow) ([#2587](https://github.com/nonebot/nonebot2/pull/2587))
|
||||||
|
- Plugin: nonebot-plugin-bf1marneserverlist [@noneflow](https://github.com/noneflow) ([#2584](https://github.com/nonebot/nonebot2/pull/2584))
|
||||||
|
- Plugin: splatoon3游戏nso查询 [@noneflow](https://github.com/noneflow) ([#2576](https://github.com/nonebot/nonebot2/pull/2576))
|
||||||
|
- Plugin: Chikari_yinpa [@noneflow](https://github.com/noneflow) ([#2573](https://github.com/nonebot/nonebot2/pull/2573))
|
||||||
|
|
||||||
|
## v2.2.0
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: 添加插件 Pydantic 相关使用方法 [@yanyongyu](https://github.com/yanyongyu) ([#2563](https://github.com/nonebot/nonebot2/pull/2563))
|
||||||
|
- Feature: 兼容 Pydantic v2 [@yanyongyu](https://github.com/yanyongyu) ([#2544](https://github.com/nonebot/nonebot2/pull/2544))
|
||||||
|
- Feature: 使用自定义配置加载替代 `pydantic-settings` [@yanyongyu](https://github.com/yanyongyu) ([#2521](https://github.com/nonebot/nonebot2/pull/2521))
|
||||||
|
- Feature: 带参数的 `RegexStr()` [@ProgramRipper](https://github.com/ProgramRipper) ([#2499](https://github.com/nonebot/nonebot2/pull/2499))
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- Fix: websockets 驱动器连接关闭 code 获取错误 [@yanyongyu](https://github.com/yanyongyu) ([#2537](https://github.com/nonebot/nonebot2/pull/2537))
|
||||||
|
- Fix: 修复 `echo` 发送空消息 [@yanyongyu](https://github.com/yanyongyu) ([#2525](https://github.com/nonebot/nonebot2/pull/2525))
|
||||||
|
- Fix: `MessageTemplate` 禁止访问私有属性 [@mnixry](https://github.com/mnixry) ([#2509](https://github.com/nonebot/nonebot2/pull/2509))
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Docs: 更新 Alconna 文档 [@lengmianzz](https://github.com/lengmianzz) ([#2568](https://github.com/nonebot/nonebot2/pull/2568))
|
||||||
|
- Docs: 添加产品赞助列表 [@yanyongyu](https://github.com/yanyongyu) ([#2566](https://github.com/nonebot/nonebot2/pull/2566))
|
||||||
|
- Docs: 修复表单标签状态更新 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#2558](https://github.com/nonebot/nonebot2/pull/2558))
|
||||||
|
- Docs: 添加 CITATION 文件 [@yanyongyu](https://github.com/yanyongyu) ([#2520](https://github.com/nonebot/nonebot2/pull/2520))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Plugin: 移除不再维护的几款插件 [@mnixry](https://github.com/mnixry) ([#2561](https://github.com/nonebot/nonebot2/pull/2561))
|
||||||
|
- CI: 更新 prettier 配置 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#2546](https://github.com/nonebot/nonebot2/pull/2546))
|
||||||
|
- Plugin: 恢复删除的插件 `nonebot-plugin-eitherchoice` [@lgc2333](https://github.com/lgc2333) ([#2502](https://github.com/nonebot/nonebot2/pull/2502))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: 定时提醒 [@noneflow](https://github.com/noneflow) ([#2559](https://github.com/nonebot/nonebot2/pull/2559))
|
||||||
|
- Plugin: 黑名单插件 [@noneflow](https://github.com/noneflow) ([#2554](https://github.com/nonebot/nonebot2/pull/2554))
|
||||||
|
- Plugin: ChatGPT 聊天 [@noneflow](https://github.com/noneflow) ([#2556](https://github.com/nonebot/nonebot2/pull/2556))
|
||||||
|
- Plugin: BA模拟抽卡 [@noneflow](https://github.com/noneflow) ([#2550](https://github.com/nonebot/nonebot2/pull/2550))
|
||||||
|
- Plugin: 随机发送图片 [@noneflow](https://github.com/noneflow) ([#2548](https://github.com/nonebot/nonebot2/pull/2548))
|
||||||
|
- Plugin: 哪吒监控插件 [@noneflow](https://github.com/noneflow) ([#2552](https://github.com/nonebot/nonebot2/pull/2552))
|
||||||
|
- Plugin: SakuraFrp [@noneflow](https://github.com/noneflow) ([#2543](https://github.com/nonebot/nonebot2/pull/2543))
|
||||||
|
- Plugin: haruka_bot_red [@noneflow](https://github.com/noneflow) ([#2541](https://github.com/nonebot/nonebot2/pull/2541))
|
||||||
|
- Plugin: nonebot-plugin-gemini [@noneflow](https://github.com/noneflow) ([#2527](https://github.com/nonebot/nonebot2/pull/2527))
|
||||||
|
- Plugin: 最终台词 [@noneflow](https://github.com/noneflow) ([#2523](https://github.com/nonebot/nonebot2/pull/2523))
|
||||||
|
- Plugin: nonebot-plugin-nekoimage [@noneflow](https://github.com/noneflow) ([#2534](https://github.com/nonebot/nonebot2/pull/2534))
|
||||||
|
- Plugin: 谷歌Bard聊天 [@noneflow](https://github.com/noneflow) ([#2529](https://github.com/nonebot/nonebot2/pull/2529))
|
||||||
|
- Plugin: nonebot-plugin-mypower [@noneflow](https://github.com/noneflow) ([#2533](https://github.com/nonebot/nonebot2/pull/2533))
|
||||||
|
- Plugin: 文心一言4适配 [@noneflow](https://github.com/noneflow) ([#2516](https://github.com/nonebot/nonebot2/pull/2516))
|
||||||
|
- Plugin: 最佳平替 [@noneflow](https://github.com/noneflow) ([#2519](https://github.com/nonebot/nonebot2/pull/2519))
|
||||||
|
- Plugin: 随机MC图 [@noneflow](https://github.com/noneflow) ([#2512](https://github.com/nonebot/nonebot2/pull/2512))
|
||||||
|
- Plugin: nonebot_plugin_nikke [@noneflow](https://github.com/noneflow) ([#2508](https://github.com/nonebot/nonebot2/pull/2508))
|
||||||
|
- Plugin: nonebot-plugin-imagemaster [@noneflow](https://github.com/noneflow) ([#2504](https://github.com/nonebot/nonebot2/pull/2504))
|
||||||
|
- Plugin: Waiter 插件 [@noneflow](https://github.com/noneflow) ([#2506](https://github.com/nonebot/nonebot2/pull/2506))
|
||||||
|
- Plugin: AntiMonkey [@noneflow](https://github.com/noneflow) ([#2501](https://github.com/nonebot/nonebot2/pull/2501))
|
||||||
|
|
||||||
## v2.1.3
|
## v2.1.3
|
||||||
|
|
||||||
### 🐛 Bug 修复
|
### 🐛 Bug 修复
|
||||||
|
@@ -1,104 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 2
|
|
||||||
description: 填写与获取插件相关的信息
|
|
||||||
|
|
||||||
options:
|
|
||||||
menu:
|
|
||||||
- category: advanced
|
|
||||||
weight: 30
|
|
||||||
---
|
|
||||||
|
|
||||||
# 插件信息
|
|
||||||
|
|
||||||
NoneBot 是一个插件化的框架,可以通过加载插件来扩展功能。同时,我们也可以通过 NoneBot 的插件系统来获取相关信息,例如插件的名称、使用方法,用于收集帮助信息等。下面我们将介绍如何为插件添加元数据,以及如何获取插件信息。
|
|
||||||
|
|
||||||
## 插件元数据
|
|
||||||
|
|
||||||
在 NoneBot 中,插件 [`Plugin`](../api/plugin/plugin.md#Plugin) 对象中存储了插件系统所需要的一系列信息。包括插件的索引名称、插件模块、插件中的事件响应器、插件父子关系等。通常,只有插件开发者才需要关心这些信息,而插件使用者或者机器人用户想要看到的是插件使用方法等帮助信息。因此,我们可以为插件添加插件元数据 `PluginMetadata`,它允许插件开发者为插件添加一些额外的信息。这些信息编写于插件模块的顶层,可以直接通过源码查看,或者通过 NoneBot 插件系统获取收集到的信息,通过其他方式发送给机器人用户等。
|
|
||||||
|
|
||||||
现在,假设我们有一个插件 `example`, 它的模块结构如下:
|
|
||||||
|
|
||||||
```tree {4-6} title=Project
|
|
||||||
📦 awesome-bot
|
|
||||||
├── 📂 awesome_bot
|
|
||||||
│ └── 📂 plugins
|
|
||||||
| └── 📂 example
|
|
||||||
| ├── 📜 __init__.py
|
|
||||||
| └── 📜 config.py
|
|
||||||
├── 📜 pyproject.toml
|
|
||||||
└── 📜 README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
我们需要在插件顶层模块 `example/__init__.py` 中添加插件元数据,如下所示:
|
|
||||||
|
|
||||||
```python {1,5-12} title=example/__init__.py
|
|
||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
|
|
||||||
from .config import Config
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="示例插件",
|
|
||||||
description="这是一个示例插件",
|
|
||||||
usage="没什么用",
|
|
||||||
type="application",
|
|
||||||
config=Config,
|
|
||||||
extra={},
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
我们可以看到,插件元数据 `PluginMetadata` 有三个基本属性:插件名称、插件描述、插件使用方法。除此之外,还有几个可选的属性(具体填写见[发布插件](../developer/plugin-publishing.mdx#填写插件元数据)章节):
|
|
||||||
|
|
||||||
- `type`:插件类别,发布插件必填。当前有效类别有:`library`(为其他插件编写提供功能),`application`(向机器人用户提供功能);
|
|
||||||
- `homepage`:插件项目主页,发布插件必填;
|
|
||||||
- `config`:插件的[配置类](../appendices/config.mdx#插件配置),如无配置类可不填;
|
|
||||||
- `supported_adapters`:支持的适配器模块名集合,若插件可以保证兼容所有适配器(即仅使用基本适配器功能)可不填写;
|
|
||||||
- `extra`:一个字典,可以用于存储任意信息。其他插件可以通过约定 `extra` 字典的键名来达成收集某些特殊信息的目的。
|
|
||||||
|
|
||||||
请注意,这里的**插件名称**是供使用者或机器人用户查看的,与插件索引名称无关。**插件索引名称(插件模块名称)**仅用于 NoneBot 插件系统**内部索引**。
|
|
||||||
|
|
||||||
## 获取插件信息
|
|
||||||
|
|
||||||
NoneBot 提供了多种获取插件对象的方法,例如获取当前所有已导入的插件:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import nonebot
|
|
||||||
|
|
||||||
plugins: set[Plugin] = nonebot.get_loaded_plugins()
|
|
||||||
```
|
|
||||||
|
|
||||||
也可以通过插件索引名称获取插件对象:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import nonebot
|
|
||||||
|
|
||||||
plugin: Plugin | None = nonebot.get_plugin("example")
|
|
||||||
```
|
|
||||||
|
|
||||||
或者通过模块路径获取插件对象:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import nonebot
|
|
||||||
|
|
||||||
plugin: Plugin | None = nonebot.get_plugin_by_module_name("awesome_bot.plugins.example")
|
|
||||||
```
|
|
||||||
|
|
||||||
如果需要获取所有当前声明的插件名称(可能还未加载),可以使用 `get_available_plugin_names` 函数:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import nonebot
|
|
||||||
|
|
||||||
plugin_names: set[str] = nonebot.get_available_plugin_names()
|
|
||||||
```
|
|
||||||
|
|
||||||
插件对象 `Plugin` 中包含了多个属性:
|
|
||||||
|
|
||||||
- `name`:插件索引名称
|
|
||||||
- `module`:插件模块
|
|
||||||
- `module_name`:插件模块路径
|
|
||||||
- `manager`:插件管理器
|
|
||||||
- `matcher`:插件中定义的事件响应器
|
|
||||||
- `parent_plugin`:插件的父插件
|
|
||||||
- `sub_plugins`:插件的子插件集合
|
|
||||||
- `metadata`:插件元数据
|
|
||||||
|
|
||||||
通过这些属性以及插件元数据,我们就可以收集所需要的插件信息了。
|
|
@@ -1,134 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 9
|
|
||||||
description: 添加服务端路由规则
|
|
||||||
|
|
||||||
options:
|
|
||||||
menu:
|
|
||||||
- category: advanced
|
|
||||||
weight: 100
|
|
||||||
---
|
|
||||||
|
|
||||||
# 添加路由
|
|
||||||
|
|
||||||
在[驱动器](./driver.md)一节中,我们了解了驱动器的两种类型。既然驱动器可以作为服务端运行,那么我们就可以向驱动器添加路由规则,从而实现自定义的 API 接口等功能。在添加路由规则时,我们需要注意驱动器的类型,详情可以参考[选择驱动器](./driver.md#配置驱动器)。
|
|
||||||
|
|
||||||
NoneBot 中,我们可以通过两种途径向 ASGI 驱动器添加路由规则:
|
|
||||||
|
|
||||||
1. 通过 NoneBot 的兼容层建立路由规则。
|
|
||||||
2. 直接向 ASGI 应用添加路由规则。
|
|
||||||
|
|
||||||
这两种途径各有优劣,前者可以在各种服务端型驱动器下运行,但并不能直接使用 ASGI 应用框架提供的特性与功能;后者直接使用 ASGI 应用,更自由、功能完整,但只能在特定类型驱动器下运行。
|
|
||||||
|
|
||||||
在向驱动器添加路由规则时,我们需要注意驱动器是否为服务端类型,我们可以通过以下方式判断:
|
|
||||||
|
|
||||||
```python {3}
|
|
||||||
from nonebot import get_driver
|
|
||||||
from nonebot.drivers import ASGIMixin
|
|
||||||
|
|
||||||
can_use = isinstance(get_driver(), ASGIMixin)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 通过兼容层添加路由
|
|
||||||
|
|
||||||
NoneBot 兼容层定义了两个数据类 `HTTPServerSetup` 和 `WebSocketServerSetup`,分别用于定义 HTTP 服务端和 WebSocket 服务端的路由规则。
|
|
||||||
|
|
||||||
### HTTP 路由
|
|
||||||
|
|
||||||
`HTTPServerSetup` 具有四个属性:
|
|
||||||
|
|
||||||
- `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。
|
|
||||||
- `method`:请求方法。类型为 `str`。
|
|
||||||
- `name`:路由名称,不可重复。类型为 `str`。
|
|
||||||
- `handle_func`:路由处理函数。类型为 `Callable[[Request], Awaitable[Response]]`。
|
|
||||||
|
|
||||||
例如,我们添加一个 `/hello` 的路由,当请求方法为 `GET` 时,返回 `200 OK` 以及返回体信息:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import get_driver
|
|
||||||
from nonebot.drivers import URL, Request, Response, ASGIMixin, HTTPServerSetup
|
|
||||||
|
|
||||||
async def hello(request: Request) -> Response:
|
|
||||||
return Response(200, content="Hello, world!")
|
|
||||||
|
|
||||||
if isinstance((driver := get_driver()), ASGIMixin):
|
|
||||||
driver.setup_http_server(
|
|
||||||
HTTPServerSetup(
|
|
||||||
path=URL("/hello"),
|
|
||||||
method="GET",
|
|
||||||
name="hello",
|
|
||||||
handle_func=hello,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
对于 `Request` 和 `Response` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。
|
|
||||||
|
|
||||||
### WebSocket 路由
|
|
||||||
|
|
||||||
`WebSocketServerSetup` 具有三个属性:
|
|
||||||
|
|
||||||
- `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。
|
|
||||||
- `name`:路由名称,不可重复。类型为 `str`。
|
|
||||||
- `handle_func`:路由处理函数。类型为 `Callable[[WebSocket], Awaitable[Any]]`。
|
|
||||||
|
|
||||||
例如,我们添加一个 `/ws` 的路由,发送所有接收到的数据:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot import get_driver
|
|
||||||
from nonebot.drivers import URL, ASGIMixin, WebSocket, WebSocketServerSetup
|
|
||||||
|
|
||||||
async def ws_handler(ws: WebSocket):
|
|
||||||
await ws.accept()
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
data = await ws.receive()
|
|
||||||
await ws.send(data)
|
|
||||||
except WebSocketClosed as e:
|
|
||||||
# handle closed
|
|
||||||
...
|
|
||||||
finally:
|
|
||||||
with contextlib.suppress(Exception):
|
|
||||||
await websocket.close()
|
|
||||||
# do some cleanup
|
|
||||||
|
|
||||||
if isinstance((driver := get_driver()), ASGIMixin):
|
|
||||||
driver.setup_websocket_server(
|
|
||||||
WebSocketServerSetup(
|
|
||||||
path=URL("/ws"),
|
|
||||||
name="ws",
|
|
||||||
handle_func=ws_handler,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
对于 `WebSocket` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。
|
|
||||||
|
|
||||||
## 使用 ASGI 应用添加路由
|
|
||||||
|
|
||||||
### 获取 ASGI 应用
|
|
||||||
|
|
||||||
NoneBot 服务端类型的驱动器具有两个属性 `server_app` 和 `asgi`,分别对应驱动框架应用和 ASGI 应用。通常情况下,这两个应用是同一个对象。我们可以通过 `get_app()` 方法快速获取:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import nonebot
|
|
||||||
|
|
||||||
app = nonebot.get_app()
|
|
||||||
asgi = nonebot.get_asgi()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 添加路由规则
|
|
||||||
|
|
||||||
在获取到了 ASGI 应用后,我们就可以直接使用 ASGI 应用框架提供的功能来添加路由规则了。这里我们以 [FastAPI](./driver.md#fastapi默认) 为例,演示如何添加路由规则。
|
|
||||||
|
|
||||||
在下面的代码中,我们添加了一个 `GET` 类型的 `/api` 路由,具体方法参考 [FastAPI 文档](https://fastapi.tiangolo.com/)。
|
|
||||||
|
|
||||||
```python
|
|
||||||
import nonebot
|
|
||||||
from fastapi import FastAPI
|
|
||||||
|
|
||||||
app: FastAPI = nonebot.get_app()
|
|
||||||
|
|
||||||
@app.get("/api")
|
|
||||||
async def custom_api():
|
|
||||||
return {"message": "Hello, world!"}
|
|
||||||
```
|
|
@@ -1,862 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 0
|
|
||||||
description: nonebot.adapters 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.adapters
|
|
||||||
|
|
||||||
本模块定义了协议适配基类,各协议请继承以下基类。
|
|
||||||
|
|
||||||
使用 [Driver.register_adapter](../drivers/index.md#Driver-register-adapter) 注册适配器。
|
|
||||||
|
|
||||||
## _abstract class_ `Bot(adapter, self_id)` {#Bot}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
Bot 基类。
|
|
||||||
|
|
||||||
用于处理上报消息,并提供 API 调用接口。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `adapter` ([Adapter](#Adapter)): 协议适配器实例
|
|
||||||
|
|
||||||
- `self_id` (str): 机器人 ID
|
|
||||||
|
|
||||||
### _instance-var_ `adapter` {#Bot-adapter}
|
|
||||||
|
|
||||||
- **类型:** [Adapter](#Adapter)
|
|
||||||
|
|
||||||
- **说明:** 协议适配器实例
|
|
||||||
|
|
||||||
### _instance-var_ `self_id` {#Bot-self-id}
|
|
||||||
|
|
||||||
- **类型:** str
|
|
||||||
|
|
||||||
- **说明:** 机器人 ID
|
|
||||||
|
|
||||||
### _property_ `type` {#Bot-type}
|
|
||||||
|
|
||||||
- **类型:** str
|
|
||||||
|
|
||||||
- **说明:** 协议适配器名称
|
|
||||||
|
|
||||||
### _property_ `config` {#Bot-config}
|
|
||||||
|
|
||||||
- **类型:** [Config](../config.md#Config)
|
|
||||||
|
|
||||||
- **说明:** 全局 NoneBot 配置
|
|
||||||
|
|
||||||
### _async method_ `call_api(api, **data)` {#Bot-call-api}
|
|
||||||
|
|
||||||
- **说明:** 调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `api` (str): API 名称
|
|
||||||
|
|
||||||
- `**data` (Any): API 数据
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Any
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
await bot.call_api("send_msg", message="hello world")
|
|
||||||
await bot.send_msg(message="hello world")
|
|
||||||
```
|
|
||||||
|
|
||||||
### _abstract async method_ `send(event, message, **kwargs)` {#Bot-send}
|
|
||||||
|
|
||||||
- **说明:** 调用机器人基础发送消息接口
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `event` ([Event](#Event)): 上报事件
|
|
||||||
|
|
||||||
- `message` (str | [Message](#Message) | [MessageSegment](#MessageSegment)): 要发送的消息
|
|
||||||
|
|
||||||
- `**kwargs` (Any): 任意额外参数
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Any
|
|
||||||
|
|
||||||
### _classmethod_ `on_calling_api(func)` {#Bot-on-calling-api}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
调用 api 预处理。
|
|
||||||
|
|
||||||
钩子函数参数:
|
|
||||||
|
|
||||||
- bot: 当前 bot 对象
|
|
||||||
- api: 调用的 api 名称
|
|
||||||
- data: api 调用的参数字典
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` ([T_CallingAPIHook](../typing.md#T-CallingAPIHook))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [T_CallingAPIHook](../typing.md#T-CallingAPIHook)
|
|
||||||
|
|
||||||
### _classmethod_ `on_called_api(func)` {#Bot-on-called-api}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
调用 api 后处理。
|
|
||||||
|
|
||||||
钩子函数参数:
|
|
||||||
|
|
||||||
- bot: 当前 bot 对象
|
|
||||||
- exception: 调用 api 时发生的错误
|
|
||||||
- api: 调用的 api 名称
|
|
||||||
- data: api 调用的参数字典
|
|
||||||
- result: api 调用的返回
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` ([T_CalledAPIHook](../typing.md#T-CalledAPIHook))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [T_CalledAPIHook](../typing.md#T-CalledAPIHook)
|
|
||||||
|
|
||||||
## _abstract class_ `Event(<auto>)` {#Event}
|
|
||||||
|
|
||||||
- **说明:** Event 基类。提供获取关键信息的方法,其余信息可直接获取。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _classmethod_ `validate(value)` {#Event-validate}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (Any)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- E
|
|
||||||
|
|
||||||
### _abstract method_ `get_type()` {#Event-get-type}
|
|
||||||
|
|
||||||
- **说明:** 获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _abstract method_ `get_event_name()` {#Event-get-event-name}
|
|
||||||
|
|
||||||
- **说明:** 获取事件名称的方法。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _abstract method_ `get_event_description()` {#Event-get-event-description}
|
|
||||||
|
|
||||||
- **说明:** 获取事件描述的方法,通常为事件具体内容。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _method_ `get_log_string()` {#Event-get-log-string}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取事件日志信息的方法。
|
|
||||||
|
|
||||||
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,
|
|
||||||
可以抛出 `NoLogException` 异常。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- NoLogException: 希望 NoneBot 隐藏该事件日志
|
|
||||||
|
|
||||||
### _abstract method_ `get_user_id()` {#Event-get-user-id}
|
|
||||||
|
|
||||||
- **说明:** 获取事件主体 id 的方法,通常是用户 id 。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _abstract method_ `get_session_id()` {#Event-get-session-id}
|
|
||||||
|
|
||||||
- **说明:** 获取会话 id 的方法,用于判断当前事件属于哪一个会话, 通常是用户 id、群组 id 组合。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _abstract method_ `get_message()` {#Event-get-message}
|
|
||||||
|
|
||||||
- **说明:** 获取事件消息内容的方法。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Message](#Message)
|
|
||||||
|
|
||||||
### _method_ `get_plaintext()` {#Event-get-plaintext}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取消息纯文本的方法。
|
|
||||||
|
|
||||||
通常不需要修改,默认通过 `get_message().extract_plain_text` 获取。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _abstract method_ `is_tome()` {#Event-is-tome}
|
|
||||||
|
|
||||||
- **说明:** 获取事件是否与机器人有关的方法。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- bool
|
|
||||||
|
|
||||||
## _abstract class_ `Adapter(driver, **kwargs)` {#Adapter}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
协议适配器基类。
|
|
||||||
|
|
||||||
通常,在 Adapter 中编写协议通信相关代码,如: 建立通信连接、处理接收与发送 data 等。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `driver` ([Driver](../drivers/index.md#Driver)): [Driver](../drivers/index.md#Driver) 实例
|
|
||||||
|
|
||||||
- `**kwargs` (Any): 其他由 [Driver.register_adapter](../drivers/index.md#Driver-register-adapter) 传入的额外参数
|
|
||||||
|
|
||||||
### _instance-var_ `driver` {#Adapter-driver}
|
|
||||||
|
|
||||||
- **类型:** [Driver](../drivers/index.md#Driver)
|
|
||||||
|
|
||||||
- **说明:** 实例
|
|
||||||
|
|
||||||
### _instance-var_ `bots` {#Adapter-bots}
|
|
||||||
|
|
||||||
- **类型:** dict[str, [Bot](#Bot)]
|
|
||||||
|
|
||||||
- **说明:** 本协议适配器已建立连接的 [Bot](#Bot) 实例
|
|
||||||
|
|
||||||
### _abstract classmethod_ `get_name()` {#Adapter-get-name}
|
|
||||||
|
|
||||||
- **说明:** 当前协议适配器的名称
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _property_ `config` {#Adapter-config}
|
|
||||||
|
|
||||||
- **类型:** [Config](../config.md#Config)
|
|
||||||
|
|
||||||
- **说明:** 全局 NoneBot 配置
|
|
||||||
|
|
||||||
### _method_ `bot_connect(bot)` {#Adapter-bot-connect}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
告知 NoneBot 建立了一个新的 [Bot](#Bot) 连接。
|
|
||||||
|
|
||||||
当有新的 [Bot](#Bot) 实例连接建立成功时调用。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](#Bot)): [Bot](#Bot) 实例
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `bot_disconnect(bot)` {#Adapter-bot-disconnect}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
告知 NoneBot [Bot](#Bot) 连接已断开。
|
|
||||||
|
|
||||||
当有 [Bot](#Bot) 实例连接断开时调用。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](#Bot)): [Bot](#Bot) 实例
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `setup_http_server(setup)` {#Adapter-setup-http-server}
|
|
||||||
|
|
||||||
- **说明:** 设置一个 HTTP 服务器路由配置
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `setup` ([HTTPServerSetup](../drivers/index.md#HTTPServerSetup))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _method_ `setup_websocket_server(setup)` {#Adapter-setup-websocket-server}
|
|
||||||
|
|
||||||
- **说明:** 设置一个 WebSocket 服务器路由配置
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `setup` ([WebSocketServerSetup](../drivers/index.md#WebSocketServerSetup))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _async method_ `request(setup)` {#Adapter-request}
|
|
||||||
|
|
||||||
- **说明:** 进行一个 HTTP 客户端请求
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `setup` ([Request](../drivers/index.md#Request))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Response](../drivers/index.md#Response)
|
|
||||||
|
|
||||||
### _method_ `websocket(setup)` {#Adapter-websocket}
|
|
||||||
|
|
||||||
- **说明:** 建立一个 WebSocket 客户端连接请求
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `setup` ([Request](../drivers/index.md#Request))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- AsyncGenerator[[WebSocket](../drivers/index.md#WebSocket), None]
|
|
||||||
|
|
||||||
## _abstract class_ `Message(<auto>)` {#Message}
|
|
||||||
|
|
||||||
- **说明:** 消息序列
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `message`: 消息内容
|
|
||||||
|
|
||||||
### _classmethod_ `template(format_string)` {#Message-template}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
创建消息模板。
|
|
||||||
|
|
||||||
用法和 `str.format` 大致相同,支持以 `Message` 对象作为消息模板并输出消息对象。
|
|
||||||
并且提供了拓展的格式化控制符,
|
|
||||||
可以通过该消息类型的 `MessageSegment` 工厂方法创建消息。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `format_string` (str | TM): 格式化模板
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [MessageTemplate](#MessageTemplate)[Self]: 消息格式化器
|
|
||||||
|
|
||||||
### _abstract classmethod_ `get_segment_class()` {#Message-get-segment-class}
|
|
||||||
|
|
||||||
- **说明:** 获取消息段类型
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- type[TMS]
|
|
||||||
|
|
||||||
### _abstract staticmethod_ `_construct(msg)` {#Message--construct}
|
|
||||||
|
|
||||||
- **说明:** 构造消息数组
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `msg` (str)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Iterable[TMS]
|
|
||||||
|
|
||||||
### _method_ `__getitem__(args)` {#Message---getitem--}
|
|
||||||
|
|
||||||
- **重载**
|
|
||||||
|
|
||||||
**1.** `(args) -> Self`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `args` (str): 消息段类型
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self: 所有类型为 `args` 的消息段
|
|
||||||
|
|
||||||
**2.** `(args) -> TMS`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `args` (tuple[str, int]): 消息段类型和索引
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- TMS: 类型为 `args[0]` 的消息段第 `args[1]` 个
|
|
||||||
|
|
||||||
**3.** `(args) -> Self`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `args` (tuple[str, slice]): 消息段类型和切片
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self: 类型为 `args[0]` 的消息段切片 `args[1]`
|
|
||||||
|
|
||||||
**4.** `(args) -> TMS`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `args` (int): 索引
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- TMS: 第 `args` 个消息段
|
|
||||||
|
|
||||||
**5.** `(args) -> Self`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `args` (slice): 切片
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self: 消息切片 `args`
|
|
||||||
|
|
||||||
### _method_ `__contains__(value)` {#Message---contains--}
|
|
||||||
|
|
||||||
- **说明:** 检查消息段是否存在
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (TMS | str): 消息段或消息段类型
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- bool: 消息内是否存在给定消息段或给定类型的消息段
|
|
||||||
|
|
||||||
### _method_ `has(value)` {#Message-has}
|
|
||||||
|
|
||||||
- **说明:** 与 [`__contains__`](#Message---contains--) 相同
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (TMS | str)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- bool
|
|
||||||
|
|
||||||
### _method_ `index(value, *args)` {#Message-index}
|
|
||||||
|
|
||||||
- **说明:** 索引消息段
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (TMS | str): 消息段或者消息段类型
|
|
||||||
|
|
||||||
- `*args` (SupportsIndex)
|
|
||||||
|
|
||||||
- `arg`: start 与 end
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- int: 索引 index
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- ValueError: 消息段不存在
|
|
||||||
|
|
||||||
### _method_ `get(type_, count=None)` {#Message-get}
|
|
||||||
|
|
||||||
- **说明:** 获取指定类型的消息段
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `type_` (str): 消息段类型
|
|
||||||
|
|
||||||
- `count` (int | None): 获取个数
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self: 构建的新消息
|
|
||||||
|
|
||||||
### _method_ `count(value)` {#Message-count}
|
|
||||||
|
|
||||||
- **说明:** 计算指定消息段的个数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (TMS | str): 消息段或消息段类型
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- int: 个数
|
|
||||||
|
|
||||||
### _method_ `only(value)` {#Message-only}
|
|
||||||
|
|
||||||
- **说明:** 检查消息中是否仅包含指定消息段
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (TMS | str): 指定消息段或消息段类型
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- bool: 是否仅包含指定消息段
|
|
||||||
|
|
||||||
### _method_ `append(obj)` {#Message-append}
|
|
||||||
|
|
||||||
- **说明:** 添加一个消息段到消息数组末尾。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `obj` (str | TMS): 要添加的消息段
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self
|
|
||||||
|
|
||||||
### _method_ `extend(obj)` {#Message-extend}
|
|
||||||
|
|
||||||
- **说明:** 拼接一个消息数组或多个消息段到消息数组末尾。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `obj` (Self | Iterable[TMS]): 要添加的消息数组
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self
|
|
||||||
|
|
||||||
### _method_ `join(iterable)` {#Message-join}
|
|
||||||
|
|
||||||
- **说明:** 将多个消息连接并将自身作为分割
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `iterable` (Iterable[TMS | Self]): 要连接的消息
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self: 连接后的消息
|
|
||||||
|
|
||||||
### _method_ `copy()` {#Message-copy}
|
|
||||||
|
|
||||||
- **说明:** 深拷贝消息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self
|
|
||||||
|
|
||||||
### _method_ `include(*types)` {#Message-include}
|
|
||||||
|
|
||||||
- **说明:** 过滤消息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `*types` (str): 包含的消息段类型
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self: 新构造的消息
|
|
||||||
|
|
||||||
### _method_ `exclude(*types)` {#Message-exclude}
|
|
||||||
|
|
||||||
- **说明:** 过滤消息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `*types` (str): 不包含的消息段类型
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self: 新构造的消息
|
|
||||||
|
|
||||||
### _method_ `extract_plain_text()` {#Message-extract-plain-text}
|
|
||||||
|
|
||||||
- **说明:** 提取消息内纯文本消息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
## _abstract class_ `MessageSegment(<auto>)` {#MessageSegment}
|
|
||||||
|
|
||||||
- **说明:** 消息段基类
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _instance-var_ `type` {#MessageSegment-type}
|
|
||||||
|
|
||||||
- **类型:** str
|
|
||||||
|
|
||||||
- **说明:** 消息段类型
|
|
||||||
|
|
||||||
### _class-var_ `data` {#MessageSegment-data}
|
|
||||||
|
|
||||||
- **类型:** dict[str, Any]
|
|
||||||
|
|
||||||
- **说明:** 消息段数据
|
|
||||||
|
|
||||||
### _abstract classmethod_ `get_message_class()` {#MessageSegment-get-message-class}
|
|
||||||
|
|
||||||
- **说明:** 获取消息数组类型
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- type[TM]
|
|
||||||
|
|
||||||
### _abstract method_ `__str__()` {#MessageSegment---str--}
|
|
||||||
|
|
||||||
- **说明:** 该消息段所代表的 str,在命令匹配部分使用
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _method_ `__add__(other)` {#MessageSegment---add--}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `other` (str | TMS | Iterable[TMS])
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- TM
|
|
||||||
|
|
||||||
### _method_ `get(key, default=None)` {#MessageSegment-get}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (str)
|
|
||||||
|
|
||||||
- `default` (Any)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _method_ `keys()` {#MessageSegment-keys}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _method_ `values()` {#MessageSegment-values}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _method_ `items()` {#MessageSegment-items}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _method_ `join(iterable)` {#MessageSegment-join}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `iterable` (Iterable[TMS | TM])
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- TM
|
|
||||||
|
|
||||||
### _method_ `copy()` {#MessageSegment-copy}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Self
|
|
||||||
|
|
||||||
### _abstract method_ `is_text()` {#MessageSegment-is-text}
|
|
||||||
|
|
||||||
- **说明:** 当前消息段是否为纯文本
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- bool
|
|
||||||
|
|
||||||
## _class_ `MessageTemplate(template, factory=str)` {#MessageTemplate}
|
|
||||||
|
|
||||||
- **说明:** 消息模板格式化实现类。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `template` (str | TM): 模板
|
|
||||||
|
|
||||||
- `factory` (type[str] | type[TM]): 消息类型工厂,默认为 `str`
|
|
||||||
|
|
||||||
### _method_ `add_format_spec(spec, name=None)` {#MessageTemplate-add-format-spec}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `spec` (FormatSpecFunc_T)
|
|
||||||
|
|
||||||
- `name` (str | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- FormatSpecFunc_T
|
|
||||||
|
|
||||||
### _method_ `format(*args, **kwargs)` {#MessageTemplate-format}
|
|
||||||
|
|
||||||
- **说明:** 根据传入参数和模板生成消息对象
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `*args`
|
|
||||||
|
|
||||||
- `**kwargs`
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _method_ `format_map(mapping)` {#MessageTemplate-format-map}
|
|
||||||
|
|
||||||
- **说明:** 根据传入字典和模板生成消息对象, 在传入字段名不是有效标识符时有用
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `mapping` (Mapping[str, Any])
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- TF
|
|
||||||
|
|
||||||
### _method_ `vformat(format_string, args, kwargs)` {#MessageTemplate-vformat}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `format_string` (str)
|
|
||||||
|
|
||||||
- `args` (Sequence[Any])
|
|
||||||
|
|
||||||
- `kwargs` (Mapping[str, Any])
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- TF
|
|
||||||
|
|
||||||
### _method_ `format_field(value, format_spec)` {#MessageTemplate-format-field}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (Any)
|
|
||||||
|
|
||||||
- `format_spec` (str)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Any
|
|
@@ -1,168 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
description: nonebot.config 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.config
|
|
||||||
|
|
||||||
本模块定义了 NoneBot 本身运行所需的配置项。
|
|
||||||
|
|
||||||
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
|
|
||||||
[`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
|
|
||||||
|
|
||||||
配置项需符合特殊格式或 json 序列化格式
|
|
||||||
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
|
||||||
|
|
||||||
## _class_ `Env(<auto>)` {#Env}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
运行环境配置。大小写不敏感。
|
|
||||||
|
|
||||||
将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _class-var_ `environment` {#Env-environment}
|
|
||||||
|
|
||||||
- **类型:** str
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
当前环境名。
|
|
||||||
|
|
||||||
NoneBot 将从 `.env.{environment}` 文件中加载配置。
|
|
||||||
|
|
||||||
## _class_ `Config(<auto>)` {#Config}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
NoneBot 主要配置。大小写不敏感。
|
|
||||||
|
|
||||||
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
|
|
||||||
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
|
|
||||||
|
|
||||||
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _class-var_ `driver` {#Config-driver}
|
|
||||||
|
|
||||||
- **类型:** str
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
NoneBot 运行所使用的 `Driver` 。继承自 [Driver](drivers/index.md#Driver) 。
|
|
||||||
|
|
||||||
配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`。
|
|
||||||
|
|
||||||
`~` 为 `nonebot.drivers.` 的缩写。
|
|
||||||
|
|
||||||
配置方法参考: [配置驱动器](https://nonebot.dev/docs/advanced/driver#%E9%85%8D%E7%BD%AE%E9%A9%B1%E5%8A%A8%E5%99%A8)
|
|
||||||
|
|
||||||
### _class-var_ `host` {#Config-host}
|
|
||||||
|
|
||||||
- **类型:** IPvAnyAddress
|
|
||||||
|
|
||||||
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的 IP/主机名。
|
|
||||||
|
|
||||||
### _class-var_ `port` {#Config-port}
|
|
||||||
|
|
||||||
- **类型:** int
|
|
||||||
|
|
||||||
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的端口。
|
|
||||||
|
|
||||||
### _class-var_ `log_level` {#Config-log-level}
|
|
||||||
|
|
||||||
- **类型:** int | str
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称。
|
|
||||||
|
|
||||||
参考 [记录日志](https://nonebot.dev/docs/appendices/log),[loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
|
||||||
|
|
||||||
:::tip 提示
|
|
||||||
日志等级名称应为大写,如 `INFO`。
|
|
||||||
:::
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```conf
|
|
||||||
LOG_LEVEL=25
|
|
||||||
LOG_LEVEL=INFO
|
|
||||||
```
|
|
||||||
|
|
||||||
### _class-var_ `api_timeout` {#Config-api-timeout}
|
|
||||||
|
|
||||||
- **类型:** float | None
|
|
||||||
|
|
||||||
- **说明:** API 请求超时时间,单位: 秒。
|
|
||||||
|
|
||||||
### _class-var_ `superusers` {#Config-superusers}
|
|
||||||
|
|
||||||
- **类型:** set[str]
|
|
||||||
|
|
||||||
- **说明:** 机器人超级用户。
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```conf
|
|
||||||
SUPERUSERS=["12345789"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### _class-var_ `nickname` {#Config-nickname}
|
|
||||||
|
|
||||||
- **类型:** set[str]
|
|
||||||
|
|
||||||
- **说明:** 机器人昵称。
|
|
||||||
|
|
||||||
### _class-var_ `command_start` {#Config-command-start}
|
|
||||||
|
|
||||||
- **类型:** set[str]
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
命令的起始标记,用于判断一条消息是不是命令。
|
|
||||||
|
|
||||||
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```conf
|
|
||||||
COMMAND_START=["/", ""]
|
|
||||||
```
|
|
||||||
|
|
||||||
### _class-var_ `command_sep` {#Config-command-sep}
|
|
||||||
|
|
||||||
- **类型:** set[str]
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
|
||||||
|
|
||||||
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```conf
|
|
||||||
COMMAND_SEP=["."]
|
|
||||||
```
|
|
||||||
|
|
||||||
### _class-var_ `session_expire_timeout` {#Config-session-expire-timeout}
|
|
||||||
|
|
||||||
- **类型:** timedelta
|
|
||||||
|
|
||||||
- **说明:** 等待用户回复的超时时间。
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```conf
|
|
||||||
SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒
|
|
||||||
SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff]
|
|
||||||
SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
|
|
||||||
```
|
|
@@ -1,47 +0,0 @@
|
|||||||
# nonebot.drivers.\_lifespan
|
|
||||||
|
|
||||||
## _class_ `Lifespan()` {#Lifespan}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
### _method_ `on_startup(func)` {#Lifespan-on-startup}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` (LIFESPAN_FUNC)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- LIFESPAN_FUNC
|
|
||||||
|
|
||||||
### _method_ `on_shutdown(func)` {#Lifespan-on-shutdown}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` (LIFESPAN_FUNC)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- LIFESPAN_FUNC
|
|
||||||
|
|
||||||
### _async method_ `startup()` {#Lifespan-startup}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _async method_ `shutdown()` {#Lifespan-shutdown}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
@@ -1,572 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 0
|
|
||||||
description: nonebot.drivers 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.drivers
|
|
||||||
|
|
||||||
本模块定义了驱动适配器基类。
|
|
||||||
|
|
||||||
各驱动请继承以下基类。
|
|
||||||
|
|
||||||
## _abstract class_ `Mixin(<auto>)` {#Mixin}
|
|
||||||
|
|
||||||
- **说明:** 可与其他驱动器共用的混入基类。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _abstract property_ `type` {#Mixin-type}
|
|
||||||
|
|
||||||
- **类型:** str
|
|
||||||
|
|
||||||
- **说明:** 混入驱动类型名称
|
|
||||||
|
|
||||||
## _abstract class_ `Driver(env, config)` {#Driver}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
驱动器基类。
|
|
||||||
|
|
||||||
驱动器控制框架的启动和停止,适配器的注册,以及机器人生命周期管理。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `env` ([Env](../config.md#Env)): 包含环境信息的 Env 对象
|
|
||||||
|
|
||||||
- `config` ([Config](../config.md#Config)): 包含配置信息的 Config 对象
|
|
||||||
|
|
||||||
### _instance-var_ `env` {#Driver-env}
|
|
||||||
|
|
||||||
- **类型:** str
|
|
||||||
|
|
||||||
- **说明:** 环境名称
|
|
||||||
|
|
||||||
### _instance-var_ `config` {#Driver-config}
|
|
||||||
|
|
||||||
- **类型:** [Config](../config.md#Config)
|
|
||||||
|
|
||||||
- **说明:** 全局配置对象
|
|
||||||
|
|
||||||
### _property_ `bots` {#Driver-bots}
|
|
||||||
|
|
||||||
- **类型:** dict[str, [Bot](../adapters/index.md#Bot)]
|
|
||||||
|
|
||||||
- **说明:** 获取当前所有已连接的 Bot
|
|
||||||
|
|
||||||
### _method_ `register_adapter(adapter, **kwargs)` {#Driver-register-adapter}
|
|
||||||
|
|
||||||
- **说明:** 注册一个协议适配器
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `adapter` (type[[Adapter](../adapters/index.md#Adapter)]): 适配器类
|
|
||||||
|
|
||||||
- `**kwargs`: 其他传递给适配器的参数
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _abstract property_ `type` {#Driver-type}
|
|
||||||
|
|
||||||
- **类型:** str
|
|
||||||
|
|
||||||
- **说明:** 驱动类型名称
|
|
||||||
|
|
||||||
### _abstract property_ `logger` {#Driver-logger}
|
|
||||||
|
|
||||||
- **类型:** untyped
|
|
||||||
|
|
||||||
- **说明:** 驱动专属 logger 日志记录器
|
|
||||||
|
|
||||||
### _abstract method_ `run(*args, **kwargs)` {#Driver-run}
|
|
||||||
|
|
||||||
- **说明:** 启动驱动框架
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `*args`
|
|
||||||
|
|
||||||
- `**kwargs`
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _abstract method_ `on_startup(func)` {#Driver-on-startup}
|
|
||||||
|
|
||||||
- **说明:** 注册一个在驱动器启动时执行的函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` (Callable)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Callable
|
|
||||||
|
|
||||||
### _abstract method_ `on_shutdown(func)` {#Driver-on-shutdown}
|
|
||||||
|
|
||||||
- **说明:** 注册一个在驱动器停止时执行的函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` (Callable)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Callable
|
|
||||||
|
|
||||||
### _classmethod_ `on_bot_connect(func)` {#Driver-on-bot-connect}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
装饰一个函数使他在 bot 连接成功时执行。
|
|
||||||
|
|
||||||
钩子函数参数:
|
|
||||||
|
|
||||||
- bot: 当前连接上的 Bot 对象
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` ([T_BotConnectionHook](../typing.md#T-BotConnectionHook))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [T_BotConnectionHook](../typing.md#T-BotConnectionHook)
|
|
||||||
|
|
||||||
### _classmethod_ `on_bot_disconnect(func)` {#Driver-on-bot-disconnect}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
装饰一个函数使他在 bot 连接断开时执行。
|
|
||||||
|
|
||||||
钩子函数参数:
|
|
||||||
|
|
||||||
- bot: 当前连接上的 Bot 对象
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` ([T_BotDisconnectionHook](../typing.md#T-BotDisconnectionHook))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [T_BotDisconnectionHook](../typing.md#T-BotDisconnectionHook)
|
|
||||||
|
|
||||||
## _class_ `Cookies(cookies=None)` {#Cookies}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `cookies` (CookieTypes)
|
|
||||||
|
|
||||||
### _method_ `set(name, value, domain="", path="/")` {#Cookies-set}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str)
|
|
||||||
|
|
||||||
- `value` (str)
|
|
||||||
|
|
||||||
- `domain` (str)
|
|
||||||
|
|
||||||
- `path` (str)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `get(name, default=None, domain=None, path=None)` {#Cookies-get}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str)
|
|
||||||
|
|
||||||
- `default` (str | None)
|
|
||||||
|
|
||||||
- `domain` (str | None)
|
|
||||||
|
|
||||||
- `path` (str | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str | None
|
|
||||||
|
|
||||||
### _method_ `delete(name, domain=None, path=None)` {#Cookies-delete}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str)
|
|
||||||
|
|
||||||
- `domain` (str | None)
|
|
||||||
|
|
||||||
- `path` (str | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `clear(domain=None, path=None)` {#Cookies-clear}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `domain` (str | None)
|
|
||||||
|
|
||||||
- `path` (str | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `update(cookies=None)` {#Cookies-update}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `cookies` (CookieTypes)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `as_header(request)` {#Cookies-as-header}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `request` (Request)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- dict[str, str]
|
|
||||||
|
|
||||||
## _class_ `Request(method, url, *, params=None, headers=None, cookies=None, content=None, data=None, json=None, files=None, version=HTTPVersion.H11, timeout=None, proxy=None)` {#Request}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `method` (str | bytes)
|
|
||||||
|
|
||||||
- `url` (URL | str | RawURL)
|
|
||||||
|
|
||||||
- `params` (QueryTypes)
|
|
||||||
|
|
||||||
- `headers` (HeaderTypes)
|
|
||||||
|
|
||||||
- `cookies` (CookieTypes)
|
|
||||||
|
|
||||||
- `content` (ContentTypes)
|
|
||||||
|
|
||||||
- `data` (DataTypes)
|
|
||||||
|
|
||||||
- `json` (Any)
|
|
||||||
|
|
||||||
- `files` (FilesTypes)
|
|
||||||
|
|
||||||
- `version` (str | HTTPVersion)
|
|
||||||
|
|
||||||
- `timeout` (float | None)
|
|
||||||
|
|
||||||
- `proxy` (str | None)
|
|
||||||
|
|
||||||
## _class_ `Response(status_code, *, headers=None, content=None, request=None)` {#Response}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `status_code` (int)
|
|
||||||
|
|
||||||
- `headers` (HeaderTypes)
|
|
||||||
|
|
||||||
- `content` (ContentTypes)
|
|
||||||
|
|
||||||
- `request` (Request | None)
|
|
||||||
|
|
||||||
## _abstract class_ `ASGIMixin(<auto>)` {#ASGIMixin}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
ASGI 服务端基类。
|
|
||||||
|
|
||||||
将后端框架封装,以满足适配器使用。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _abstract property_ `server_app` {#ASGIMixin-server-app}
|
|
||||||
|
|
||||||
- **类型:** Any
|
|
||||||
|
|
||||||
- **说明:** 驱动 APP 对象
|
|
||||||
|
|
||||||
### _abstract property_ `asgi` {#ASGIMixin-asgi}
|
|
||||||
|
|
||||||
- **类型:** Any
|
|
||||||
|
|
||||||
- **说明:** 驱动 ASGI 对象
|
|
||||||
|
|
||||||
### _abstract method_ `setup_http_server(setup)` {#ASGIMixin-setup-http-server}
|
|
||||||
|
|
||||||
- **说明:** 设置一个 HTTP 服务器路由配置
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `setup` ([HTTPServerSetup](#HTTPServerSetup))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _abstract method_ `setup_websocket_server(setup)` {#ASGIMixin-setup-websocket-server}
|
|
||||||
|
|
||||||
- **说明:** 设置一个 WebSocket 服务器路由配置
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `setup` ([WebSocketServerSetup](#WebSocketServerSetup))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
## _abstract class_ `WebSocket(*, request)` {#WebSocket}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `request` (Request)
|
|
||||||
|
|
||||||
### _abstract property_ `closed` {#WebSocket-closed}
|
|
||||||
|
|
||||||
- **类型:** bool
|
|
||||||
|
|
||||||
- **说明:** 连接是否已经关闭
|
|
||||||
|
|
||||||
### _abstract async method_ `accept()` {#WebSocket-accept}
|
|
||||||
|
|
||||||
- **说明:** 接受 WebSocket 连接请求
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _abstract async method_ `close(code=1000, reason="")` {#WebSocket-close}
|
|
||||||
|
|
||||||
- **说明:** 关闭 WebSocket 连接请求
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `code` (int)
|
|
||||||
|
|
||||||
- `reason` (str)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _abstract async method_ `receive()` {#WebSocket-receive}
|
|
||||||
|
|
||||||
- **说明:** 接收一条 WebSocket text/bytes 信息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str | bytes
|
|
||||||
|
|
||||||
### _abstract async method_ `receive_text()` {#WebSocket-receive-text}
|
|
||||||
|
|
||||||
- **说明:** 接收一条 WebSocket text 信息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _abstract async method_ `receive_bytes()` {#WebSocket-receive-bytes}
|
|
||||||
|
|
||||||
- **说明:** 接收一条 WebSocket binary 信息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- bytes
|
|
||||||
|
|
||||||
### _async method_ `send(data)` {#WebSocket-send}
|
|
||||||
|
|
||||||
- **说明:** 发送一条 WebSocket text/bytes 信息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `data` (str | bytes)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _abstract async method_ `send_text(data)` {#WebSocket-send-text}
|
|
||||||
|
|
||||||
- **说明:** 发送一条 WebSocket text 信息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `data` (str)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _abstract async method_ `send_bytes(data)` {#WebSocket-send-bytes}
|
|
||||||
|
|
||||||
- **说明:** 发送一条 WebSocket binary 信息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `data` (bytes)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
## _enum_ `HTTPVersion` {#HTTPVersion}
|
|
||||||
|
|
||||||
- **说明:** An enumeration.
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
- `H10: '1.0'`
|
|
||||||
|
|
||||||
- `H11: '1.1'`
|
|
||||||
|
|
||||||
- `H2: '2'`
|
|
||||||
|
|
||||||
## _abstract class_ `ForwardMixin(<auto>)` {#ForwardMixin}
|
|
||||||
|
|
||||||
- **说明:** 客户端混入基类。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
## _abstract class_ `ReverseMixin(<auto>)` {#ReverseMixin}
|
|
||||||
|
|
||||||
- **说明:** 服务端混入基类。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
## _var_ `ForwardDriver` {#ForwardDriver}
|
|
||||||
|
|
||||||
- **类型:** ForwardMixin
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
支持客户端请求的驱动器。
|
|
||||||
|
|
||||||
**Deprecated**,请使用 [ForwardMixin](#ForwardMixin) 或其子类代替。
|
|
||||||
|
|
||||||
## _var_ `ReverseDriver` {#ReverseDriver}
|
|
||||||
|
|
||||||
- **类型:** ReverseMixin
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
支持服务端请求的驱动器。
|
|
||||||
|
|
||||||
**Deprecated**,请使用 [ReverseMixin](#ReverseMixin) 或其子类代替。
|
|
||||||
|
|
||||||
## _def_ `combine_driver(driver, *mixins)` {#combine-driver}
|
|
||||||
|
|
||||||
- **说明:** 将一个驱动器和多个混入类合并。
|
|
||||||
|
|
||||||
- **重载**
|
|
||||||
|
|
||||||
**1.** `(driver) -> type[D]`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `driver` (type[D])
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- type[D]
|
|
||||||
|
|
||||||
**2.** `(driver, *mixins) -> type[CombinedDriver]`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `driver` (type[D])
|
|
||||||
|
|
||||||
- `*mixins` (type[Mixin])
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- type[CombinedDriver]
|
|
||||||
|
|
||||||
## _abstract class_ `HTTPClientMixin(<auto>)` {#HTTPClientMixin}
|
|
||||||
|
|
||||||
- **说明:** HTTP 客户端混入基类。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _abstract async method_ `request(setup)` {#HTTPClientMixin-request}
|
|
||||||
|
|
||||||
- **说明:** 发送一个 HTTP 请求
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `setup` ([Request](#Request))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Response](#Response)
|
|
||||||
|
|
||||||
## _class_ `HTTPServerSetup(<auto>)` {#HTTPServerSetup}
|
|
||||||
|
|
||||||
- **说明:** HTTP 服务器路由配置。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
## _abstract class_ `WebSocketClientMixin(<auto>)` {#WebSocketClientMixin}
|
|
||||||
|
|
||||||
- **说明:** WebSocket 客户端混入基类。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _abstract method_ `websocket(setup)` {#WebSocketClientMixin-websocket}
|
|
||||||
|
|
||||||
- **说明:** 发起一个 WebSocket 连接
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `setup` ([Request](#Request))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- AsyncGenerator[[WebSocket](#WebSocket), None]
|
|
||||||
|
|
||||||
## _class_ `WebSocketServerSetup(<auto>)` {#WebSocketServerSetup}
|
|
||||||
|
|
||||||
- **说明:** WebSocket 服务器路由配置。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
@@ -1,283 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 0
|
|
||||||
description: nonebot 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot
|
|
||||||
|
|
||||||
本模块主要定义了 NoneBot 启动所需函数,供 bot 入口文件调用。
|
|
||||||
|
|
||||||
## 快捷导入
|
|
||||||
|
|
||||||
为方便使用,本模块从子模块导入了部分内容,以下内容可以直接通过本模块导入:
|
|
||||||
|
|
||||||
- `on` => [`on`](plugin/on.md#on)
|
|
||||||
- `on_metaevent` => [`on_metaevent`](plugin/on.md#on-metaevent)
|
|
||||||
- `on_message` => [`on_message`](plugin/on.md#on-message)
|
|
||||||
- `on_notice` => [`on_notice`](plugin/on.md#on-notice)
|
|
||||||
- `on_request` => [`on_request`](plugin/on.md#on-request)
|
|
||||||
- `on_startswith` => [`on_startswith`](plugin/on.md#on-startswith)
|
|
||||||
- `on_endswith` => [`on_endswith`](plugin/on.md#on-endswith)
|
|
||||||
- `on_fullmatch` => [`on_fullmatch`](plugin/on.md#on-fullmatch)
|
|
||||||
- `on_keyword` => [`on_keyword`](plugin/on.md#on-keyword)
|
|
||||||
- `on_command` => [`on_command`](plugin/on.md#on-command)
|
|
||||||
- `on_shell_command` => [`on_shell_command`](plugin/on.md#on-shell-command)
|
|
||||||
- `on_regex` => [`on_regex`](plugin/on.md#on-regex)
|
|
||||||
- `on_type` => [`on_type`](plugin/on.md#on-type)
|
|
||||||
- `CommandGroup` => [`CommandGroup`](plugin/on.md#CommandGroup)
|
|
||||||
- `Matchergroup` => [`MatcherGroup`](plugin/on.md#MatcherGroup)
|
|
||||||
- `load_plugin` => [`load_plugin`](plugin/load.md#load-plugin)
|
|
||||||
- `load_plugins` => [`load_plugins`](plugin/load.md#load-plugins)
|
|
||||||
- `load_all_plugins` => [`load_all_plugins`](plugin/load.md#load-all-plugins)
|
|
||||||
- `load_from_json` => [`load_from_json`](plugin/load.md#load-from-json)
|
|
||||||
- `load_from_toml` => [`load_from_toml`](plugin/load.md#load-from-toml)
|
|
||||||
- `load_builtin_plugin` =>
|
|
||||||
[`load_builtin_plugin`](plugin/load.md#load-builtin-plugin)
|
|
||||||
- `load_builtin_plugins` =>
|
|
||||||
[`load_builtin_plugins`](plugin/load.md#load-builtin-plugins)
|
|
||||||
- `get_plugin` => [`get_plugin`](plugin/index.md#get-plugin)
|
|
||||||
- `get_plugin_by_module_name` =>
|
|
||||||
[`get_plugin_by_module_name`](plugin/index.md#get-plugin-by-module-name)
|
|
||||||
- `get_loaded_plugins` =>
|
|
||||||
[`get_loaded_plugins`](plugin/index.md#get-loaded-plugins)
|
|
||||||
- `get_available_plugin_names` =>
|
|
||||||
[`get_available_plugin_names`](plugin/index.md#get-available-plugin-names)
|
|
||||||
- `require` => [`require`](plugin/load.md#require)
|
|
||||||
|
|
||||||
## _def_ `get_driver()` {#get-driver}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取全局 [Driver](drivers/index.md#Driver) 实例。
|
|
||||||
|
|
||||||
可用于在计划任务的回调等情形中获取当前 [Driver](drivers/index.md#Driver) 实例。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Driver](drivers/index.md#Driver): 全局 [Driver](drivers/index.md#Driver) 对象
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
driver = nonebot.get_driver()
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `get_adapter(name)` {#get-adapter}
|
|
||||||
|
|
||||||
- **说明:** 获取已注册的 [Adapter](adapters/index.md#Adapter) 实例。
|
|
||||||
|
|
||||||
- **重载**
|
|
||||||
|
|
||||||
**1.** `(name) -> Adapter`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str): 适配器名称
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Adapter](adapters/index.md#Adapter): 指定名称的 [Adapter](adapters/index.md#Adapter) 对象
|
|
||||||
|
|
||||||
**2.** `(name) -> A`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (type[A]): 适配器类型
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- A: 指定类型的 [Adapter](adapters/index.md#Adapter) 对象
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- ValueError: 指定的 [Adapter](adapters/index.md#Adapter) 未注册
|
|
||||||
|
|
||||||
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
from nonebot.adapters.console import Adapter
|
|
||||||
adapter = nonebot.get_adapter(Adapter)
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `get_adapters()` {#get-adapters}
|
|
||||||
|
|
||||||
- **说明:** 获取所有已注册的 [Adapter](adapters/index.md#Adapter) 实例。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- dict[str, [Adapter](adapters/index.md#Adapter)]: 所有 [Adapter](adapters/index.md#Adapter) 实例字典
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
adapters = nonebot.get_adapters()
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `get_app()` {#get-app}
|
|
||||||
|
|
||||||
- **说明:** 获取全局 [ASGIMixin](drivers/index.md#ASGIMixin) 对应的 Server App 对象。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Any: Server App 对象
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- AssertionError: 全局 Driver 对象不是 [ASGIMixin](drivers/index.md#ASGIMixin) 类型
|
|
||||||
|
|
||||||
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = nonebot.get_app()
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `get_asgi()` {#get-asgi}
|
|
||||||
|
|
||||||
- **说明:** 获取全局 [ASGIMixin](drivers/index.md#ASGIMixin) 对应 [ASGI](https://asgi.readthedocs.io/) 对象。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Any: ASGI 对象
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- AssertionError: 全局 Driver 对象不是 [ASGIMixin](drivers/index.md#ASGIMixin) 类型
|
|
||||||
|
|
||||||
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
asgi = nonebot.get_asgi()
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `get_bot(self_id=None)` {#get-bot}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取一个连接到 NoneBot 的 [Bot](adapters/index.md#Bot) 对象。
|
|
||||||
|
|
||||||
当提供 `self_id` 时,此函数是 `get_bots()[self_id]` 的简写;
|
|
||||||
当不提供时,返回一个 [Bot](adapters/index.md#Bot)。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `self_id` (str | None): 用来识别 [Bot](adapters/index.md#Bot) 的 [Bot.self_id](adapters/index.md#Bot-self-id) 属性
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Bot](adapters/index.md#Bot): [Bot](adapters/index.md#Bot) 对象
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- KeyError: 对应 self_id 的 Bot 不存在
|
|
||||||
|
|
||||||
- ValueError: 没有传入 self_id 且没有 Bot 可用
|
|
||||||
|
|
||||||
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
assert nonebot.get_bot("12345") == nonebot.get_bots()["12345"]
|
|
||||||
|
|
||||||
another_unspecified_bot = nonebot.get_bot()
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `get_bots()` {#get-bots}
|
|
||||||
|
|
||||||
- **说明:** 获取所有连接到 NoneBot 的 [Bot](adapters/index.md#Bot) 对象。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- dict[str, [Bot](adapters/index.md#Bot)]: 一个以 [Bot.self_id](adapters/index.md#Bot-self-id) 为键
|
|
||||||
|
|
||||||
[Bot](adapters/index.md#Bot) 对象为值的字典
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
bots = nonebot.get_bots()
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `init(*, _env_file=None, **kwargs)` {#init}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
初始化 NoneBot 以及 全局 [Driver](drivers/index.md#Driver) 对象。
|
|
||||||
|
|
||||||
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
|
|
||||||
|
|
||||||
也可以传入自定义的 `_env_file` 来指定 NoneBot 从该文件读取配置。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `_env_file` (DotenvType | None): 配置文件名,默认从 `.env.{env_name}` 中读取配置
|
|
||||||
|
|
||||||
- `**kwargs` (Any): 任意变量,将会存储到 [Driver.config](drivers/index.md#Driver-config) 对象里
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
nonebot.init(database=Database(...))
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `run(*args, **kwargs)` {#run}
|
|
||||||
|
|
||||||
- **说明:** 启动 NoneBot,即运行全局 [Driver](drivers/index.md#Driver) 对象。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `*args` (Any): 传入 [Driver.run](drivers/index.md#Driver-run) 的位置参数
|
|
||||||
|
|
||||||
- `**kwargs` (Any): 传入 [Driver.run](drivers/index.md#Driver-run) 的命名参数
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
nonebot.run(host="127.0.0.1", port=8080)
|
|
||||||
```
|
|
@@ -1,749 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 3
|
|
||||||
description: nonebot.matcher 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.matcher
|
|
||||||
|
|
||||||
本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
|
||||||
|
|
||||||
## _class_ `Matcher()` {#Matcher}
|
|
||||||
|
|
||||||
- **说明:** 事件响应器类
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
### _instance-var_ `handlers` {#Matcher-handlers}
|
|
||||||
|
|
||||||
- **类型:** list[[Dependent](dependencies/index.md#Dependent)[Any]]
|
|
||||||
|
|
||||||
- **说明:** 事件响应器拥有的事件处理函数列表
|
|
||||||
|
|
||||||
### _class-var_ `type` {#Matcher-type}
|
|
||||||
|
|
||||||
- **类型:** ClassVar[str]
|
|
||||||
|
|
||||||
- **说明:** 事件响应器类型
|
|
||||||
|
|
||||||
### _class-var_ `rule` {#Matcher-rule}
|
|
||||||
|
|
||||||
- **类型:** ClassVar[[Rule](rule.md#Rule)]
|
|
||||||
|
|
||||||
- **说明:** 事件响应器匹配规则
|
|
||||||
|
|
||||||
### _class-var_ `permission` {#Matcher-permission}
|
|
||||||
|
|
||||||
- **类型:** ClassVar[[Permission](permission.md#Permission)]
|
|
||||||
|
|
||||||
- **说明:** 事件响应器触发权限
|
|
||||||
|
|
||||||
### _class-var_ `priority` {#Matcher-priority}
|
|
||||||
|
|
||||||
- **类型:** ClassVar[int]
|
|
||||||
|
|
||||||
- **说明:** 事件响应器优先级
|
|
||||||
|
|
||||||
### _class-var_ `block` {#Matcher-block}
|
|
||||||
|
|
||||||
- **类型:** bool
|
|
||||||
|
|
||||||
- **说明:** 事件响应器是否阻止事件传播
|
|
||||||
|
|
||||||
### _class-var_ `temp` {#Matcher-temp}
|
|
||||||
|
|
||||||
- **类型:** ClassVar[bool]
|
|
||||||
|
|
||||||
- **说明:** 事件响应器是否为临时
|
|
||||||
|
|
||||||
### _class-var_ `expire_time` {#Matcher-expire-time}
|
|
||||||
|
|
||||||
- **类型:** ClassVar[datetime | None]
|
|
||||||
|
|
||||||
- **说明:** 事件响应器过期时间点
|
|
||||||
|
|
||||||
### _classmethod_ `new(type_="", rule=None, permission=None, handlers=None, temp=False, priority=1, block=False, *, plugin=None, module=None, source=None, expire_time=None, default_state=None, default_type_updater=None, default_permission_updater=None)` {#Matcher-new}
|
|
||||||
|
|
||||||
- **说明:** 创建一个新的事件响应器,并存储至 `matchers <#matchers>`\_
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `type_` (str): 事件响应器类型,与 `event.get_type()` 一致时触发,空字符串表示任意
|
|
||||||
|
|
||||||
- `rule` ([Rule](rule.md#Rule) | None): 匹配规则
|
|
||||||
|
|
||||||
- `permission` ([Permission](permission.md#Permission) | None): 权限
|
|
||||||
|
|
||||||
- `handlers` (list[[T\_Handler](typing.md#T-Handler) | [Dependent](dependencies/index.md#Dependent)[Any]] | None): 事件处理函数列表
|
|
||||||
|
|
||||||
- `temp` (bool): 是否为临时事件响应器,即触发一次后删除
|
|
||||||
|
|
||||||
- `priority` (int): 响应优先级
|
|
||||||
|
|
||||||
- `block` (bool): 是否阻止事件向更低优先级的响应器传播
|
|
||||||
|
|
||||||
- `plugin` ([Plugin](plugin/plugin.md#Plugin) | None): **Deprecated.** 事件响应器所在插件
|
|
||||||
|
|
||||||
- `module` (ModuleType | None): **Deprecated.** 事件响应器所在模块
|
|
||||||
|
|
||||||
- `source` (MatcherSource | None): 事件响应器源代码上下文信息
|
|
||||||
|
|
||||||
- `expire_time` (datetime | timedelta | None): 事件响应器最终有效时间点,过时即被删除
|
|
||||||
|
|
||||||
- `default_state` ([T_State](typing.md#T-State) | None): 默认状态 `state`
|
|
||||||
|
|
||||||
- `default_type_updater` ([T_TypeUpdater](typing.md#T-TypeUpdater) | [Dependent](dependencies/index.md#Dependent)[str] | None): 默认事件类型更新函数
|
|
||||||
|
|
||||||
- `default_permission_updater` ([T_PermissionUpdater](typing.md#T-PermissionUpdater) | [Dependent](dependencies/index.md#Dependent)[[Permission](permission.md#Permission)] | None): 默认会话权限更新函数
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- type[Matcher]: 新的事件响应器类
|
|
||||||
|
|
||||||
### _classmethod_ `destroy()` {#Matcher-destroy}
|
|
||||||
|
|
||||||
- **说明:** 销毁当前的事件响应器
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _classmethod_ `check_perm(bot, event, stack=None, dependency_cache=None)` {#Matcher-check-perm}
|
|
||||||
|
|
||||||
- **说明:** 检查是否满足触发权限
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](adapters/index.md#Bot)): Bot 对象
|
|
||||||
|
|
||||||
- `event` ([Event](adapters/index.md#Event)): 上报事件
|
|
||||||
|
|
||||||
- `stack` (AsyncExitStack | None): 异步上下文栈
|
|
||||||
|
|
||||||
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None): 依赖缓存
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- bool: 是否满足权限
|
|
||||||
|
|
||||||
### _classmethod_ `check_rule(bot, event, state, stack=None, dependency_cache=None)` {#Matcher-check-rule}
|
|
||||||
|
|
||||||
- **说明:** 检查是否满足匹配规则
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](adapters/index.md#Bot)): Bot 对象
|
|
||||||
|
|
||||||
- `event` ([Event](adapters/index.md#Event)): 上报事件
|
|
||||||
|
|
||||||
- `state` ([T_State](typing.md#T-State)): 当前状态
|
|
||||||
|
|
||||||
- `stack` (AsyncExitStack | None): 异步上下文栈
|
|
||||||
|
|
||||||
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None): 依赖缓存
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- bool: 是否满足匹配规则
|
|
||||||
|
|
||||||
### _classmethod_ `type_updater(func)` {#Matcher-type-updater}
|
|
||||||
|
|
||||||
- **说明:** 装饰一个函数来更改当前事件响应器的默认响应事件类型更新函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` ([T_TypeUpdater](typing.md#T-TypeUpdater)): 响应事件类型更新函数
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [T_TypeUpdater](typing.md#T-TypeUpdater)
|
|
||||||
|
|
||||||
### _classmethod_ `permission_updater(func)` {#Matcher-permission-updater}
|
|
||||||
|
|
||||||
- **说明:** 装饰一个函数来更改当前事件响应器的默认会话权限更新函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `func` ([T_PermissionUpdater](typing.md#T-PermissionUpdater)): 会话权限更新函数
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [T_PermissionUpdater](typing.md#T-PermissionUpdater)
|
|
||||||
|
|
||||||
### _classmethod_ `append_handler(handler, parameterless=None)` {#Matcher-append-handler}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `handler` ([T_Handler](typing.md#T-Handler))
|
|
||||||
|
|
||||||
- `parameterless` (Iterable[Any] | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Dependent](dependencies/index.md#Dependent)[Any]
|
|
||||||
|
|
||||||
### _classmethod_ `handle(parameterless=None)` {#Matcher-handle}
|
|
||||||
|
|
||||||
- **说明:** 装饰一个函数来向事件响应器直接添加一个处理函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `parameterless` (Iterable[Any] | None): 非参数类型依赖列表
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- ([T_Handler](typing.md#T-Handler)) -> [T_Handler](typing.md#T-Handler)
|
|
||||||
|
|
||||||
### _classmethod_ `receive(id="", parameterless=None)` {#Matcher-receive}
|
|
||||||
|
|
||||||
- **说明:** 装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `id` (str): 消息 ID
|
|
||||||
|
|
||||||
- `parameterless` (Iterable[Any] | None): 非参数类型依赖列表
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- ([T_Handler](typing.md#T-Handler)) -> [T_Handler](typing.md#T-Handler)
|
|
||||||
|
|
||||||
### _classmethod_ `got(key, prompt=None, parameterless=None)` {#Matcher-got}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
|
||||||
|
|
||||||
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,
|
|
||||||
如果 `key` 已存在则直接继续运行
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (str): 参数名
|
|
||||||
|
|
||||||
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 在参数不存在时向用户发送的消息
|
|
||||||
|
|
||||||
- `parameterless` (Iterable[Any] | None): 非参数类型依赖列表
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- ([T_Handler](typing.md#T-Handler)) -> [T_Handler](typing.md#T-Handler)
|
|
||||||
|
|
||||||
### _classmethod_ `send(message, **kwargs)` {#Matcher-send}
|
|
||||||
|
|
||||||
- **说明:** 发送一条消息给当前交互用户
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `message` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate)): 消息内容
|
|
||||||
|
|
||||||
- `**kwargs` (Any): [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- Any
|
|
||||||
|
|
||||||
### _classmethod_ `finish(message=None, **kwargs)` {#Matcher-finish}
|
|
||||||
|
|
||||||
- **说明:** 发送一条消息给当前交互用户并结束当前事件响应器
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `message` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
|
|
||||||
|
|
||||||
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- NoReturn
|
|
||||||
|
|
||||||
### _classmethod_ `pause(prompt=None, **kwargs)` {#Matcher-pause}
|
|
||||||
|
|
||||||
- **说明:** 发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
|
|
||||||
|
|
||||||
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- NoReturn
|
|
||||||
|
|
||||||
### _classmethod_ `reject(prompt=None, **kwargs)` {#Matcher-reject}
|
|
||||||
|
|
||||||
- **说明:** 最近使用 `got` / `receive` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
|
|
||||||
|
|
||||||
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- NoReturn
|
|
||||||
|
|
||||||
### _classmethod_ `reject_arg(key, prompt=None, **kwargs)` {#Matcher-reject-arg}
|
|
||||||
|
|
||||||
- **说明:** 最近使用 `got` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一条消息后从头开始执行当前处理函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (str): 参数名
|
|
||||||
|
|
||||||
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
|
|
||||||
|
|
||||||
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- NoReturn
|
|
||||||
|
|
||||||
### _classmethod_ `reject_receive(id="", prompt=None, **kwargs)` {#Matcher-reject-receive}
|
|
||||||
|
|
||||||
- **说明:** 最近使用 `receive` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `id` (str): 消息 id
|
|
||||||
|
|
||||||
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
|
|
||||||
|
|
||||||
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- NoReturn
|
|
||||||
|
|
||||||
### _classmethod_ `skip()` {#Matcher-skip}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
跳过当前事件处理函数,继续下一个处理函数
|
|
||||||
|
|
||||||
通常在事件处理函数的依赖中使用。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- NoReturn
|
|
||||||
|
|
||||||
### _method_ `get_receive(id, default=None)` {#Matcher-get-receive}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取一个 `receive` 事件
|
|
||||||
|
|
||||||
如果没有找到对应的事件,返回 `default` 值
|
|
||||||
|
|
||||||
- **重载**
|
|
||||||
|
|
||||||
**1.** `(id) -> Event | None`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `id` (str)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Event](adapters/index.md#Event) | None
|
|
||||||
|
|
||||||
**2.** `(id, default) -> Event | T`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `id` (str)
|
|
||||||
|
|
||||||
- `default` (T)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Event](adapters/index.md#Event) | T
|
|
||||||
|
|
||||||
### _method_ `set_receive(id, event)` {#Matcher-set-receive}
|
|
||||||
|
|
||||||
- **说明:** 设置一个 `receive` 事件
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `id` (str)
|
|
||||||
|
|
||||||
- `event` ([Event](adapters/index.md#Event))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `get_last_receive(default=None)` {#Matcher-get-last-receive}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取最近一次 `receive` 事件
|
|
||||||
|
|
||||||
如果没有事件,返回 `default` 值
|
|
||||||
|
|
||||||
- **重载**
|
|
||||||
|
|
||||||
**1.** `() -> Event | None`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Event](adapters/index.md#Event) | None
|
|
||||||
|
|
||||||
**2.** `(default) -> Event | T`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `default` (T)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Event](adapters/index.md#Event) | T
|
|
||||||
|
|
||||||
### _method_ `get_arg(key, default=None)` {#Matcher-get-arg}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取一个 `got` 消息
|
|
||||||
|
|
||||||
如果没有找到对应的消息,返回 `default` 值
|
|
||||||
|
|
||||||
- **重载**
|
|
||||||
|
|
||||||
**1.** `(key) -> Message | None`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (str)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Message](adapters/index.md#Message) | None
|
|
||||||
|
|
||||||
**2.** `(key, default) -> Message | T`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (str)
|
|
||||||
|
|
||||||
- `default` (T)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Message](adapters/index.md#Message) | T
|
|
||||||
|
|
||||||
### _method_ `set_arg(key, message)` {#Matcher-set-arg}
|
|
||||||
|
|
||||||
- **说明:** 设置一个 `got` 消息
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (str)
|
|
||||||
|
|
||||||
- `message` ([Message](adapters/index.md#Message))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `set_target(target, cache=True)` {#Matcher-set-target}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `target` (str)
|
|
||||||
|
|
||||||
- `cache` (bool)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `get_target(default=None)` {#Matcher-get-target}
|
|
||||||
|
|
||||||
- **重载**
|
|
||||||
|
|
||||||
**1.** `() -> str | None`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str | None
|
|
||||||
|
|
||||||
**2.** `(default) -> str | T`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `default` (T)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str | T
|
|
||||||
|
|
||||||
### _method_ `stop_propagation()` {#Matcher-stop-propagation}
|
|
||||||
|
|
||||||
- **说明:** 阻止事件传播
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _async method_ `update_type(bot, event, stack=None, dependency_cache=None)` {#Matcher-update-type}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](adapters/index.md#Bot))
|
|
||||||
|
|
||||||
- `event` ([Event](adapters/index.md#Event))
|
|
||||||
|
|
||||||
- `stack` (AsyncExitStack | None)
|
|
||||||
|
|
||||||
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- str
|
|
||||||
|
|
||||||
### _async method_ `update_permission(bot, event, stack=None, dependency_cache=None)` {#Matcher-update-permission}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](adapters/index.md#Bot))
|
|
||||||
|
|
||||||
- `event` ([Event](adapters/index.md#Event))
|
|
||||||
|
|
||||||
- `stack` (AsyncExitStack | None)
|
|
||||||
|
|
||||||
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Permission](permission.md#Permission)
|
|
||||||
|
|
||||||
### _async method_ `resolve_reject()` {#Matcher-resolve-reject}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _method_ `ensure_context(bot, event)` {#Matcher-ensure-context}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](adapters/index.md#Bot))
|
|
||||||
|
|
||||||
- `event` ([Event](adapters/index.md#Event))
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _async method_ `simple_run(bot, event, state, stack=None, dependency_cache=None)` {#Matcher-simple-run}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](adapters/index.md#Bot))
|
|
||||||
|
|
||||||
- `event` ([Event](adapters/index.md#Event))
|
|
||||||
|
|
||||||
- `state` ([T_State](typing.md#T-State))
|
|
||||||
|
|
||||||
- `stack` (AsyncExitStack | None)
|
|
||||||
|
|
||||||
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
### _async method_ `run(bot, event, state, stack=None, dependency_cache=None)` {#Matcher-run}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `bot` ([Bot](adapters/index.md#Bot))
|
|
||||||
|
|
||||||
- `event` ([Event](adapters/index.md#Event))
|
|
||||||
|
|
||||||
- `state` ([T_State](typing.md#T-State))
|
|
||||||
|
|
||||||
- `stack` (AsyncExitStack | None)
|
|
||||||
|
|
||||||
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
## _var_ `matchers` {#matchers}
|
|
||||||
|
|
||||||
- **类型:** untyped
|
|
||||||
|
|
||||||
## _class_ `MatcherManager()` {#MatcherManager}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
事件响应器管理器
|
|
||||||
|
|
||||||
实现了常用字典操作,用于管理事件响应器。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
### _method_ `keys()` {#MatcherManager-keys}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- KeysView[int]
|
|
||||||
|
|
||||||
### _method_ `values()` {#MatcherManager-values}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- ValuesView[list[type[[Matcher](#Matcher)]]]
|
|
||||||
|
|
||||||
### _method_ `items()` {#MatcherManager-items}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- ItemsView[int, list[type[[Matcher](#Matcher)]]]
|
|
||||||
|
|
||||||
### _method_ `get(key, default=None)` {#MatcherManager-get}
|
|
||||||
|
|
||||||
- **重载**
|
|
||||||
|
|
||||||
**1.** `(key) -> list[type[Matcher]] | None`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (int)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- list[type[[Matcher](#Matcher)]] | None
|
|
||||||
|
|
||||||
**2.** `(key, default) -> list[type[Matcher]] | T`
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (int)
|
|
||||||
|
|
||||||
- `default` (T)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- list[type[[Matcher](#Matcher)]] | T
|
|
||||||
|
|
||||||
### _method_ `pop(key)` {#MatcherManager-pop}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (int)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- list[type[[Matcher](#Matcher)]]
|
|
||||||
|
|
||||||
### _method_ `popitem()` {#MatcherManager-popitem}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- tuple[int, list[type[[Matcher](#Matcher)]]]
|
|
||||||
|
|
||||||
### _method_ `clear()` {#MatcherManager-clear}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `update(__m)` {#MatcherManager-update}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `__m` (MutableMapping[int, list[type[[Matcher](#Matcher)]]])
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _method_ `setdefault(key, default)` {#MatcherManager-setdefault}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `key` (int)
|
|
||||||
|
|
||||||
- `default` (list[type[[Matcher](#Matcher)]])
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- list[type[[Matcher](#Matcher)]]
|
|
||||||
|
|
||||||
### _method_ `set_provider(provider_class)` {#MatcherManager-set-provider}
|
|
||||||
|
|
||||||
- **说明:** 设置事件响应器存储器
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `provider_class` (type[[MatcherProvider](#MatcherProvider)]): 事件响应器存储器类
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
## _abstract class_ `MatcherProvider(matchers)` {#MatcherProvider}
|
|
||||||
|
|
||||||
- **说明:** 事件响应器存储器基类
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `matchers` (Mapping[int, list[type[[Matcher](#Matcher)]]]): 当前存储器中已有的事件响应器
|
|
||||||
|
|
||||||
## _var_ `DEFAULT_PROVIDER_CLASS` {#DEFAULT-PROVIDER-CLASS}
|
|
||||||
|
|
||||||
- **类型:** untyped
|
|
||||||
|
|
||||||
- **说明:** 默认存储器类型
|
|
@@ -1,95 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 0
|
|
||||||
description: nonebot.plugin 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.plugin
|
|
||||||
|
|
||||||
本模块为 NoneBot 插件开发提供便携的定义函数。
|
|
||||||
|
|
||||||
## 快捷导入
|
|
||||||
|
|
||||||
为方便使用,本模块从子模块导入了部分内容,以下内容可以直接通过本模块导入:
|
|
||||||
|
|
||||||
- `on` => [`on`](on.md#on)
|
|
||||||
- `on_metaevent` => [`on_metaevent`](on.md#on-metaevent)
|
|
||||||
- `on_message` => [`on_message`](on.md#on-message)
|
|
||||||
- `on_notice` => [`on_notice`](on.md#on-notice)
|
|
||||||
- `on_request` => [`on_request`](on.md#on-request)
|
|
||||||
- `on_startswith` => [`on_startswith`](on.md#on-startswith)
|
|
||||||
- `on_endswith` => [`on_endswith`](on.md#on-endswith)
|
|
||||||
- `on_fullmatch` => [`on_fullmatch`](on.md#on-fullmatch)
|
|
||||||
- `on_keyword` => [`on_keyword`](on.md#on-keyword)
|
|
||||||
- `on_command` => [`on_command`](on.md#on-command)
|
|
||||||
- `on_shell_command` => [`on_shell_command`](on.md#on-shell-command)
|
|
||||||
- `on_regex` => [`on_regex`](on.md#on-regex)
|
|
||||||
- `on_type` => [`on_type`](on.md#on-type)
|
|
||||||
- `CommandGroup` => [`CommandGroup`](on.md#CommandGroup)
|
|
||||||
- `Matchergroup` => [`MatcherGroup`](on.md#MatcherGroup)
|
|
||||||
- `load_plugin` => [`load_plugin`](load.md#load-plugin)
|
|
||||||
- `load_plugins` => [`load_plugins`](load.md#load-plugins)
|
|
||||||
- `load_all_plugins` => [`load_all_plugins`](load.md#load-all-plugins)
|
|
||||||
- `load_from_json` => [`load_from_json`](load.md#load-from-json)
|
|
||||||
- `load_from_toml` => [`load_from_toml`](load.md#load-from-toml)
|
|
||||||
- `load_builtin_plugin` =>
|
|
||||||
[`load_builtin_plugin`](load.md#load-builtin-plugin)
|
|
||||||
- `load_builtin_plugins` =>
|
|
||||||
[`load_builtin_plugins`](load.md#load-builtin-plugins)
|
|
||||||
- `require` => [`require`](load.md#require)
|
|
||||||
- `PluginMetadata` => [`PluginMetadata`](plugin.md#PluginMetadata)
|
|
||||||
|
|
||||||
## _def_ `get_plugin(name)` {#get-plugin}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取已经导入的某个插件。
|
|
||||||
|
|
||||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str): 插件名,即 [Plugin.name](plugin.md#Plugin-name)。
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Plugin](plugin.md#Plugin) | None
|
|
||||||
|
|
||||||
## _def_ `get_plugin_by_module_name(module_name)` {#get-plugin-by-module-name}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
通过模块名获取已经导入的某个插件。
|
|
||||||
|
|
||||||
如果提供的模块名为某个插件的子模块,同样会返回该插件。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `module_name` (str): 模块名,即 [Plugin.module_name](plugin.md#Plugin-module-name)。
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Plugin](plugin.md#Plugin) | None
|
|
||||||
|
|
||||||
## _def_ `get_loaded_plugins()` {#get-loaded-plugins}
|
|
||||||
|
|
||||||
- **说明:** 获取当前已导入的所有插件。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[[Plugin](plugin.md#Plugin)]
|
|
||||||
|
|
||||||
## _def_ `get_available_plugin_names()` {#get-available-plugin-names}
|
|
||||||
|
|
||||||
- **说明:** 获取当前所有可用的插件名(包含尚未加载的插件)。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[str]
|
|
@@ -1,165 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
description: nonebot.plugin.load 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.plugin.load
|
|
||||||
|
|
||||||
本模块定义插件加载接口。
|
|
||||||
|
|
||||||
## _def_ `load_plugin(module_path)` {#load-plugin}
|
|
||||||
|
|
||||||
- **说明:** 加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `module_path` (str | Path): 插件名称 `path.to.your.plugin` 或插件路径 `pathlib.Path(path/to/your/plugin)`
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Plugin](plugin.md#Plugin) | None
|
|
||||||
|
|
||||||
## _def_ `load_plugins(*plugin_dir)` {#load-plugins}
|
|
||||||
|
|
||||||
- **说明:** 导入文件夹下多个插件,以 `_` 开头的插件不会被导入!
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `*plugin_dir` (str): 文件夹路径
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[[Plugin](plugin.md#Plugin)]
|
|
||||||
|
|
||||||
## _def_ `load_all_plugins(module_path, plugin_dir)` {#load-all-plugins}
|
|
||||||
|
|
||||||
- **说明:** 导入指定列表中的插件以及指定目录下多个插件,以 `_` 开头的插件不会被导入!
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `module_path` (Iterable[str]): 指定插件集合
|
|
||||||
|
|
||||||
- `plugin_dir` (Iterable[str]): 指定文件夹路径集合
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[[Plugin](plugin.md#Plugin)]
|
|
||||||
|
|
||||||
## _def_ `load_from_json(file_path, encoding="utf-8")` {#load-from-json}
|
|
||||||
|
|
||||||
- **说明:** 导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件。 以 `_` 开头的插件不会被导入!
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `file_path` (str): 指定 json 文件路径
|
|
||||||
|
|
||||||
- `encoding` (str): 指定 json 文件编码
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[[Plugin](plugin.md#Plugin)]
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```json title=plugins.json
|
|
||||||
{
|
|
||||||
"plugins": ["some_plugin"],
|
|
||||||
"plugin_dirs": ["some_dir"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
nonebot.load_from_json("plugins.json")
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `load_from_toml(file_path, encoding="utf-8")` {#load-from-toml}
|
|
||||||
|
|
||||||
- **说明:** 导入指定 toml 文件 `[tool.nonebot]` 中的 `plugins` 以及 `plugin_dirs` 下多个插件。 以 `_` 开头的插件不会被导入!
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `file_path` (str): 指定 toml 文件路径
|
|
||||||
|
|
||||||
- `encoding` (str): 指定 toml 文件编码
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[[Plugin](plugin.md#Plugin)]
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```toml title=pyproject.toml
|
|
||||||
[tool.nonebot]
|
|
||||||
plugins = ["some_plugin"]
|
|
||||||
plugin_dirs = ["some_dir"]
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
nonebot.load_from_toml("pyproject.toml")
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `load_builtin_plugin(name)` {#load-builtin-plugin}
|
|
||||||
|
|
||||||
- **说明:** 导入 NoneBot 内置插件。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str): 插件名称
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Plugin](plugin.md#Plugin) | None
|
|
||||||
|
|
||||||
## _def_ `load_builtin_plugins(*plugins)` {#load-builtin-plugins}
|
|
||||||
|
|
||||||
- **说明:** 导入多个 NoneBot 内置插件。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `*plugins` (str): 插件名称列表
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[[Plugin](plugin.md#Plugin)]
|
|
||||||
|
|
||||||
## _def_ `require(name)` {#require}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取一个插件的导出内容。
|
|
||||||
|
|
||||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str): 插件名,即 [Plugin.name](plugin.md#Plugin-name)。
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- ModuleType
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- RuntimeError: 插件无法加载
|
|
||||||
|
|
||||||
## _def_ `inherit_supported_adapters(*names)` {#inherit-supported-adapters}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取已加载插件的适配器支持状态集合。
|
|
||||||
|
|
||||||
如果传入了多个插件名称,返回值会自动取交集。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `*names` (str): 插件名称列表。
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[str] | None
|
|
||||||
|
|
||||||
- **异常**
|
|
||||||
|
|
||||||
- RuntimeError: 插件未加载
|
|
||||||
|
|
||||||
- ValueError: 插件缺少元数据
|
|
@@ -1,128 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 5
|
|
||||||
description: nonebot.plugin.manager 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.plugin.manager
|
|
||||||
|
|
||||||
本模块实现插件加载流程。
|
|
||||||
|
|
||||||
参考: [import hooks](https://docs.python.org/3/reference/import.html#import-hooks), [PEP302](https://www.python.org/dev/peps/pep-0302/)
|
|
||||||
|
|
||||||
## _class_ `PluginManager(plugins=None, search_path=None)` {#PluginManager}
|
|
||||||
|
|
||||||
- **说明:** 插件管理器。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `plugins` (Iterable[str] | None): 独立插件模块名集合。
|
|
||||||
|
|
||||||
- `search_path` (Iterable[str] | None): 插件搜索路径(文件夹)。
|
|
||||||
|
|
||||||
### _property_ `third_party_plugins` {#PluginManager-third-party-plugins}
|
|
||||||
|
|
||||||
- **类型:** set[str]
|
|
||||||
|
|
||||||
- **说明:** 返回所有独立插件名称。
|
|
||||||
|
|
||||||
### _property_ `searched_plugins` {#PluginManager-searched-plugins}
|
|
||||||
|
|
||||||
- **类型:** set[str]
|
|
||||||
|
|
||||||
- **说明:** 返回已搜索到的插件名称。
|
|
||||||
|
|
||||||
### _property_ `available_plugins` {#PluginManager-available-plugins}
|
|
||||||
|
|
||||||
- **类型:** set[str]
|
|
||||||
|
|
||||||
- **说明:** 返回当前插件管理器中可用的插件名称。
|
|
||||||
|
|
||||||
### _method_ `prepare_plugins()` {#PluginManager-prepare-plugins}
|
|
||||||
|
|
||||||
- **说明:** 搜索插件并缓存插件名称。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[str]
|
|
||||||
|
|
||||||
### _method_ `load_plugin(name)` {#PluginManager-load-plugin}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
加载指定插件。
|
|
||||||
|
|
||||||
对于独立插件,可以使用完整插件模块名或者插件名称。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str): 插件名称。
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Plugin](plugin.md#Plugin) | None
|
|
||||||
|
|
||||||
### _method_ `load_all_plugins()` {#PluginManager-load-all-plugins}
|
|
||||||
|
|
||||||
- **说明:** 加载所有可用插件。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
empty
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- set[[Plugin](plugin.md#Plugin)]
|
|
||||||
|
|
||||||
## _class_ `PluginFinder(<auto>)` {#PluginFinder}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
auto
|
|
||||||
|
|
||||||
### _method_ `find_spec(fullname, path, target=None)` {#PluginFinder-find-spec}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `fullname` (str)
|
|
||||||
|
|
||||||
- `path` (Sequence[str] | None)
|
|
||||||
|
|
||||||
- `target` (ModuleType | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- untyped
|
|
||||||
|
|
||||||
## _class_ `PluginLoader(manager, fullname, path)` {#PluginLoader}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `manager` (PluginManager)
|
|
||||||
|
|
||||||
- `fullname` (str)
|
|
||||||
|
|
||||||
- `path`
|
|
||||||
|
|
||||||
### _method_ `create_module(spec)` {#PluginLoader-create-module}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `spec`
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- ModuleType | None
|
|
||||||
|
|
||||||
### _method_ `exec_module(module)` {#PluginLoader-exec-module}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `module` (ModuleType)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user