mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-08 11:46:43 +00:00
Compare commits
256 Commits
v2.0.0-bet
...
v2.0.0rc2
Author | SHA1 | Date | |
---|---|---|---|
|
d83866f03b | ||
|
cb83e76e16 | ||
|
1644615462 | ||
|
89d8abf863 | ||
|
f8cf7c94ae | ||
|
bef494615f | ||
|
6e110e725e | ||
|
85390a14b6 | ||
|
276041e314 | ||
|
2922da7b2f | ||
|
c783ab5e9b | ||
|
139190bff7 | ||
|
1524434444 | ||
|
b6857d59b8 | ||
|
a7b0eb10a0 | ||
|
0eadb44e20 | ||
|
6b43209d37 | ||
|
a50990bef2 | ||
|
f1525c1ecd | ||
|
7df9756205 | ||
|
376a720881 | ||
|
c7377647fa | ||
|
cfbd6f1e4d | ||
|
cecc853f25 | ||
|
fe5f85517e | ||
|
66040a7e44 | ||
|
81d2f017f6 | ||
|
4355025f87 | ||
|
0bc8a39578 | ||
|
7308f57776 | ||
|
7de8912edb | ||
|
42a4edc3ee | ||
|
4a12429a38 | ||
|
092f6d05f5 | ||
|
2c0c05dca1 | ||
|
31bafc832f | ||
|
e1720d8ea0 | ||
|
08be5724b9 | ||
|
828714a4e3 | ||
|
a79eeb73a6 | ||
|
c1aec637d5 | ||
|
12cc08efbd | ||
|
f410af72dc | ||
|
113021cdf4 | ||
|
fcc23f98f8 | ||
|
0b693c419e | ||
|
36e5b81510 | ||
|
71f17bebaa | ||
|
2f45f25d13 | ||
|
17d52446c3 | ||
|
2304aaf22b | ||
|
a70c37de69 | ||
|
1cabc18277 | ||
|
ed1235ed11 | ||
|
9952b4e838 | ||
|
e81390a104 | ||
|
7bc22289b4 | ||
|
bfa4a079bf | ||
|
403850262b | ||
|
ed25e7aa39 | ||
|
7ab480f044 | ||
|
d7d6152094 | ||
|
2954e58c77 | ||
|
f684b96433 | ||
|
4388d1c9e6 | ||
|
876acf3e88 | ||
|
963e73f517 | ||
|
bdc9e44142 | ||
|
4f2efb7304 | ||
|
6ae891124f | ||
|
145bd4d4e1 | ||
|
ab54049909 | ||
|
7aa554f5a2 | ||
|
5566777374 | ||
|
b2594f61de | ||
|
1ad1e0606c | ||
|
583d5060db | ||
|
eaa3dbdfa8 | ||
|
03f378690a | ||
|
512c66ccc0 | ||
|
9d20c7510a | ||
|
29ad8a6686 | ||
|
db534b8824 | ||
|
67b96528af | ||
|
a5929f80f7 | ||
|
9619477a27 | ||
|
8377680fd7 | ||
|
3e3d6f91a5 | ||
|
1d3d886004 | ||
|
ebc5a3cc9e | ||
|
1092767a51 | ||
|
ab227ee64b | ||
|
00b37fb3d2 | ||
|
e6494dc98e | ||
|
c80869f952 | ||
|
2be72eac5e | ||
|
830c4f8c6a | ||
|
138fb458b7 | ||
|
2c93f82ef3 | ||
|
77aa16c2fc | ||
|
945da7151e | ||
|
41ea0df0a5 | ||
|
cec45cf89c | ||
|
2de8c66c70 | ||
|
0bcc4277e5 | ||
|
d3dd93b36c | ||
|
e9bd81d9bb | ||
|
997d4f5042 | ||
|
3bb321c519 | ||
|
fe92d29322 | ||
|
3234871b53 | ||
|
03543f01f2 | ||
|
ba5c0303c7 | ||
|
e56fdd04ad | ||
|
9f10bb70db | ||
|
71aad502d1 | ||
|
ab85b8651e | ||
|
4fe8929441 | ||
|
5c303710f6 | ||
|
68d2ada94b | ||
|
75470fe157 | ||
|
47b3fc516a | ||
|
84c24b014f | ||
|
756cde6525 | ||
|
57ef19af94 | ||
|
5927b517e2 | ||
|
132205bfcc | ||
|
b31dfa9ab0 | ||
|
b249802c38 | ||
|
9df705aaa7 | ||
|
a0df535f0c | ||
|
31022a653d | ||
|
ba77443dde | ||
|
984f743097 | ||
|
638a9c94af | ||
|
92f1d5a4d7 | ||
|
248af2ae1a | ||
|
4c37be7312 | ||
|
2cb8eafa81 | ||
|
05bff5ec17 | ||
|
13245cb58f | ||
|
37bc7326b5 | ||
|
f6d189d8c5 | ||
|
600ef7031f | ||
|
7bedf7c8d0 | ||
|
f62ee5893c | ||
|
71234e9a68 | ||
|
3bbca0fa70 | ||
|
20f144ba93 | ||
|
4c8bc9f0cb | ||
|
064509f26b | ||
|
8c42490a7e | ||
|
179d7105c9 | ||
|
1c14e638c8 | ||
|
c6eef06b55 | ||
|
beef564a22 | ||
|
672f2ceecc | ||
|
28142402d7 | ||
|
b886329fb8 | ||
|
a0b186aff3 | ||
|
595c64e760 | ||
|
5114749073 | ||
|
af2d7b5797 | ||
|
56943c0908 | ||
|
45478deb95 | ||
|
d281ec5bf9 | ||
|
291a7cbb8b | ||
|
41259546bd | ||
|
f96038241f | ||
|
b051320d78 | ||
|
d12efac9f4 | ||
|
f87a38a30a | ||
|
bf016b3f69 | ||
|
373f5255f1 | ||
|
5a35015195 | ||
|
d3a2f1dc08 | ||
|
f1aec4eb10 | ||
|
cd30be21ba | ||
|
f150a9ee89 | ||
|
e68281f60f | ||
|
32be64485a | ||
|
c76f492305 | ||
|
29b0351644 | ||
|
ef3350fd9c | ||
|
459699de5c | ||
|
31c3eb8fd6 | ||
|
1cfdee2645 | ||
|
4e76518a58 | ||
|
b53e029df1 | ||
|
c1faf68806 | ||
|
fe64b904ff | ||
|
a621ade449 | ||
|
3fda978064 | ||
|
60ab93164c | ||
|
2b22d5abda | ||
|
e3a4834383 | ||
|
21087036af | ||
|
94d336ef4d | ||
|
07707213a5 | ||
|
7579878fb4 | ||
|
6599b6420e | ||
|
135c6e8168 | ||
|
743e7363ea | ||
|
a101428c81 | ||
|
0d2b1f693e | ||
|
e5a53dfd5c | ||
|
915c2b3e43 | ||
|
767b6a9913 | ||
|
3f8af04803 | ||
|
00af815b8a | ||
|
24df594b97 | ||
|
d6567f9288 | ||
|
4eb158245e | ||
|
ef35266d3e | ||
|
1d1beb100a | ||
|
06ab6093b7 | ||
|
c1ce7fb940 | ||
|
6e03ddbf12 | ||
|
40c8787828 | ||
|
be459e0bbb | ||
|
1056828f90 | ||
|
ef18e8943d | ||
|
92ff1df419 | ||
|
be5ac88a18 | ||
|
fac647370a | ||
|
05a3891903 | ||
|
4deae8f00c | ||
|
0f70e975b0 | ||
|
982680be91 | ||
|
96b0a863e6 | ||
|
d64bb37c6d | ||
|
51d7f1783d | ||
|
64c18379c9 | ||
|
2735f0cba9 | ||
|
660dbaf3b8 | ||
|
898c29d7ee | ||
|
cdc507bab9 | ||
|
f32bcdc1fc | ||
|
013602da21 | ||
|
4974c596ec | ||
|
0620bec51f | ||
|
8870e6a26e | ||
|
0e3ed0e7ab | ||
|
6cc3b68447 | ||
|
549a37b172 | ||
|
16394ad68b | ||
|
57e580c255 | ||
|
6c23d89494 | ||
|
675e70f579 | ||
|
7a098b96f8 | ||
|
c9794bf91d | ||
|
1766d4da69 | ||
|
6583bc8c61 | ||
|
179f16346a | ||
|
badb0c9ff4 | ||
|
ee0ea85e40 |
@@ -1,15 +0,0 @@
|
|||||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.238.1/containers/codespaces-linux/.devcontainer/base.Dockerfile
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/vscode/devcontainers/universal:2-focal
|
|
||||||
|
|
||||||
# ** [Optional] Uncomment this section to install additional packages. **
|
|
||||||
# USER root
|
|
||||||
#
|
|
||||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|
||||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
|
||||||
|
|
||||||
USER codespace
|
|
||||||
|
|
||||||
# [Required] Poetry
|
|
||||||
RUN curl -sSL https://install.python-poetry.org | python - -y
|
|
||||||
RUN poetry config virtualenvs.in-project true
|
|
@@ -1,32 +1,13 @@
|
|||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
|
||||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.1/containers/codespaces-linux
|
|
||||||
{
|
{
|
||||||
"name": "GitHub Codespaces (Default)",
|
"name": "Default Linux Universal",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
|
||||||
"build": {
|
"features": {
|
||||||
"dockerfile": "Dockerfile"
|
"ghcr.io/devcontainers-contrib/features/poetry:1": {}
|
||||||
},
|
},
|
||||||
|
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && yarn install",
|
||||||
// Configure tool-specific properties.
|
|
||||||
"customizations": {
|
"customizations": {
|
||||||
// Configure properties specific to VS Code.
|
|
||||||
"vscode": {
|
"vscode": {
|
||||||
// Set *default* container specific settings.json values on container create.
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"go.toolsManagement.checkForUpdates": "local",
|
|
||||||
"go.useLanguageServer": true,
|
|
||||||
"go.gopath": "/go",
|
|
||||||
"python.defaultInterpreterPath": "/opt/python/latest/bin/python",
|
|
||||||
"python.linting.enabled": true,
|
|
||||||
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
|
|
||||||
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
|
|
||||||
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
|
|
||||||
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
|
|
||||||
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
|
|
||||||
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
|
|
||||||
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
|
|
||||||
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
|
|
||||||
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
|
|
||||||
"python.analysis.diagnosticMode": "workspace",
|
"python.analysis.diagnosticMode": "workspace",
|
||||||
"python.analysis.typeCheckingMode": "basic",
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
"[python]": {
|
"[python]": {
|
||||||
@@ -50,7 +31,6 @@
|
|||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"lldb.executable": "/usr/bin/lldb",
|
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"**/__pycache__": true
|
"**/__pycache__": true
|
||||||
},
|
},
|
||||||
@@ -59,10 +39,7 @@
|
|||||||
"**/__pycache__": true
|
"**/__pycache__": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"GitHub.vscode-pull-request-github",
|
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"ms-python.vscode-pylance",
|
"ms-python.vscode-pylance",
|
||||||
"ms-python.isort",
|
"ms-python.isort",
|
||||||
@@ -72,27 +49,5 @@
|
|||||||
"bradlc.vscode-tailwindcss"
|
"bradlc.vscode-tailwindcss"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
"remoteUser": "codespace",
|
|
||||||
|
|
||||||
"overrideCommand": false,
|
|
||||||
|
|
||||||
"mounts": [
|
|
||||||
"source=codespaces-linux-var-lib-docker,target=/var/lib/docker,type=volume"
|
|
||||||
],
|
|
||||||
|
|
||||||
"runArgs": [
|
|
||||||
"--cap-add=SYS_PTRACE",
|
|
||||||
"--security-opt",
|
|
||||||
"seccomp=unconfined",
|
|
||||||
"--privileged",
|
|
||||||
"--init"
|
|
||||||
],
|
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
||||||
// "forwardPorts": [],
|
|
||||||
|
|
||||||
// "oryx build" will automatically install your dependencies and attempt to build your project
|
|
||||||
"postCreateCommand": "poetry install && poetry run pre-commit install && yarn install"
|
|
||||||
}
|
}
|
||||||
|
4
.github/actions/setup-node/action.yml
vendored
4
.github/actions/setup-node/action.yml
vendored
@@ -9,10 +9,10 @@ runs:
|
|||||||
node-version: "16"
|
node-version: "16"
|
||||||
|
|
||||||
- id: yarn-cache-dir-path
|
- id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
21
.github/actions/setup-python/action.yml
vendored
21
.github/actions/setup-python/action.yml
vendored
@@ -5,27 +5,20 @@ inputs:
|
|||||||
python-version:
|
python-version:
|
||||||
description: Python version
|
description: Python version
|
||||||
required: false
|
required: false
|
||||||
default: "3.9"
|
default: "3.10"
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- id: python
|
- name: Install poetry
|
||||||
uses: actions/setup-python@v2
|
run: pipx install poetry
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
architecture: "x64"
|
architecture: "x64"
|
||||||
|
cache: "poetry"
|
||||||
- uses: Gr1N/setup-poetry@v7
|
|
||||||
|
|
||||||
- id: poetry-cache
|
|
||||||
run: echo "::set-output name=dir::$(poetry config virtualenvs.path)"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ${{ steps.poetry-cache.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-poetry-${{ steps.python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
|
||||||
|
|
||||||
- run: poetry install -E all
|
- run: poetry install -E all
|
||||||
shell: bash
|
shell: bash
|
||||||
|
2
.github/workflows/codecov.yml
vendored
2
.github/workflows/codecov.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
env:
|
env:
|
||||||
|
@@ -13,20 +13,20 @@ repos:
|
|||||||
stages: [commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.6.0
|
rev: 22.10.0
|
||||||
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: v2.7.1
|
rev: v3.0.0-alpha.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml]
|
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
||||||
stages: [commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/nonebot/nonemoji
|
- repo: https://github.com/nonebot/nonemoji
|
||||||
rev: v0.1.2
|
rev: v0.1.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: nonemoji
|
- id: nonemoji
|
||||||
stages: [prepare-commit-msg]
|
stages: [prepare-commit-msg]
|
||||||
|
@@ -33,7 +33,7 @@ pre-commit install
|
|||||||
|
|
||||||
### 使用 GitHub Codespaces(Dev Container)
|
### 使用 GitHub Codespaces(Dev Container)
|
||||||
|
|
||||||
使用 GitHub Codespaces 选择 `NoneBot2` 项目,然后选择 `.devcontainer/devcontainer.json` 配置即可。
|
[](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=289605524)
|
||||||
|
|
||||||
### Commit 规范
|
### Commit 规范
|
||||||
|
|
||||||
|
45
README.md
45
README.md
@@ -21,7 +21,7 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
|||||||
<a href="https://pypi.python.org/pypi/nonebot2">
|
<a href="https://pypi.python.org/pypi/nonebot2">
|
||||||
<img src="https://img.shields.io/pypi/v/nonebot2" alt="pypi">
|
<img src="https://img.shields.io/pypi/v/nonebot2" alt="pypi">
|
||||||
</a>
|
</a>
|
||||||
<img src="https://img.shields.io/badge/python-3.7.3+-blue" alt="python">
|
<img src="https://img.shields.io/badge/python-3.8+-blue" alt="python">
|
||||||
<a href="https://codecov.io/gh/nonebot/nonebot2">
|
<a href="https://codecov.io/gh/nonebot/nonebot2">
|
||||||
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
|
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
|
||||||
</a>
|
</a>
|
||||||
@@ -38,17 +38,20 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
|||||||
<a href="https://onebot.dev/">
|
<a href="https://onebot.dev/">
|
||||||
<img src="https://img.shields.io/badge/OneBot-v12-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
|
<img src="https://img.shields.io/badge/OneBot-v12-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
|
|
||||||
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk">
|
|
||||||
</a>
|
|
||||||
<a href="https://core.telegram.org/bots/api">
|
<a href="https://core.telegram.org/bots/api">
|
||||||
<img src="https://img.shields.io/badge/telegram-Bot-lightgrey?style=social&logo=telegram" alt="telegram">
|
<img src="https://img.shields.io/badge/telegram-Bot-lightgrey?style=social&logo=telegram" alt="telegram">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://open.feishu.cn/document/home/index">
|
<a href="https://open.feishu.cn/document/home/index">
|
||||||
<img src="https://img.shields.io/badge/%E9%A3%9E%E4%B9%A6-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNyAyOUMyMSAyOSAyNSAyNi45MzM5IDI4IDIzLjQwNjVDMzYgMTQgNDEuNDI0MiAxNi44MTY2IDQ0IDE3Ljk5OThDMzguNSAyMC45OTk4IDQwLjUgMjkuNjIzMyAzMyAzNS45OTk4QzI4LjM4MiAzOS45MjU5IDIzLjQ5NDUgNDEuMDE0IDE5IDQxQzEyLjUyMzEgNDAuOTc5OSA2Ljg2MjI2IDM3Ljc2MzcgNCAzNS40MDYzVjE2Ljk5OTgiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNNS42NDgwOCAxNS44NjY5QzUuMDIyMzEgMTQuOTU2NyAzLjc3NzE1IDE0LjcyNjEgMi44NjY5NCAxNS4zNTE5QzEuOTU2NzMgMTUuOTc3NyAxLjcyNjE1IDE3LjIyMjggMi4zNTE5MiAxOC4xMzMxTDUuNjQ4MDggMTUuODY2OVpNMzYuMDAyMSAzNS43MzA5QzM2Ljk1OCAzNS4xNzc0IDM3LjI4NDMgMzMuOTUzOSAzNi43MzA5IDMyLjk5NzlDMzYuMTc3NCAzMi4wNDIgMzQuOTUzOSAzMS43MTU3IDMzLjk5NzkgMzIuMjY5MUwzNi4wMDIxIDM1LjczMDlaTTIuMzUxOTIgMTguMTMzMUM1LjI0MzUgMjIuMzM5IDEwLjc5OTIgMjguMTQ0IDE2Ljg4NjUgMzIuMjIzOUMxOS45MzQ1IDM0LjI2NjcgMjMuMjE3IDM1Ljk0NiAyNi40NDkgMzYuNzMyNEMyOS42OTQ2IDM3LjUyMiAzMy4wNDUxIDM3LjQ0MjggMzYuMDAyMSAzNS43MzA5TDMzLjk5NzkgMzIuMjY5MUMzMi4yMDQ5IDMzLjMwNzIgMjkuOTkyOSAzMy40NzggMjcuMzk0NyAzMi44NDU4QzI0Ljc4MyAzMi4yMTAzIDIxLjk0MDUgMzAuNzk1OCAxOS4xMTM1IDI4LjkwMTFDMTMuNDUwOCAyNS4xMDYgOC4yNTY1IDE5LjY2MSA1LjY0ODA4IDE1Ljg2NjlMMi4zNTE5MiAxOC4xMzMxWiIgZmlsbD0iIzMzMyIvPjxwYXRoIGQ9Ik0zMy41OTQ1IDE3QzMyLjgzOTggMTQuNzAyNyAzMC44NTQ5IDkuOTQwNTQgMjcuNTk0NSA3SDExLjU5NDVDMTUuMjE3MSAxMC42NzU3IDIzIDE2IDI3IDI0IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+" alt="feishu">
|
<img src="https://img.shields.io/badge/%E9%A3%9E%E4%B9%A6-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNyAyOUMyMSAyOSAyNSAyNi45MzM5IDI4IDIzLjQwNjVDMzYgMTQgNDEuNDI0MiAxNi44MTY2IDQ0IDE3Ljk5OThDMzguNSAyMC45OTk4IDQwLjUgMjkuNjIzMyAzMyAzNS45OTk4QzI4LjM4MiAzOS45MjU5IDIzLjQ5NDUgNDEuMDE0IDE5IDQxQzEyLjUyMzEgNDAuOTc5OSA2Ljg2MjI2IDM3Ljc2MzcgNCAzNS40MDYzVjE2Ljk5OTgiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNNS42NDgwOCAxNS44NjY5QzUuMDIyMzEgMTQuOTU2NyAzLjc3NzE1IDE0LjcyNjEgMi44NjY5NCAxNS4zNTE5QzEuOTU2NzMgMTUuOTc3NyAxLjcyNjE1IDE3LjIyMjggMi4zNTE5MiAxOC4xMzMxTDUuNjQ4MDggMTUuODY2OVpNMzYuMDAyMSAzNS43MzA5QzM2Ljk1OCAzNS4xNzc0IDM3LjI4NDMgMzMuOTUzOSAzNi43MzA5IDMyLjk5NzlDMzYuMTc3NCAzMi4wNDIgMzQuOTUzOSAzMS43MTU3IDMzLjk5NzkgMzIuMjY5MUwzNi4wMDIxIDM1LjczMDlaTTIuMzUxOTIgMTguMTMzMUM1LjI0MzUgMjIuMzM5IDEwLjc5OTIgMjguMTQ0IDE2Ljg4NjUgMzIuMjIzOUMxOS45MzQ1IDM0LjI2NjcgMjMuMjE3IDM1Ljk0NiAyNi40NDkgMzYuNzMyNEMyOS42OTQ2IDM3LjUyMiAzMy4wNDUxIDM3LjQ0MjggMzYuMDAyMSAzNS43MzA5TDMzLjk5NzkgMzIuMjY5MUMzMi4yMDQ5IDMzLjMwNzIgMjkuOTkyOSAzMy40NzggMjcuMzk0NyAzMi44NDU4QzI0Ljc4MyAzMi4yMTAzIDIxLjk0MDUgMzAuNzk1OCAxOS4xMTM1IDI4LjkwMTFDMTMuNDUwOCAyNS4xMDYgOC4yNTY1IDE5LjY2MSA1LjY0ODA4IDE1Ljg2NjlMMi4zNTE5MiAxOC4xMzMxWiIgZmlsbD0iIzMzMyIvPjxwYXRoIGQ9Ik0zMy41OTQ1IDE3QzMyLjgzOTggMTQuNzAyNyAzMC44NTQ5IDkuOTQwNTQgMjcuNTk0NSA3SDExLjU5NDVDMTUuMjE3MSAxMC42NzU3IDIzIDE2IDI3IDI0IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+" alt="feishu">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://docs.github.com/en/developers/apps">
|
||||||
|
<img src="https://img.shields.io/badge/GitHub-Bot-181717?style=social&logo=github" alt="github"/>
|
||||||
|
</a>
|
||||||
<a href="https://bot.q.qq.com/wiki/">
|
<a href="https://bot.q.qq.com/wiki/">
|
||||||
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
|
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
|
||||||
|
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
|
||||||
|
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk">
|
||||||
|
</a>
|
||||||
</a>
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
|
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
|
||||||
@@ -92,17 +95,29 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
- 生而可靠:100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://v2.nonebot.dev/docs/start/editor-support))
|
- 生而可靠:100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://v2.nonebot.dev/docs/start/editor-support))
|
||||||
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
|
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
|
||||||
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
||||||
- [OneBot 协议](https://onebot.dev/) (QQ 等)
|
|
||||||
- [钉钉](https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p)
|
| 协议名称 | 状态 | 注释 |
|
||||||
- [Telegram](https://core.telegram.org/bots/api)
|
| :--------------------------------------------------------: | :--: | :----------------------------------------------------------------: |
|
||||||
- [飞书](https://open.feishu.cn/document/home/index)
|
| [OneBot 协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号等[平台](https://onebot.dev/ecosystem.html) |
|
||||||
- [QQ 频道](https://bot.q.qq.com/wiki/)
|
| [Telegram](https://core.telegram.org/bots/api) | ✅ | |
|
||||||
- 坚实后盾:支持多种 web 框架,可自定义替换
|
| [飞书](https://open.feishu.cn/document/home/index) | ✅ | |
|
||||||
- [FastAPI](https://fastapi.tiangolo.com/)
|
| [GitHub](https://docs.github.com/en/developers/apps) | ✅ | GitHub APP & OAuth APP |
|
||||||
- [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask)
|
| [QQ 频道](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 |
|
||||||
- [aiohttp](https://docs.aiohttp.org/en/stable/)
|
| [钉钉](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer |
|
||||||
- [httpx](https://www.python-httpx.org/)
|
| Console | ✅ | 控制台交互 |
|
||||||
- [websockets](https://websockets.readthedocs.io/en/stable/)
|
| [开黑啦](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
|
||||||
|
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | 由社区贡献 |
|
||||||
|
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | ↗️ | 由社区贡献 |
|
||||||
|
|
||||||
|
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||||
|
|
||||||
|
| 驱动框架 | 类型 |
|
||||||
|
| :--------------------------------------------------------: | :----: |
|
||||||
|
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
|
||||||
|
| [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask) | 服务端 |
|
||||||
|
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
|
||||||
|
| [httpx](https://www.python-httpx.org/) | 客户端 |
|
||||||
|
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
|
||||||
|
|
||||||
更多:[概览](https://v2.nonebot.dev/docs/)
|
更多:[概览](https://v2.nonebot.dev/docs/)
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
|
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
|
||||||
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
|
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
|
||||||
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
|
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
|
||||||
|
- `on_type` => {ref}``on_type` <nonebot.plugin.on.on_type>`
|
||||||
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
|
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
|
||||||
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
|
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
|
||||||
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
|
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
|
||||||
@@ -29,7 +30,6 @@
|
|||||||
- `get_plugin_by_module_name` => {ref}``get_plugin_by_module_name` <nonebot.plugin.get_plugin_by_module_name>`
|
- `get_plugin_by_module_name` => {ref}``get_plugin_by_module_name` <nonebot.plugin.get_plugin_by_module_name>`
|
||||||
- `get_loaded_plugins` => {ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
|
- `get_loaded_plugins` => {ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
|
||||||
- `get_available_plugin_names` => {ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
|
- `get_available_plugin_names` => {ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
|
||||||
- `export` => {ref}``export` <nonebot.plugin.export.export>`
|
|
||||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
@@ -37,24 +37,24 @@ FrontMatter:
|
|||||||
description: nonebot 模块
|
description: nonebot 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import importlib
|
import importlib
|
||||||
|
from importlib.metadata import version
|
||||||
from typing import Any, Dict, Type, Optional
|
from typing import Any, Dict, Type, Optional
|
||||||
|
|
||||||
|
import loguru
|
||||||
|
from pydantic.env_settings import DotenvType
|
||||||
|
|
||||||
|
from nonebot.log import logger
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.utils import escape_tag
|
from nonebot.utils import escape_tag
|
||||||
from nonebot.config import Env, Config
|
from nonebot.config import Env, Config
|
||||||
from nonebot.log import logger, default_filter
|
|
||||||
from nonebot.drivers import Driver, ReverseDriver, combine_driver
|
from nonebot.drivers import Driver, ReverseDriver, combine_driver
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pkg_resources
|
__version__ = version("nonebot2")
|
||||||
|
|
||||||
_dist: pkg_resources.Distribution = pkg_resources.get_distribution("nonebot2")
|
|
||||||
__version__ = _dist.version
|
|
||||||
VERSION = _dist.parsed_version
|
|
||||||
except Exception: # pragma: no cover
|
except Exception: # pragma: no cover
|
||||||
__version__ = None
|
__version__ = None
|
||||||
VERSION = None
|
|
||||||
|
|
||||||
_driver: Optional[Driver] = None
|
_driver: Optional[Driver] = None
|
||||||
|
|
||||||
@@ -172,8 +172,7 @@ def get_bots() -> Dict[str, Bot]:
|
|||||||
bots = nonebot.get_bots()
|
bots = nonebot.get_bots()
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
driver = get_driver()
|
return get_driver().bots
|
||||||
return driver.bots
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_dot_notation(
|
def _resolve_dot_notation(
|
||||||
@@ -207,7 +206,16 @@ def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
|
|||||||
return combine_driver(DriverClass, *mixins)
|
return combine_driver(DriverClass, *mixins)
|
||||||
|
|
||||||
|
|
||||||
def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
def _log_patcher(record: "loguru.Record"):
|
||||||
|
record["name"] = (
|
||||||
|
plugin.name
|
||||||
|
if (module_name := record["name"])
|
||||||
|
and (plugin := get_plugin_by_module_name(module_name))
|
||||||
|
else (module_name and module_name.split(".")[0])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def init(*, _env_file: Optional[DotenvType] = None, **kwargs: Any) -> None:
|
||||||
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
|
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
|
||||||
|
|
||||||
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
|
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
|
||||||
@@ -227,13 +235,17 @@ def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
|||||||
if not _driver:
|
if not _driver:
|
||||||
logger.success("NoneBot is initializing...")
|
logger.success("NoneBot is initializing...")
|
||||||
env = Env()
|
env = Env()
|
||||||
|
_env_file = _env_file or f".env.{env.environment}"
|
||||||
config = Config(
|
config = Config(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
_common_config=env.dict(),
|
_env_file=(".env", _env_file)
|
||||||
_env_file=_env_file or f".env.{env.environment}",
|
if isinstance(_env_file, (str, os.PathLike))
|
||||||
|
else _env_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
default_filter.level = config.log_level
|
logger.configure(
|
||||||
|
extra={"nonebot_log_level": config.log_level}, patcher=_log_patcher
|
||||||
|
)
|
||||||
logger.opt(colors=True).info(
|
logger.opt(colors=True).info(
|
||||||
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
|
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
|
||||||
)
|
)
|
||||||
@@ -241,7 +253,7 @@ def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
|||||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
|
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
|
||||||
)
|
)
|
||||||
|
|
||||||
DriverClass: Type[Driver] = _resolve_combine_expr(config.driver)
|
DriverClass = _resolve_combine_expr(config.driver)
|
||||||
_driver = DriverClass(env, config)
|
_driver = DriverClass(env, config)
|
||||||
|
|
||||||
|
|
||||||
@@ -262,7 +274,7 @@ def run(*args: Any, **kwargs: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
from nonebot.plugin import on as on
|
from nonebot.plugin import on as on
|
||||||
from nonebot.plugin import export as export
|
from nonebot.plugin import on_type as on_type
|
||||||
from nonebot.plugin import require as require
|
from nonebot.plugin import require as require
|
||||||
from nonebot.plugin import on_regex as on_regex
|
from nonebot.plugin import on_regex as on_regex
|
||||||
from nonebot.plugin import on_notice as on_notice
|
from nonebot.plugin import on_notice as on_notice
|
||||||
|
@@ -7,21 +7,6 @@ FrontMatter:
|
|||||||
description: nonebot.adapters 模块
|
description: nonebot.adapters 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Iterable
|
|
||||||
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
pkg_resources.declare_namespace(__name__)
|
|
||||||
del pkg_resources
|
|
||||||
except ImportError:
|
|
||||||
import pkgutil
|
|
||||||
|
|
||||||
__path__: Iterable[str] = pkgutil.extend_path(__path__, __name__) # type: ignore
|
|
||||||
del pkgutil
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from nonebot.internal.adapter import Bot as Bot
|
from nonebot.internal.adapter import Bot as Bot
|
||||||
from nonebot.internal.adapter import Event as Event
|
from nonebot.internal.adapter import Event as Event
|
||||||
from nonebot.internal.adapter import Adapter as Adapter
|
from nonebot.internal.adapter import Adapter as Adapter
|
||||||
|
@@ -14,18 +14,17 @@ from datetime import timedelta
|
|||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
|
from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
|
||||||
|
|
||||||
from pydantic import BaseSettings, IPvAnyAddress
|
from pydantic.utils import deep_update
|
||||||
|
from pydantic import Extra, BaseSettings, IPvAnyAddress
|
||||||
from pydantic.env_settings import (
|
from pydantic.env_settings import (
|
||||||
|
DotenvType,
|
||||||
SettingsError,
|
SettingsError,
|
||||||
EnvSettingsSource,
|
EnvSettingsSource,
|
||||||
InitSettingsSource,
|
InitSettingsSource,
|
||||||
SettingsSourceCallable,
|
SettingsSourceCallable,
|
||||||
read_env_file,
|
|
||||||
env_file_sentinel,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.utils import escape_tag
|
|
||||||
|
|
||||||
|
|
||||||
class CustomEnvSettings(EnvSettingsSource):
|
class CustomEnvSettings(EnvSettingsSource):
|
||||||
@@ -33,33 +32,15 @@ class CustomEnvSettings(EnvSettingsSource):
|
|||||||
"""
|
"""
|
||||||
Build environment variables suitable for passing to the Model.
|
Build environment variables suitable for passing to the Model.
|
||||||
"""
|
"""
|
||||||
d: Dict[str, Optional[str]] = {}
|
d: Dict[str, Any] = {}
|
||||||
|
|
||||||
if settings.__config__.case_sensitive:
|
if settings.__config__.case_sensitive:
|
||||||
env_vars: Mapping[str, Optional[str]] = os.environ # pragma: no cover
|
env_vars: Mapping[str, Optional[str]] = os.environ # pragma: no cover
|
||||||
else:
|
else:
|
||||||
env_vars = {k.lower(): v for k, v in os.environ.items()}
|
env_vars = {k.lower(): v for k, v in os.environ.items()}
|
||||||
|
|
||||||
env_file_vars: Dict[str, Optional[str]] = {}
|
env_file_vars = self._read_env_files(settings.__config__.case_sensitive)
|
||||||
env_file = (
|
env_vars = {**env_file_vars, **env_vars}
|
||||||
self.env_file
|
|
||||||
if self.env_file != env_file_sentinel
|
|
||||||
else settings.__config__.env_file
|
|
||||||
)
|
|
||||||
env_file_encoding = (
|
|
||||||
self.env_file_encoding
|
|
||||||
if self.env_file_encoding is not None
|
|
||||||
else settings.__config__.env_file_encoding
|
|
||||||
)
|
|
||||||
if env_file is not None:
|
|
||||||
env_path = Path(env_file)
|
|
||||||
if env_path.is_file():
|
|
||||||
env_file_vars = read_env_file(
|
|
||||||
env_path,
|
|
||||||
encoding=env_file_encoding, # type: ignore
|
|
||||||
case_sensitive=settings.__config__.case_sensitive,
|
|
||||||
)
|
|
||||||
env_vars = {**env_file_vars, **env_vars}
|
|
||||||
|
|
||||||
for field in settings.__fields__.values():
|
for field in settings.__fields__.values():
|
||||||
env_val: Optional[str] = None
|
env_val: Optional[str] = None
|
||||||
@@ -70,29 +51,56 @@ class CustomEnvSettings(EnvSettingsSource):
|
|||||||
if env_val is not None:
|
if env_val is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
if env_val is None:
|
is_complex, allow_parse_failure = self.field_is_complex(field)
|
||||||
continue
|
if is_complex:
|
||||||
|
if env_val is None:
|
||||||
|
if env_val_built := self.explode_env_vars(field, env_vars):
|
||||||
|
d[field.alias] = env_val_built
|
||||||
|
else:
|
||||||
|
# field is complex and there's a value, decode that as JSON, then add explode_env_vars
|
||||||
|
try:
|
||||||
|
env_val = settings.__config__.parse_env_var(field.name, env_val)
|
||||||
|
except ValueError as e:
|
||||||
|
if not allow_parse_failure:
|
||||||
|
raise SettingsError(
|
||||||
|
f'error parsing env var "{env_name}"' # type: ignore
|
||||||
|
) from e
|
||||||
|
|
||||||
if field.is_complex():
|
if isinstance(env_val, dict):
|
||||||
try:
|
d[field.alias] = deep_update(
|
||||||
env_val = settings.__config__.json_loads(env_val)
|
env_val, self.explode_env_vars(field, env_vars)
|
||||||
except ValueError as e: # pragma: no cover
|
)
|
||||||
raise SettingsError(
|
else:
|
||||||
f'error parsing JSON for "{env_name}"' # type: ignore
|
d[field.alias] = env_val
|
||||||
) from e
|
elif env_val is not None:
|
||||||
d[field.alias] = env_val
|
# simplest case, field is not complex, we only need to add the value if it was found
|
||||||
|
d[field.alias] = env_val
|
||||||
|
|
||||||
if env_file_vars:
|
# remain user custom config
|
||||||
for env_name in env_file_vars.keys():
|
for env_name in env_file_vars:
|
||||||
env_val = env_vars[env_name]
|
env_val = env_vars[env_name]
|
||||||
|
if env_val and (val_striped := env_val.strip()):
|
||||||
|
# there's a value, decode that as JSON
|
||||||
try:
|
try:
|
||||||
if env_val:
|
env_val = settings.__config__.parse_env_var(env_name, val_striped)
|
||||||
env_val = settings.__config__.json_loads(env_val.strip())
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.opt(colors=True, exception=e).trace(
|
logger.trace(
|
||||||
f"Error while parsing JSON for {escape_tag(env_name)}. Assumed as string."
|
"Error while parsing JSON for "
|
||||||
|
f"{env_name!r}={val_striped!r}. "
|
||||||
|
"Assumed as string."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# explode value when it's a nested dict
|
||||||
|
env_name, *nested_keys = env_name.split(self.env_nested_delimiter)
|
||||||
|
if nested_keys and (env_name not in d or isinstance(d[env_name], dict)):
|
||||||
|
result = {}
|
||||||
|
*keys, last_key = nested_keys
|
||||||
|
_tmp = result
|
||||||
|
for key in keys:
|
||||||
|
_tmp = _tmp.setdefault(key, {})
|
||||||
|
_tmp[last_key] = env_val
|
||||||
|
d[env_name] = deep_update(d.get(env_name, {}), result)
|
||||||
|
elif not nested_keys:
|
||||||
d[env_name] = env_val
|
d[env_name] = env_val
|
||||||
|
|
||||||
return d
|
return d
|
||||||
@@ -105,6 +113,9 @@ class BaseConfig(BaseSettings):
|
|||||||
return self.__dict__.get(name)
|
return self.__dict__.get(name)
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
extra = Extra.allow
|
||||||
|
env_nested_delimiter = "__"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def customise_sources(
|
def customise_sources(
|
||||||
cls,
|
cls,
|
||||||
@@ -116,7 +127,10 @@ class BaseConfig(BaseSettings):
|
|||||||
return (
|
return (
|
||||||
init_settings,
|
init_settings,
|
||||||
CustomEnvSettings(
|
CustomEnvSettings(
|
||||||
env_settings.env_file, env_settings.env_file_encoding
|
env_settings.env_file,
|
||||||
|
env_settings.env_file_encoding,
|
||||||
|
env_settings.env_nested_delimiter,
|
||||||
|
env_settings.env_prefix_len,
|
||||||
),
|
),
|
||||||
InitSettingsSource(common_config),
|
InitSettingsSource(common_config),
|
||||||
file_secret_settings,
|
file_secret_settings,
|
||||||
@@ -136,7 +150,6 @@ class Env(BaseConfig):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
extra = "allow"
|
|
||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
|
|
||||||
|
|
||||||
@@ -149,8 +162,7 @@ class Config(BaseConfig):
|
|||||||
配置方法参考: [配置](https://v2.nonebot.dev/docs/tutorial/configuration)
|
配置方法参考: [配置](https://v2.nonebot.dev/docs/tutorial/configuration)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_env_file: str = ".env"
|
_env_file: DotenvType = ".env", ".env.prod"
|
||||||
_common_config: Dict[str, Any] = {}
|
|
||||||
|
|
||||||
# nonebot configs
|
# nonebot configs
|
||||||
driver: str = "~fastapi"
|
driver: str = "~fastapi"
|
||||||
@@ -230,8 +242,7 @@ class Config(BaseConfig):
|
|||||||
# or from env file using json loads
|
# or from env file using json loads
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
extra = "allow"
|
env_file = ".env", ".env.prod"
|
||||||
env_file = ".env.prod"
|
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
|
@@ -4,7 +4,7 @@ FrontMatter:
|
|||||||
sidebar_position: 9
|
sidebar_position: 9
|
||||||
description: nonebot.consts 模块
|
description: nonebot.consts 模块
|
||||||
"""
|
"""
|
||||||
from typing_extensions import Literal
|
from typing import Literal
|
||||||
|
|
||||||
# used by Matcher
|
# used by Matcher
|
||||||
RECEIVE_KEY: Literal["_receive_{id}"] = "_receive_{id}"
|
RECEIVE_KEY: Literal["_receive_{id}"] = "_receive_{id}"
|
||||||
@@ -42,3 +42,11 @@ REGEX_GROUP: Literal["_matched_groups"] = "_matched_groups"
|
|||||||
"""正则匹配 group 元组存储 key"""
|
"""正则匹配 group 元组存储 key"""
|
||||||
REGEX_DICT: Literal["_matched_dict"] = "_matched_dict"
|
REGEX_DICT: Literal["_matched_dict"] = "_matched_dict"
|
||||||
"""正则匹配 group 字典存储 key"""
|
"""正则匹配 group 字典存储 key"""
|
||||||
|
STARTSWITH_KEY: Literal["_startswith"] = "_startswith"
|
||||||
|
"""响应触发前缀 key"""
|
||||||
|
ENDSWITH_KEY: Literal["_endswith"] = "_endswith"
|
||||||
|
"""响应触发后缀 key"""
|
||||||
|
FULLMATCH_KEY: Literal["_fullmatch"] = "_fullmatch"
|
||||||
|
"""响应触发完整消息 key"""
|
||||||
|
KEYWORD_KEY: Literal["_keyword"] = "_keyword"
|
||||||
|
"""响应触发关键字 key"""
|
||||||
|
@@ -6,15 +6,31 @@ FrontMatter:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, Dict, List, Type, Generic, TypeVar, Callable, Optional
|
from dataclasses import field, dataclass
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Type,
|
||||||
|
Tuple,
|
||||||
|
Generic,
|
||||||
|
TypeVar,
|
||||||
|
Callable,
|
||||||
|
Iterable,
|
||||||
|
Optional,
|
||||||
|
Awaitable,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
|
||||||
from pydantic import BaseConfig
|
from pydantic import BaseConfig
|
||||||
from pydantic.schema import get_annotation_from_field_info
|
from pydantic.schema import get_annotation_from_field_info
|
||||||
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
|
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.exception import TypeMisMatch
|
from nonebot.typing import _DependentCallable
|
||||||
|
from nonebot.exception import SkippedException
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
from nonebot.utils import run_sync, is_coroutine_callable
|
||||||
|
|
||||||
from .utils import check_field_type, get_typed_signature
|
from .utils import check_field_type, get_typed_signature
|
||||||
@@ -31,25 +47,29 @@ class Param(abc.ABC, FieldInfo):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, dependent: "Dependent", name: str, param: inspect.Parameter
|
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
|
||||||
) -> Optional["Param"]:
|
) -> Optional["Param"]:
|
||||||
return None
|
return
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_parameterless(
|
def _check_parameterless(
|
||||||
cls, dependent: "Dependent", value: Any
|
cls, value: Any, allow_types: Tuple[Type["Param"], ...]
|
||||||
) -> Optional["Param"]:
|
) -> Optional["Param"]:
|
||||||
return None
|
return
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def _solve(self, **kwargs: Any) -> Any:
|
async def _solve(self, **kwargs: Any) -> Any:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def _check(self, **kwargs: Any) -> None:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class CustomConfig(BaseConfig):
|
class CustomConfig(BaseConfig):
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
class Dependent(Generic[R]):
|
class Dependent(Generic[R]):
|
||||||
"""依赖注入容器
|
"""依赖注入容器
|
||||||
|
|
||||||
@@ -61,101 +81,70 @@ class Dependent(Generic[R]):
|
|||||||
allow_types: 允许的参数类型
|
allow_types: 允许的参数类型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
call: _DependentCallable[R]
|
||||||
self,
|
params: Tuple[ModelField] = field(default_factory=tuple)
|
||||||
*,
|
parameterless: Tuple[Param] = field(default_factory=tuple)
|
||||||
call: Callable[..., Any],
|
|
||||||
pre_checkers: Optional[List[Param]] = None,
|
|
||||||
params: Optional[List[ModelField]] = None,
|
|
||||||
parameterless: Optional[List[Param]] = None,
|
|
||||||
allow_types: Optional[List[Type[Param]]] = None,
|
|
||||||
) -> None:
|
|
||||||
self.call = call
|
|
||||||
self.pre_checkers = pre_checkers or []
|
|
||||||
self.params = params or []
|
|
||||||
self.parameterless = parameterless or []
|
|
||||||
self.allow_types = allow_types or []
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
if inspect.isfunction(self.call) or inspect.isclass(self.call):
|
||||||
|
call_str = self.call.__name__
|
||||||
|
else:
|
||||||
|
call_str = repr(self.call)
|
||||||
return (
|
return (
|
||||||
f"<Dependent call={self.call}, params={self.params},"
|
f"Dependent(call={call_str}"
|
||||||
f" parameterless={self.parameterless}>"
|
+ (f", parameterless={self.parameterless}" if self.parameterless else "")
|
||||||
|
+ ")"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.__repr__()
|
|
||||||
|
|
||||||
async def __call__(self, **kwargs: Any) -> R:
|
async def __call__(self, **kwargs: Any) -> R:
|
||||||
|
# do pre-check
|
||||||
|
await self.check(**kwargs)
|
||||||
|
|
||||||
|
# solve param values
|
||||||
values = await self.solve(**kwargs)
|
values = await self.solve(**kwargs)
|
||||||
|
|
||||||
|
# call function
|
||||||
if is_coroutine_callable(self.call):
|
if is_coroutine_callable(self.call):
|
||||||
return await self.call(**values)
|
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
|
||||||
else:
|
else:
|
||||||
return await run_sync(self.call)(**values)
|
return await run_sync(cast(Callable[..., R], self.call))(**values)
|
||||||
|
|
||||||
def parse_param(self, name: str, param: inspect.Parameter) -> Param:
|
@staticmethod
|
||||||
for allow_type in self.allow_types:
|
def parse_params(
|
||||||
field_info = allow_type._check_param(self, name, param)
|
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
|
||||||
if field_info:
|
) -> Tuple[ModelField]:
|
||||||
return field_info
|
fields: List[ModelField] = []
|
||||||
else:
|
params = get_typed_signature(call).parameters.values()
|
||||||
raise ValueError(
|
|
||||||
f"Unknown parameter {name} for function {self.call} with type {param.annotation}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def parse_parameterless(self, value: Any) -> Param:
|
for param in params:
|
||||||
for allow_type in self.allow_types:
|
|
||||||
field_info = allow_type._check_parameterless(self, value)
|
|
||||||
if field_info:
|
|
||||||
return field_info
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
f"Unknown parameterless {value} for function {self.call} with type {type(value)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def prepend_parameterless(self, value: Any) -> None:
|
|
||||||
self.parameterless.insert(0, self.parse_parameterless(value))
|
|
||||||
|
|
||||||
def append_parameterless(self, value: Any) -> None:
|
|
||||||
self.parameterless.append(self.parse_parameterless(value))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def parse(
|
|
||||||
cls: Type[T],
|
|
||||||
*,
|
|
||||||
call: Callable[..., Any],
|
|
||||||
parameterless: Optional[List[Any]] = None,
|
|
||||||
allow_types: Optional[List[Type[Param]]] = None,
|
|
||||||
) -> T:
|
|
||||||
signature = get_typed_signature(call)
|
|
||||||
params = signature.parameters
|
|
||||||
dependent = cls(
|
|
||||||
call=call,
|
|
||||||
allow_types=allow_types,
|
|
||||||
)
|
|
||||||
|
|
||||||
for param_name, param in params.items():
|
|
||||||
default_value = Required
|
default_value = Required
|
||||||
if param.default != param.empty:
|
if param.default != param.empty:
|
||||||
default_value = param.default
|
default_value = param.default
|
||||||
|
|
||||||
if isinstance(default_value, Param):
|
if isinstance(default_value, Param):
|
||||||
field_info = default_value
|
field_info = default_value
|
||||||
default_value = field_info.default
|
|
||||||
else:
|
else:
|
||||||
field_info = dependent.parse_param(param_name, param)
|
for allow_type in allow_types:
|
||||||
default_value = field_info.default
|
if field_info := allow_type._check_param(param, allow_types):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Unknown parameter {param.name} for function {call} with type {param.annotation}"
|
||||||
|
)
|
||||||
|
|
||||||
|
default_value = field_info.default
|
||||||
|
|
||||||
annotation: Any = Any
|
annotation: Any = Any
|
||||||
required = default_value == Required
|
required = default_value == Required
|
||||||
if param.annotation != param.empty:
|
if param.annotation != param.empty:
|
||||||
annotation = param.annotation
|
annotation = param.annotation
|
||||||
annotation = get_annotation_from_field_info(
|
annotation = get_annotation_from_field_info(
|
||||||
annotation, field_info, param_name
|
annotation, field_info, param.name
|
||||||
)
|
)
|
||||||
dependent.params.append(
|
|
||||||
|
fields.append(
|
||||||
ModelField(
|
ModelField(
|
||||||
name=param_name,
|
name=param.name,
|
||||||
type_=annotation,
|
type_=annotation,
|
||||||
class_validators=None,
|
class_validators=None,
|
||||||
model_config=CustomConfig,
|
model_config=CustomConfig,
|
||||||
@@ -165,49 +154,72 @@ class Dependent(Generic[R]):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
parameterless_params = [
|
return tuple(fields)
|
||||||
dependent.parse_parameterless(param) for param in (parameterless or [])
|
|
||||||
]
|
|
||||||
dependent.parameterless.extend(parameterless_params)
|
|
||||||
|
|
||||||
logger.trace(
|
@staticmethod
|
||||||
f"Parsed dependent with call={call}, "
|
def parse_parameterless(
|
||||||
f"params={[param.field_info for param in dependent.params]}, "
|
parameterless: Tuple[Any, ...], allow_types: Tuple[Type[Param], ...]
|
||||||
f"parameterless={dependent.parameterless}"
|
) -> Tuple[Param, ...]:
|
||||||
|
parameterless_params: List[Param] = []
|
||||||
|
for value in parameterless:
|
||||||
|
for allow_type in allow_types:
|
||||||
|
if param := allow_type._check_parameterless(value, allow_types):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown parameterless {value}")
|
||||||
|
parameterless_params.append(param)
|
||||||
|
return tuple(parameterless_params)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(
|
||||||
|
cls,
|
||||||
|
*,
|
||||||
|
call: _DependentCallable[R],
|
||||||
|
parameterless: Optional[Iterable[Any]] = None,
|
||||||
|
allow_types: Iterable[Type[Param]],
|
||||||
|
) -> "Dependent[R]":
|
||||||
|
allow_types = tuple(allow_types)
|
||||||
|
|
||||||
|
params = cls.parse_params(call, allow_types)
|
||||||
|
parameterless_params = (
|
||||||
|
tuple()
|
||||||
|
if parameterless is None
|
||||||
|
else cls.parse_parameterless(tuple(parameterless), allow_types)
|
||||||
)
|
)
|
||||||
|
|
||||||
return dependent
|
return cls(call, params, parameterless_params)
|
||||||
|
|
||||||
async def solve(
|
async def check(self, **params: Any) -> None:
|
||||||
self,
|
try:
|
||||||
**params: Any,
|
await asyncio.gather(
|
||||||
) -> Dict[str, Any]:
|
*(param._check(**params) for param in self.parameterless)
|
||||||
values: Dict[str, Any] = {}
|
)
|
||||||
|
await asyncio.gather(
|
||||||
|
*(
|
||||||
|
cast(Param, param.field_info)._check(**params)
|
||||||
|
for param in self.params
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except SkippedException as e:
|
||||||
|
logger.trace(f"{self} skipped due to {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
for checker in self.pre_checkers:
|
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
||||||
await checker._solve(**params)
|
value = await cast(Param, field.field_info)._solve(**params)
|
||||||
|
if value is Undefined:
|
||||||
|
value = field.get_default()
|
||||||
|
return check_field_type(field, value)
|
||||||
|
|
||||||
|
async def solve(self, **params: Any) -> Dict[str, Any]:
|
||||||
|
# solve parameterless
|
||||||
for param in self.parameterless:
|
for param in self.parameterless:
|
||||||
await param._solve(**params)
|
await param._solve(**params)
|
||||||
|
|
||||||
for field in self.params:
|
# solve param values
|
||||||
field_info = field.field_info
|
values = await asyncio.gather(
|
||||||
assert isinstance(field_info, Param), "Params must be subclasses of Param"
|
*(self._solve_field(field, params) for field in self.params)
|
||||||
value = await field_info._solve(**params)
|
)
|
||||||
if value is Undefined:
|
return {field.name: value for field, value in zip(self.params, values)}
|
||||||
value = field.get_default()
|
|
||||||
|
|
||||||
try:
|
|
||||||
values[field.name] = check_field_type(field, value)
|
|
||||||
except TypeMisMatch:
|
|
||||||
logger.debug(
|
|
||||||
f"{field_info} "
|
|
||||||
f"type {type(value)} not match depends {self.call} "
|
|
||||||
f"annotation {field._type_display()}, ignored"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {"CustomConfig": False}
|
__autodoc__ = {"CustomConfig": False}
|
||||||
|
@@ -4,11 +4,11 @@ FrontMatter:
|
|||||||
description: nonebot.dependencies.utils 模块
|
description: nonebot.dependencies.utils 模块
|
||||||
"""
|
"""
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, Dict, TypeVar, Callable
|
from typing import Any, Dict, TypeVar, Callable, ForwardRef
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic.fields import ModelField
|
from pydantic.fields import ModelField
|
||||||
from pydantic.typing import ForwardRef, evaluate_forwardref
|
from pydantic.typing import evaluate_forwardref
|
||||||
|
|
||||||
from nonebot.exception import TypeMisMatch
|
from nonebot.exception import TypeMisMatch
|
||||||
|
|
||||||
@@ -28,8 +28,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
|||||||
)
|
)
|
||||||
for param in signature.parameters.values()
|
for param in signature.parameters.values()
|
||||||
]
|
]
|
||||||
typed_signature = inspect.Signature(typed_params)
|
return inspect.Signature(typed_params)
|
||||||
return typed_signature
|
|
||||||
|
|
||||||
|
|
||||||
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import signal
|
import signal
|
||||||
import asyncio
|
import asyncio
|
||||||
import threading
|
import threading
|
||||||
from typing import Set, Union, Callable, Awaitable
|
from typing import Set, Union, Callable, Awaitable, cast
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.drivers import Driver
|
from nonebot.drivers import Driver
|
||||||
@@ -9,8 +9,7 @@ from nonebot.typing import overrides
|
|||||||
from nonebot.config import Env, Config
|
from nonebot.config import Env, Config
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
from nonebot.utils import run_sync, is_coroutine_callable
|
||||||
|
|
||||||
STARTUP_FUNC = Callable[[], Union[None, Awaitable[None]]]
|
HOOK_FUNC = Union[Callable[[], None], Callable[[], Awaitable[None]]]
|
||||||
SHUTDOWN_FUNC = Callable[[], Union[None, Awaitable[None]]]
|
|
||||||
HANDLED_SIGNALS = (
|
HANDLED_SIGNALS = (
|
||||||
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
||||||
signal.SIGTERM, # Unix signal 15. Sent by `kill <pid>`.
|
signal.SIGTERM, # Unix signal 15. Sent by `kill <pid>`.
|
||||||
@@ -20,8 +19,8 @@ HANDLED_SIGNALS = (
|
|||||||
class BlockDriver(Driver):
|
class BlockDriver(Driver):
|
||||||
def __init__(self, env: Env, config: Config):
|
def __init__(self, env: Env, config: Config):
|
||||||
super().__init__(env, config)
|
super().__init__(env, config)
|
||||||
self.startup_funcs: Set[STARTUP_FUNC] = set()
|
self.startup_funcs: Set[HOOK_FUNC] = set()
|
||||||
self.shutdown_funcs: Set[SHUTDOWN_FUNC] = set()
|
self.shutdown_funcs: Set[HOOK_FUNC] = set()
|
||||||
self.should_exit: asyncio.Event = asyncio.Event()
|
self.should_exit: asyncio.Event = asyncio.Event()
|
||||||
self.force_exit: bool = False
|
self.force_exit: bool = False
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ class BlockDriver(Driver):
|
|||||||
return logger
|
return logger
|
||||||
|
|
||||||
@overrides(Driver)
|
@overrides(Driver)
|
||||||
def on_startup(self, func: STARTUP_FUNC) -> STARTUP_FUNC:
|
def on_startup(self, func: HOOK_FUNC) -> HOOK_FUNC:
|
||||||
"""
|
"""
|
||||||
注册一个启动时执行的函数
|
注册一个启动时执行的函数
|
||||||
"""
|
"""
|
||||||
@@ -46,7 +45,7 @@ class BlockDriver(Driver):
|
|||||||
return func
|
return func
|
||||||
|
|
||||||
@overrides(Driver)
|
@overrides(Driver)
|
||||||
def on_shutdown(self, func: SHUTDOWN_FUNC) -> SHUTDOWN_FUNC:
|
def on_shutdown(self, func: HOOK_FUNC) -> HOOK_FUNC:
|
||||||
"""
|
"""
|
||||||
注册一个停止时执行的函数
|
注册一个停止时执行的函数
|
||||||
"""
|
"""
|
||||||
@@ -71,7 +70,9 @@ class BlockDriver(Driver):
|
|||||||
async def startup(self):
|
async def startup(self):
|
||||||
# run startup
|
# run startup
|
||||||
cors = [
|
cors = [
|
||||||
startup() if is_coroutine_callable(startup) else run_sync(startup)()
|
cast(Callable[..., Awaitable[None]], startup)()
|
||||||
|
if is_coroutine_callable(startup)
|
||||||
|
else run_sync(startup)()
|
||||||
for startup in self.startup_funcs
|
for startup in self.startup_funcs
|
||||||
]
|
]
|
||||||
if cors:
|
if cors:
|
||||||
@@ -94,7 +95,9 @@ class BlockDriver(Driver):
|
|||||||
logger.info("Waiting for application shutdown.")
|
logger.info("Waiting for application shutdown.")
|
||||||
# run shutdown
|
# run shutdown
|
||||||
cors = [
|
cors = [
|
||||||
shutdown() if is_coroutine_callable(shutdown) else run_sync(shutdown)()
|
cast(Callable[..., Awaitable[None]], shutdown)()
|
||||||
|
if is_coroutine_callable(shutdown)
|
||||||
|
else run_sync(shutdown)()
|
||||||
for shutdown in self.shutdown_funcs
|
for shutdown in self.shutdown_funcs
|
||||||
]
|
]
|
||||||
if cors:
|
if cors:
|
||||||
|
@@ -27,7 +27,7 @@ from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_dr
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import aiohttp
|
import aiohttp
|
||||||
except ImportError:
|
except ImportError: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`"
|
"Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`"
|
||||||
) from None
|
) from None
|
||||||
|
@@ -9,7 +9,9 @@ FrontMatter:
|
|||||||
description: nonebot.drivers.fastapi 模块
|
description: nonebot.drivers.fastapi 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import contextlib
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Any, List, Tuple, Union, Callable, Optional
|
from typing import Any, List, Tuple, Union, Callable, Optional
|
||||||
|
|
||||||
@@ -186,14 +188,12 @@ class Driver(ReverseDriver):
|
|||||||
setup: HTTPServerSetup,
|
setup: HTTPServerSetup,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
json: Any = None
|
json: Any = None
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
json = await request.json()
|
json = await request.json()
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
data: Optional[dict] = None
|
data: Optional[dict] = None
|
||||||
files: Optional[List[Tuple[str, FileTypes]]] = None
|
files: Optional[List[Tuple[str, FileTypes]]] = None
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
form = await request.form()
|
form = await request.form()
|
||||||
data = {}
|
data = {}
|
||||||
files = []
|
files = []
|
||||||
@@ -204,8 +204,7 @@ class Driver(ReverseDriver):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data[key] = value
|
data[key] = value
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
http_request = BaseRequest(
|
http_request = BaseRequest(
|
||||||
request.method,
|
request.method,
|
||||||
str(request.url),
|
str(request.url),
|
||||||
@@ -219,7 +218,9 @@ class Driver(ReverseDriver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = await setup.handle_func(http_request)
|
response = await setup.handle_func(http_request)
|
||||||
return Response(response.content, response.status_code, dict(response.headers))
|
return Response(
|
||||||
|
response.content, response.status_code, dict(response.headers.items())
|
||||||
|
)
|
||||||
|
|
||||||
async def _handle_ws(self, websocket: WebSocket, setup: WebSocketServerSetup):
|
async def _handle_ws(self, websocket: WebSocket, setup: WebSocketServerSetup):
|
||||||
request = BaseRequest(
|
request = BaseRequest(
|
||||||
@@ -261,7 +262,7 @@ class FastAPIWebSocket(BaseWebSocket):
|
|||||||
async def close(
|
async def close(
|
||||||
self, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = ""
|
self, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = ""
|
||||||
) -> None:
|
) -> None:
|
||||||
await self.websocket.close(code)
|
await self.websocket.close(code, reason)
|
||||||
|
|
||||||
@overrides(BaseWebSocket)
|
@overrides(BaseWebSocket)
|
||||||
async def receive(self) -> Union[str, bytes]:
|
async def receive(self) -> Union[str, bytes]:
|
||||||
|
@@ -31,7 +31,7 @@ from nonebot.drivers import (
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import httpx
|
import httpx
|
||||||
except ImportError:
|
except ImportError: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install httpx by using `pip install nonebot2[httpx]`"
|
"Please install httpx by using `pip install nonebot2[httpx]`"
|
||||||
) from None
|
) from None
|
||||||
|
@@ -37,7 +37,7 @@ try:
|
|||||||
from quart import Quart, Request, Response
|
from quart import Quart, Request, Response
|
||||||
from quart.datastructures import FileStorage
|
from quart.datastructures import FileStorage
|
||||||
from quart import Websocket as QuartWebSocket
|
from quart import Websocket as QuartWebSocket
|
||||||
except ImportError:
|
except ImportError: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install Quart by using `pip install nonebot2[quart]`"
|
"Please install Quart by using `pip install nonebot2[quart]`"
|
||||||
) from None
|
) from None
|
||||||
|
@@ -30,10 +30,10 @@ from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
|
|||||||
try:
|
try:
|
||||||
from websockets.exceptions import ConnectionClosed
|
from websockets.exceptions import ConnectionClosed
|
||||||
from websockets.legacy.client import Connect, WebSocketClientProtocol
|
from websockets.legacy.client import Connect, WebSocketClientProtocol
|
||||||
except ImportError:
|
except ImportError: # pragma: no cover
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Please install websockets by using `pip install nonebot2[websockets]`"
|
"Please install websockets by using `pip install nonebot2[websockets]`"
|
||||||
)
|
) from None
|
||||||
|
|
||||||
logger = logging.Logger("websockets.client", "INFO")
|
logger = logging.Logger("websockets.client", "INFO")
|
||||||
logger.addHandler(LoguruHandler())
|
logger.addHandler(LoguruHandler())
|
||||||
|
@@ -46,10 +46,14 @@ class ParserExit(NoneBotException):
|
|||||||
self.status = status
|
self.status = status
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<ParserExit status={self.status} message={self.message}>"
|
return (
|
||||||
|
f"ParserExit(status={self.status}"
|
||||||
|
+ (f", message={self.message!r}" if self.message else "")
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
@@ -68,10 +72,10 @@ class IgnoredException(ProcessException):
|
|||||||
def __init__(self, reason: Any):
|
def __init__(self, reason: Any):
|
||||||
self.reason: Any = reason
|
self.reason: Any = reason
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<IgnoredException, reason={self.reason}>"
|
return f"IgnoredException(reason={self.reason!r})"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
@@ -99,11 +103,14 @@ class TypeMisMatch(SkippedException):
|
|||||||
self.param: ModelField = param
|
self.param: ModelField = param
|
||||||
self.value: Any = value
|
self.value: Any = value
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<TypeMisMatch, param={self.param}, value={self.value}>"
|
return (
|
||||||
|
f"TypeMisMatch(param={self.param.name}, "
|
||||||
|
f"type={self.param._type_display()}, value={self.value!r}>"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
class MockApiException(ProcessException):
|
class MockApiException(ProcessException):
|
||||||
@@ -116,10 +123,10 @@ class MockApiException(ProcessException):
|
|||||||
def __init__(self, result: Any):
|
def __init__(self, result: Any):
|
||||||
self.result = result
|
self.result = result
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<ApiCancelledException, result={self.result}>"
|
return f"MockApiException(result={self.result!r})"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
@@ -195,7 +202,8 @@ class AdapterException(NoneBotException):
|
|||||||
adapter_name: 标识 adapter
|
adapter_name: 标识 adapter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, adapter_name: str) -> None:
|
def __init__(self, adapter_name: str, *args: object) -> None:
|
||||||
|
super().__init__(*args)
|
||||||
self.adapter_name: str = adapter_name
|
self.adapter_name: str = adapter_name
|
||||||
|
|
||||||
|
|
||||||
@@ -231,4 +239,11 @@ class WebSocketClosed(DriverException):
|
|||||||
self.reason = reason
|
self.reason = reason
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<WebSocketClosed code={self.code} reason={self.reason}>"
|
return (
|
||||||
|
f"WebSocketClosed(code={self.code}"
|
||||||
|
+ (f", reason={self.reason!r}" if self.reason else "")
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.__repr__()
|
||||||
|
@@ -33,6 +33,9 @@ class Adapter(abc.ABC):
|
|||||||
self.bots: Dict[str, Bot] = {}
|
self.bots: Dict[str, Bot] = {}
|
||||||
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
|
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Adapter(name={self.get_name()!r})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_name(cls) -> str:
|
def get_name(cls) -> str:
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import abc
|
import abc
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing_extensions import Protocol
|
from typing import TYPE_CHECKING, Any, Set, Union, Optional, Protocol
|
||||||
from typing import TYPE_CHECKING, Any, Set, Union, Optional
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.config import Config
|
from nonebot.config import Config
|
||||||
@@ -14,10 +13,9 @@ if TYPE_CHECKING:
|
|||||||
from .adapter import Adapter
|
from .adapter import Adapter
|
||||||
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):
|
||||||
@@ -41,7 +39,14 @@ class Bot(abc.ABC):
|
|||||||
self.self_id: str = self_id
|
self.self_id: str = self_id
|
||||||
"""机器人 ID"""
|
"""机器人 ID"""
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> _ApiCall:
|
def __repr__(self) -> str:
|
||||||
|
return f"Bot(type={self.type!r}, self_id={self.self_id!r})"
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> "_ApiCall":
|
||||||
|
if name.startswith("__") and name.endswith("__"):
|
||||||
|
raise AttributeError(
|
||||||
|
f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
||||||
|
)
|
||||||
return partial(self.call_api, name)
|
return partial(self.call_api, name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -72,8 +77,7 @@ class Bot(abc.ABC):
|
|||||||
skip_calling_api: bool = False
|
skip_calling_api: bool = False
|
||||||
exception: Optional[Exception] = None
|
exception: Optional[Exception] = None
|
||||||
|
|
||||||
coros = list(map(lambda x: x(self, api, data), self._calling_api_hook))
|
if coros := [hook(self, api, data) for hook in self._calling_api_hook]:
|
||||||
if coros:
|
|
||||||
try:
|
try:
|
||||||
logger.debug("Running CallingAPI hooks...")
|
logger.debug("Running CallingAPI hooks...")
|
||||||
await asyncio.gather(*coros)
|
await asyncio.gather(*coros)
|
||||||
@@ -95,10 +99,9 @@ class Bot(abc.ABC):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
exception = e
|
exception = e
|
||||||
|
|
||||||
coros = list(
|
if coros := [
|
||||||
map(lambda x: x(self, exception, api, data, result), self._called_api_hook)
|
hook(self, exception, api, data, result) for hook in self._called_api_hook
|
||||||
)
|
]:
|
||||||
if coros:
|
|
||||||
try:
|
try:
|
||||||
logger.debug("Running CalledAPI hooks...")
|
logger.debug("Running CalledAPI hooks...")
|
||||||
await asyncio.gather(*coros)
|
await asyncio.gather(*coros)
|
||||||
|
@@ -186,7 +186,7 @@ class Message(List[TMS], abc.ABC):
|
|||||||
elif isinstance(other, Iterable):
|
elif isinstance(other, Iterable):
|
||||||
self.extend(other)
|
self.extend(other)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported type: {type(other)}") # pragma: no cover
|
raise TypeError(f"Unsupported type {type(other)!r}")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
@@ -56,6 +56,9 @@ class MessageTemplate(Formatter, Generic[TF]):
|
|||||||
self.factory: Type[TF] = factory
|
self.factory: Type[TF] = factory
|
||||||
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
|
||||||
|
|
||||||
def add_format_spec(
|
def add_format_spec(
|
||||||
self, spec: FormatSpecFunc_T, name: Optional[str] = None
|
self, spec: FormatSpecFunc_T, name: Optional[str] = None
|
||||||
) -> FormatSpecFunc_T:
|
) -> FormatSpecFunc_T:
|
||||||
|
@@ -40,12 +40,18 @@ class Driver(abc.ABC):
|
|||||||
"""环境名称"""
|
"""环境名称"""
|
||||||
self.config: Config = config
|
self.config: Config = config
|
||||||
"""全局配置对象"""
|
"""全局配置对象"""
|
||||||
self._clients: Dict[str, "Bot"] = {}
|
self._bots: Dict[str, "Bot"] = {}
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"Driver(type={self.type!r}, "
|
||||||
|
f"adapters={len(self._adapters)}, bots={len(self._bots)})"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bots(self) -> Dict[str, "Bot"]:
|
def bots(self) -> Dict[str, "Bot"]:
|
||||||
"""获取当前所有已连接的 Bot"""
|
"""获取当前所有已连接的 Bot"""
|
||||||
return self._clients
|
return self._bots
|
||||||
|
|
||||||
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
|
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
|
||||||
"""注册一个协议适配器
|
"""注册一个协议适配器
|
||||||
@@ -124,9 +130,9 @@ class Driver(abc.ABC):
|
|||||||
|
|
||||||
def _bot_connect(self, bot: "Bot") -> None:
|
def _bot_connect(self, bot: "Bot") -> None:
|
||||||
"""在连接成功后,调用该函数来注册 bot 对象"""
|
"""在连接成功后,调用该函数来注册 bot 对象"""
|
||||||
if bot.self_id in self._clients:
|
if bot.self_id in self._bots:
|
||||||
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
||||||
self._clients[bot.self_id] = bot
|
self._bots[bot.self_id] = bot
|
||||||
|
|
||||||
async def _run_hook(bot: "Bot") -> None:
|
async def _run_hook(bot: "Bot") -> None:
|
||||||
coros = list(
|
coros = list(
|
||||||
@@ -148,8 +154,8 @@ class Driver(abc.ABC):
|
|||||||
|
|
||||||
def _bot_disconnect(self, bot: "Bot") -> None:
|
def _bot_disconnect(self, bot: "Bot") -> None:
|
||||||
"""在连接断开后,调用该函数来注销 bot 对象"""
|
"""在连接断开后,调用该函数来注销 bot 对象"""
|
||||||
if bot.self_id in self._clients:
|
if bot.self_id in self._bots:
|
||||||
del self._clients[bot.self_id]
|
del self._bots[bot.self_id]
|
||||||
|
|
||||||
async def _run_hook(bot: "Bot") -> None:
|
async def _run_hook(bot: "Bot") -> None:
|
||||||
coros = list(
|
coros = list(
|
||||||
@@ -233,13 +239,11 @@ def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Dr
|
|||||||
if not mixins:
|
if not mixins:
|
||||||
return driver
|
return driver
|
||||||
|
|
||||||
class CombinedDriver(*mixins, driver, ForwardDriver): # type: ignore
|
def type_(self: ForwardDriver) -> str:
|
||||||
@property
|
return (
|
||||||
def type(self) -> str:
|
driver.type.__get__(self)
|
||||||
return (
|
+ "+"
|
||||||
driver.type.__get__(self)
|
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
|
||||||
+ "+"
|
)
|
||||||
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
|
|
||||||
)
|
|
||||||
|
|
||||||
return CombinedDriver
|
return type("CombinedDriver", (*mixins, driver, ForwardDriver), {"type": property(type_)}) # type: ignore
|
||||||
|
@@ -131,9 +131,7 @@ class Request:
|
|||||||
self.files.append((name, file_info)) # type: ignore
|
self.files.append((name, file_info)) # type: ignore
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
class_name = self.__class__.__name__
|
return f"{self.__class__.__name__}(method={self.method!r}, url='{self.url!s}')"
|
||||||
url = str(self.url)
|
|
||||||
return f"<{class_name}({self.method!r}, {url!r})>"
|
|
||||||
|
|
||||||
|
|
||||||
class Response:
|
class Response:
|
||||||
@@ -161,12 +159,18 @@ class Response:
|
|||||||
# request
|
# request
|
||||||
self.request: Optional[Request] = request
|
self.request: Optional[Request] = request
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}(status_code={self.status_code!r})"
|
||||||
|
|
||||||
|
|
||||||
class WebSocket(abc.ABC):
|
class WebSocket(abc.ABC):
|
||||||
def __init__(self, *, request: Request):
|
def __init__(self, *, request: Request):
|
||||||
# request
|
# request
|
||||||
self.request: Request = request
|
self.request: Request = request
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}('{self.request.url!s}')"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def closed(self) -> bool:
|
def closed(self) -> bool:
|
||||||
@@ -320,17 +324,14 @@ class Cookies(MutableMapping):
|
|||||||
return len(self.jar)
|
return len(self.jar)
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[Cookie]:
|
def __iter__(self) -> Iterator[Cookie]:
|
||||||
return (cookie for cookie in self.jar)
|
return iter(self.jar)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
cookies_repr = ", ".join(
|
cookies_repr = ", ".join(
|
||||||
[
|
f"Cookie({cookie.name}={cookie.value} for {cookie.domain})"
|
||||||
f"<Cookie {cookie.name}={cookie.value} for {cookie.domain} />"
|
for cookie in self.jar
|
||||||
for cookie in self.jar
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
return f"{self.__class__.__name__}({cookies_repr})"
|
||||||
return f"<Cookies [{cookies_repr}]>"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
11
nonebot/internal/matcher/__init__.py
Normal file
11
nonebot/internal/matcher/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from .manager import MatcherManager as MatcherManager
|
||||||
|
from .provider import MatcherProvider as MatcherProvider
|
||||||
|
from .provider import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
|
||||||
|
|
||||||
|
matchers = MatcherManager()
|
||||||
|
|
||||||
|
from .matcher import Matcher as Matcher
|
||||||
|
from .matcher import current_bot as current_bot
|
||||||
|
from .matcher import current_event as current_event
|
||||||
|
from .matcher import current_handler as current_handler
|
||||||
|
from .matcher import current_matcher as current_matcher
|
104
nonebot/internal/matcher/manager.py
Normal file
104
nonebot/internal/matcher/manager.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
List,
|
||||||
|
Type,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
TypeVar,
|
||||||
|
Iterator,
|
||||||
|
KeysView,
|
||||||
|
Optional,
|
||||||
|
ItemsView,
|
||||||
|
ValuesView,
|
||||||
|
MutableMapping,
|
||||||
|
overload,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .provider import DEFAULT_PROVIDER_CLASS, MatcherProvider
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .matcher import Matcher
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
||||||
|
"""事件响应器管理器
|
||||||
|
|
||||||
|
实现了常用字典操作,用于管理事件响应器。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.provider: MatcherProvider = DEFAULT_PROVIDER_CLASS({})
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"MatcherManager(provider={self.provider!r})"
|
||||||
|
|
||||||
|
def __contains__(self, o: object) -> bool:
|
||||||
|
return o in self.provider
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[int]:
|
||||||
|
return iter(self.provider)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.provider)
|
||||||
|
|
||||||
|
def __getitem__(self, key: int) -> List[Type["Matcher"]]:
|
||||||
|
return self.provider[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: int, value: List[Type["Matcher"]]) -> None:
|
||||||
|
self.provider[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: int) -> None:
|
||||||
|
del self.provider[key]
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
return isinstance(other, MatcherManager) and self.provider == other.provider
|
||||||
|
|
||||||
|
def keys(self) -> KeysView[int]:
|
||||||
|
return self.provider.keys()
|
||||||
|
|
||||||
|
def values(self) -> ValuesView[List[Type["Matcher"]]]:
|
||||||
|
return self.provider.values()
|
||||||
|
|
||||||
|
def items(self) -> ItemsView[int, List[Type["Matcher"]]]:
|
||||||
|
return self.provider.items()
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get(self, key: int) -> Optional[List[Type["Matcher"]]]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get(self, key: int, default: T) -> Union[List[Type["Matcher"]], T]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def get(
|
||||||
|
self, key: int, default: Optional[T] = None
|
||||||
|
) -> Optional[Union[List[Type["Matcher"]], T]]:
|
||||||
|
return self.provider.get(key, default)
|
||||||
|
|
||||||
|
def pop(self, key: int) -> List[Type["Matcher"]]:
|
||||||
|
return self.provider.pop(key)
|
||||||
|
|
||||||
|
def popitem(self) -> Tuple[int, List[Type["Matcher"]]]:
|
||||||
|
return self.provider.popitem()
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
self.provider.clear()
|
||||||
|
|
||||||
|
def update(self, __m: MutableMapping[int, List[Type["Matcher"]]]) -> None:
|
||||||
|
self.provider.update(__m)
|
||||||
|
|
||||||
|
def setdefault(
|
||||||
|
self, key: int, default: List[Type["Matcher"]]
|
||||||
|
) -> List[Type["Matcher"]]:
|
||||||
|
return self.provider.setdefault(key, default)
|
||||||
|
|
||||||
|
def set_provider(self, provider_class: Type[MatcherProvider]) -> None:
|
||||||
|
"""设置事件响应器存储器
|
||||||
|
|
||||||
|
参数:
|
||||||
|
provider_class: 事件响应器存储器类
|
||||||
|
"""
|
||||||
|
self.provider = provider_class(self.provider)
|
@@ -1,23 +1,32 @@
|
|||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from collections import defaultdict
|
|
||||||
from contextlib import AsyncExitStack
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from contextlib import AsyncExitStack, contextmanager
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Dict,
|
|
||||||
List,
|
List,
|
||||||
Type,
|
Type,
|
||||||
Union,
|
Union,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Callable,
|
Callable,
|
||||||
|
Iterable,
|
||||||
NoReturn,
|
NoReturn,
|
||||||
Optional,
|
Optional,
|
||||||
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
|
from nonebot.internal.rule import Rule
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
|
from nonebot.internal.permission import USER, User, Permission
|
||||||
|
from nonebot.internal.adapter import (
|
||||||
|
Bot,
|
||||||
|
Event,
|
||||||
|
Message,
|
||||||
|
MessageSegment,
|
||||||
|
MessageTemplate,
|
||||||
|
)
|
||||||
from nonebot.consts import (
|
from nonebot.consts import (
|
||||||
ARG_KEY,
|
ARG_KEY,
|
||||||
RECEIVE_KEY,
|
RECEIVE_KEY,
|
||||||
@@ -34,18 +43,13 @@ from nonebot.typing import (
|
|||||||
T_PermissionUpdater,
|
T_PermissionUpdater,
|
||||||
)
|
)
|
||||||
from nonebot.exception import (
|
from nonebot.exception import (
|
||||||
TypeMisMatch,
|
|
||||||
PausedException,
|
PausedException,
|
||||||
StopPropagation,
|
StopPropagation,
|
||||||
SkippedException,
|
SkippedException,
|
||||||
FinishedException,
|
FinishedException,
|
||||||
RejectedException,
|
RejectedException,
|
||||||
)
|
)
|
||||||
|
from nonebot.internal.params import (
|
||||||
from .rule import Rule
|
|
||||||
from .permission import USER, Permission
|
|
||||||
from .adapter import Bot, Event, Message, MessageSegment, MessageTemplate
|
|
||||||
from .params import (
|
|
||||||
Depends,
|
Depends,
|
||||||
ArgParam,
|
ArgParam,
|
||||||
BotParam,
|
BotParam,
|
||||||
@@ -56,13 +60,13 @@ from .params import (
|
|||||||
MatcherParam,
|
MatcherParam,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from . import matchers
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from nonebot.plugin import Plugin
|
from nonebot.plugin import Plugin
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
|
|
||||||
"""用于存储当前所有的事件响应器"""
|
|
||||||
current_bot: ContextVar[Bot] = ContextVar("current_bot")
|
current_bot: ContextVar[Bot] = ContextVar("current_bot")
|
||||||
current_event: ContextVar[Event] = ContextVar("current_event")
|
current_event: ContextVar[Event] = ContextVar("current_event")
|
||||||
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
||||||
@@ -71,29 +75,16 @@ current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
|||||||
|
|
||||||
class MatcherMeta(type):
|
class MatcherMeta(type):
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
module: Optional[str]
|
|
||||||
plugin_name: Optional[str]
|
|
||||||
module_name: Optional[str]
|
module_name: Optional[str]
|
||||||
module_prefix: Optional[str]
|
|
||||||
type: str
|
type: str
|
||||||
rule: Rule
|
|
||||||
permission: Permission
|
|
||||||
handlers: List[T_Handler]
|
|
||||||
priority: int
|
|
||||||
block: bool
|
|
||||||
temp: bool
|
|
||||||
expire_time: Optional[datetime]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Matcher from {self.module_name or 'unknown'}, "
|
f"Matcher(type={self.type!r}"
|
||||||
f"type={self.type}, priority={self.priority}, "
|
+ (f", module={self.module_name}" if self.module_name else "")
|
||||||
f"temp={self.temp}>"
|
+ ")"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return repr(self)
|
|
||||||
|
|
||||||
|
|
||||||
class Matcher(metaclass=MatcherMeta):
|
class Matcher(metaclass=MatcherMeta):
|
||||||
"""事件响应器类"""
|
"""事件响应器类"""
|
||||||
@@ -132,7 +123,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
_default_permission_updater: Optional[Dependent[Permission]] = None
|
_default_permission_updater: Optional[Dependent[Permission]] = None
|
||||||
"""事件响应器权限更新函数"""
|
"""事件响应器权限更新函数"""
|
||||||
|
|
||||||
HANDLER_PARAM_TYPES = [
|
HANDLER_PARAM_TYPES = (
|
||||||
DependParam,
|
DependParam,
|
||||||
BotParam,
|
BotParam,
|
||||||
EventParam,
|
EventParam,
|
||||||
@@ -140,7 +131,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
ArgParam,
|
ArgParam,
|
||||||
MatcherParam,
|
MatcherParam,
|
||||||
DefaultParam,
|
DefaultParam,
|
||||||
]
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.handlers = self.handlers.copy()
|
self.handlers = self.handlers.copy()
|
||||||
@@ -148,13 +139,11 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
|
f"Matcher(type={self.type!r}"
|
||||||
f"priority={self.priority}, temp={self.temp}>"
|
+ (f", module={self.module_name}" if self.module_name else "")
|
||||||
|
+ ")"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return repr(self)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(
|
def new(
|
||||||
cls,
|
cls,
|
||||||
@@ -218,27 +207,35 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
"temp": temp,
|
"temp": temp,
|
||||||
"expire_time": (
|
"expire_time": (
|
||||||
expire_time
|
expire_time
|
||||||
if isinstance(expire_time, datetime)
|
and (
|
||||||
else expire_time and datetime.now() + expire_time
|
expire_time
|
||||||
|
if isinstance(expire_time, datetime)
|
||||||
|
else datetime.now() + expire_time
|
||||||
|
)
|
||||||
),
|
),
|
||||||
"priority": priority,
|
"priority": priority,
|
||||||
"block": block,
|
"block": block,
|
||||||
"_default_state": default_state or {},
|
"_default_state": default_state or {},
|
||||||
"_default_type_updater": (
|
"_default_type_updater": (
|
||||||
default_type_updater
|
default_type_updater
|
||||||
if isinstance(default_type_updater, Dependent)
|
and (
|
||||||
else default_type_updater
|
default_type_updater
|
||||||
and Dependent[str].parse(
|
if isinstance(default_type_updater, Dependent)
|
||||||
call=default_type_updater, allow_types=cls.HANDLER_PARAM_TYPES
|
else Dependent[str].parse(
|
||||||
|
call=default_type_updater,
|
||||||
|
allow_types=cls.HANDLER_PARAM_TYPES,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"_default_permission_updater": (
|
"_default_permission_updater": (
|
||||||
default_permission_updater
|
default_permission_updater
|
||||||
if isinstance(default_permission_updater, Dependent)
|
and (
|
||||||
else default_permission_updater
|
default_permission_updater
|
||||||
and Dependent[Permission].parse(
|
if isinstance(default_permission_updater, Dependent)
|
||||||
call=default_permission_updater,
|
else Dependent[Permission].parse(
|
||||||
allow_types=cls.HANDLER_PARAM_TYPES,
|
call=default_permission_updater,
|
||||||
|
allow_types=cls.HANDLER_PARAM_TYPES,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -326,7 +323,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def append_handler(
|
def append_handler(
|
||||||
cls, handler: T_Handler, parameterless: Optional[List[Any]] = None
|
cls, handler: T_Handler, parameterless: Optional[Iterable[Any]] = None
|
||||||
) -> Dependent[Any]:
|
) -> Dependent[Any]:
|
||||||
handler_ = Dependent[Any].parse(
|
handler_ = Dependent[Any].parse(
|
||||||
call=handler,
|
call=handler,
|
||||||
@@ -338,7 +335,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def handle(
|
def handle(
|
||||||
cls, parameterless: Optional[List[Any]] = None
|
cls, parameterless: Optional[Iterable[Any]] = None
|
||||||
) -> Callable[[T_Handler], T_Handler]:
|
) -> Callable[[T_Handler], T_Handler]:
|
||||||
"""装饰一个函数来向事件响应器直接添加一个处理函数
|
"""装饰一个函数来向事件响应器直接添加一个处理函数
|
||||||
|
|
||||||
@@ -354,7 +351,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def receive(
|
def receive(
|
||||||
cls, id: str = "", parameterless: Optional[List[Any]] = None
|
cls, id: str = "", parameterless: Optional[Iterable[Any]] = None
|
||||||
) -> Callable[[T_Handler], T_Handler]:
|
) -> Callable[[T_Handler], T_Handler]:
|
||||||
"""装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
|
"""装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
|
||||||
|
|
||||||
@@ -372,14 +369,21 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
return
|
return
|
||||||
await matcher.reject()
|
await matcher.reject()
|
||||||
|
|
||||||
_parameterless = [Depends(_receive), *(parameterless or [])]
|
_parameterless = (Depends(_receive), *(parameterless or tuple()))
|
||||||
|
|
||||||
def _decorator(func: T_Handler) -> T_Handler:
|
def _decorator(func: T_Handler) -> T_Handler:
|
||||||
|
|
||||||
if cls.handlers and cls.handlers[-1].call is func:
|
if cls.handlers and cls.handlers[-1].call is func:
|
||||||
func_handler = cls.handlers[-1]
|
func_handler = cls.handlers[-1]
|
||||||
for depend in reversed(_parameterless):
|
new_handler = Dependent(
|
||||||
func_handler.prepend_parameterless(depend)
|
call=func_handler.call,
|
||||||
|
params=func_handler.params,
|
||||||
|
parameterless=Dependent.parse_parameterless(
|
||||||
|
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
|
||||||
|
)
|
||||||
|
+ func_handler.parameterless,
|
||||||
|
)
|
||||||
|
cls.handlers[-1] = new_handler
|
||||||
else:
|
else:
|
||||||
cls.append_handler(func, parameterless=_parameterless)
|
cls.append_handler(func, parameterless=_parameterless)
|
||||||
|
|
||||||
@@ -392,7 +396,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
cls,
|
cls,
|
||||||
key: str,
|
key: str,
|
||||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||||
parameterless: Optional[List[Any]] = None,
|
parameterless: Optional[Iterable[Any]] = None,
|
||||||
) -> Callable[[T_Handler], T_Handler]:
|
) -> Callable[[T_Handler], T_Handler]:
|
||||||
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
||||||
|
|
||||||
@@ -413,17 +417,21 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
return
|
return
|
||||||
await matcher.reject(prompt)
|
await matcher.reject(prompt)
|
||||||
|
|
||||||
_parameterless = [
|
_parameterless = (Depends(_key_getter), *(parameterless or tuple()))
|
||||||
Depends(_key_getter),
|
|
||||||
*(parameterless or []),
|
|
||||||
]
|
|
||||||
|
|
||||||
def _decorator(func: T_Handler) -> T_Handler:
|
def _decorator(func: T_Handler) -> T_Handler:
|
||||||
|
|
||||||
if cls.handlers and cls.handlers[-1].call is func:
|
if cls.handlers and cls.handlers[-1].call is func:
|
||||||
func_handler = cls.handlers[-1]
|
func_handler = cls.handlers[-1]
|
||||||
for depend in reversed(_parameterless):
|
new_handler = Dependent(
|
||||||
func_handler.prepend_parameterless(depend)
|
call=func_handler.call,
|
||||||
|
params=func_handler.params,
|
||||||
|
parameterless=Dependent.parse_parameterless(
|
||||||
|
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
|
||||||
|
)
|
||||||
|
+ func_handler.parameterless,
|
||||||
|
)
|
||||||
|
cls.handlers[-1] = new_handler
|
||||||
else:
|
else:
|
||||||
cls.append_handler(func, parameterless=_parameterless)
|
cls.append_handler(func, parameterless=_parameterless)
|
||||||
|
|
||||||
@@ -551,7 +559,17 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
"""
|
"""
|
||||||
raise SkippedException
|
raise SkippedException
|
||||||
|
|
||||||
def get_receive(self, id: str, default: T = None) -> Union[Event, T]:
|
@overload
|
||||||
|
def get_receive(self, id: str) -> Union[Event, None]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get_receive(self, id: str, default: T) -> Union[Event, T]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_receive(
|
||||||
|
self, id: str, default: Optional[T] = None
|
||||||
|
) -> Optional[Union[Event, T]]:
|
||||||
"""获取一个 `receive` 事件
|
"""获取一个 `receive` 事件
|
||||||
|
|
||||||
如果没有找到对应的事件,返回 `default` 值
|
如果没有找到对应的事件,返回 `default` 值
|
||||||
@@ -563,14 +581,34 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
self.state[RECEIVE_KEY.format(id=id)] = event
|
self.state[RECEIVE_KEY.format(id=id)] = event
|
||||||
self.state[LAST_RECEIVE_KEY] = event
|
self.state[LAST_RECEIVE_KEY] = event
|
||||||
|
|
||||||
def get_last_receive(self, default: T = None) -> Union[Event, T]:
|
@overload
|
||||||
|
def get_last_receive(self) -> Union[Event, None]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get_last_receive(self, default: T) -> Union[Event, T]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_last_receive(
|
||||||
|
self, default: Optional[T] = None
|
||||||
|
) -> Optional[Union[Event, T]]:
|
||||||
"""获取最近一次 `receive` 事件
|
"""获取最近一次 `receive` 事件
|
||||||
|
|
||||||
如果没有事件,返回 `default` 值
|
如果没有事件,返回 `default` 值
|
||||||
"""
|
"""
|
||||||
return self.state.get(LAST_RECEIVE_KEY, default)
|
return self.state.get(LAST_RECEIVE_KEY, default)
|
||||||
|
|
||||||
def get_arg(self, key: str, default: T = None) -> Union[Message, T]:
|
@overload
|
||||||
|
def get_arg(self, key: str) -> Union[Message, None]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get_arg(self, key: str, default: T) -> Union[Message, T]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_arg(
|
||||||
|
self, key: str, default: Optional[T] = None
|
||||||
|
) -> Optional[Union[Message, T]]:
|
||||||
"""获取一个 `got` 消息
|
"""获取一个 `got` 消息
|
||||||
|
|
||||||
如果没有找到对应的消息,返回 `default` 值
|
如果没有找到对应的消息,返回 `default` 值
|
||||||
@@ -587,7 +625,15 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
else:
|
else:
|
||||||
self.state[REJECT_TARGET] = target
|
self.state[REJECT_TARGET] = target
|
||||||
|
|
||||||
def get_target(self, default: T = None) -> Union[str, T]:
|
@overload
|
||||||
|
def get_target(self) -> Union[str, None]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def get_target(self, default: T) -> 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)
|
||||||
|
|
||||||
def stop_propagation(self):
|
def stop_propagation(self):
|
||||||
@@ -596,15 +642,21 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
|
|
||||||
async def update_type(self, bot: Bot, event: Event) -> str:
|
async def update_type(self, bot: Bot, event: Event) -> str:
|
||||||
updater = self.__class__._default_type_updater
|
updater = self.__class__._default_type_updater
|
||||||
if not updater:
|
return (
|
||||||
return "message"
|
await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
if updater
|
||||||
|
else "message"
|
||||||
|
)
|
||||||
|
|
||||||
async def update_permission(self, bot: Bot, event: Event) -> Permission:
|
async def update_permission(self, bot: Bot, event: Event) -> Permission:
|
||||||
updater = self.__class__._default_permission_updater
|
if updater := self.__class__._default_permission_updater:
|
||||||
if not updater:
|
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||||
return USER(event.get_session_id(), perm=self.permission)
|
permission = self.permission
|
||||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
if len(permission.checkers) == 1 and isinstance(
|
||||||
|
user_perm := tuple(permission.checkers)[0].call, User
|
||||||
|
):
|
||||||
|
permission = user_perm.perm
|
||||||
|
return USER(event.get_session_id(), perm=permission)
|
||||||
|
|
||||||
async def resolve_reject(self):
|
async def resolve_reject(self):
|
||||||
handler = current_handler.get()
|
handler = current_handler.get()
|
||||||
@@ -612,6 +664,18 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
if REJECT_CACHE_TARGET in self.state:
|
if REJECT_CACHE_TARGET in self.state:
|
||||||
self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET]
|
self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET]
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def ensure_context(self, bot: Bot, event: Event):
|
||||||
|
b_t = current_bot.set(bot)
|
||||||
|
e_t = current_event.set(event)
|
||||||
|
m_t = current_matcher.set(self)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
current_bot.reset(b_t)
|
||||||
|
current_event.reset(e_t)
|
||||||
|
current_matcher.reset(m_t)
|
||||||
|
|
||||||
async def simple_run(
|
async def simple_run(
|
||||||
self,
|
self,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
@@ -621,43 +685,34 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
dependency_cache: Optional[T_DependencyCache] = None,
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
):
|
):
|
||||||
logger.trace(
|
logger.trace(
|
||||||
f"Matcher {self} run with incoming args: "
|
f"{self} run with incoming args: "
|
||||||
f"bot={bot}, event={event}, state={state}"
|
f"bot={bot}, event={event!r}, state={state!r}"
|
||||||
)
|
)
|
||||||
b_t = current_bot.set(bot)
|
|
||||||
e_t = current_event.set(event)
|
|
||||||
m_t = current_matcher.set(self)
|
|
||||||
try:
|
|
||||||
# Refresh preprocess state
|
|
||||||
self.state.update(state)
|
|
||||||
|
|
||||||
while self.handlers:
|
with self.ensure_context(bot, event):
|
||||||
handler = self.handlers.pop(0)
|
try:
|
||||||
current_handler.set(handler)
|
# Refresh preprocess state
|
||||||
logger.debug(f"Running handler {handler}")
|
self.state.update(state)
|
||||||
try:
|
|
||||||
await handler(
|
while self.handlers:
|
||||||
matcher=self,
|
handler = self.handlers.pop(0)
|
||||||
bot=bot,
|
current_handler.set(handler)
|
||||||
event=event,
|
logger.debug(f"Running handler {handler}")
|
||||||
state=self.state,
|
try:
|
||||||
stack=stack,
|
await handler(
|
||||||
dependency_cache=dependency_cache,
|
matcher=self,
|
||||||
)
|
bot=bot,
|
||||||
except TypeMisMatch as e:
|
event=event,
|
||||||
logger.debug(
|
state=self.state,
|
||||||
f"Handler {handler} param {e.param.name} value {e.value} "
|
stack=stack,
|
||||||
f"mismatch type {e.param._type_display()}, skipped"
|
dependency_cache=dependency_cache,
|
||||||
)
|
)
|
||||||
except SkippedException as e:
|
except SkippedException:
|
||||||
logger.debug(f"Handler {handler} skipped")
|
logger.debug(f"Handler {handler} skipped")
|
||||||
except StopPropagation:
|
except StopPropagation:
|
||||||
self.block = True
|
self.block = True
|
||||||
finally:
|
finally:
|
||||||
logger.info(f"Matcher {self} running complete")
|
logger.info(f"{self} running complete")
|
||||||
current_bot.reset(b_t)
|
|
||||||
current_event.reset(e_t)
|
|
||||||
current_matcher.reset(m_t)
|
|
||||||
|
|
||||||
# 运行handlers
|
# 运行handlers
|
||||||
async def run(
|
async def run(
|
||||||
@@ -712,14 +767,3 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
)
|
)
|
||||||
except FinishedException:
|
except FinishedException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {
|
|
||||||
"MatcherMeta": False,
|
|
||||||
"Matcher.get_target": False,
|
|
||||||
"Matcher.set_target": False,
|
|
||||||
"Matcher.update_type": False,
|
|
||||||
"Matcher.update_permission": False,
|
|
||||||
"Matcher.resolve_reject": False,
|
|
||||||
"Matcher.simple_run": False,
|
|
||||||
}
|
|
27
nonebot/internal/matcher/provider.py
Normal file
27
nonebot/internal/matcher/provider.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import abc
|
||||||
|
from collections import defaultdict
|
||||||
|
from typing import TYPE_CHECKING, List, Type, Mapping, MutableMapping
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .matcher import Matcher
|
||||||
|
|
||||||
|
|
||||||
|
class MatcherProvider(abc.ABC, MutableMapping[int, List[Type["Matcher"]]]):
|
||||||
|
"""事件响应器存储器基类
|
||||||
|
|
||||||
|
参数:
|
||||||
|
matchers: 当前存储器中已有的事件响应器
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __init__(self, matchers: Mapping[int, List[Type["Matcher"]]]):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class _DictProvider(defaultdict, MatcherProvider):
|
||||||
|
def __init__(self, matchers: Mapping[int, List[Type["Matcher"]]]):
|
||||||
|
super().__init__(list, matchers)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PROVIDER_CLASS = _DictProvider
|
||||||
|
"""默认存储器类型"""
|
@@ -1,14 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
import warnings
|
|
||||||
from typing_extensions import Literal
|
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Optional, cast
|
|
||||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||||
|
from typing import TYPE_CHECKING, Any, Type, Tuple, Literal, Callable, Optional, cast
|
||||||
|
|
||||||
from pydantic.fields import Required, Undefined, ModelField
|
from pydantic.fields import Required, Undefined, ModelField
|
||||||
|
|
||||||
from nonebot.log import logger
|
|
||||||
from nonebot.exception import TypeMisMatch
|
|
||||||
from nonebot.dependencies.utils import check_field_type
|
from nonebot.dependencies.utils import check_field_type
|
||||||
from nonebot.dependencies import Param, Dependent, CustomConfig
|
from nonebot.dependencies import Param, Dependent, CustomConfig
|
||||||
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
||||||
@@ -40,7 +36,7 @@ class DependsInner:
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
dep = get_name(self.dependency)
|
dep = get_name(self.dependency)
|
||||||
cache = "" if self.use_cache else ", use_cache=False"
|
cache = "" if self.use_cache else ", use_cache=False"
|
||||||
return f"{self.__class__.__name__}({dep}{cache})"
|
return f"DependsInner({dep}{cache})"
|
||||||
|
|
||||||
|
|
||||||
def Depends(
|
def Depends(
|
||||||
@@ -75,12 +71,12 @@ def Depends(
|
|||||||
class DependParam(Param):
|
class DependParam(Param):
|
||||||
"""子依赖参数"""
|
"""子依赖参数"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Depends({self.extra['dependent']})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls,
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
dependent: Dependent,
|
|
||||||
name: str,
|
|
||||||
param: inspect.Parameter,
|
|
||||||
) -> Optional["DependParam"]:
|
) -> Optional["DependParam"]:
|
||||||
if isinstance(param.default, DependsInner):
|
if isinstance(param.default, DependsInner):
|
||||||
dependency: T_Handler
|
dependency: T_Handler
|
||||||
@@ -91,22 +87,20 @@ class DependParam(Param):
|
|||||||
dependency = param.default.dependency
|
dependency = param.default.dependency
|
||||||
sub_dependent = Dependent[Any].parse(
|
sub_dependent = Dependent[Any].parse(
|
||||||
call=dependency,
|
call=dependency,
|
||||||
allow_types=dependent.allow_types,
|
allow_types=allow_types,
|
||||||
)
|
)
|
||||||
dependent.pre_checkers.extend(sub_dependent.pre_checkers)
|
|
||||||
sub_dependent.pre_checkers.clear()
|
|
||||||
return cls(
|
return cls(
|
||||||
Required, use_cache=param.default.use_cache, dependent=sub_dependent
|
Required, use_cache=param.default.use_cache, dependent=sub_dependent
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_parameterless(
|
def _check_parameterless(
|
||||||
cls, dependent: "Dependent", value: Any
|
cls, value: Any, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["Param"]:
|
) -> Optional["Param"]:
|
||||||
if isinstance(value, DependsInner):
|
if isinstance(value, DependsInner):
|
||||||
assert value.dependency, "Dependency cannot be empty"
|
assert value.dependency, "Dependency cannot be empty"
|
||||||
dependent = Dependent[Any].parse(
|
dependent = Dependent[Any].parse(
|
||||||
call=value.dependency, allow_types=dependent.allow_types
|
call=value.dependency, allow_types=allow_types
|
||||||
)
|
)
|
||||||
return cls(Required, use_cache=value.use_cache, dependent=dependent)
|
return cls(Required, use_cache=value.use_cache, dependent=dependent)
|
||||||
|
|
||||||
@@ -120,8 +114,7 @@ class DependParam(Param):
|
|||||||
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.extra["dependent"]
|
||||||
sub_dependent.call = cast(Callable[..., Any], sub_dependent.call)
|
call = cast(Callable[..., Any], sub_dependent.call)
|
||||||
call = sub_dependent.call
|
|
||||||
|
|
||||||
# solve sub dependency with current cache
|
# solve sub dependency with current cache
|
||||||
sub_values = await sub_dependent.solve(
|
sub_values = await sub_dependent.solve(
|
||||||
@@ -133,7 +126,7 @@ class DependParam(Param):
|
|||||||
# run dependency function
|
# run dependency function
|
||||||
task: asyncio.Task[Any]
|
task: asyncio.Task[Any]
|
||||||
if use_cache and call in dependency_cache:
|
if use_cache and call in dependency_cache:
|
||||||
solved = await dependency_cache[call]
|
return await dependency_cache[call]
|
||||||
elif is_gen_callable(call) or is_async_gen_callable(call):
|
elif is_gen_callable(call) or is_async_gen_callable(call):
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
stack, AsyncExitStack
|
stack, AsyncExitStack
|
||||||
@@ -144,134 +137,124 @@ class DependParam(Param):
|
|||||||
cm = asynccontextmanager(call)(**sub_values)
|
cm = asynccontextmanager(call)(**sub_values)
|
||||||
task = asyncio.create_task(stack.enter_async_context(cm))
|
task = asyncio.create_task(stack.enter_async_context(cm))
|
||||||
dependency_cache[call] = task
|
dependency_cache[call] = task
|
||||||
solved = await task
|
return await task
|
||||||
elif is_coroutine_callable(call):
|
elif is_coroutine_callable(call):
|
||||||
task = asyncio.create_task(call(**sub_values))
|
task = asyncio.create_task(call(**sub_values))
|
||||||
dependency_cache[call] = task
|
dependency_cache[call] = task
|
||||||
solved = await task
|
return await task
|
||||||
else:
|
else:
|
||||||
task = asyncio.create_task(run_sync(call)(**sub_values))
|
task = asyncio.create_task(run_sync(call)(**sub_values))
|
||||||
dependency_cache[call] = task
|
dependency_cache[call] = task
|
||||||
solved = await task
|
return await task
|
||||||
|
|
||||||
return solved
|
async def _check(self, **kwargs: Any) -> None:
|
||||||
|
# run sub dependent pre-checkers
|
||||||
|
sub_dependent: Dependent = self.extra["dependent"]
|
||||||
class _BotChecker(Param):
|
await sub_dependent.check(**kwargs)
|
||||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
|
||||||
field: ModelField = self.extra["field"]
|
|
||||||
try:
|
|
||||||
return check_field_type(field, bot)
|
|
||||||
except TypeMisMatch:
|
|
||||||
logger.debug(
|
|
||||||
f"Bot type {type(bot)} not match "
|
|
||||||
f"annotation {field._type_display()}, ignored"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
class BotParam(Param):
|
class BotParam(Param):
|
||||||
"""{ref}`nonebot.adapters.Bot` 参数"""
|
"""{ref}`nonebot.adapters.Bot` 参数"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
"BotParam("
|
||||||
|
+ (
|
||||||
|
repr(cast(ModelField, checker).type_)
|
||||||
|
if (checker := self.extra.get("checker"))
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["BotParam"]:
|
) -> Optional["BotParam"]:
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
|
|
||||||
if param.default == param.empty:
|
if param.default == param.empty:
|
||||||
if generic_check_issubclass(param.annotation, Bot):
|
if generic_check_issubclass(param.annotation, Bot):
|
||||||
|
checker: Optional[ModelField] = None
|
||||||
if param.annotation is not Bot:
|
if param.annotation is not Bot:
|
||||||
dependent.pre_checkers.append(
|
checker = ModelField(
|
||||||
_BotChecker(
|
name=param.name,
|
||||||
Required,
|
type_=param.annotation,
|
||||||
field=ModelField(
|
class_validators=None,
|
||||||
name=name,
|
model_config=CustomConfig,
|
||||||
type_=param.annotation,
|
default=None,
|
||||||
class_validators=None,
|
required=True,
|
||||||
model_config=CustomConfig,
|
|
||||||
default=None,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return cls(Required)
|
return cls(Required, checker=checker)
|
||||||
elif param.annotation == param.empty and name == "bot":
|
elif param.annotation == param.empty and param.name == "bot":
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||||
return bot
|
return bot
|
||||||
|
|
||||||
|
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
||||||
class _EventChecker(Param):
|
if checker := self.extra.get("checker"):
|
||||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
check_field_type(checker, bot)
|
||||||
field: ModelField = self.extra["field"]
|
|
||||||
try:
|
|
||||||
return check_field_type(field, event)
|
|
||||||
except TypeMisMatch:
|
|
||||||
logger.debug(
|
|
||||||
f"Event type {type(event)} not match "
|
|
||||||
f"annotation {field._type_display()}, ignored"
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
class EventParam(Param):
|
class EventParam(Param):
|
||||||
"""{ref}`nonebot.adapters.Event` 参数"""
|
"""{ref}`nonebot.adapters.Event` 参数"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
"EventParam("
|
||||||
|
+ (
|
||||||
|
repr(cast(ModelField, checker).type_)
|
||||||
|
if (checker := self.extra.get("checker"))
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["EventParam"]:
|
) -> Optional["EventParam"]:
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Event
|
||||||
|
|
||||||
if param.default == param.empty:
|
if param.default == param.empty:
|
||||||
if generic_check_issubclass(param.annotation, Event):
|
if generic_check_issubclass(param.annotation, Event):
|
||||||
|
checker: Optional[ModelField] = None
|
||||||
if param.annotation is not Event:
|
if param.annotation is not Event:
|
||||||
dependent.pre_checkers.append(
|
checker = ModelField(
|
||||||
_EventChecker(
|
name=param.name,
|
||||||
Required,
|
type_=param.annotation,
|
||||||
field=ModelField(
|
class_validators=None,
|
||||||
name=name,
|
model_config=CustomConfig,
|
||||||
type_=param.annotation,
|
default=None,
|
||||||
class_validators=None,
|
required=True,
|
||||||
model_config=CustomConfig,
|
|
||||||
default=None,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return cls(Required)
|
return cls(Required, checker=checker)
|
||||||
elif param.annotation == param.empty and name == "event":
|
elif param.annotation == param.empty and param.name == "event":
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
||||||
class StateInner(T_State):
|
if checker := self.extra.get("checker", None):
|
||||||
...
|
check_field_type(checker, event)
|
||||||
|
|
||||||
|
|
||||||
def State() -> T_State:
|
|
||||||
"""**Deprecated**: 事件处理状态参数,请直接使用 {ref}`nonebot.typing.T_State`"""
|
|
||||||
warnings.warn("State() is deprecated, use `T_State` instead", DeprecationWarning)
|
|
||||||
return StateInner()
|
|
||||||
|
|
||||||
|
|
||||||
class StateParam(Param):
|
class StateParam(Param):
|
||||||
"""事件处理状态参数"""
|
"""事件处理状态参数"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "StateParam()"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["StateParam"]:
|
) -> Optional["StateParam"]:
|
||||||
if isinstance(param.default, StateInner):
|
if param.default == param.empty:
|
||||||
return cls(Required)
|
|
||||||
elif param.default == param.empty:
|
|
||||||
if param.annotation is T_State:
|
if param.annotation is T_State:
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
elif param.annotation == param.empty and name == "state":
|
elif param.annotation == param.empty and param.name == "state":
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||||
@@ -281,14 +264,17 @@ class StateParam(Param):
|
|||||||
class MatcherParam(Param):
|
class MatcherParam(Param):
|
||||||
"""事件响应器实例参数"""
|
"""事件响应器实例参数"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "MatcherParam()"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["MatcherParam"]:
|
) -> Optional["MatcherParam"]:
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
|
|
||||||
if generic_check_issubclass(param.annotation, Matcher) or (
|
if generic_check_issubclass(param.annotation, Matcher) or (
|
||||||
param.annotation == param.empty and name == "matcher"
|
param.annotation == param.empty and param.name == "matcher"
|
||||||
):
|
):
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
@@ -303,6 +289,9 @@ class ArgInner:
|
|||||||
self.key = key
|
self.key = key
|
||||||
self.type = type
|
self.type = type
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"ArgInner(key={self.key!r}, type={self.type!r})"
|
||||||
|
|
||||||
|
|
||||||
def Arg(key: Optional[str] = None) -> Any:
|
def Arg(key: Optional[str] = None) -> Any:
|
||||||
"""`got` 的 Arg 参数消息"""
|
"""`got` 的 Arg 参数消息"""
|
||||||
@@ -322,12 +311,17 @@ def ArgPlainText(key: Optional[str] = None) -> str:
|
|||||||
class ArgParam(Param):
|
class ArgParam(Param):
|
||||||
"""`got` 的 Arg 参数"""
|
"""`got` 的 Arg 参数"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["ArgParam"]:
|
) -> Optional["ArgParam"]:
|
||||||
if isinstance(param.default, ArgInner):
|
if isinstance(param.default, ArgInner):
|
||||||
return cls(Required, key=param.default.key or name, type=param.default.type)
|
return cls(
|
||||||
|
Required, key=param.default.key or param.name, type=param.default.type
|
||||||
|
)
|
||||||
|
|
||||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||||
message = matcher.get_arg(self.extra["key"])
|
message = matcher.get_arg(self.extra["key"])
|
||||||
@@ -344,12 +338,15 @@ class ArgParam(Param):
|
|||||||
class ExceptionParam(Param):
|
class ExceptionParam(Param):
|
||||||
"""`run_postprocessor` 的异常参数"""
|
"""`run_postprocessor` 的异常参数"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "ExceptionParam()"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["ExceptionParam"]:
|
) -> Optional["ExceptionParam"]:
|
||||||
if generic_check_issubclass(param.annotation, Exception) or (
|
if generic_check_issubclass(param.annotation, Exception) or (
|
||||||
param.annotation == param.empty and name == "exception"
|
param.annotation == param.empty and param.name == "exception"
|
||||||
):
|
):
|
||||||
return cls(Required)
|
return cls(Required)
|
||||||
|
|
||||||
@@ -360,9 +357,12 @@ class ExceptionParam(Param):
|
|||||||
class DefaultParam(Param):
|
class DefaultParam(Param):
|
||||||
"""默认值参数"""
|
"""默认值参数"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"DefaultParam(default={self.default!r})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_param(
|
def _check_param(
|
||||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||||
) -> Optional["DefaultParam"]:
|
) -> Optional["DefaultParam"]:
|
||||||
if param.default != param.empty:
|
if param.default != param.empty:
|
||||||
return cls(param.default)
|
return cls(param.default)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import Any, Set, Tuple, Union, NoReturn, Optional, Coroutine
|
from typing import Set, Tuple, Union, NoReturn, Optional
|
||||||
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.utils import run_coro_with_catch
|
from nonebot.utils import run_coro_with_catch
|
||||||
@@ -37,16 +37,19 @@ 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]] = set(
|
self.checkers: Set[Dependent[bool]] = {
|
||||||
checker
|
checker
|
||||||
if isinstance(checker, Dependent)
|
if isinstance(checker, Dependent)
|
||||||
else Dependent[bool].parse(
|
else Dependent[bool].parse(
|
||||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||||
)
|
)
|
||||||
for checker in checkers
|
for checker in checkers
|
||||||
)
|
}
|
||||||
"""存储 `PermissionChecker`"""
|
"""存储 `PermissionChecker`"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Permission({', '.join(repr(checker) for checker in self.checkers)})"
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
@@ -121,10 +124,20 @@ class User:
|
|||||||
self.users = users
|
self.users = users
|
||||||
self.perm = perm
|
self.perm = perm
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"User(users={self.users}"
|
||||||
|
+ (f", permission={self.perm})" if self.perm else "")
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
|
||||||
async def __call__(self, bot: Bot, event: Event) -> bool:
|
async def __call__(self, bot: Bot, event: Event) -> bool:
|
||||||
|
try:
|
||||||
|
session = event.get_session_id()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
return bool(
|
return bool(
|
||||||
event.get_session_id() in self.users
|
session in self.users and (self.perm is None or await self.perm(bot, event))
|
||||||
and (self.perm is None or await self.perm(bot, event))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -37,16 +37,19 @@ 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]] = set(
|
self.checkers: Set[Dependent[bool]] = {
|
||||||
checker
|
checker
|
||||||
if isinstance(checker, Dependent)
|
if isinstance(checker, Dependent)
|
||||||
else Dependent[bool].parse(
|
else Dependent[bool].parse(
|
||||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||||
)
|
)
|
||||||
for checker in checkers
|
for checker in checkers
|
||||||
)
|
}
|
||||||
"""存储 `RuleChecker`"""
|
"""存储 `RuleChecker`"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Rule({', '.join(repr(checker) for checker in self.checkers)})"
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
|
@@ -14,16 +14,14 @@ FrontMatter:
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Union
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import loguru
|
import loguru
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# avoid sphinx autodoc resolve annotation failed
|
# avoid sphinx autodoc resolve annotation failed
|
||||||
# because loguru module do not have `Logger` class actually
|
# because loguru module do not have `Logger` class actually
|
||||||
from loguru import Logger
|
from loguru import Logger, Record
|
||||||
|
|
||||||
from nonebot.plugin import Plugin
|
|
||||||
|
|
||||||
# logger = logging.getLogger("nonebot")
|
# logger = logging.getLogger("nonebot")
|
||||||
logger: "Logger" = loguru.logger
|
logger: "Logger" = loguru.logger
|
||||||
@@ -47,26 +45,10 @@ logger: "Logger" = loguru.logger
|
|||||||
# logger.addHandler(default_handler)
|
# logger.addHandler(default_handler)
|
||||||
|
|
||||||
|
|
||||||
class Filter:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.level: Union[int, str] = "INFO"
|
|
||||||
|
|
||||||
def __call__(self, record):
|
|
||||||
module_name: str = record["name"]
|
|
||||||
# TODO: get plugin name instead of module name
|
|
||||||
# module = sys.modules.get(module_name)
|
|
||||||
# if module and hasattr(module, "__plugin__"):
|
|
||||||
# plugin: "Plugin" = getattr(module, "__plugin__")
|
|
||||||
# module_name = plugin.module_name
|
|
||||||
record["name"] = module_name.split(".")[0]
|
|
||||||
levelno = (
|
|
||||||
logger.level(self.level).no if isinstance(self.level, str) else self.level
|
|
||||||
)
|
|
||||||
return record["level"].no >= levelno
|
|
||||||
|
|
||||||
|
|
||||||
class LoguruHandler(logging.Handler): # pragma: no cover
|
class LoguruHandler(logging.Handler): # pragma: no cover
|
||||||
def emit(self, record):
|
"""logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。"""
|
||||||
|
|
||||||
|
def emit(self, record: logging.LogRecord):
|
||||||
try:
|
try:
|
||||||
level = logger.level(record.levelname).name
|
level = logger.level(record.levelname).name
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -82,9 +64,13 @@ class LoguruHandler(logging.Handler): # pragma: no cover
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logger.remove()
|
def default_filter(record: "Record"):
|
||||||
default_filter: Filter = Filter()
|
"""默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。"""
|
||||||
"""默认日志等级过滤器"""
|
log_level = record["extra"].get("nonebot_log_level", "INFO")
|
||||||
|
levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level
|
||||||
|
return record["level"].no >= levelno
|
||||||
|
|
||||||
|
|
||||||
default_format: str = (
|
default_format: str = (
|
||||||
"<g>{time:MM-DD HH:mm:ss}</g> "
|
"<g>{time:MM-DD HH:mm:ss}</g> "
|
||||||
"[<lvl>{level}</lvl>] "
|
"[<lvl>{level}</lvl>] "
|
||||||
@@ -93,6 +79,8 @@ default_format: str = (
|
|||||||
"{message}"
|
"{message}"
|
||||||
)
|
)
|
||||||
"""默认日志格式"""
|
"""默认日志格式"""
|
||||||
|
|
||||||
|
logger.remove()
|
||||||
logger_id = logger.add(
|
logger_id = logger.add(
|
||||||
sys.stdout,
|
sys.stdout,
|
||||||
level=0,
|
level=0,
|
||||||
@@ -101,4 +89,4 @@ logger_id = logger.add(
|
|||||||
format=default_format,
|
format=default_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
__autodoc__ = {"Filter": False, "LoguruHandler": False}
|
__autodoc__ = {"logger_id": False}
|
||||||
|
@@ -9,10 +9,16 @@ from nonebot.internal.matcher import Matcher as Matcher
|
|||||||
from nonebot.internal.matcher import matchers as matchers
|
from nonebot.internal.matcher import matchers as matchers
|
||||||
from nonebot.internal.matcher import current_bot as current_bot
|
from nonebot.internal.matcher import current_bot as current_bot
|
||||||
from nonebot.internal.matcher import current_event as current_event
|
from nonebot.internal.matcher import current_event as current_event
|
||||||
|
from nonebot.internal.matcher import MatcherManager as MatcherManager
|
||||||
|
from nonebot.internal.matcher import MatcherProvider as MatcherProvider
|
||||||
from nonebot.internal.matcher import current_handler as current_handler
|
from nonebot.internal.matcher import current_handler as current_handler
|
||||||
from nonebot.internal.matcher import current_matcher as current_matcher
|
from nonebot.internal.matcher import current_matcher as current_matcher
|
||||||
|
from nonebot.internal.matcher import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"Matcher": True,
|
"Matcher": True,
|
||||||
"matchers": True,
|
"matchers": True,
|
||||||
|
"MatcherManager": True,
|
||||||
|
"MatcherProvider": True,
|
||||||
|
"DEFAULT_PROVIDER_CLASS": True,
|
||||||
}
|
}
|
||||||
|
@@ -8,9 +8,10 @@ FrontMatter:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import contextlib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional, Coroutine
|
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.rule import TrieRule
|
from nonebot.rule import TrieRule
|
||||||
@@ -50,14 +51,14 @@ _event_postprocessors: Set[Dependent[Any]] = set()
|
|||||||
_run_preprocessors: Set[Dependent[Any]] = set()
|
_run_preprocessors: Set[Dependent[Any]] = set()
|
||||||
_run_postprocessors: Set[Dependent[Any]] = set()
|
_run_postprocessors: Set[Dependent[Any]] = set()
|
||||||
|
|
||||||
EVENT_PCS_PARAMS = [
|
EVENT_PCS_PARAMS = (
|
||||||
DependParam,
|
DependParam,
|
||||||
BotParam,
|
BotParam,
|
||||||
EventParam,
|
EventParam,
|
||||||
StateParam,
|
StateParam,
|
||||||
DefaultParam,
|
DefaultParam,
|
||||||
]
|
)
|
||||||
RUN_PREPCS_PARAMS = [
|
RUN_PREPCS_PARAMS = (
|
||||||
DependParam,
|
DependParam,
|
||||||
BotParam,
|
BotParam,
|
||||||
EventParam,
|
EventParam,
|
||||||
@@ -65,8 +66,8 @@ RUN_PREPCS_PARAMS = [
|
|||||||
ArgParam,
|
ArgParam,
|
||||||
MatcherParam,
|
MatcherParam,
|
||||||
DefaultParam,
|
DefaultParam,
|
||||||
]
|
)
|
||||||
RUN_POSTPCS_PARAMS = [
|
RUN_POSTPCS_PARAMS = (
|
||||||
DependParam,
|
DependParam,
|
||||||
ExceptionParam,
|
ExceptionParam,
|
||||||
BotParam,
|
BotParam,
|
||||||
@@ -75,7 +76,7 @@ RUN_POSTPCS_PARAMS = [
|
|||||||
ArgParam,
|
ArgParam,
|
||||||
MatcherParam,
|
MatcherParam,
|
||||||
DefaultParam,
|
DefaultParam,
|
||||||
]
|
)
|
||||||
|
|
||||||
|
|
||||||
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
||||||
@@ -120,10 +121,8 @@ async def _check_matcher(
|
|||||||
dependency_cache: Optional[T_DependencyCache] = None,
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
matchers[priority].remove(Matcher)
|
matchers[priority].remove(Matcher)
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -138,11 +137,8 @@ async def _check_matcher(
|
|||||||
return
|
return
|
||||||
|
|
||||||
if Matcher.temp:
|
if Matcher.temp:
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
matchers[priority].remove(Matcher)
|
matchers[priority].remove(Matcher)
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
|
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
|
||||||
|
|
||||||
|
|
||||||
@@ -157,73 +153,68 @@ async def _run_matcher(
|
|||||||
logger.info(f"Event will be handled by {Matcher}")
|
logger.info(f"Event will be handled by {Matcher}")
|
||||||
|
|
||||||
matcher = Matcher()
|
matcher = Matcher()
|
||||||
|
if coros := [
|
||||||
coros = list(
|
run_coro_with_catch(
|
||||||
map(
|
proc(
|
||||||
lambda x: run_coro_with_catch(
|
matcher=matcher,
|
||||||
x(
|
bot=bot,
|
||||||
matcher=matcher,
|
event=event,
|
||||||
bot=bot,
|
state=state,
|
||||||
event=event,
|
stack=stack,
|
||||||
state=state,
|
dependency_cache=dependency_cache,
|
||||||
stack=stack,
|
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
),
|
|
||||||
(SkippedException,),
|
|
||||||
),
|
),
|
||||||
_run_preprocessors,
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
)
|
for proc in _run_preprocessors
|
||||||
if coros:
|
]:
|
||||||
try:
|
# ensure matcher function can be correctly called
|
||||||
await asyncio.gather(*coros)
|
with matcher.ensure_context(bot, event):
|
||||||
except IgnoredException:
|
try:
|
||||||
logger.opt(colors=True).info(
|
await asyncio.gather(*coros)
|
||||||
f"Matcher {matcher} running is <b>cancelled</b>"
|
except IgnoredException:
|
||||||
)
|
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
"<r><bg #f8bbd0>Error when running RunPreProcessors. Running cancelled!</bg #f8bbd0></r>"
|
||||||
"Running cancelled!</bg #f8bbd0></r>"
|
)
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
exception = None
|
exception = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Running matcher {matcher}")
|
logger.debug(f"Running {matcher}")
|
||||||
await matcher.run(bot, event, state, stack, dependency_cache)
|
await matcher.run(bot, event, state, stack, dependency_cache)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>"
|
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
exception = e
|
exception = e
|
||||||
|
|
||||||
coros = list(
|
if coros := [
|
||||||
map(
|
run_coro_with_catch(
|
||||||
lambda x: run_coro_with_catch(
|
proc(
|
||||||
x(
|
matcher=matcher,
|
||||||
matcher=matcher,
|
exception=exception,
|
||||||
exception=exception,
|
bot=bot,
|
||||||
bot=bot,
|
event=event,
|
||||||
event=event,
|
state=matcher.state,
|
||||||
state=matcher.state,
|
stack=stack,
|
||||||
stack=stack,
|
dependency_cache=dependency_cache,
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
),
|
|
||||||
(SkippedException,),
|
|
||||||
),
|
),
|
||||||
_run_postprocessors,
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
)
|
for proc in _run_postprocessors
|
||||||
if coros:
|
]:
|
||||||
try:
|
# ensure matcher function can be correctly called
|
||||||
await asyncio.gather(*coros)
|
with matcher.ensure_context(bot, event):
|
||||||
except Exception as e:
|
try:
|
||||||
logger.opt(colors=True, exception=e).error(
|
await asyncio.gather(*coros)
|
||||||
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
|
except Exception as e:
|
||||||
)
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
if matcher.block:
|
if matcher.block:
|
||||||
raise StopPropagation
|
raise StopPropagation
|
||||||
@@ -244,7 +235,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
show_log = True
|
show_log = True
|
||||||
log_msg = f"<m>{escape_tag(bot.type.upper())} {escape_tag(bot.self_id)}</m> | "
|
log_msg = f"<m>{escape_tag(bot.type)} {escape_tag(bot.self_id)}</m> | "
|
||||||
try:
|
try:
|
||||||
log_msg += event.get_log_string()
|
log_msg += event.get_log_string()
|
||||||
except NoLogException:
|
except NoLogException:
|
||||||
@@ -256,22 +247,19 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
dependency_cache: T_DependencyCache = {}
|
dependency_cache: T_DependencyCache = {}
|
||||||
|
|
||||||
async with AsyncExitStack() as stack:
|
async with AsyncExitStack() as stack:
|
||||||
coros = list(
|
if coros := [
|
||||||
map(
|
run_coro_with_catch(
|
||||||
lambda x: run_coro_with_catch(
|
proc(
|
||||||
x(
|
bot=bot,
|
||||||
bot=bot,
|
event=event,
|
||||||
event=event,
|
state=state,
|
||||||
state=state,
|
stack=stack,
|
||||||
stack=stack,
|
dependency_cache=dependency_cache,
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
),
|
|
||||||
(SkippedException,),
|
|
||||||
),
|
),
|
||||||
_event_preprocessors,
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
)
|
for proc in _event_preprocessors
|
||||||
if coros:
|
]:
|
||||||
try:
|
try:
|
||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Running PreProcessors...")
|
logger.debug("Running PreProcessors...")
|
||||||
@@ -324,22 +312,19 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
|
||||||
coros = list(
|
if coros := [
|
||||||
map(
|
run_coro_with_catch(
|
||||||
lambda x: run_coro_with_catch(
|
proc(
|
||||||
x(
|
bot=bot,
|
||||||
bot=bot,
|
event=event,
|
||||||
event=event,
|
state=state,
|
||||||
state=state,
|
stack=stack,
|
||||||
stack=stack,
|
dependency_cache=dependency_cache,
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
),
|
|
||||||
(SkippedException,),
|
|
||||||
),
|
),
|
||||||
_event_postprocessors,
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
)
|
for proc in _event_postprocessors
|
||||||
if coros:
|
]:
|
||||||
try:
|
try:
|
||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Running PostProcessors...")
|
logger.debug("Running PostProcessors...")
|
||||||
|
@@ -5,17 +5,16 @@ FrontMatter:
|
|||||||
description: nonebot.params 模块
|
description: nonebot.params 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, List, Tuple, Optional
|
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||||
|
|
||||||
from nonebot.typing import T_State
|
from nonebot.typing import T_State
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.adapters import Event, Message
|
|
||||||
from nonebot.internal.params import Arg as Arg
|
from nonebot.internal.params import Arg as Arg
|
||||||
from nonebot.internal.params import State as State
|
|
||||||
from nonebot.internal.params import ArgStr as ArgStr
|
from nonebot.internal.params import ArgStr as ArgStr
|
||||||
from nonebot.internal.params import Depends as Depends
|
from nonebot.internal.params import Depends as Depends
|
||||||
from nonebot.internal.params import ArgParam as ArgParam
|
from nonebot.internal.params import ArgParam as ArgParam
|
||||||
from nonebot.internal.params import BotParam as BotParam
|
from nonebot.internal.params import BotParam as BotParam
|
||||||
|
from nonebot.adapters import Event, Message, MessageSegment
|
||||||
from nonebot.internal.params import EventParam as EventParam
|
from nonebot.internal.params import EventParam as EventParam
|
||||||
from nonebot.internal.params import StateParam as StateParam
|
from nonebot.internal.params import StateParam as StateParam
|
||||||
from nonebot.internal.params import DependParam as DependParam
|
from nonebot.internal.params import DependParam as DependParam
|
||||||
@@ -30,10 +29,14 @@ from nonebot.consts import (
|
|||||||
SHELL_ARGS,
|
SHELL_ARGS,
|
||||||
SHELL_ARGV,
|
SHELL_ARGV,
|
||||||
CMD_ARG_KEY,
|
CMD_ARG_KEY,
|
||||||
|
KEYWORD_KEY,
|
||||||
RAW_CMD_KEY,
|
RAW_CMD_KEY,
|
||||||
REGEX_GROUP,
|
REGEX_GROUP,
|
||||||
|
ENDSWITH_KEY,
|
||||||
CMD_START_KEY,
|
CMD_START_KEY,
|
||||||
|
FULLMATCH_KEY,
|
||||||
REGEX_MATCHED,
|
REGEX_MATCHED,
|
||||||
|
STARTSWITH_KEY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -110,15 +113,15 @@ def CommandStart() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _shell_command_args(state: T_State) -> Any:
|
def _shell_command_args(state: T_State) -> Any:
|
||||||
return state[SHELL_ARGS]
|
return state[SHELL_ARGS] # Namespace or ParserExit
|
||||||
|
|
||||||
|
|
||||||
def ShellCommandArgs():
|
def ShellCommandArgs() -> Any:
|
||||||
"""shell 命令解析后的参数字典"""
|
"""shell 命令解析后的参数字典"""
|
||||||
return Depends(_shell_command_args, use_cache=False)
|
return Depends(_shell_command_args, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
def _shell_command_argv(state: T_State) -> List[str]:
|
def _shell_command_argv(state: T_State) -> List[Union[str, MessageSegment]]:
|
||||||
return state[SHELL_ARGV]
|
return state[SHELL_ARGV]
|
||||||
|
|
||||||
|
|
||||||
@@ -154,6 +157,42 @@ def RegexDict() -> Dict[str, Any]:
|
|||||||
return Depends(_regex_dict, use_cache=False)
|
return Depends(_regex_dict, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _startswith(state: T_State) -> str:
|
||||||
|
return state[STARTSWITH_KEY]
|
||||||
|
|
||||||
|
|
||||||
|
def Startswith() -> str:
|
||||||
|
"""响应触发前缀"""
|
||||||
|
return Depends(_startswith, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _endswith(state: T_State) -> str:
|
||||||
|
return state[ENDSWITH_KEY]
|
||||||
|
|
||||||
|
|
||||||
|
def Endswith() -> str:
|
||||||
|
"""响应触发后缀"""
|
||||||
|
return Depends(_endswith, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _fullmatch(state: T_State) -> str:
|
||||||
|
return state[FULLMATCH_KEY]
|
||||||
|
|
||||||
|
|
||||||
|
def Fullmatch() -> str:
|
||||||
|
"""响应触发完整消息"""
|
||||||
|
return Depends(_fullmatch, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _keyword(state: T_State) -> str:
|
||||||
|
return state[KEYWORD_KEY]
|
||||||
|
|
||||||
|
|
||||||
|
def Keyword() -> str:
|
||||||
|
"""响应触发关键字"""
|
||||||
|
return Depends(_keyword, use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
def Received(id: Optional[str] = None, default: Any = None) -> Any:
|
def Received(id: Optional[str] = None, default: Any = None) -> Any:
|
||||||
"""`receive` 事件参数"""
|
"""`receive` 事件参数"""
|
||||||
|
|
||||||
@@ -174,7 +213,6 @@ def LastReceived(default: Any = None) -> Any:
|
|||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"Arg": True,
|
"Arg": True,
|
||||||
"State": True,
|
|
||||||
"ArgStr": True,
|
"ArgStr": True,
|
||||||
"Depends": True,
|
"Depends": True,
|
||||||
"ArgParam": True,
|
"ArgParam": True,
|
||||||
|
@@ -20,6 +20,9 @@ class Message:
|
|||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "Message()"
|
||||||
|
|
||||||
async def __call__(self, type: str = EventType()) -> bool:
|
async def __call__(self, type: str = EventType()) -> bool:
|
||||||
return type == "message"
|
return type == "message"
|
||||||
|
|
||||||
@@ -29,6 +32,9 @@ class Notice:
|
|||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "Notice()"
|
||||||
|
|
||||||
async def __call__(self, type: str = EventType()) -> bool:
|
async def __call__(self, type: str = EventType()) -> bool:
|
||||||
return type == "notice"
|
return type == "notice"
|
||||||
|
|
||||||
@@ -38,6 +44,9 @@ class Request:
|
|||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "Request()"
|
||||||
|
|
||||||
async def __call__(self, type: str = EventType()) -> bool:
|
async def __call__(self, type: str = EventType()) -> bool:
|
||||||
return type == "request"
|
return type == "request"
|
||||||
|
|
||||||
@@ -47,6 +56,9 @@ class MetaEvent:
|
|||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "MetaEvent()"
|
||||||
|
|
||||||
async def __call__(self, type: str = EventType()) -> bool:
|
async def __call__(self, type: str = EventType()) -> bool:
|
||||||
return type == "meta_event"
|
return type == "meta_event"
|
||||||
|
|
||||||
@@ -78,16 +90,23 @@ class SuperUser:
|
|||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "Superuser()"
|
||||||
|
|
||||||
async def __call__(self, bot: Bot, event: Event) -> bool:
|
async def __call__(self, bot: Bot, event: Event) -> bool:
|
||||||
return event.get_type() == "message" and (
|
try:
|
||||||
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}"
|
user_id = event.get_user_id()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{user_id}"
|
||||||
in bot.config.superusers
|
in bot.config.superusers
|
||||||
or event.get_user_id() in bot.config.superusers # 兼容旧配置
|
or user_id in bot.config.superusers # 兼容旧配置
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SUPERUSER: Permission = Permission(SuperUser())
|
SUPERUSER: Permission = Permission(SuperUser())
|
||||||
"""匹配任意超级用户消息类型事件"""
|
"""匹配任意超级用户事件"""
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"Permission": True,
|
"Permission": True,
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
|
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
|
||||||
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
|
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
|
||||||
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
|
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
|
||||||
|
- `on_type` => {ref}``on_type` <nonebot.plugin.on.on_type>`
|
||||||
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
|
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
|
||||||
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
|
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
|
||||||
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
|
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
|
||||||
@@ -25,8 +26,8 @@
|
|||||||
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
||||||
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
||||||
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
||||||
- `export` => {ref}``export` <nonebot.plugin.export.export>`
|
|
||||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||||
|
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.plugin.PluginMetadata>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
@@ -85,13 +86,12 @@ def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
|
|||||||
参数:
|
参数:
|
||||||
module_name: 模块名,即 {ref}`nonebot.plugin.plugin.Plugin.module_name`。
|
module_name: 模块名,即 {ref}`nonebot.plugin.plugin.Plugin.module_name`。
|
||||||
"""
|
"""
|
||||||
splits = module_name.split(".")
|
|
||||||
loaded = {plugin.module_name: plugin for plugin in _plugins.values()}
|
loaded = {plugin.module_name: plugin for plugin in _plugins.values()}
|
||||||
while splits:
|
has_parent = True
|
||||||
name = ".".join(splits)
|
while has_parent:
|
||||||
if name in loaded:
|
if module_name in loaded:
|
||||||
return loaded[name]
|
return loaded[module_name]
|
||||||
splits.pop()
|
module_name, *has_parent = module_name.rsplit(".", 1)
|
||||||
|
|
||||||
|
|
||||||
def get_loaded_plugins() -> Set["Plugin"]:
|
def get_loaded_plugins() -> Set["Plugin"]:
|
||||||
@@ -106,8 +106,7 @@ def get_available_plugin_names() -> Set[str]:
|
|||||||
|
|
||||||
from .on import on as on
|
from .on import on as on
|
||||||
from .manager import PluginManager
|
from .manager import PluginManager
|
||||||
from .export import Export as Export
|
from .on import on_type as on_type
|
||||||
from .export import export as export
|
|
||||||
from .load import require as require
|
from .load import require as require
|
||||||
from .on import on_regex as on_regex
|
from .on import on_regex as on_regex
|
||||||
from .plugin import Plugin as Plugin
|
from .plugin import Plugin as Plugin
|
||||||
|
@@ -1,64 +0,0 @@
|
|||||||
"""本模块定义了插件导出的内容对象。
|
|
||||||
|
|
||||||
在新版插件系统中,推荐优先使用直接 import 所需要的插件内容。
|
|
||||||
|
|
||||||
FrontMatter:
|
|
||||||
sidebar_position: 4
|
|
||||||
description: nonebot.plugin.export 模块
|
|
||||||
"""
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from . import _current_plugin_chain
|
|
||||||
|
|
||||||
|
|
||||||
class Export(dict):
|
|
||||||
"""插件导出内容以使得其他插件可以获得。
|
|
||||||
|
|
||||||
用法:
|
|
||||||
```python
|
|
||||||
nonebot.export().default = "bar"
|
|
||||||
|
|
||||||
@nonebot.export()
|
|
||||||
def some_function():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# this doesn't work before python 3.9
|
|
||||||
# use
|
|
||||||
# export = nonebot.export(); @export.sub
|
|
||||||
# instead
|
|
||||||
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
|
|
||||||
@nonebot.export().sub
|
|
||||||
def something_else():
|
|
||||||
pass
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __call__(self, func, **kwargs):
|
|
||||||
self[func.__name__] = func
|
|
||||||
self.update(kwargs)
|
|
||||||
return func
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
super().__setitem__(key, Export(value) if isinstance(value, dict) else value)
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
self[name] = Export(value) if isinstance(value, dict) else value
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name not in self:
|
|
||||||
self[name] = Export()
|
|
||||||
return self[name]
|
|
||||||
|
|
||||||
|
|
||||||
def export() -> Export:
|
|
||||||
"""获取当前插件的导出内容对象"""
|
|
||||||
warnings.warn(
|
|
||||||
"nonebot.export() is deprecated. "
|
|
||||||
"See https://github.com/nonebot/nonebot2/issues/935.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
plugins = _current_plugin_chain.get()
|
|
||||||
if not plugins:
|
|
||||||
raise RuntimeError("Export outside of the plugin!")
|
|
||||||
return plugins[-1].export
|
|
@@ -5,24 +5,30 @@ FrontMatter:
|
|||||||
description: nonebot.plugin.load 模块
|
description: nonebot.plugin.load 模块
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import warnings
|
from pathlib import Path
|
||||||
from typing import Set, Iterable, Optional
|
from types import ModuleType
|
||||||
|
from typing import Set, Union, Iterable, Optional
|
||||||
|
|
||||||
import tomlkit
|
import tomlkit
|
||||||
|
|
||||||
from .export import Export
|
from nonebot.utils import path_to_module_name
|
||||||
|
|
||||||
from .plugin import Plugin
|
from .plugin import Plugin
|
||||||
from .manager import PluginManager
|
from .manager import PluginManager
|
||||||
from . import _managers, get_plugin, _module_name_to_plugin_name
|
from . import _managers, get_plugin, _module_name_to_plugin_name
|
||||||
|
|
||||||
|
|
||||||
def load_plugin(module_path: str) -> Optional[Plugin]:
|
def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
||||||
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
module_path: 插件名称 `path.to.your.plugin`
|
module_path: 插件名称 `path.to.your.plugin` 或插件路径 `pathlib.Path(path/to/your/plugin)`
|
||||||
"""
|
"""
|
||||||
|
module_path = (
|
||||||
|
path_to_module_name(module_path)
|
||||||
|
if isinstance(module_path, Path)
|
||||||
|
else module_path
|
||||||
|
)
|
||||||
manager = PluginManager([module_path])
|
manager = PluginManager([module_path])
|
||||||
_managers.append(manager)
|
_managers.append(manager)
|
||||||
return manager.load_plugin(module_path)
|
return manager.load_plugin(module_path)
|
||||||
@@ -74,6 +80,8 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
|||||||
"""
|
"""
|
||||||
with open(file_path, "r", encoding=encoding) as f:
|
with open(file_path, "r", encoding=encoding) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise TypeError("json file must contains a dict!")
|
||||||
plugins = data.get("plugins")
|
plugins = data.get("plugins")
|
||||||
plugin_dirs = data.get("plugin_dirs")
|
plugin_dirs = data.get("plugin_dirs")
|
||||||
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
||||||
@@ -103,15 +111,10 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
|||||||
data = tomlkit.parse(f.read()) # type: ignore
|
data = tomlkit.parse(f.read()) # type: ignore
|
||||||
|
|
||||||
nonebot_data = data.get("tool", {}).get("nonebot")
|
nonebot_data = data.get("tool", {}).get("nonebot")
|
||||||
if not nonebot_data:
|
if nonebot_data is None:
|
||||||
nonebot_data = data.get("nonebot", {}).get("plugins")
|
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
|
||||||
if nonebot_data:
|
if not isinstance(nonebot_data, dict):
|
||||||
warnings.warn(
|
raise TypeError("'[tool.nonebot]' must be a Table!")
|
||||||
"[nonebot.plugins] table is deprecated. Use [tool.nonebot] instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
|
|
||||||
plugins = nonebot_data.get("plugins", [])
|
plugins = nonebot_data.get("plugins", [])
|
||||||
plugin_dirs = nonebot_data.get("plugin_dirs", [])
|
plugin_dirs = nonebot_data.get("plugin_dirs", [])
|
||||||
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
||||||
@@ -143,7 +146,7 @@ def _find_manager_by_name(name: str) -> Optional[PluginManager]:
|
|||||||
return manager
|
return manager
|
||||||
|
|
||||||
|
|
||||||
def require(name: str) -> Export:
|
def require(name: str) -> ModuleType:
|
||||||
"""获取一个插件的导出内容。
|
"""获取一个插件的导出内容。
|
||||||
|
|
||||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||||
@@ -156,11 +159,10 @@ def require(name: str) -> Export:
|
|||||||
"""
|
"""
|
||||||
plugin = get_plugin(_module_name_to_plugin_name(name))
|
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||||
if not plugin:
|
if not plugin:
|
||||||
manager = _find_manager_by_name(name)
|
if manager := _find_manager_by_name(name):
|
||||||
if manager:
|
|
||||||
plugin = manager.load_plugin(name)
|
plugin = manager.load_plugin(name)
|
||||||
else:
|
else:
|
||||||
plugin = load_plugin(name)
|
plugin = load_plugin(name)
|
||||||
if not plugin:
|
if not plugin:
|
||||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||||
return plugin.export
|
return plugin.module
|
||||||
|
@@ -17,7 +17,7 @@ from importlib.machinery import PathFinder, SourceFileLoader
|
|||||||
from typing import Set, Dict, List, Union, Iterable, Optional, Sequence
|
from typing import Set, Dict, List, Union, Iterable, Optional, Sequence
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.utils import escape_tag
|
from nonebot.utils import escape_tag, path_to_module_name
|
||||||
|
|
||||||
from .plugin import Plugin, PluginMetadata
|
from .plugin import Plugin, PluginMetadata
|
||||||
from . import (
|
from . import (
|
||||||
@@ -51,6 +51,9 @@ class PluginManager:
|
|||||||
self._searched_plugin_names: Dict[str, Path] = {}
|
self._searched_plugin_names: Dict[str, Path] = {}
|
||||||
self.prepare_plugins()
|
self.prepare_plugins()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def third_party_plugins(self) -> Set[str]:
|
def third_party_plugins(self) -> Set[str]:
|
||||||
"""返回所有独立插件名称。"""
|
"""返回所有独立插件名称。"""
|
||||||
@@ -66,13 +69,6 @@ class PluginManager:
|
|||||||
"""返回当前插件管理器中可用的插件名称。"""
|
"""返回当前插件管理器中可用的插件名称。"""
|
||||||
return self.third_party_plugins | self.searched_plugins
|
return self.third_party_plugins | self.searched_plugins
|
||||||
|
|
||||||
def _path_to_module_name(self, path: Path) -> str:
|
|
||||||
rel_path = path.resolve().relative_to(Path(".").resolve())
|
|
||||||
if rel_path.stem == "__init__":
|
|
||||||
return ".".join(rel_path.parts[:-1])
|
|
||||||
else:
|
|
||||||
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
|
|
||||||
|
|
||||||
def _previous_plugins(self) -> Set[str]:
|
def _previous_plugins(self) -> Set[str]:
|
||||||
_pre_managers: List[PluginManager]
|
_pre_managers: List[PluginManager]
|
||||||
if self in _managers:
|
if self in _managers:
|
||||||
@@ -86,7 +82,6 @@ class PluginManager:
|
|||||||
|
|
||||||
def prepare_plugins(self) -> Set[str]:
|
def prepare_plugins(self) -> Set[str]:
|
||||||
"""搜索插件并缓存插件名称。"""
|
"""搜索插件并缓存插件名称。"""
|
||||||
|
|
||||||
# get all previous ready to load plugins
|
# get all previous ready to load plugins
|
||||||
previous_plugins = self._previous_plugins()
|
previous_plugins = self._previous_plugins()
|
||||||
searched_plugins: Dict[str, Path] = {}
|
searched_plugins: Dict[str, Path] = {}
|
||||||
@@ -118,11 +113,13 @@ class PluginManager:
|
|||||||
f"Plugin already exists: {module_info.name}! Check your plugin name"
|
f"Plugin already exists: {module_info.name}! Check your plugin name"
|
||||||
)
|
)
|
||||||
|
|
||||||
module_spec = module_info.module_finder.find_spec(module_info.name, None)
|
if not (
|
||||||
if not module_spec:
|
module_spec := module_info.module_finder.find_spec(
|
||||||
|
module_info.name, None
|
||||||
|
)
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
module_path = module_spec.origin
|
if not (module_path := module_spec.origin):
|
||||||
if not module_path:
|
|
||||||
continue
|
continue
|
||||||
searched_plugins[module_info.name] = Path(module_path).resolve()
|
searched_plugins[module_info.name] = Path(module_path).resolve()
|
||||||
|
|
||||||
@@ -146,7 +143,7 @@ class PluginManager:
|
|||||||
module = importlib.import_module(self._third_party_plugin_names[name])
|
module = importlib.import_module(self._third_party_plugin_names[name])
|
||||||
elif name in self._searched_plugin_names:
|
elif name in self._searched_plugin_names:
|
||||||
module = importlib.import_module(
|
module = importlib.import_module(
|
||||||
self._path_to_module_name(self._searched_plugin_names[name])
|
path_to_module_name(self._searched_plugin_names[name])
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
|
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
|
||||||
@@ -154,8 +151,7 @@ class PluginManager:
|
|||||||
logger.opt(colors=True).success(
|
logger.opt(colors=True).success(
|
||||||
f'Succeeded to import "<y>{escape_tag(name)}</y>"'
|
f'Succeeded to import "<y>{escape_tag(name)}</y>"'
|
||||||
)
|
)
|
||||||
plugin = getattr(module, "__plugin__", None)
|
if (plugin := getattr(module, "__plugin__", None)) is None:
|
||||||
if plugin is None:
|
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Module {module.__name__} is not loaded as a plugin! "
|
f"Module {module.__name__} is not loaded as a plugin! "
|
||||||
"Make sure not to import it before loading."
|
"Make sure not to import it before loading."
|
||||||
|
@@ -10,6 +10,7 @@ from types import ModuleType
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
||||||
|
|
||||||
|
from nonebot.adapters import Event
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
@@ -19,6 +20,7 @@ from nonebot.rule import (
|
|||||||
ArgumentParser,
|
ArgumentParser,
|
||||||
regex,
|
regex,
|
||||||
command,
|
command,
|
||||||
|
is_type,
|
||||||
keyword,
|
keyword,
|
||||||
endswith,
|
endswith,
|
||||||
fullmatch,
|
fullmatch,
|
||||||
@@ -30,9 +32,8 @@ from .manager import _current_plugin_chain
|
|||||||
|
|
||||||
|
|
||||||
def _store_matcher(matcher: Type[Matcher]) -> None:
|
def _store_matcher(matcher: Type[Matcher]) -> None:
|
||||||
plugins = _current_plugin_chain.get()
|
|
||||||
# only store the matcher defined in the plugin
|
# only store the matcher defined in the plugin
|
||||||
if plugins:
|
if plugins := _current_plugin_chain.get():
|
||||||
plugins[-1].matcher.add(matcher)
|
plugins[-1].matcher.add(matcher)
|
||||||
|
|
||||||
|
|
||||||
@@ -368,7 +369,7 @@ def on_command(
|
|||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
|
|
||||||
commands = set([cmd]) | (aliases or set())
|
commands = {cmd} | (aliases or set())
|
||||||
block = kwargs.pop("block", False)
|
block = kwargs.pop("block", False)
|
||||||
return on_message(
|
return on_message(
|
||||||
command(*commands) & rule, block=block, **kwargs, _depth=_depth + 1
|
command(*commands) & rule, block=block, **kwargs, _depth=_depth + 1
|
||||||
@@ -403,7 +404,7 @@ def on_shell_command(
|
|||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
|
|
||||||
commands = set([cmd]) | (aliases or set())
|
commands = {cmd} | (aliases or set())
|
||||||
return on_message(
|
return on_message(
|
||||||
shell_command(*commands, parser=parser) & rule,
|
shell_command(*commands, parser=parser) & rule,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -437,7 +438,57 @@ def on_regex(
|
|||||||
return on_message(regex(pattern, flags) & rule, **kwargs, _depth=_depth + 1)
|
return on_message(regex(pattern, flags) & rule, **kwargs, _depth=_depth + 1)
|
||||||
|
|
||||||
|
|
||||||
class CommandGroup:
|
def on_type(
|
||||||
|
types: Union[Type[Event], Tuple[Type[Event]]],
|
||||||
|
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||||
|
*,
|
||||||
|
_depth: int = 0,
|
||||||
|
**kwargs,
|
||||||
|
) -> Type[Matcher]:
|
||||||
|
"""注册一个事件响应器,并且当事件为指定类型时响应。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
types: 事件类型
|
||||||
|
rule: 事件响应规则
|
||||||
|
permission: 事件响应权限
|
||||||
|
handlers: 事件处理函数列表
|
||||||
|
temp: 是否为临时事件响应器(仅执行一次)
|
||||||
|
expire_time: 事件响应器最终有效时间点,过时即被删除
|
||||||
|
priority: 事件响应器优先级
|
||||||
|
block: 是否阻止事件向更低优先级传递
|
||||||
|
state: 默认 state
|
||||||
|
"""
|
||||||
|
event_types = types if isinstance(types, tuple) else (types,)
|
||||||
|
return on(rule=is_type(*event_types) & rule, **kwargs, _depth=_depth + 1)
|
||||||
|
|
||||||
|
|
||||||
|
class _Group:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""创建一个事件响应器组合,参数为默认值,与 `on` 一致"""
|
||||||
|
self.matchers: List[Type[Matcher]] = []
|
||||||
|
"""组内事件响应器列表"""
|
||||||
|
self.base_kwargs: Dict[str, Any] = kwargs
|
||||||
|
"""其他传递给 `on` 的参数默认值"""
|
||||||
|
|
||||||
|
def _get_final_kwargs(
|
||||||
|
self, update: Dict[str, Any], *, exclude: Optional[Set[str]] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""获取最终传递给 `on` 的参数
|
||||||
|
|
||||||
|
参数:
|
||||||
|
update: 更新的关键字参数
|
||||||
|
exclude: 需要排除的参数
|
||||||
|
"""
|
||||||
|
final_kwargs = self.base_kwargs.copy()
|
||||||
|
final_kwargs.update(update)
|
||||||
|
if exclude:
|
||||||
|
for key in exclude:
|
||||||
|
final_kwargs.pop(key, None)
|
||||||
|
final_kwargs["_depth"] = 1
|
||||||
|
return final_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class CommandGroup(_Group):
|
||||||
"""命令组,用于声明一组有相同名称前缀的命令。
|
"""命令组,用于声明一组有相同名称前缀的命令。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
@@ -453,12 +504,13 @@ class CommandGroup:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
|
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
|
||||||
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
|
||||||
"""命令前缀"""
|
"""命令前缀"""
|
||||||
if "aliases" in kwargs:
|
super().__init__(**kwargs)
|
||||||
del kwargs["aliases"]
|
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||||
self.base_kwargs: Dict[str, Any] = kwargs
|
self.base_kwargs.pop("aliases", None)
|
||||||
"""其他传递给 `on_command` 的参数默认值"""
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"CommandGroup(cmd={self.basecmd}, matchers={len(self.matchers)})"
|
||||||
|
|
||||||
def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||||
"""注册一个新的命令。新参数将会覆盖命令组默认值
|
"""注册一个新的命令。新参数将会覆盖命令组默认值
|
||||||
@@ -477,10 +529,9 @@ class CommandGroup:
|
|||||||
"""
|
"""
|
||||||
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
||||||
cmd = self.basecmd + sub_cmd
|
cmd = self.basecmd + sub_cmd
|
||||||
|
matcher = on_command(cmd, **self._get_final_kwargs(kwargs))
|
||||||
final_kwargs = self.base_kwargs.copy()
|
self.matchers.append(matcher)
|
||||||
final_kwargs.update(kwargs)
|
return matcher
|
||||||
return on_command(cmd, **final_kwargs, _depth=1)
|
|
||||||
|
|
||||||
def shell_command(
|
def shell_command(
|
||||||
self, cmd: Union[str, Tuple[str, ...]], **kwargs
|
self, cmd: Union[str, Tuple[str, ...]], **kwargs
|
||||||
@@ -502,21 +553,16 @@ class CommandGroup:
|
|||||||
"""
|
"""
|
||||||
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
||||||
cmd = self.basecmd + sub_cmd
|
cmd = self.basecmd + sub_cmd
|
||||||
|
matcher = on_shell_command(cmd, **self._get_final_kwargs(kwargs))
|
||||||
final_kwargs = self.base_kwargs.copy()
|
self.matchers.append(matcher)
|
||||||
final_kwargs.update(kwargs)
|
return matcher
|
||||||
return on_shell_command(cmd, **final_kwargs, _depth=1)
|
|
||||||
|
|
||||||
|
|
||||||
class MatcherGroup:
|
class MatcherGroup(_Group):
|
||||||
"""事件响应器组合,统一管理。为 `Matcher` 创建提供默认属性。"""
|
"""事件响应器组合,统一管理。为 `Matcher` 创建提供默认属性。"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __repr__(self) -> str:
|
||||||
"""创建一个事件响应器组合,参数为默认值,与 `on` 一致"""
|
return f"MatcherGroup(matchers={len(self.matchers)})"
|
||||||
self.matchers: List[Type[Matcher]] = []
|
|
||||||
"""组内事件响应器列表"""
|
|
||||||
self.base_kwargs: Dict[str, Any] = kwargs
|
|
||||||
"""其他传递给 `on` 的参数默认值"""
|
|
||||||
|
|
||||||
def on(self, **kwargs) -> Type[Matcher]:
|
def on(self, **kwargs) -> Type[Matcher]:
|
||||||
"""注册一个基础事件响应器,可自定义类型。
|
"""注册一个基础事件响应器,可自定义类型。
|
||||||
@@ -532,9 +578,7 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
matcher = on(**self._get_final_kwargs(kwargs))
|
||||||
final_kwargs.update(kwargs)
|
|
||||||
matcher = on(**final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -550,11 +594,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_metaevent(**final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
final_kwargs.pop("permission", None)
|
|
||||||
matcher = on_metaevent(**final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -571,10 +612,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_message(**final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_message(**final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -590,10 +629,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_notice(**final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_notice(**final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -609,10 +646,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_request(**final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_request(**final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -633,10 +668,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_startswith(msg, **final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_startswith(msg, **final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -655,10 +688,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_endswith(msg, **final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_endswith(msg, **final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -677,10 +708,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_fullmatch(msg, **final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_fullmatch(msg, **final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -698,10 +727,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_keyword(keywords, **final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_keyword(keywords, **final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -727,10 +754,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_command(cmd, aliases=aliases, **final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_command(cmd, aliases=aliases, **final_kwargs, _depth=1)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -760,12 +785,8 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_shell_command(cmd, aliases=aliases, parser=parser, **final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
|
||||||
matcher = on_shell_command(
|
|
||||||
cmd, aliases=aliases, parser=parser, **final_kwargs, _depth=1
|
|
||||||
)
|
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
|
||||||
@@ -788,9 +809,28 @@ class MatcherGroup:
|
|||||||
block: 是否阻止事件向更低优先级传递
|
block: 是否阻止事件向更低优先级传递
|
||||||
state: 默认 state
|
state: 默认 state
|
||||||
"""
|
"""
|
||||||
final_kwargs = self.base_kwargs.copy()
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
final_kwargs.update(kwargs)
|
matcher = on_regex(pattern, flags=flags, **final_kwargs)
|
||||||
final_kwargs.pop("type", None)
|
self.matchers.append(matcher)
|
||||||
matcher = on_regex(pattern, flags=flags, **final_kwargs, _depth=1)
|
return matcher
|
||||||
|
|
||||||
|
def on_type(
|
||||||
|
self, types: Union[Type[Event], Tuple[Type[Event]]], **kwargs
|
||||||
|
) -> Type[Matcher]:
|
||||||
|
"""注册一个事件响应器,并且当事件为指定类型时响应。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
types: 事件类型
|
||||||
|
rule: 事件响应规则
|
||||||
|
permission: 事件响应权限
|
||||||
|
handlers: 事件处理函数列表
|
||||||
|
temp: 是否为临时事件响应器(仅执行一次)
|
||||||
|
expire_time: 事件响应器最终有效时间点,过时即被删除
|
||||||
|
priority: 事件响应器优先级
|
||||||
|
block: 是否阻止事件向更低优先级传递
|
||||||
|
state: 默认 state
|
||||||
|
"""
|
||||||
|
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||||
|
matcher = on_type(types, **final_kwargs)
|
||||||
self.matchers.append(matcher)
|
self.matchers.append(matcher)
|
||||||
return matcher
|
return matcher
|
||||||
|
@@ -2,6 +2,7 @@ import re
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Set, List, Type, Tuple, Union, Optional
|
from typing import Set, List, Type, Tuple, Union, Optional
|
||||||
|
|
||||||
|
from nonebot.adapters import Event
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
@@ -152,6 +153,18 @@ def on_regex(
|
|||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: Optional[T_State] = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> Type[Matcher]: ...
|
||||||
|
def on_type(
|
||||||
|
types: Union[Type[Event], Tuple[Type[Event]]],
|
||||||
|
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||||
|
*,
|
||||||
|
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||||
|
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
||||||
|
temp: bool = ...,
|
||||||
|
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
||||||
|
priority: int = ...,
|
||||||
|
block: bool = ...,
|
||||||
|
state: Optional[T_State] = ...,
|
||||||
|
) -> Type[Matcher]: ...
|
||||||
|
|
||||||
class CommandGroup:
|
class CommandGroup:
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -171,8 +184,8 @@ class CommandGroup:
|
|||||||
self,
|
self,
|
||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: Union[str, Tuple[str, ...]],
|
||||||
*,
|
*,
|
||||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
|
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||||
|
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
||||||
temp: bool = ...,
|
temp: bool = ...,
|
||||||
@@ -186,7 +199,7 @@ class CommandGroup:
|
|||||||
cmd: Union[str, Tuple[str, ...]],
|
cmd: Union[str, Tuple[str, ...]],
|
||||||
*,
|
*,
|
||||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
|
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
||||||
parser: Optional[ArgumentParser] = ...,
|
parser: Optional[ArgumentParser] = ...,
|
||||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
||||||
@@ -367,3 +380,16 @@ class MatcherGroup:
|
|||||||
block: bool = ...,
|
block: bool = ...,
|
||||||
state: Optional[T_State] = ...,
|
state: Optional[T_State] = ...,
|
||||||
) -> Type[Matcher]: ...
|
) -> Type[Matcher]: ...
|
||||||
|
def on_type(
|
||||||
|
self,
|
||||||
|
types: Union[Type[Event], Tuple[Type[Event]]],
|
||||||
|
*,
|
||||||
|
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||||
|
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||||
|
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
||||||
|
temp: bool = ...,
|
||||||
|
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
||||||
|
priority: int = ...,
|
||||||
|
block: bool = ...,
|
||||||
|
state: Optional[T_State] = ...,
|
||||||
|
) -> Type[Matcher]: ...
|
||||||
|
@@ -12,7 +12,6 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
|
|
||||||
from .export import Export
|
|
||||||
from . import _plugins as plugins # FIXME: backport for nonebug
|
from . import _plugins as plugins # FIXME: backport for nonebug
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -46,8 +45,6 @@ class Plugin:
|
|||||||
"""点分割模块路径"""
|
"""点分割模块路径"""
|
||||||
manager: "PluginManager"
|
manager: "PluginManager"
|
||||||
"""导入该插件的插件管理器"""
|
"""导入该插件的插件管理器"""
|
||||||
export: Export = field(default_factory=Export)
|
|
||||||
"""**Deprecated:** 插件内定义的导出内容"""
|
|
||||||
matcher: Set[Type[Matcher]] = field(default_factory=set)
|
matcher: Set[Type[Matcher]] = field(default_factory=set)
|
||||||
"""插件内定义的 `Matcher`"""
|
"""插件内定义的 `Matcher`"""
|
||||||
parent_plugin: Optional["Plugin"] = None
|
parent_plugin: Optional["Plugin"] = None
|
||||||
|
@@ -7,5 +7,5 @@ echo = on_command("echo", to_me())
|
|||||||
|
|
||||||
|
|
||||||
@echo.handle()
|
@echo.handle()
|
||||||
async def echo_escape(message: Message = CommandArg()):
|
async def handle_echo(message: Message = CommandArg()):
|
||||||
await echo.send(message=message)
|
await echo.send(message=message)
|
||||||
|
@@ -15,8 +15,7 @@ async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
|
|||||||
yield result
|
yield result
|
||||||
else:
|
else:
|
||||||
current_event_id = id(event)
|
current_event_id = id(event)
|
||||||
event_id = _running_matcher.get(session_id, None)
|
if event_id := _running_matcher.get(session_id, None):
|
||||||
if event_id:
|
|
||||||
result = event_id != current_event_id
|
result = event_id != current_event_id
|
||||||
else:
|
else:
|
||||||
_running_matcher[session_id] = current_event_id
|
_running_matcher[session_id] = current_event_id
|
||||||
|
361
nonebot/rule.py
361
nonebot/rule.py
@@ -10,11 +10,26 @@ FrontMatter:
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
from itertools import product
|
from argparse import Action
|
||||||
from argparse import Namespace
|
from argparse import ArgumentError
|
||||||
from typing_extensions import TypedDict
|
from itertools import chain, product
|
||||||
|
from argparse import Namespace as Namespace
|
||||||
from argparse import ArgumentParser as ArgParser
|
from argparse import ArgumentParser as ArgParser
|
||||||
from typing import Any, List, Tuple, Union, Optional, Sequence, NamedTuple
|
from typing import (
|
||||||
|
IO,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
List,
|
||||||
|
Type,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
TypeVar,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
TypedDict,
|
||||||
|
NamedTuple,
|
||||||
|
cast,
|
||||||
|
overload,
|
||||||
|
)
|
||||||
|
|
||||||
from pygtrie import CharTrie
|
from pygtrie import CharTrie
|
||||||
|
|
||||||
@@ -23,15 +38,8 @@ from nonebot.log import logger
|
|||||||
from nonebot.typing import T_State
|
from nonebot.typing import T_State
|
||||||
from nonebot.exception import ParserExit
|
from nonebot.exception import ParserExit
|
||||||
from nonebot.internal.rule import Rule as Rule
|
from nonebot.internal.rule import Rule as Rule
|
||||||
|
from nonebot.params import Command, EventToMe, CommandArg
|
||||||
from nonebot.adapters import Bot, Event, Message, MessageSegment
|
from nonebot.adapters import Bot, Event, Message, MessageSegment
|
||||||
from nonebot.params import (
|
|
||||||
Command,
|
|
||||||
EventToMe,
|
|
||||||
EventType,
|
|
||||||
CommandArg,
|
|
||||||
EventMessage,
|
|
||||||
EventPlainText,
|
|
||||||
)
|
|
||||||
from nonebot.consts import (
|
from nonebot.consts import (
|
||||||
CMD_KEY,
|
CMD_KEY,
|
||||||
PREFIX_KEY,
|
PREFIX_KEY,
|
||||||
@@ -39,12 +47,18 @@ from nonebot.consts import (
|
|||||||
SHELL_ARGS,
|
SHELL_ARGS,
|
||||||
SHELL_ARGV,
|
SHELL_ARGV,
|
||||||
CMD_ARG_KEY,
|
CMD_ARG_KEY,
|
||||||
|
KEYWORD_KEY,
|
||||||
RAW_CMD_KEY,
|
RAW_CMD_KEY,
|
||||||
REGEX_GROUP,
|
REGEX_GROUP,
|
||||||
|
ENDSWITH_KEY,
|
||||||
CMD_START_KEY,
|
CMD_START_KEY,
|
||||||
|
FULLMATCH_KEY,
|
||||||
REGEX_MATCHED,
|
REGEX_MATCHED,
|
||||||
|
STARTSWITH_KEY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
CMD_RESULT = TypedDict(
|
CMD_RESULT = TypedDict(
|
||||||
"CMD_RESULT",
|
"CMD_RESULT",
|
||||||
{
|
{
|
||||||
@@ -83,8 +97,7 @@ class TrieRule:
|
|||||||
message_seg: MessageSegment = message[0]
|
message_seg: MessageSegment = message[0]
|
||||||
if message_seg.is_text():
|
if message_seg.is_text():
|
||||||
segment_text = str(message_seg).lstrip()
|
segment_text = str(message_seg).lstrip()
|
||||||
pf = cls.prefix.longest_prefix(segment_text)
|
if pf := cls.prefix.longest_prefix(segment_text):
|
||||||
if pf:
|
|
||||||
value: TRIE_VALUE = pf.value
|
value: TRIE_VALUE = pf.value
|
||||||
prefix[RAW_CMD_KEY] = pf.key
|
prefix[RAW_CMD_KEY] = pf.key
|
||||||
prefix[CMD_START_KEY] = value.command_start
|
prefix[CMD_START_KEY] = value.command_start
|
||||||
@@ -113,19 +126,35 @@ class StartswithRule:
|
|||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.ignorecase = ignorecase
|
self.ignorecase = ignorecase
|
||||||
|
|
||||||
async def __call__(
|
def __repr__(self) -> str:
|
||||||
self, type: str = EventType(), text: str = EventPlainText()
|
return f"Startswith(msg={self.msg}, ignorecase={self.ignorecase})"
|
||||||
) -> Any:
|
|
||||||
if type != "message":
|
def __eq__(self, other: object) -> bool:
|
||||||
return False
|
return (
|
||||||
return bool(
|
isinstance(other, StartswithRule)
|
||||||
re.match(
|
and frozenset(self.msg) == frozenset(other.msg)
|
||||||
f"^(?:{'|'.join(re.escape(prefix) for prefix in self.msg)})",
|
and self.ignorecase == other.ignorecase
|
||||||
text,
|
|
||||||
re.IGNORECASE if self.ignorecase else 0,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((frozenset(self.msg), self.ignorecase))
|
||||||
|
|
||||||
|
async def __call__(self, event: Event, state: T_State) -> bool:
|
||||||
|
if event.get_type() != "message":
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
text = event.get_plaintext()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
if match := re.match(
|
||||||
|
f"^(?:{'|'.join(re.escape(prefix) for prefix in self.msg)})",
|
||||||
|
text,
|
||||||
|
re.IGNORECASE if self.ignorecase else 0,
|
||||||
|
):
|
||||||
|
state[STARTSWITH_KEY] = match.group()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def startswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
def startswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||||
"""匹配消息纯文本开头。
|
"""匹配消息纯文本开头。
|
||||||
@@ -154,19 +183,35 @@ class EndswithRule:
|
|||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.ignorecase = ignorecase
|
self.ignorecase = ignorecase
|
||||||
|
|
||||||
async def __call__(
|
def __repr__(self) -> str:
|
||||||
self, type: str = EventType(), text: str = EventPlainText()
|
return f"Endswith(msg={self.msg}, ignorecase={self.ignorecase})"
|
||||||
) -> Any:
|
|
||||||
if type != "message":
|
def __eq__(self, other: object) -> bool:
|
||||||
return False
|
return (
|
||||||
return bool(
|
isinstance(other, EndswithRule)
|
||||||
re.search(
|
and frozenset(self.msg) == frozenset(other.msg)
|
||||||
f"(?:{'|'.join(re.escape(prefix) for prefix in self.msg)})$",
|
and self.ignorecase == other.ignorecase
|
||||||
text,
|
|
||||||
re.IGNORECASE if self.ignorecase else 0,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((frozenset(self.msg), self.ignorecase))
|
||||||
|
|
||||||
|
async def __call__(self, event: Event, state: T_State) -> bool:
|
||||||
|
if event.get_type() != "message":
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
text = event.get_plaintext()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
if match := re.search(
|
||||||
|
f"(?:{'|'.join(re.escape(suffix) for suffix in self.msg)})$",
|
||||||
|
text,
|
||||||
|
re.IGNORECASE if self.ignorecase else 0,
|
||||||
|
):
|
||||||
|
state[ENDSWITH_KEY] = match.group()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def endswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
def endswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||||
"""匹配消息纯文本结尾。
|
"""匹配消息纯文本结尾。
|
||||||
@@ -192,17 +237,37 @@ class FullmatchRule:
|
|||||||
__slots__ = ("msg", "ignorecase")
|
__slots__ = ("msg", "ignorecase")
|
||||||
|
|
||||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||||
self.msg = frozenset(map(str.casefold, msg) if ignorecase else msg)
|
self.msg = tuple(map(str.casefold, msg) if ignorecase else msg)
|
||||||
self.ignorecase = ignorecase
|
self.ignorecase = ignorecase
|
||||||
|
|
||||||
async def __call__(
|
def __repr__(self) -> str:
|
||||||
self, type_: str = EventType(), text: str = EventPlainText()
|
return f"Fullmatch(msg={self.msg}, ignorecase={self.ignorecase})"
|
||||||
) -> bool:
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
return (
|
return (
|
||||||
type_ == "message"
|
isinstance(other, FullmatchRule)
|
||||||
and (text.casefold() if self.ignorecase else text) in self.msg
|
and frozenset(self.msg) == frozenset(other.msg)
|
||||||
|
and self.ignorecase == other.ignorecase
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((frozenset(self.msg), self.ignorecase))
|
||||||
|
|
||||||
|
async def __call__(self, event: Event, state: T_State) -> bool:
|
||||||
|
if event.get_type() != "message":
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
text = event.get_plaintext()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
if not text:
|
||||||
|
return False
|
||||||
|
text = text.casefold() if self.ignorecase else text
|
||||||
|
if text in self.msg:
|
||||||
|
state[FULLMATCH_KEY] = text
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def fullmatch(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
def fullmatch(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||||
"""完全匹配消息。
|
"""完全匹配消息。
|
||||||
@@ -229,12 +294,30 @@ class KeywordsRule:
|
|||||||
def __init__(self, *keywords: str):
|
def __init__(self, *keywords: str):
|
||||||
self.keywords = keywords
|
self.keywords = keywords
|
||||||
|
|
||||||
async def __call__(
|
def __repr__(self) -> str:
|
||||||
self, type: str = EventType(), text: str = EventPlainText()
|
return f"Keywords(keywords={self.keywords})"
|
||||||
) -> bool:
|
|
||||||
if type != "message":
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, KeywordsRule) and frozenset(
|
||||||
|
self.keywords
|
||||||
|
) == frozenset(other.keywords)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(frozenset(self.keywords))
|
||||||
|
|
||||||
|
async def __call__(self, event: Event, state: T_State) -> bool:
|
||||||
|
if event.get_type() != "message":
|
||||||
return False
|
return False
|
||||||
return bool(text and any(keyword in text for keyword in self.keywords))
|
try:
|
||||||
|
text = event.get_plaintext()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
if not text:
|
||||||
|
return False
|
||||||
|
if key := next((k for k in self.keywords if k in text), None):
|
||||||
|
state[KEYWORD_KEY] = key
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def keyword(*keywords: str) -> Rule:
|
def keyword(*keywords: str) -> Rule:
|
||||||
@@ -257,14 +340,22 @@ class CommandRule:
|
|||||||
__slots__ = ("cmds",)
|
__slots__ = ("cmds",)
|
||||||
|
|
||||||
def __init__(self, cmds: List[Tuple[str, ...]]):
|
def __init__(self, cmds: List[Tuple[str, ...]]):
|
||||||
self.cmds = cmds
|
self.cmds = tuple(cmds)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Command(cmds={self.cmds})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, CommandRule) and frozenset(self.cmds) == frozenset(
|
||||||
|
other.cmds
|
||||||
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((frozenset(self.cmds),))
|
||||||
|
|
||||||
async def __call__(self, cmd: Optional[Tuple[str, ...]] = Command()) -> bool:
|
async def __call__(self, cmd: Optional[Tuple[str, ...]] = Command()) -> bool:
|
||||||
return cmd in self.cmds
|
return cmd in self.cmds
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<Command {self.cmds}>"
|
|
||||||
|
|
||||||
|
|
||||||
def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
|
def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
|
||||||
"""匹配消息命令。
|
"""匹配消息命令。
|
||||||
@@ -320,25 +411,48 @@ class ArgumentParser(ArgParser):
|
|||||||
参考文档: [argparse](https://docs.python.org/3/library/argparse.html)
|
参考文档: [argparse](https://docs.python.org/3/library/argparse.html)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _print_message(self, message, file=None):
|
if TYPE_CHECKING:
|
||||||
old_message: str = getattr(self, "message", "")
|
|
||||||
if old_message:
|
|
||||||
old_message += "\n"
|
|
||||||
old_message += message
|
|
||||||
setattr(self, "message", old_message)
|
|
||||||
|
|
||||||
def exit(self, status: int = 0, message: Optional[str] = None):
|
@overload
|
||||||
raise ParserExit(
|
def parse_args(
|
||||||
status=status, message=message or getattr(self, "message", None)
|
self, args: Optional[Sequence[Union[str, MessageSegment]]] = ...
|
||||||
|
) -> Namespace:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_args(
|
||||||
|
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: None
|
||||||
|
) -> Namespace:
|
||||||
|
... # type: ignore[misc]
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_args(
|
||||||
|
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||||
|
) -> T:
|
||||||
|
...
|
||||||
|
|
||||||
|
def parse_args(
|
||||||
|
self,
|
||||||
|
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||||
|
namespace: Optional[T] = None,
|
||||||
|
) -> Union[Namespace, T]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def _parse_optional(
|
||||||
|
self, arg_string: Union[str, MessageSegment]
|
||||||
|
) -> Optional[Tuple[Optional[Action], str, Optional[str]]]:
|
||||||
|
return (
|
||||||
|
super()._parse_optional(arg_string) if isinstance(arg_string, str) else None
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse_args(
|
def _print_message(self, message: str, file: Optional[IO[str]] = None):
|
||||||
self,
|
if message:
|
||||||
args: Optional[Sequence[str]] = None,
|
setattr(self, "_message", getattr(self, "_message", "") + message)
|
||||||
namespace: Optional[Namespace] = None,
|
|
||||||
) -> Namespace:
|
def exit(self, status: int = 0, message: Optional[str] = None):
|
||||||
setattr(self, "message", "")
|
if message:
|
||||||
return super().parse_args(args=args, namespace=namespace) # type: ignore
|
self._print_message(message)
|
||||||
|
raise ParserExit(status=status, message=getattr(self, "_message", None))
|
||||||
|
|
||||||
|
|
||||||
class ShellCommandRule:
|
class ShellCommandRule:
|
||||||
@@ -352,28 +466,48 @@ class ShellCommandRule:
|
|||||||
__slots__ = ("cmds", "parser")
|
__slots__ = ("cmds", "parser")
|
||||||
|
|
||||||
def __init__(self, cmds: List[Tuple[str, ...]], parser: Optional[ArgumentParser]):
|
def __init__(self, cmds: List[Tuple[str, ...]], parser: Optional[ArgumentParser]):
|
||||||
self.cmds = cmds
|
self.cmds = tuple(cmds)
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"ShellCommand(cmds={self.cmds}, parser={self.parser})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return (
|
||||||
|
isinstance(other, ShellCommandRule)
|
||||||
|
and frozenset(self.cmds) == frozenset(other.cmds)
|
||||||
|
and self.parser is other.parser
|
||||||
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((frozenset(self.cmds), self.parser))
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
state: T_State,
|
state: T_State,
|
||||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||||
msg: Optional[Message] = CommandArg(),
|
msg: Optional[Message] = CommandArg(),
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if cmd in self.cmds and msg is not None:
|
if cmd not in self.cmds or msg is None:
|
||||||
message = str(msg)
|
|
||||||
state[SHELL_ARGV] = shlex.split(message)
|
|
||||||
if self.parser:
|
|
||||||
try:
|
|
||||||
args = self.parser.parse_args(state[SHELL_ARGV])
|
|
||||||
state[SHELL_ARGS] = args
|
|
||||||
except ParserExit as e:
|
|
||||||
state[SHELL_ARGS] = e
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
state[SHELL_ARGV] = list(
|
||||||
|
chain.from_iterable(
|
||||||
|
shlex.split(str(seg)) if cast(MessageSegment, seg).is_text() else (seg,)
|
||||||
|
for seg in msg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.parser:
|
||||||
|
try:
|
||||||
|
args = self.parser.parse_args(state[SHELL_ARGV])
|
||||||
|
state[SHELL_ARGS] = args
|
||||||
|
except ArgumentError as e:
|
||||||
|
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
|
||||||
|
except ParserExit as e:
|
||||||
|
state[SHELL_ARGS] = e
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def shell_command(
|
def shell_command(
|
||||||
*cmds: Union[str, Tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
*cmds: Union[str, Tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
||||||
@@ -452,16 +586,27 @@ class RegexRule:
|
|||||||
self.regex = regex
|
self.regex = regex
|
||||||
self.flags = flags
|
self.flags = flags
|
||||||
|
|
||||||
async def __call__(
|
def __repr__(self) -> str:
|
||||||
self,
|
return f"Regex(regex={self.regex!r}, flags={self.flags})"
|
||||||
state: T_State,
|
|
||||||
type: str = EventType(),
|
def __eq__(self, other: object) -> bool:
|
||||||
msg: Message = EventMessage(),
|
return (
|
||||||
) -> bool:
|
isinstance(other, RegexRule)
|
||||||
if type != "message":
|
and self.regex == other.regex
|
||||||
|
and self.flags == other.flags
|
||||||
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.regex, self.flags))
|
||||||
|
|
||||||
|
async def __call__(self, event: Event, state: T_State) -> bool:
|
||||||
|
if event.get_type() != "message":
|
||||||
return False
|
return False
|
||||||
matched = re.search(self.regex, str(msg), self.flags)
|
try:
|
||||||
if matched:
|
msg = event.get_message()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
if matched := re.search(self.regex, str(msg), self.flags):
|
||||||
state[REGEX_MATCHED] = matched.group()
|
state[REGEX_MATCHED] = matched.group()
|
||||||
state[REGEX_GROUP] = matched.groups()
|
state[REGEX_GROUP] = matched.groups()
|
||||||
state[REGEX_DICT] = matched.groupdict()
|
state[REGEX_DICT] = matched.groupdict()
|
||||||
@@ -498,6 +643,15 @@ class ToMeRule:
|
|||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "ToMe()"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, ToMeRule)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.__class__,))
|
||||||
|
|
||||||
async def __call__(self, to_me: bool = EventToMe()) -> bool:
|
async def __call__(self, to_me: bool = EventToMe()) -> bool:
|
||||||
return to_me
|
return to_me
|
||||||
|
|
||||||
@@ -508,6 +662,37 @@ def to_me() -> Rule:
|
|||||||
return Rule(ToMeRule())
|
return Rule(ToMeRule())
|
||||||
|
|
||||||
|
|
||||||
|
class IsTypeRule:
|
||||||
|
"""检查事件类型是否为指定类型。"""
|
||||||
|
|
||||||
|
__slots__ = ("types",)
|
||||||
|
|
||||||
|
def __init__(self, *types: Type[Event]):
|
||||||
|
self.types = types
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"IsType(types={tuple(type.__name__ for type in self.types)})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, IsTypeRule) and self.types == other.types
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.types,))
|
||||||
|
|
||||||
|
async def __call__(self, event: Event) -> bool:
|
||||||
|
return isinstance(event, self.types)
|
||||||
|
|
||||||
|
|
||||||
|
def is_type(*types: Type[Event]) -> Rule:
|
||||||
|
"""匹配事件类型。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
types: 事件类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
return Rule(IsTypeRule(*types))
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {
|
__autodoc__ = {
|
||||||
"Rule": True,
|
"Rule": True,
|
||||||
"Rule.__call__": True,
|
"Rule.__call__": True,
|
||||||
|
@@ -11,6 +11,7 @@ FrontMatter:
|
|||||||
sidebar_position: 11
|
sidebar_position: 11
|
||||||
description: nonebot.typing 模块
|
description: nonebot.typing 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
@@ -28,6 +29,8 @@ if TYPE_CHECKING:
|
|||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
|
T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
|
||||||
|
|
||||||
|
|
||||||
@@ -41,10 +44,14 @@ def overrides(InterfaceClass: object) -> Callable[[T_Wrapped], T_Wrapped]:
|
|||||||
return overrider
|
return overrider
|
||||||
|
|
||||||
|
|
||||||
|
# state
|
||||||
T_State = Dict[Any, Any]
|
T_State = Dict[Any, Any]
|
||||||
"""事件处理状态 State 类型"""
|
"""事件处理状态 State 类型"""
|
||||||
|
|
||||||
T_BotConnectionHook = Callable[..., Awaitable[Any]]
|
_DependentCallable = Union[Callable[..., T], Callable[..., Awaitable[T]]]
|
||||||
|
|
||||||
|
# driver hooks
|
||||||
|
T_BotConnectionHook = _DependentCallable[Any]
|
||||||
"""Bot 连接建立时钩子函数
|
"""Bot 连接建立时钩子函数
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -53,7 +60,7 @@ T_BotConnectionHook = Callable[..., Awaitable[Any]]
|
|||||||
- BotParam: Bot 对象
|
- BotParam: Bot 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_BotDisconnectionHook = Callable[..., Awaitable[Any]]
|
T_BotDisconnectionHook = _DependentCallable[Any]
|
||||||
"""Bot 连接断开时钩子函数
|
"""Bot 连接断开时钩子函数
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -62,6 +69,8 @@ T_BotDisconnectionHook = Callable[..., Awaitable[Any]]
|
|||||||
- BotParam: Bot 对象
|
- BotParam: Bot 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# api hooks
|
||||||
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
|
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
|
||||||
"""`bot.call_api` 钩子函数"""
|
"""`bot.call_api` 钩子函数"""
|
||||||
T_CalledAPIHook = Callable[
|
T_CalledAPIHook = Callable[
|
||||||
@@ -69,7 +78,8 @@ T_CalledAPIHook = Callable[
|
|||||||
]
|
]
|
||||||
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
||||||
|
|
||||||
T_EventPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
# event hooks
|
||||||
|
T_EventPreProcessor = _DependentCallable[Any]
|
||||||
"""事件预处理函数 EventPreProcessor 类型
|
"""事件预处理函数 EventPreProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -80,7 +90,7 @@ T_EventPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
|||||||
- StateParam: State 对象
|
- StateParam: State 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_EventPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
T_EventPostProcessor = _DependentCallable[Any]
|
||||||
"""事件预处理函数 EventPostProcessor 类型
|
"""事件预处理函数 EventPostProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -91,7 +101,9 @@ T_EventPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
|||||||
- StateParam: State 对象
|
- StateParam: State 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_RunPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
|
||||||
|
# matcher run hooks
|
||||||
|
T_RunPreProcessor = _DependentCallable[Any]
|
||||||
"""事件响应器运行前预处理函数 RunPreProcessor 类型
|
"""事件响应器运行前预处理函数 RunPreProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -103,7 +115,7 @@ T_RunPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_RunPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
T_RunPostProcessor = _DependentCallable[Any]
|
||||||
"""事件响应器运行后后处理函数 RunPostProcessor 类型
|
"""事件响应器运行后后处理函数 RunPostProcessor 类型
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -117,7 +129,8 @@ T_RunPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
|||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
|
|
||||||
T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
# rule, permission
|
||||||
|
T_RuleChecker = _DependentCallable[bool]
|
||||||
"""RuleChecker 即判断是否响应事件的处理函数。
|
"""RuleChecker 即判断是否响应事件的处理函数。
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -128,7 +141,7 @@ T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
|||||||
- StateParam: State 对象
|
- StateParam: State 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_PermissionChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
T_PermissionChecker = _DependentCallable[bool]
|
||||||
"""PermissionChecker 即判断事件是否满足权限的处理函数。
|
"""PermissionChecker 即判断事件是否满足权限的处理函数。
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -139,9 +152,9 @@ T_PermissionChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
|||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
|
|
||||||
T_Handler = Callable[..., Any]
|
T_Handler = _DependentCallable[Any]
|
||||||
"""Handler 处理函数。"""
|
"""Handler 处理函数。"""
|
||||||
T_TypeUpdater = Callable[..., Union[str, Awaitable[str]]]
|
T_TypeUpdater = _DependentCallable[str]
|
||||||
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 `message`。
|
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 `message`。
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -153,7 +166,7 @@ T_TypeUpdater = Callable[..., Union[str, Awaitable[str]]]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_PermissionUpdater = Callable[..., Union["Permission", Awaitable["Permission"]]]
|
T_PermissionUpdater = _DependentCallable["Permission"]
|
||||||
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。默认会更新为当前事件的触发对象。
|
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。默认会更新为当前事件的触发对象。
|
||||||
|
|
||||||
依赖参数:
|
依赖参数:
|
||||||
@@ -165,5 +178,5 @@ T_PermissionUpdater = Callable[..., Union["Permission", Awaitable["Permission"]]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_DependencyCache = Dict[Callable[..., Any], "Task[Any]"]
|
T_DependencyCache = Dict[_DependentCallable[Any], "Task[Any]"]
|
||||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||||
|
@@ -10,6 +10,7 @@ import json
|
|||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
from pathlib import Path
|
||||||
from functools import wraps, partial
|
from functools import wraps, partial
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing_extensions import ParamSpec, get_args, get_origin
|
from typing_extensions import ParamSpec, get_args, get_origin
|
||||||
@@ -24,6 +25,7 @@ from typing import (
|
|||||||
Coroutine,
|
Coroutine,
|
||||||
AsyncGenerator,
|
AsyncGenerator,
|
||||||
ContextManager,
|
ContextManager,
|
||||||
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydantic.typing import is_union, is_none_type
|
from pydantic.typing import is_union, is_none_type
|
||||||
@@ -129,11 +131,28 @@ async def run_sync_ctx_manager(
|
|||||||
await run_sync(cm.__exit__)(None, None, None)
|
await run_sync(cm.__exit__)(None, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
async def run_coro_with_catch(
|
async def run_coro_with_catch(
|
||||||
coro: Coroutine[Any, Any, T],
|
coro: Coroutine[Any, Any, T],
|
||||||
exc: Tuple[Type[Exception], ...],
|
exc: Tuple[Type[Exception], ...],
|
||||||
return_on_err: R = None,
|
) -> Union[T, None]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
async def run_coro_with_catch(
|
||||||
|
coro: Coroutine[Any, Any, T],
|
||||||
|
exc: Tuple[Type[Exception], ...],
|
||||||
|
return_on_err: R,
|
||||||
) -> Union[T, R]:
|
) -> Union[T, R]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
async def run_coro_with_catch(
|
||||||
|
coro: Coroutine[Any, Any, T],
|
||||||
|
exc: Tuple[Type[Exception], ...],
|
||||||
|
return_on_err: Optional[R] = None,
|
||||||
|
) -> Optional[Union[T, R]]:
|
||||||
try:
|
try:
|
||||||
return await coro
|
return await coro
|
||||||
except exc:
|
except exc:
|
||||||
@@ -147,6 +166,14 @@ def get_name(obj: Any) -> str:
|
|||||||
return obj.__class__.__name__
|
return obj.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def path_to_module_name(path: Path) -> str:
|
||||||
|
rel_path = path.resolve().relative_to(Path(".").resolve())
|
||||||
|
if rel_path.stem == "__init__":
|
||||||
|
return ".".join(rel_path.parts[:-1])
|
||||||
|
else:
|
||||||
|
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
|
||||||
|
|
||||||
|
|
||||||
class DataclassEncoder(json.JSONEncoder):
|
class DataclassEncoder(json.JSONEncoder):
|
||||||
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
|
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
|
||||||
|
|
||||||
@@ -173,7 +200,7 @@ def logger_wrapper(logger_name: str):
|
|||||||
|
|
||||||
def log(level: str, message: str, exception: Optional[Exception] = None):
|
def log(level: str, message: str, exception: Optional[Exception] = None):
|
||||||
logger.opt(colors=True, exception=exception).log(
|
logger.opt(colors=True, exception=exception).log(
|
||||||
level, f"<m>{escape_tag(logger_name)}</m> | " + message
|
level, f"<m>{escape_tag(logger_name)}</m> | {message}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return log
|
return log
|
||||||
|
@@ -17,7 +17,7 @@ _✨ NoneBot 本地文档插件 ✨_
|
|||||||
<a href="https://pypi.python.org/pypi/nonebot-plugin-docs">
|
<a href="https://pypi.python.org/pypi/nonebot-plugin-docs">
|
||||||
<img src="https://img.shields.io/pypi/v/nonebot-plugin-docs.svg" alt="pypi">
|
<img src="https://img.shields.io/pypi/v/nonebot-plugin-docs.svg" alt="pypi">
|
||||||
</a>
|
</a>
|
||||||
<img src="https://img.shields.io/badge/python-3.7+-blue.svg" alt="python">
|
<img src="https://img.shields.io/badge/python-3.8+-blue.svg" alt="python">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 使用方式
|
## 使用方式
|
||||||
|
@@ -12,7 +12,7 @@ include = ["nonebot_plugin_docs/dist/**/*"]
|
|||||||
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7.3"
|
python = "^3.8"
|
||||||
nonebot2 = "^2.0.0-beta.1"
|
nonebot2 = "^2.0.0-beta.1"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
|
1392
poetry.lock
generated
1392
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.0.0-beta.5"
|
version = "2.0.0rc2"
|
||||||
description = "An asynchronous python bot framework."
|
description = "An asynchronous python bot framework."
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -22,29 +22,34 @@ packages = [
|
|||||||
include = ["nonebot/py.typed"]
|
include = ["nonebot/py.typed"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7.3"
|
python = "^3.8"
|
||||||
yarl = "^1.7.2"
|
yarl = "^1.7.2"
|
||||||
loguru = "^0.6.0"
|
loguru = "^0.6.0"
|
||||||
pygtrie = "^2.4.1"
|
pygtrie = "^2.4.1"
|
||||||
fastapi = "^0.79.0"
|
fastapi = ">=0.87.0,<1.0.0"
|
||||||
tomlkit = ">=0.10.0,<1.0.0"
|
tomlkit = ">=0.10.0,<1.0.0"
|
||||||
typing-extensions = ">=3.10.0,<5.0.0"
|
typing-extensions = ">=3.10.0,<5.0.0"
|
||||||
Quart = { version = "^0.17.0", optional = true }
|
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
|
||||||
websockets = { version="^10.0", optional = true }
|
uvicorn = { version = ">=0.20.0,<1.0.0", extras = ["standard"] }
|
||||||
pydantic = { version = "~1.9.0", extras = ["dotenv"] }
|
|
||||||
uvicorn = { version = "^0.18.0", extras = ["standard"] }
|
|
||||||
aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }
|
|
||||||
httpx = { version = ">=0.20.0, <1.0.0", extras = ["http2"], optional = true }
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
websockets = { version="^10.0", optional = true }
|
||||||
|
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
||||||
|
aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }
|
||||||
|
httpx = { version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true }
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
isort = "^5.10.1"
|
isort = "^5.10.1"
|
||||||
black = "^22.1.0"
|
black = "^22.1.0"
|
||||||
nonemoji = "^0.1.2"
|
nonemoji = "^0.1.2"
|
||||||
pytest-cov = "^3.0.0"
|
|
||||||
pre-commit = "^2.16.0"
|
pre-commit = "^2.16.0"
|
||||||
pytest-xdist = "^2.5.0"
|
|
||||||
pytest-asyncio = "^0.19.0"
|
[tool.poetry.group.test.dependencies]
|
||||||
|
pytest-cov = "^4.0.0"
|
||||||
|
pytest-xdist = "^3.0.2"
|
||||||
|
pytest-asyncio = "^0.20.0"
|
||||||
nonebug = { git = "https://github.com/nonebot/nonebug.git" }
|
nonebug = { git = "https://github.com/nonebot/nonebug.git" }
|
||||||
|
|
||||||
|
[tool.poetry.group.docs.dependencies]
|
||||||
nb-autodoc = { git = "https://github.com/nonebot/nb-autodoc.git" }
|
nb-autodoc = { git = "https://github.com/nonebot/nb-autodoc.git" }
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
@@ -64,7 +69,7 @@ filterwarnings = [
|
|||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = ["py37", "py38", "py39", "py310"]
|
target-version = ["py38", "py39", "py310", "py311"]
|
||||||
include = '\.pyi?$'
|
include = '\.pyi?$'
|
||||||
extend-exclude = '''
|
extend-exclude = '''
|
||||||
'''
|
'''
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
[report]
|
[report]
|
||||||
exclude_lines =
|
exclude_lines =
|
||||||
def __repr__
|
def __repr__
|
||||||
|
def __str__
|
||||||
pragma: no cover
|
pragma: no cover
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@(abc\.)?abstractmethod
|
@(abc\.)?abstractmethod
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
ENVIRONMENT=test
|
ENVIRONMENT=test
|
||||||
COMMON_CONFIG=common
|
COMMON_CONFIG=common
|
||||||
|
COMMON_OVERRIDE=old
|
||||||
|
@@ -1,5 +1,13 @@
|
|||||||
LOG_LEVEL=TRACE
|
LOG_LEVEL=TRACE
|
||||||
NICKNAME=["test"]
|
NICKNAME=["test"]
|
||||||
SUPERUSERS=["test", "fake:faketest"]
|
SUPERUSERS=["test", "fake:faketest"]
|
||||||
|
COMMON_OVERRIDE=new
|
||||||
CONFIG_FROM_ENV=
|
CONFIG_FROM_ENV=
|
||||||
CONFIG_OVERRIDE=old
|
CONFIG_OVERRIDE=old
|
||||||
|
NESTED_DICT={"a": 1}
|
||||||
|
NESTED_DICT__B=2
|
||||||
|
NESTED_DICT__C__D=3
|
||||||
|
NESTED_MISSING_DICT__A=1
|
||||||
|
NESTED_MISSING_DICT__B__C=2
|
||||||
|
NOT_NESTED=some string
|
||||||
|
NOT_NESTED__A=1
|
||||||
|
1
tests/plugins.invalid.json
Normal file
1
tests/plugins.invalid.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[]
|
2
tests/plugins.invalid.toml
Normal file
2
tests/plugins.invalid.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[tool]
|
||||||
|
nonebot = []
|
4
tests/plugins.json
Normal file
4
tests/plugins.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"plugins": [],
|
||||||
|
"plugin_dirs": ["plugins"]
|
||||||
|
}
|
3
tests/plugins.toml
Normal file
3
tests/plugins.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tool.nonebot]
|
||||||
|
plugins = []
|
||||||
|
plugin_dirs = ["plugins"]
|
@@ -1,6 +1,2 @@
|
|||||||
from nonebot import export
|
|
||||||
|
|
||||||
|
|
||||||
@export()
|
|
||||||
def test():
|
def test():
|
||||||
return "export"
|
return "export"
|
||||||
|
@@ -3,5 +3,7 @@ from datetime import datetime, timedelta
|
|||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
|
|
||||||
test_temp_matcher = Matcher.new("test", temp=True)
|
test_temp_matcher = Matcher.new("test", temp=True)
|
||||||
test_datetime_matcher = Matcher.new("test", expire_time=datetime.now())
|
test_datetime_matcher = Matcher.new(
|
||||||
|
"test", expire_time=datetime.now() - timedelta(seconds=1)
|
||||||
|
)
|
||||||
test_timedelta_matcher = Matcher.new("test", expire_time=timedelta(seconds=-1))
|
test_timedelta_matcher = Matcher.new("test", expire_time=timedelta(seconds=-1))
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import USER, Permission
|
||||||
|
|
||||||
default_permission = Permission()
|
default_permission = Permission()
|
||||||
|
|
||||||
test_permission_updater = Matcher.new(permission=default_permission)
|
test_permission_updater = Matcher.new(permission=default_permission)
|
||||||
|
|
||||||
|
test_user_permission_updater = Matcher.new(
|
||||||
|
permission=USER("test", perm=default_permission)
|
||||||
|
)
|
||||||
|
|
||||||
test_custom_updater = Matcher.new(permission=default_permission)
|
test_custom_updater = Matcher.new(permission=default_permission)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -4,10 +4,14 @@ from nonebot.typing import T_State
|
|||||||
from nonebot.adapters import Message
|
from nonebot.adapters import Message
|
||||||
from nonebot.params import (
|
from nonebot.params import (
|
||||||
Command,
|
Command,
|
||||||
|
Keyword,
|
||||||
|
Endswith,
|
||||||
|
Fullmatch,
|
||||||
RegexDict,
|
RegexDict,
|
||||||
CommandArg,
|
CommandArg,
|
||||||
RawCommand,
|
RawCommand,
|
||||||
RegexGroup,
|
RegexGroup,
|
||||||
|
Startswith,
|
||||||
CommandStart,
|
CommandStart,
|
||||||
RegexMatched,
|
RegexMatched,
|
||||||
ShellCommandArgs,
|
ShellCommandArgs,
|
||||||
@@ -65,3 +69,19 @@ async def regex_group(regex_group: Tuple = RegexGroup()) -> Tuple:
|
|||||||
|
|
||||||
async def regex_matched(regex_matched: str = RegexMatched()) -> str:
|
async def regex_matched(regex_matched: str = RegexMatched()) -> str:
|
||||||
return regex_matched
|
return regex_matched
|
||||||
|
|
||||||
|
|
||||||
|
async def startswith(startswith: str = Startswith()) -> str:
|
||||||
|
return startswith
|
||||||
|
|
||||||
|
|
||||||
|
async def endswith(endswith: str = Endswith()) -> str:
|
||||||
|
return endswith
|
||||||
|
|
||||||
|
|
||||||
|
async def fullmatch(fullmatch: str = Fullmatch()) -> str:
|
||||||
|
return fullmatch
|
||||||
|
|
||||||
|
|
||||||
|
async def keyword(keyword: str = Keyword()) -> str:
|
||||||
|
return keyword
|
||||||
|
1
tests/plugins/plugin/__init__.py
Normal file
1
tests/plugins/plugin/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import matchers
|
243
tests/plugins/plugin/matchers.py
Normal file
243
tests/plugins/plugin/matchers.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from nonebot.adapters import Event
|
||||||
|
from nonebot import (
|
||||||
|
CommandGroup,
|
||||||
|
MatcherGroup,
|
||||||
|
on,
|
||||||
|
on_type,
|
||||||
|
on_regex,
|
||||||
|
on_notice,
|
||||||
|
on_command,
|
||||||
|
on_keyword,
|
||||||
|
on_message,
|
||||||
|
on_request,
|
||||||
|
on_endswith,
|
||||||
|
on_fullmatch,
|
||||||
|
on_metaevent,
|
||||||
|
on_startswith,
|
||||||
|
on_shell_command,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def rule() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def permission() -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def handler():
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
expire_time = datetime.now(timezone.utc)
|
||||||
|
priority = 100
|
||||||
|
state = {"test": "test"}
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on = on(
|
||||||
|
"test",
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_metaevent = on_metaevent(
|
||||||
|
rule=rule,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_message = on_message(
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_notice = on_notice(
|
||||||
|
rule=rule,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_request = on_request(
|
||||||
|
rule=rule,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_startswith = on_startswith(
|
||||||
|
"test",
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_endswith = on_endswith(
|
||||||
|
"test",
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_fullmatch = on_fullmatch(
|
||||||
|
"test",
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_keyword = on_keyword(
|
||||||
|
{"test"},
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_command = on_command(
|
||||||
|
"test",
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_shell_command = on_shell_command(
|
||||||
|
"test",
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_regex = on_regex(
|
||||||
|
"test",
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEvent(Event):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
matcher_on_type = on_type(
|
||||||
|
TestEvent,
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
cmd_group = CommandGroup(
|
||||||
|
"test",
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
matcher_sub_cmd = cmd_group.command("sub")
|
||||||
|
matcher_sub_shell_cmd = cmd_group.shell_command("sub")
|
||||||
|
|
||||||
|
|
||||||
|
matcher_group = MatcherGroup(
|
||||||
|
rule=rule,
|
||||||
|
permission=permission,
|
||||||
|
handlers=[handler],
|
||||||
|
temp=True,
|
||||||
|
expire_time=expire_time,
|
||||||
|
priority=priority,
|
||||||
|
block=True,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
matcher_group_on = matcher_group.on(type="test")
|
||||||
|
matcher_group_on_metaevent = matcher_group.on_metaevent()
|
||||||
|
matcher_group_on_message = matcher_group.on_message()
|
||||||
|
matcher_group_on_notice = matcher_group.on_notice()
|
||||||
|
matcher_group_on_request = matcher_group.on_request()
|
||||||
|
matcher_group_on_startswith = matcher_group.on_startswith("test")
|
||||||
|
matcher_group_on_endswith = matcher_group.on_endswith("test")
|
||||||
|
matcher_group_on_fullmatch = matcher_group.on_fullmatch("test")
|
||||||
|
matcher_group_on_keyword = matcher_group.on_keyword({"test"})
|
||||||
|
matcher_group_on_command = matcher_group.on_command("test")
|
||||||
|
matcher_group_on_shell_command = matcher_group.on_shell_command("test")
|
||||||
|
matcher_group_on_regex = matcher_group.on_regex("test")
|
||||||
|
matcher_group_on_type = matcher_group.on_type(TestEvent)
|
@@ -11,11 +11,11 @@ def test_template_basis():
|
|||||||
|
|
||||||
def test_template_message():
|
def test_template_message():
|
||||||
Message = make_fake_message()
|
Message = make_fake_message()
|
||||||
template = Message.template("{a:custom}{b:text}{c:image}")
|
template = Message.template("{a:custom}{b:text}{c:image}/{d}")
|
||||||
|
|
||||||
@template.add_format_spec
|
@template.add_format_spec
|
||||||
def custom(input: str) -> str:
|
def custom(input: str) -> str:
|
||||||
return input + "-custom!"
|
return f"{input}-custom!"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
template.add_format_spec(custom)
|
template.add_format_spec(custom)
|
||||||
@@ -24,12 +24,17 @@ def test_template_message():
|
|||||||
else:
|
else:
|
||||||
raise AssertionError("Should raise ValueError")
|
raise AssertionError("Should raise ValueError")
|
||||||
|
|
||||||
format_args = {"a": "custom", "b": "text", "c": "https://example.com/test"}
|
format_args = {
|
||||||
|
"a": "custom",
|
||||||
|
"b": "text",
|
||||||
|
"c": "https://example.com/test",
|
||||||
|
"d": 114,
|
||||||
|
}
|
||||||
formatted = template.format(**format_args)
|
formatted = template.format(**format_args)
|
||||||
|
|
||||||
assert template.format_map(format_args) == formatted
|
assert template.format_map(format_args) == formatted
|
||||||
assert formatted.extract_plain_text() == "custom-custom!text"
|
assert formatted.extract_plain_text() == "custom-custom!text/114"
|
||||||
assert str(formatted) == "custom-custom!text[fake:image]"
|
assert str(formatted) == "custom-custom!text[fake:image]/114"
|
||||||
|
|
||||||
|
|
||||||
def test_rich_template_message():
|
def test_rich_template_message():
|
||||||
|
@@ -78,3 +78,27 @@ async def test_reverse_driver(app: App):
|
|||||||
assert await ws.receive_bytes() == b"pong"
|
assert await ws.receive_bytes() == b"pong"
|
||||||
|
|
||||||
await ws.close()
|
await ws.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"nonebug_init, driver_type",
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
{"driver": "nonebot.drivers.fastapi:Driver+nonebot.drivers.aiohttp:Mixin"},
|
||||||
|
"fastapi+aiohttp",
|
||||||
|
id="fastapi+aiohttp",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
{"driver": "~httpx:Driver+~websockets"},
|
||||||
|
"block_driver+httpx+websockets",
|
||||||
|
id="httpx+websockets",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
indirect=["nonebug_init"],
|
||||||
|
)
|
||||||
|
async def test_combine_driver(app: App, driver_type: str):
|
||||||
|
import nonebot
|
||||||
|
|
||||||
|
driver = nonebot.get_driver()
|
||||||
|
assert driver.type == driver_type
|
||||||
|
@@ -29,6 +29,10 @@ async def test_init(nonebug_init):
|
|||||||
assert config.config_override == "new"
|
assert config.config_override == "new"
|
||||||
assert config.config_from_init == "init"
|
assert config.config_from_init == "init"
|
||||||
assert config.common_config == "common"
|
assert config.common_config == "common"
|
||||||
|
assert config.common_override == "new"
|
||||||
|
assert config.nested_dict == {"a": 1, "b": 2, "c": {"d": 3}}
|
||||||
|
assert config.nested_missing_dict == {"a": 1, "b": {"c": 2}}
|
||||||
|
assert config.not_nested == "some string"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -61,7 +65,7 @@ async def test_get(monkeypatch: pytest.MonkeyPatch, nonebug_clear):
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_bot()
|
get_bot()
|
||||||
|
|
||||||
monkeypatch.setattr(driver, "_clients", {"test": "test"})
|
monkeypatch.setattr(driver, "_bots", {"test": "test"})
|
||||||
assert get_bot() == "test"
|
assert get_bot() == "test"
|
||||||
assert get_bot("test") == "test"
|
assert get_bot("test") == "test"
|
||||||
assert get_bots() == {"test": "test"}
|
assert get_bots() == {"test": "test"}
|
||||||
|
@@ -104,6 +104,7 @@ async def test_permission_updater(app: App, load_plugin):
|
|||||||
default_permission,
|
default_permission,
|
||||||
test_custom_updater,
|
test_custom_updater,
|
||||||
test_permission_updater,
|
test_permission_updater,
|
||||||
|
test_user_permission_updater,
|
||||||
)
|
)
|
||||||
|
|
||||||
event = make_fake_event(_session_id="test")()
|
event = make_fake_event(_session_id="test")()
|
||||||
@@ -119,6 +120,19 @@ async def test_permission_updater(app: App, load_plugin):
|
|||||||
assert checker.users == ("test",)
|
assert checker.users == ("test",)
|
||||||
assert checker.perm is default_permission
|
assert checker.perm is default_permission
|
||||||
|
|
||||||
|
user_permission = list(test_user_permission_updater.permission.checkers)[0].call
|
||||||
|
assert isinstance(user_permission, User)
|
||||||
|
assert user_permission.perm is default_permission
|
||||||
|
async with app.test_api() as ctx:
|
||||||
|
bot = ctx.create_bot()
|
||||||
|
matcher = test_user_permission_updater()
|
||||||
|
new_perm = await matcher.update_permission(bot, event)
|
||||||
|
assert len(new_perm.checkers) == 1
|
||||||
|
checker = list(new_perm.checkers)[0].call
|
||||||
|
assert isinstance(checker, User)
|
||||||
|
assert checker.users == ("test",)
|
||||||
|
assert checker.perm is default_permission
|
||||||
|
|
||||||
assert test_custom_updater.permission is default_permission
|
assert test_custom_updater.permission is default_permission
|
||||||
async with app.test_api() as ctx:
|
async with app.test_api() as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
11
tests/test_matcher/test_provider.py
Normal file
11
tests/test_matcher/test_provider.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import pytest
|
||||||
|
from nonebug import App
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_manager(app: App, load_plugin):
|
||||||
|
from nonebot.matcher import DEFAULT_PROVIDER_CLASS, matchers
|
||||||
|
|
||||||
|
default_provider = matchers.provider
|
||||||
|
matchers.set_provider(DEFAULT_PROVIDER_CLASS)
|
||||||
|
assert matchers.provider == default_provider
|
@@ -168,15 +168,23 @@ async def test_state(app: App, load_plugin):
|
|||||||
SHELL_ARGS,
|
SHELL_ARGS,
|
||||||
SHELL_ARGV,
|
SHELL_ARGV,
|
||||||
CMD_ARG_KEY,
|
CMD_ARG_KEY,
|
||||||
|
KEYWORD_KEY,
|
||||||
RAW_CMD_KEY,
|
RAW_CMD_KEY,
|
||||||
REGEX_GROUP,
|
REGEX_GROUP,
|
||||||
|
ENDSWITH_KEY,
|
||||||
CMD_START_KEY,
|
CMD_START_KEY,
|
||||||
|
FULLMATCH_KEY,
|
||||||
REGEX_MATCHED,
|
REGEX_MATCHED,
|
||||||
|
STARTSWITH_KEY,
|
||||||
)
|
)
|
||||||
from plugins.param.param_state import (
|
from plugins.param.param_state import (
|
||||||
state,
|
state,
|
||||||
command,
|
command,
|
||||||
|
keyword,
|
||||||
|
endswith,
|
||||||
|
fullmatch,
|
||||||
regex_dict,
|
regex_dict,
|
||||||
|
startswith,
|
||||||
command_arg,
|
command_arg,
|
||||||
raw_command,
|
raw_command,
|
||||||
regex_group,
|
regex_group,
|
||||||
@@ -201,6 +209,10 @@ async def test_state(app: App, load_plugin):
|
|||||||
REGEX_MATCHED: "[cq:test,arg=value]",
|
REGEX_MATCHED: "[cq:test,arg=value]",
|
||||||
REGEX_GROUP: ("test", "arg=value"),
|
REGEX_GROUP: ("test", "arg=value"),
|
||||||
REGEX_DICT: {"type": "test", "arg": "value"},
|
REGEX_DICT: {"type": "test", "arg": "value"},
|
||||||
|
STARTSWITH_KEY: "startswith",
|
||||||
|
ENDSWITH_KEY: "endswith",
|
||||||
|
FULLMATCH_KEY: "fullmatch",
|
||||||
|
KEYWORD_KEY: "keyword",
|
||||||
}
|
}
|
||||||
|
|
||||||
async with app.test_dependent(state, allow_types=[StateParam]) as ctx:
|
async with app.test_dependent(state, allow_types=[StateParam]) as ctx:
|
||||||
@@ -271,6 +283,30 @@ async def test_state(app: App, load_plugin):
|
|||||||
ctx.pass_params(state=fake_state)
|
ctx.pass_params(state=fake_state)
|
||||||
ctx.should_return(fake_state[REGEX_DICT])
|
ctx.should_return(fake_state[REGEX_DICT])
|
||||||
|
|
||||||
|
async with app.test_dependent(
|
||||||
|
startswith, allow_types=[StateParam, DependParam]
|
||||||
|
) as ctx:
|
||||||
|
ctx.pass_params(state=fake_state)
|
||||||
|
ctx.should_return(fake_state[STARTSWITH_KEY])
|
||||||
|
|
||||||
|
async with app.test_dependent(
|
||||||
|
endswith, allow_types=[StateParam, DependParam]
|
||||||
|
) as ctx:
|
||||||
|
ctx.pass_params(state=fake_state)
|
||||||
|
ctx.should_return(fake_state[ENDSWITH_KEY])
|
||||||
|
|
||||||
|
async with app.test_dependent(
|
||||||
|
fullmatch, allow_types=[StateParam, DependParam]
|
||||||
|
) as ctx:
|
||||||
|
ctx.pass_params(state=fake_state)
|
||||||
|
ctx.should_return(fake_state[FULLMATCH_KEY])
|
||||||
|
|
||||||
|
async with app.test_dependent(
|
||||||
|
keyword, allow_types=[StateParam, DependParam]
|
||||||
|
) as ctx:
|
||||||
|
ctx.pass_params(state=fake_state)
|
||||||
|
ctx.should_return(fake_state[KEYWORD_KEY])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher(app: App, load_plugin):
|
async def test_matcher(app: App, load_plugin):
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import Tuple, Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
|
||||||
@@ -142,10 +144,11 @@ async def test_metaevent(
|
|||||||
("message", "test", True),
|
("message", "test", True),
|
||||||
("message", "foo", False),
|
("message", "foo", False),
|
||||||
("message", "faketest", True),
|
("message", "faketest", True),
|
||||||
("notice", "test", False),
|
("message", None, False),
|
||||||
|
("notice", "test", True),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_startswith(
|
async def test_superuser(
|
||||||
app: App,
|
app: App,
|
||||||
type: str,
|
type: str,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
@@ -163,3 +166,29 @@ async def test_startswith(
|
|||||||
async with app.test_api() as ctx:
|
async with app.test_api() as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
assert await dependent(bot=bot, event=event) == expected
|
assert await dependent(bot=bot, event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"session_ids,session_id,expected",
|
||||||
|
[
|
||||||
|
(("user", "foo"), "user", True),
|
||||||
|
(("user", "foo"), "bar", False),
|
||||||
|
(("user", "foo"), None, False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_user(
|
||||||
|
app: App, session_ids: Tuple[str, ...], session_id: Optional[str], expected: bool
|
||||||
|
):
|
||||||
|
from nonebot.permission import USER, User
|
||||||
|
|
||||||
|
dependent = list(USER(*session_ids).checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
|
||||||
|
assert isinstance(checker, User)
|
||||||
|
|
||||||
|
event = make_fake_event(_session_id=session_id)()
|
||||||
|
|
||||||
|
async with app.test_api() as ctx:
|
||||||
|
bot = ctx.create_bot()
|
||||||
|
assert await dependent(bot=bot, event=event) == expected
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from typing import TYPE_CHECKING, Set
|
from typing import TYPE_CHECKING, Set
|
||||||
|
|
||||||
@@ -10,7 +11,21 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_load_plugin(app: App, load_plugin: Set["Plugin"]):
|
async def test_load_plugin(app: App):
|
||||||
|
import nonebot
|
||||||
|
|
||||||
|
# check regular
|
||||||
|
assert nonebot.load_plugin("plugins.metadata")
|
||||||
|
|
||||||
|
# check path
|
||||||
|
assert nonebot.load_plugin(Path("plugins/export"))
|
||||||
|
|
||||||
|
# check not found
|
||||||
|
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_plugins(app: App, load_plugin: Set["Plugin"]):
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.plugin import PluginManager
|
from nonebot.plugin import PluginManager
|
||||||
|
|
||||||
@@ -34,9 +49,6 @@ async def test_load_plugin(app: App, load_plugin: Set["Plugin"]):
|
|||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
PluginManager(search_path=["plugins"]).load_all_plugins()
|
PluginManager(search_path=["plugins"]).load_all_plugins()
|
||||||
|
|
||||||
# check not found
|
|
||||||
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_load_nested_plugin(app: App, load_plugin: Set["Plugin"]):
|
async def test_load_nested_plugin(app: App, load_plugin: Set["Plugin"]):
|
||||||
@@ -51,6 +63,29 @@ async def test_load_nested_plugin(app: App, load_plugin: Set["Plugin"]):
|
|||||||
assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}
|
assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_json(app: App):
|
||||||
|
import nonebot
|
||||||
|
|
||||||
|
nonebot.load_from_json("./plugins.json")
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
nonebot.load_from_json("./plugins.invalid.json")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_toml(app: App):
|
||||||
|
import nonebot
|
||||||
|
|
||||||
|
nonebot.load_from_toml("./plugins.toml")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
nonebot.load_from_toml("./plugins.empty.toml")
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
nonebot.load_from_toml("./plugins.invalid.toml")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_bad_plugin(app: App):
|
async def test_bad_plugin(app: App):
|
||||||
import nonebot
|
import nonebot
|
||||||
|
116
tests/test_plugin/test_on.py
Normal file
116
tests/test_plugin/test_on.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
from typing import Type, Optional
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from nonebug import App
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_on(app: App, load_plugin):
|
||||||
|
import nonebot
|
||||||
|
import plugins.plugin.matchers as module
|
||||||
|
from nonebot.typing import T_RuleChecker
|
||||||
|
from nonebot.matcher import Matcher, matchers
|
||||||
|
from nonebot.rule import (
|
||||||
|
RegexRule,
|
||||||
|
IsTypeRule,
|
||||||
|
CommandRule,
|
||||||
|
EndswithRule,
|
||||||
|
KeywordsRule,
|
||||||
|
FullmatchRule,
|
||||||
|
StartswithRule,
|
||||||
|
ShellCommandRule,
|
||||||
|
)
|
||||||
|
from plugins.plugin.matchers import (
|
||||||
|
TestEvent,
|
||||||
|
rule,
|
||||||
|
state,
|
||||||
|
handler,
|
||||||
|
priority,
|
||||||
|
matcher_on,
|
||||||
|
permission,
|
||||||
|
expire_time,
|
||||||
|
matcher_on_type,
|
||||||
|
matcher_sub_cmd,
|
||||||
|
matcher_group_on,
|
||||||
|
matcher_on_regex,
|
||||||
|
matcher_on_notice,
|
||||||
|
matcher_on_command,
|
||||||
|
matcher_on_keyword,
|
||||||
|
matcher_on_message,
|
||||||
|
matcher_on_request,
|
||||||
|
matcher_on_endswith,
|
||||||
|
matcher_on_fullmatch,
|
||||||
|
matcher_on_metaevent,
|
||||||
|
matcher_group_on_type,
|
||||||
|
matcher_on_startswith,
|
||||||
|
matcher_sub_shell_cmd,
|
||||||
|
matcher_group_on_regex,
|
||||||
|
matcher_group_on_notice,
|
||||||
|
matcher_group_on_command,
|
||||||
|
matcher_group_on_keyword,
|
||||||
|
matcher_group_on_message,
|
||||||
|
matcher_group_on_request,
|
||||||
|
matcher_on_shell_command,
|
||||||
|
matcher_group_on_endswith,
|
||||||
|
matcher_group_on_fullmatch,
|
||||||
|
matcher_group_on_metaevent,
|
||||||
|
matcher_group_on_startswith,
|
||||||
|
matcher_group_on_shell_command,
|
||||||
|
)
|
||||||
|
|
||||||
|
plugin = nonebot.get_plugin("plugin")
|
||||||
|
|
||||||
|
def _check(
|
||||||
|
matcher: Type[Matcher],
|
||||||
|
pre_rule: Optional[T_RuleChecker],
|
||||||
|
has_permission: bool,
|
||||||
|
):
|
||||||
|
assert {dependent.call for dependent in matcher.rule.checkers} == (
|
||||||
|
{pre_rule, rule} if pre_rule else {rule}
|
||||||
|
)
|
||||||
|
if has_permission:
|
||||||
|
assert {dependent.call for dependent in matcher.permission.checkers} == {
|
||||||
|
permission
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
assert not matcher.permission.checkers
|
||||||
|
assert [dependent.call for dependent in matcher.handlers] == [handler]
|
||||||
|
assert matcher.temp is True
|
||||||
|
assert matcher.expire_time == expire_time
|
||||||
|
assert matcher in matchers[priority]
|
||||||
|
assert matcher.block is True
|
||||||
|
assert matcher._default_state == state
|
||||||
|
|
||||||
|
assert matcher.plugin is plugin
|
||||||
|
assert matcher.module is module
|
||||||
|
assert matcher.plugin_name == "plugin"
|
||||||
|
assert matcher.module_name == "plugins.plugin.matchers"
|
||||||
|
|
||||||
|
_check(matcher_on, None, True)
|
||||||
|
_check(matcher_on_metaevent, None, False)
|
||||||
|
_check(matcher_on_message, None, True)
|
||||||
|
_check(matcher_on_notice, None, False)
|
||||||
|
_check(matcher_on_request, None, False)
|
||||||
|
_check(matcher_on_startswith, StartswithRule(("test",)), True)
|
||||||
|
_check(matcher_on_endswith, EndswithRule(("test",)), True)
|
||||||
|
_check(matcher_on_fullmatch, FullmatchRule(("test",)), True)
|
||||||
|
_check(matcher_on_keyword, KeywordsRule("test"), True)
|
||||||
|
_check(matcher_on_command, CommandRule([("test",)]), True)
|
||||||
|
_check(matcher_on_shell_command, ShellCommandRule([("test",)], None), True)
|
||||||
|
_check(matcher_on_regex, RegexRule("test"), True)
|
||||||
|
_check(matcher_on_type, IsTypeRule(TestEvent), True)
|
||||||
|
_check(matcher_sub_cmd, CommandRule([("test", "sub")]), True)
|
||||||
|
_check(matcher_sub_shell_cmd, ShellCommandRule([("test", "sub")], None), True)
|
||||||
|
_check(matcher_group_on, None, True)
|
||||||
|
_check(matcher_group_on_metaevent, None, False)
|
||||||
|
_check(matcher_group_on_message, None, True)
|
||||||
|
_check(matcher_group_on_notice, None, False)
|
||||||
|
_check(matcher_group_on_request, None, False)
|
||||||
|
_check(matcher_group_on_startswith, StartswithRule(("test",)), True)
|
||||||
|
_check(matcher_group_on_endswith, EndswithRule(("test",)), True)
|
||||||
|
_check(matcher_group_on_fullmatch, FullmatchRule(("test",)), True)
|
||||||
|
_check(matcher_group_on_keyword, KeywordsRule("test"), True)
|
||||||
|
_check(matcher_group_on_command, CommandRule([("test",)]), True)
|
||||||
|
_check(matcher_group_on_shell_command, ShellCommandRule([("test",)], None), True)
|
||||||
|
_check(matcher_group_on_regex, RegexRule("test"), True)
|
||||||
|
_check(matcher_group_on_type, IsTypeRule(TestEvent), True)
|
@@ -1,4 +1,5 @@
|
|||||||
from typing import Tuple, Union
|
import sys
|
||||||
|
from typing import Dict, Tuple, Union, Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
@@ -51,6 +52,7 @@ async def test_rule(app: App):
|
|||||||
("prefix", True, "message", "Prefix_", True),
|
("prefix", True, "message", "Prefix_", True),
|
||||||
("prefix", False, "message", "prefoo", False),
|
("prefix", False, "message", "prefoo", False),
|
||||||
("prefix", False, "message", "fooprefix", False),
|
("prefix", False, "message", "fooprefix", False),
|
||||||
|
("prefix", False, "message", None, False),
|
||||||
(("prefix", "foo"), False, "message", "fooprefix", True),
|
(("prefix", "foo"), False, "message", "fooprefix", True),
|
||||||
("prefix", False, "notice", "foo", False),
|
("prefix", False, "notice", "foo", False),
|
||||||
],
|
],
|
||||||
@@ -60,22 +62,27 @@ async def test_startswith(
|
|||||||
msg: Union[str, Tuple[str, ...]],
|
msg: Union[str, Tuple[str, ...]],
|
||||||
ignorecase: bool,
|
ignorecase: bool,
|
||||||
type: str,
|
type: str,
|
||||||
text: str,
|
text: Optional[str],
|
||||||
expected: bool,
|
expected: bool,
|
||||||
):
|
):
|
||||||
|
from nonebot.consts import STARTSWITH_KEY
|
||||||
from nonebot.rule import StartswithRule, startswith
|
from nonebot.rule import StartswithRule, startswith
|
||||||
|
|
||||||
test_startswith = startswith(msg, ignorecase)
|
test_startswith = startswith(msg, ignorecase)
|
||||||
dependent = list(test_startswith.checkers)[0]
|
dependent = list(test_startswith.checkers)[0]
|
||||||
checker = dependent.call
|
checker = dependent.call
|
||||||
|
|
||||||
|
msg = (msg,) if isinstance(msg, str) else msg
|
||||||
|
|
||||||
assert isinstance(checker, StartswithRule)
|
assert isinstance(checker, StartswithRule)
|
||||||
assert checker.msg == (msg,) if isinstance(msg, str) else msg
|
assert checker.msg == msg
|
||||||
assert checker.ignorecase == ignorecase
|
assert checker.ignorecase == ignorecase
|
||||||
|
|
||||||
message = make_fake_message()(text)
|
message = text if text is None else make_fake_message()(text)
|
||||||
event = make_fake_event(_type=type, _message=message)()
|
event = make_fake_event(_type=type, _message=message)()
|
||||||
assert await dependent(event=event) == expected
|
for prefix in msg:
|
||||||
|
state = {STARTSWITH_KEY: prefix}
|
||||||
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -88,6 +95,7 @@ async def test_startswith(
|
|||||||
("suffix", True, "message", "_Suffix", True),
|
("suffix", True, "message", "_Suffix", True),
|
||||||
("suffix", False, "message", "suffoo", False),
|
("suffix", False, "message", "suffoo", False),
|
||||||
("suffix", False, "message", "suffixfoo", False),
|
("suffix", False, "message", "suffixfoo", False),
|
||||||
|
("suffix", False, "message", None, False),
|
||||||
(("suffix", "foo"), False, "message", "suffixfoo", True),
|
(("suffix", "foo"), False, "message", "suffixfoo", True),
|
||||||
("suffix", False, "notice", "foo", False),
|
("suffix", False, "notice", "foo", False),
|
||||||
],
|
],
|
||||||
@@ -97,22 +105,27 @@ async def test_endswith(
|
|||||||
msg: Union[str, Tuple[str, ...]],
|
msg: Union[str, Tuple[str, ...]],
|
||||||
ignorecase: bool,
|
ignorecase: bool,
|
||||||
type: str,
|
type: str,
|
||||||
text: str,
|
text: Optional[str],
|
||||||
expected: bool,
|
expected: bool,
|
||||||
):
|
):
|
||||||
|
from nonebot.consts import ENDSWITH_KEY
|
||||||
from nonebot.rule import EndswithRule, endswith
|
from nonebot.rule import EndswithRule, endswith
|
||||||
|
|
||||||
test_endswith = endswith(msg, ignorecase)
|
test_endswith = endswith(msg, ignorecase)
|
||||||
dependent = list(test_endswith.checkers)[0]
|
dependent = list(test_endswith.checkers)[0]
|
||||||
checker = dependent.call
|
checker = dependent.call
|
||||||
|
|
||||||
|
msg = (msg,) if isinstance(msg, str) else msg
|
||||||
|
|
||||||
assert isinstance(checker, EndswithRule)
|
assert isinstance(checker, EndswithRule)
|
||||||
assert checker.msg == (msg,) if isinstance(msg, str) else msg
|
assert checker.msg == msg
|
||||||
assert checker.ignorecase == ignorecase
|
assert checker.ignorecase == ignorecase
|
||||||
|
|
||||||
message = make_fake_message()(text)
|
message = text if text is None else make_fake_message()(text)
|
||||||
event = make_fake_event(_type=type, _message=message)()
|
event = make_fake_event(_type=type, _message=message)()
|
||||||
assert await dependent(event=event) == expected
|
for suffix in msg:
|
||||||
|
state = {ENDSWITH_KEY: suffix}
|
||||||
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -125,6 +138,7 @@ async def test_endswith(
|
|||||||
("fullmatch", True, "message", "Fullmatch", True),
|
("fullmatch", True, "message", "Fullmatch", True),
|
||||||
("fullmatch", False, "message", "fullfoo", False),
|
("fullmatch", False, "message", "fullfoo", False),
|
||||||
("fullmatch", False, "message", "_fullmatch_", False),
|
("fullmatch", False, "message", "_fullmatch_", False),
|
||||||
|
("fullmatch", False, "message", None, False),
|
||||||
(("fullmatch", "foo"), False, "message", "fullmatchfoo", False),
|
(("fullmatch", "foo"), False, "message", "fullmatchfoo", False),
|
||||||
("fullmatch", False, "notice", "foo", False),
|
("fullmatch", False, "notice", "foo", False),
|
||||||
],
|
],
|
||||||
@@ -134,22 +148,27 @@ async def test_fullmatch(
|
|||||||
msg: Union[str, Tuple[str, ...]],
|
msg: Union[str, Tuple[str, ...]],
|
||||||
ignorecase: bool,
|
ignorecase: bool,
|
||||||
type: str,
|
type: str,
|
||||||
text: str,
|
text: Optional[str],
|
||||||
expected: bool,
|
expected: bool,
|
||||||
):
|
):
|
||||||
|
from nonebot.consts import FULLMATCH_KEY
|
||||||
from nonebot.rule import FullmatchRule, fullmatch
|
from nonebot.rule import FullmatchRule, fullmatch
|
||||||
|
|
||||||
test_fullmatch = fullmatch(msg, ignorecase)
|
test_fullmatch = fullmatch(msg, ignorecase)
|
||||||
dependent = list(test_fullmatch.checkers)[0]
|
dependent = list(test_fullmatch.checkers)[0]
|
||||||
checker = dependent.call
|
checker = dependent.call
|
||||||
|
|
||||||
|
msg = (msg,) if isinstance(msg, str) else msg
|
||||||
|
|
||||||
assert isinstance(checker, FullmatchRule)
|
assert isinstance(checker, FullmatchRule)
|
||||||
assert checker.msg == {msg} if isinstance(msg, str) else {*msg}
|
assert checker.msg == msg
|
||||||
assert checker.ignorecase == ignorecase
|
assert checker.ignorecase == ignorecase
|
||||||
|
|
||||||
message = make_fake_message()(text)
|
message = text if text is None else make_fake_message()(text)
|
||||||
event = make_fake_event(_type=type, _message=message)()
|
event = make_fake_event(_type=type, _message=message)()
|
||||||
assert await dependent(event=event) == expected
|
for full in msg:
|
||||||
|
state = {FULLMATCH_KEY: full}
|
||||||
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -158,16 +177,19 @@ async def test_fullmatch(
|
|||||||
[
|
[
|
||||||
(("key",), "message", "_key_", True),
|
(("key",), "message", "_key_", True),
|
||||||
(("key", "foo"), "message", "_foo_", True),
|
(("key", "foo"), "message", "_foo_", True),
|
||||||
|
(("key",), "message", None, False),
|
||||||
(("key",), "notice", "foo", False),
|
(("key",), "notice", "foo", False),
|
||||||
|
(("key",), "message", "foo", False),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_keyword(
|
async def test_keyword(
|
||||||
app: App,
|
app: App,
|
||||||
kws: Tuple[str, ...],
|
kws: Tuple[str, ...],
|
||||||
type: str,
|
type: str,
|
||||||
text: str,
|
text: Optional[str],
|
||||||
expected: bool,
|
expected: bool,
|
||||||
):
|
):
|
||||||
|
from nonebot.consts import KEYWORD_KEY
|
||||||
from nonebot.rule import KeywordsRule, keyword
|
from nonebot.rule import KeywordsRule, keyword
|
||||||
|
|
||||||
test_keyword = keyword(*kws)
|
test_keyword = keyword(*kws)
|
||||||
@@ -177,9 +199,11 @@ async def test_keyword(
|
|||||||
assert isinstance(checker, KeywordsRule)
|
assert isinstance(checker, KeywordsRule)
|
||||||
assert checker.keywords == kws
|
assert checker.keywords == kws
|
||||||
|
|
||||||
message = make_fake_message()(text)
|
message = text if text is None else make_fake_message()(text)
|
||||||
event = make_fake_event(_type=type, _message=message)()
|
event = make_fake_event(_type=type, _message=message)()
|
||||||
assert await dependent(event=event) == expected
|
for kw in kws:
|
||||||
|
state = {KEYWORD_KEY: kw}
|
||||||
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -195,16 +219,157 @@ async def test_command(app: App, cmds: Tuple[Tuple[str, ...]]):
|
|||||||
checker = dependent.call
|
checker = dependent.call
|
||||||
|
|
||||||
assert isinstance(checker, CommandRule)
|
assert isinstance(checker, CommandRule)
|
||||||
assert checker.cmds == list(cmds)
|
assert checker.cmds == cmds
|
||||||
|
|
||||||
for cmd in cmds:
|
for cmd in cmds:
|
||||||
state = {PREFIX_KEY: {CMD_KEY: cmd}}
|
state = {PREFIX_KEY: {CMD_KEY: cmd}}
|
||||||
assert await dependent(state=state)
|
assert await dependent(state=state)
|
||||||
|
|
||||||
|
|
||||||
# TODO: shell command
|
@pytest.mark.asyncio
|
||||||
|
async def test_shell_command(app: App):
|
||||||
|
from nonebot.typing import T_State
|
||||||
|
from nonebot.exception import ParserExit
|
||||||
|
from nonebot.consts import CMD_KEY, PREFIX_KEY, SHELL_ARGS, SHELL_ARGV, CMD_ARG_KEY
|
||||||
|
from nonebot.rule import Namespace, ArgumentParser, ShellCommandRule, shell_command
|
||||||
|
|
||||||
# TODO: regex
|
state: T_State
|
||||||
|
CMD = ("test",)
|
||||||
|
Message = make_fake_message()
|
||||||
|
MessageSegment = Message.get_segment_class()
|
||||||
|
|
||||||
|
test_not_cmd = shell_command(CMD)
|
||||||
|
dependent = list(test_not_cmd.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = Message()
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: ("not",), CMD_ARG_KEY: message}}
|
||||||
|
assert not await dependent(event=event, state=state)
|
||||||
|
|
||||||
|
test_no_parser = shell_command(CMD)
|
||||||
|
dependent = list(test_no_parser.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = Message()
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||||
|
assert await dependent(event=event, state=state)
|
||||||
|
assert state[SHELL_ARGV] == []
|
||||||
|
assert SHELL_ARGS not in state
|
||||||
|
|
||||||
|
parser = ArgumentParser("test")
|
||||||
|
parser.add_argument("-a", required=True)
|
||||||
|
|
||||||
|
test_simple_parser = shell_command(CMD, parser=parser)
|
||||||
|
dependent = list(test_simple_parser.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = Message("-a 1")
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||||
|
assert await dependent(event=event, state=state)
|
||||||
|
assert state[SHELL_ARGV] == ["-a", "1"]
|
||||||
|
assert state[SHELL_ARGS] == Namespace(a="1")
|
||||||
|
|
||||||
|
test_parser_help = shell_command(CMD, parser=parser)
|
||||||
|
dependent = list(test_parser_help.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = Message("-h")
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||||
|
assert await dependent(event=event, state=state)
|
||||||
|
assert state[SHELL_ARGV] == ["-h"]
|
||||||
|
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||||
|
assert state[SHELL_ARGS].status == 0
|
||||||
|
assert state[SHELL_ARGS].message == parser.format_help()
|
||||||
|
|
||||||
|
test_parser_error = shell_command(CMD, parser=parser)
|
||||||
|
dependent = list(test_parser_error.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = Message()
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||||
|
assert await dependent(event=event, state=state)
|
||||||
|
assert state[SHELL_ARGV] == []
|
||||||
|
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||||
|
assert state[SHELL_ARGS].status != 0
|
||||||
|
|
||||||
|
test_message_parser = shell_command(CMD, parser=parser)
|
||||||
|
dependent = list(test_message_parser.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = MessageSegment.text("-a") + MessageSegment.image("test")
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||||
|
assert await dependent(event=event, state=state)
|
||||||
|
assert state[SHELL_ARGV] == ["-a", MessageSegment.image("test")]
|
||||||
|
assert state[SHELL_ARGS] == Namespace(a=MessageSegment.image("test"))
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 9):
|
||||||
|
parser = ArgumentParser("test", exit_on_error=False)
|
||||||
|
parser.add_argument("-a", required=True)
|
||||||
|
|
||||||
|
test_not_exit = shell_command(CMD, parser=parser)
|
||||||
|
dependent = list(test_not_exit.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
assert isinstance(checker, ShellCommandRule)
|
||||||
|
message = Message()
|
||||||
|
event = make_fake_event(_message=message)()
|
||||||
|
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||||
|
assert await dependent(event=event, state=state)
|
||||||
|
assert state[SHELL_ARGV] == []
|
||||||
|
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||||
|
assert state[SHELL_ARGS].status != 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pattern,type,text,expected,matched,group,dict",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
r"(?P<key>key\d)",
|
||||||
|
"message",
|
||||||
|
"_key1_",
|
||||||
|
True,
|
||||||
|
"key1",
|
||||||
|
("key1",),
|
||||||
|
{"key": "key1"},
|
||||||
|
),
|
||||||
|
(r"foo", "message", None, False, None, None, None),
|
||||||
|
(r"foo", "notice", "foo", False, None, None, None),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_regex(
|
||||||
|
app: App,
|
||||||
|
pattern: str,
|
||||||
|
type: str,
|
||||||
|
text: Optional[str],
|
||||||
|
expected: bool,
|
||||||
|
matched: Optional[str],
|
||||||
|
group: Optional[Tuple[str, ...]],
|
||||||
|
dict: Optional[Dict[str, str]],
|
||||||
|
):
|
||||||
|
from nonebot.typing import T_State
|
||||||
|
from nonebot.rule import RegexRule, regex
|
||||||
|
from nonebot.consts import REGEX_DICT, REGEX_GROUP, REGEX_MATCHED
|
||||||
|
|
||||||
|
test_regex = regex(pattern)
|
||||||
|
dependent = list(test_regex.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
|
||||||
|
assert isinstance(checker, RegexRule)
|
||||||
|
assert checker.regex == pattern
|
||||||
|
|
||||||
|
message = text if text is None else make_fake_message()(text)
|
||||||
|
event = make_fake_event(_type=type, _message=message)()
|
||||||
|
state = {}
|
||||||
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
assert state.get(REGEX_MATCHED) == matched
|
||||||
|
assert state.get(REGEX_GROUP) == group
|
||||||
|
assert state.get(REGEX_DICT) == dict
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -212,11 +377,32 @@ async def test_command(app: App, cmds: Tuple[Tuple[str, ...]]):
|
|||||||
async def test_to_me(app: App, expected: bool):
|
async def test_to_me(app: App, expected: bool):
|
||||||
from nonebot.rule import ToMeRule, to_me
|
from nonebot.rule import ToMeRule, to_me
|
||||||
|
|
||||||
test_keyword = to_me()
|
test_to_me = to_me()
|
||||||
dependent = list(test_keyword.checkers)[0]
|
dependent = list(test_to_me.checkers)[0]
|
||||||
checker = dependent.call
|
checker = dependent.call
|
||||||
|
|
||||||
assert isinstance(checker, ToMeRule)
|
assert isinstance(checker, ToMeRule)
|
||||||
|
|
||||||
event = make_fake_event(_to_me=expected)()
|
event = make_fake_event(_to_me=expected)()
|
||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_is_type(app: App):
|
||||||
|
from nonebot.rule import IsTypeRule, is_type
|
||||||
|
|
||||||
|
Event1 = make_fake_event()
|
||||||
|
Event2 = make_fake_event()
|
||||||
|
Event3 = make_fake_event()
|
||||||
|
|
||||||
|
test_type = is_type(Event1, Event2)
|
||||||
|
dependent = list(test_type.checkers)[0]
|
||||||
|
checker = dependent.call
|
||||||
|
|
||||||
|
assert isinstance(checker, IsTypeRule)
|
||||||
|
|
||||||
|
event = Event1()
|
||||||
|
assert await dependent(event=event)
|
||||||
|
|
||||||
|
event = Event3()
|
||||||
|
assert not await dependent(event=event)
|
||||||
|
@@ -65,7 +65,7 @@ def make_fake_event(
|
|||||||
_type: str = "message",
|
_type: str = "message",
|
||||||
_name: str = "test",
|
_name: str = "test",
|
||||||
_description: str = "test",
|
_description: str = "test",
|
||||||
_user_id: str = "test",
|
_user_id: Optional[str] = "test",
|
||||||
_session_id: Optional[str] = "test",
|
_session_id: Optional[str] = "test",
|
||||||
_message: Optional["Message"] = None,
|
_message: Optional["Message"] = None,
|
||||||
_to_me: bool = True,
|
_to_me: bool = True,
|
||||||
@@ -86,7 +86,9 @@ def make_fake_event(
|
|||||||
return _description
|
return _description
|
||||||
|
|
||||||
def get_user_id(self) -> str:
|
def get_user_id(self) -> str:
|
||||||
return _user_id
|
if _user_id is not None:
|
||||||
|
return _user_id
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_session_id(self) -> str:
|
def get_session_id(self) -> str:
|
||||||
if _session_id is not None:
|
if _session_id is not None:
|
||||||
|
@@ -8,7 +8,7 @@ slug: /
|
|||||||
|
|
||||||
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
|
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
|
||||||
|
|
||||||
需要注意的是,NoneBot2 仅支持 **Python 3.7.3 以上版本**
|
需要注意的是,NoneBot2 仅支持 **Python 3.8 以上版本**
|
||||||
|
|
||||||
## 特色
|
## 特色
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ import Asciinema from "@site/src/components/Asciinema";
|
|||||||
# 安装 NoneBot2
|
# 安装 NoneBot2
|
||||||
|
|
||||||
:::warning 注意
|
:::warning 注意
|
||||||
请确保你的 Python 版本 >= 3.7.3。
|
请确保你的 Python 版本 >= 3.8。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::warning 注意
|
:::warning 注意
|
||||||
|
@@ -277,7 +277,7 @@ async def _(foo: str = CommandStart()): ...
|
|||||||
|
|
||||||
### ShellCommandArgs
|
### ShellCommandArgs
|
||||||
|
|
||||||
获取 shell 命令解析后的参数。
|
获取 shell 命令解析后的参数,支持 MessageSegment 富文本(如:图片)。
|
||||||
|
|
||||||
:::tip 提示
|
:::tip 提示
|
||||||
如果参数解析失败,则为 [`ParserExit`](../../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。
|
如果参数解析失败,则为 [`ParserExit`](../../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。
|
||||||
@@ -288,21 +288,28 @@ async def _(foo: str = CommandStart()): ...
|
|||||||
```python {8,12}
|
```python {8,12}
|
||||||
from nonebot import on_shell_command
|
from nonebot import on_shell_command
|
||||||
from nonebot.params import ShellCommandArgs
|
from nonebot.params import ShellCommandArgs
|
||||||
|
from nonebot.rule import Namespace, ArgumentParser
|
||||||
|
|
||||||
|
parser = ArgumentParser("demo")
|
||||||
|
# parser.add_argument ...
|
||||||
matcher = on_shell_command("cmd", parser)
|
matcher = on_shell_command("cmd", parser)
|
||||||
|
|
||||||
# 解析失败
|
# 解析失败
|
||||||
@matcher.handle()
|
@matcher.handle()
|
||||||
async def _(foo: ParserExit = ShellCommandArgs()): ...
|
async def _(foo: ParserExit = ShellCommandArgs()):
|
||||||
|
if foo.status == 0:
|
||||||
|
foo.message # help message
|
||||||
|
else:
|
||||||
|
foo.message # error message
|
||||||
|
|
||||||
# 解析成功
|
# 解析成功
|
||||||
@matcher.handle()
|
@matcher.handle()
|
||||||
async def _(foo: Dict[str, Any] = ShellCommandArgs()): ...
|
async def _(foo: Namespace = ShellCommandArgs()): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### ShellCommandArgv
|
### ShellCommandArgv
|
||||||
|
|
||||||
获取 shell 命令解析前的参数列表。
|
获取 shell 命令解析前的参数列表,支持 MessageSegment 富文本(如:图片)。
|
||||||
|
|
||||||
```python {7}
|
```python {7}
|
||||||
from nonebot import on_shell_command
|
from nonebot import on_shell_command
|
||||||
@@ -311,7 +318,7 @@ from nonebot.params import ShellCommandArgs
|
|||||||
matcher = on_shell_command("cmd")
|
matcher = on_shell_command("cmd")
|
||||||
|
|
||||||
@matcher.handle()
|
@matcher.handle()
|
||||||
async def _(foo: List[str] = ShellCommandArgv()): ...
|
async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### RegexMatched
|
### RegexMatched
|
||||||
@@ -356,6 +363,62 @@ matcher = on_regex("regex")
|
|||||||
async def _(foo: Dict[str, Any] = RegexDict()): ...
|
async def _(foo: Dict[str, Any] = RegexDict()): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Startswith
|
||||||
|
|
||||||
|
获取触发响应器的消息前缀字符串。
|
||||||
|
|
||||||
|
```python {7}
|
||||||
|
from nonebot import on_startswith
|
||||||
|
from nonebot.params import Startswith
|
||||||
|
|
||||||
|
matcher = on_startswith("prefix")
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(foo: str = Startswith()): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Endswith
|
||||||
|
|
||||||
|
获取触发响应器的消息后缀字符串。
|
||||||
|
|
||||||
|
```python {7}
|
||||||
|
from nonebot import on_endswith
|
||||||
|
from nonebot.params import Endswith
|
||||||
|
|
||||||
|
matcher = on_endswith("suffix")
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(foo: str = Endswith()): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fullmatch
|
||||||
|
|
||||||
|
获取触发响应器的消息字符串。
|
||||||
|
|
||||||
|
```python {7}
|
||||||
|
from nonebot import on_fullmatch
|
||||||
|
from nonebot.params import Fullmatch
|
||||||
|
|
||||||
|
matcher = on_fullmatch("fullmatch")
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(foo: str = Fullmatch()): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keyword
|
||||||
|
|
||||||
|
获取触发响应器的关键字字符串。
|
||||||
|
|
||||||
|
```python {7}
|
||||||
|
from nonebot import on_keyword
|
||||||
|
from nonebot.params import Keyword
|
||||||
|
|
||||||
|
matcher = on_keyword({"keyword"})
|
||||||
|
|
||||||
|
@matcher.handle()
|
||||||
|
async def _(foo: str = Keyword()): ...
|
||||||
|
```
|
||||||
|
|
||||||
### Matcher
|
### Matcher
|
||||||
|
|
||||||
获取当前事件响应器实例。
|
获取当前事件响应器实例。
|
||||||
|
@@ -5,6 +5,181 @@ toc_max_heading_level: 2
|
|||||||
|
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
## v2.0.0rc2
|
||||||
|
|
||||||
|
### 💥 破坏性变更
|
||||||
|
|
||||||
|
- Feature: 使用 `importlib.metadata` 替换 `pkg_resources` [@A-kirami](https://github.com/A-kirami) ([#1388](https://github.com/nonebot/nonebot2/pull/1388))
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: 支持自定义 matchers 存储管理 [@yanyongyu](https://github.com/yanyongyu) ([#1395](https://github.com/nonebot/nonebot2/pull/1395))
|
||||||
|
- Feature: 升级 devcontainer 配置 [@yanyongyu](https://github.com/yanyongyu) ([#1392](https://github.com/nonebot/nonebot2/pull/1392))
|
||||||
|
- Feature: 使用 `importlib.metadata` 替换 `pkg_resources` [@A-kirami](https://github.com/A-kirami) ([#1388](https://github.com/nonebot/nonebot2/pull/1388))
|
||||||
|
- CI: 测试环境添加 Python 3.11 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#1366](https://github.com/nonebot/nonebot2/pull/1366))
|
||||||
|
- Feature: 新增 dotenv 嵌套配置项支持 [@yanyongyu](https://github.com/yanyongyu) ([#1324](https://github.com/nonebot/nonebot2/pull/1324))
|
||||||
|
- Feature: 添加 State 响应器触发消息注入 [@A-kirami](https://github.com/A-kirami) ([#1315](https://github.com/nonebot/nonebot2/pull/1315))
|
||||||
|
- Remove: 移除无用的 namespace 声明 [@yanyongyu](https://github.com/yanyongyu) ([#1306](https://github.com/nonebot/nonebot2/pull/1306))
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- Fix: Bot `__getattr__` 不再对 `__xxx__` 方法返回 [@synodriver](https://github.com/synodriver) ([#1398](https://github.com/nonebot/nonebot2/pull/1398))
|
||||||
|
- Fix: 修复 run pre/post hook 没有在正确的上下文中运行 [@yanyongyu](https://github.com/yanyongyu) ([#1391](https://github.com/nonebot/nonebot2/pull/1391))
|
||||||
|
|
||||||
|
### 📝 文档
|
||||||
|
|
||||||
|
- Docs: 添加 ntchat 社区适配器 [@JustUndertaker](https://github.com/JustUndertaker) ([#1414](https://github.com/nonebot/nonebot2/pull/1414))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Plugin: b 站用户信息查询 [@Ikaros-521](https://github.com/Ikaros-521) ([#1410](https://github.com/nonebot/nonebot2/pull/1410))
|
||||||
|
- Plugin: 由于 Sena-nana 项目拆分,之前的插件地址更改 [@sena-nana](https://github.com/sena-nana) ([#1378](https://github.com/nonebot/nonebot2/pull/1378))
|
||||||
|
- Plugin: 更新 ayaka 插件的主页链接 [@bridgeL](https://github.com/bridgeL) ([#1346](https://github.com/nonebot/nonebot2/pull/1346))
|
||||||
|
- Plugin: 补充 novelai 插件信息 [@sena-nana](https://github.com/sena-nana) ([#1333](https://github.com/nonebot/nonebot2/pull/1333))
|
||||||
|
- Bot: 修改 Inkar Suki 描述 [@HornCopper](https://github.com/HornCopper) ([#1312](https://github.com/nonebot/nonebot2/pull/1312))
|
||||||
|
- Plugin: 修改插件 MCQQ MCRcon 主页地址 [@17TheWord](https://github.com/17TheWord) ([#1303](https://github.com/nonebot/nonebot2/pull/1303))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: 谁在窥屏 [@yanyongyu](https://github.com/yanyongyu) ([#1416](https://github.com/nonebot/nonebot2/pull/1416))
|
||||||
|
- Plugin: 免费版 NovelAI 生图插件 [@yanyongyu](https://github.com/yanyongyu) ([#1408](https://github.com/nonebot/nonebot2/pull/1408))
|
||||||
|
- Plugin: sky 光遇 [@yanyongyu](https://github.com/yanyongyu) ([#1394](https://github.com/nonebot/nonebot2/pull/1394))
|
||||||
|
- Plugin: Colab-NovelAI [@yanyongyu](https://github.com/yanyongyu) ([#1390](https://github.com/nonebot/nonebot2/pull/1390))
|
||||||
|
- Plugin: b 站用户直播号、粉丝、舰团数查询 [@yanyongyu](https://github.com/yanyongyu) ([#1385](https://github.com/nonebot/nonebot2/pull/1385))
|
||||||
|
- Plugin: 投胎模拟器 [@yanyongyu](https://github.com/yanyongyu) ([#1382](https://github.com/nonebot/nonebot2/pull/1382))
|
||||||
|
- Plugin: Apex API Query [@yanyongyu](https://github.com/yanyongyu) ([#1375](https://github.com/nonebot/nonebot2/pull/1375))
|
||||||
|
- Plugin: 随个人 [@yanyongyu](https://github.com/yanyongyu) ([#1373](https://github.com/nonebot/nonebot2/pull/1373))
|
||||||
|
- Plugin: 动漫资源获取 [@yanyongyu](https://github.com/yanyongyu) ([#1371](https://github.com/nonebot/nonebot2/pull/1371))
|
||||||
|
- Plugin: 日麻小工具 [@yanyongyu](https://github.com/yanyongyu) ([#1365](https://github.com/nonebot/nonebot2/pull/1365))
|
||||||
|
- Plugin: 图像超分辨率增强 [@yanyongyu](https://github.com/yanyongyu) ([#1362](https://github.com/nonebot/nonebot2/pull/1362))
|
||||||
|
- Plugin: 二次元化图像 [@yanyongyu](https://github.com/yanyongyu) ([#1360](https://github.com/nonebot/nonebot2/pull/1360))
|
||||||
|
- Plugin: 日麻寄分器 [@yanyongyu](https://github.com/yanyongyu) ([#1357](https://github.com/nonebot/nonebot2/pull/1357))
|
||||||
|
- Plugin: 文本生成器 [@yanyongyu](https://github.com/yanyongyu) ([#1355](https://github.com/nonebot/nonebot2/pull/1355))
|
||||||
|
- Plugin: 反嘴臭插件 [@yanyongyu](https://github.com/yanyongyu) ([#1350](https://github.com/nonebot/nonebot2/pull/1350))
|
||||||
|
- Plugin: 用户\&群聊黑名单 [@yanyongyu](https://github.com/yanyongyu) ([#1348](https://github.com/nonebot/nonebot2/pull/1348))
|
||||||
|
- Plugin: NoneBot SQLAlchemy 封装 [@yanyongyu](https://github.com/yanyongyu) ([#1345](https://github.com/nonebot/nonebot2/pull/1345))
|
||||||
|
- Plugin: 通用抽图/语音 [@yanyongyu](https://github.com/yanyongyu) ([#1341](https://github.com/nonebot/nonebot2/pull/1341))
|
||||||
|
- Plugin: kfcrazy [@yanyongyu](https://github.com/yanyongyu) ([#1339](https://github.com/nonebot/nonebot2/pull/1339))
|
||||||
|
- Plugin: 二次元图像鉴赏 [@yanyongyu](https://github.com/yanyongyu) ([#1337](https://github.com/nonebot/nonebot2/pull/1337))
|
||||||
|
- Plugin: ayaka 衍生插件 - 坏词撤回 [@yanyongyu](https://github.com/yanyongyu) ([#1335](https://github.com/nonebot/nonebot2/pull/1335))
|
||||||
|
- Plugin: ayaka 衍生插件 - 时区助手 [@yanyongyu](https://github.com/yanyongyu) ([#1332](https://github.com/nonebot/nonebot2/pull/1332))
|
||||||
|
- Plugin: ayaka 衍生插件 - 谁是卧底 [@yanyongyu](https://github.com/yanyongyu) ([#1330](https://github.com/nonebot/nonebot2/pull/1330))
|
||||||
|
- Plugin: ayaka 衍生插件 - 小游戏合集 [@yanyongyu](https://github.com/yanyongyu) ([#1328](https://github.com/nonebot/nonebot2/pull/1328))
|
||||||
|
- Plugin: bnhhsh -「不能好好说话!」 [@yanyongyu](https://github.com/yanyongyu) ([#1326](https://github.com/nonebot/nonebot2/pull/1326))
|
||||||
|
- Plugin: AI 绘图 [@yanyongyu](https://github.com/yanyongyu) ([#1323](https://github.com/nonebot/nonebot2/pull/1323))
|
||||||
|
- Plugin: novelai [@yanyongyu](https://github.com/yanyongyu) ([#1319](https://github.com/nonebot/nonebot2/pull/1319))
|
||||||
|
- Plugin: 游戏王小程序查价 [@yanyongyu](https://github.com/yanyongyu) ([#1317](https://github.com/nonebot/nonebot2/pull/1317))
|
||||||
|
- Plugin: 监测群事件 [@yanyongyu](https://github.com/yanyongyu) ([#1320](https://github.com/nonebot/nonebot2/pull/1320))
|
||||||
|
- Plugin: 轮盘禁言小游戏 [@yanyongyu](https://github.com/yanyongyu) ([#1311](https://github.com/nonebot/nonebot2/pull/1311))
|
||||||
|
- Plugin: 真白萌自动签到 [@yanyongyu](https://github.com/yanyongyu) ([#1308](https://github.com/nonebot/nonebot2/pull/1308))
|
||||||
|
- Plugin: BiliRequestAll [@yanyongyu](https://github.com/yanyongyu) ([#1302](https://github.com/nonebot/nonebot2/pull/1302))
|
||||||
|
- Plugin: 监听者 [@yanyongyu](https://github.com/yanyongyu) ([#1299](https://github.com/nonebot/nonebot2/pull/1299))
|
||||||
|
|
||||||
|
### 🍻 机器人发布
|
||||||
|
|
||||||
|
- Bot: Bread Dog Bot [@yanyongyu](https://github.com/yanyongyu) ([#1380](https://github.com/nonebot/nonebot2/pull/1380))
|
||||||
|
- Bot: hsbot [@yanyongyu](https://github.com/yanyongyu) ([#1369](https://github.com/nonebot/nonebot2/pull/1369))
|
||||||
|
|
||||||
|
### 🍻 适配器发布
|
||||||
|
|
||||||
|
- Adapter: Ntchat [@yanyongyu](https://github.com/yanyongyu) ([#1314](https://github.com/nonebot/nonebot2/pull/1314))
|
||||||
|
|
||||||
|
## v2.0.0-rc.1
|
||||||
|
|
||||||
|
### 💥 破坏性变更
|
||||||
|
|
||||||
|
- Feature: `SUPERUSER` 权限匹配任意超管事件 [@AkiraXie](https://github.com/AkiraXie) ([#1275](https://github.com/nonebot/nonebot2/pull/1275))
|
||||||
|
- Remove: 移除过时的 State 注入参数 [@yanyongyu](https://github.com/yanyongyu) ([#1160](https://github.com/nonebot/nonebot2/pull/1160))
|
||||||
|
- Remove: 移除过时的 `nonebot.plugins` toml 配置 [@yanyongyu](https://github.com/yanyongyu) ([#1151](https://github.com/nonebot/nonebot2/pull/1151))
|
||||||
|
- Remove: 移除 Python 3.7 支持 [@yanyongyu](https://github.com/yanyongyu) ([#1148](https://github.com/nonebot/nonebot2/pull/1148))
|
||||||
|
- Remove: 删除过时的 Export 功能 [@yanyongyu](https://github.com/yanyongyu) ([#1125](https://github.com/nonebot/nonebot2/pull/1125))
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
- Feature: `SUPERUSER` 权限匹配任意超管事件 [@AkiraXie](https://github.com/AkiraXie) ([#1275](https://github.com/nonebot/nonebot2/pull/1275))
|
||||||
|
- Feature: 改进 `CommandGroup` 与 `MatcherGroup` 的结构 [@A-kirami](https://github.com/A-kirami) ([#1240](https://github.com/nonebot/nonebot2/pull/1240))
|
||||||
|
- Feature: 调整日志输出格式与等级 [@yanyongyu](https://github.com/yanyongyu) ([#1233](https://github.com/nonebot/nonebot2/pull/1233))
|
||||||
|
- Feature: 优化依赖注入结构 [@yanyongyu](https://github.com/yanyongyu) ([#1227](https://github.com/nonebot/nonebot2/pull/1227))
|
||||||
|
- Featue: `load_plugin` 支持 `pathlib.Path` [@Lancercmd](https://github.com/Lancercmd) ([#1194](https://github.com/nonebot/nonebot2/pull/1194))
|
||||||
|
- Feature: 新增事件类型过滤 rule [@yanyongyu](https://github.com/yanyongyu) ([#1183](https://github.com/nonebot/nonebot2/pull/1183))
|
||||||
|
- Feature: shell command 添加富文本支持 [@yanyongyu](https://github.com/yanyongyu) ([#1171](https://github.com/nonebot/nonebot2/pull/1171))
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- Fix: 内置规则和权限没有捕获错误 [@yanyongyu](https://github.com/yanyongyu) ([#1291](https://github.com/nonebot/nonebot2/pull/1291))
|
||||||
|
- Fix: 修复 User 会话权限更新嵌套问题 [@yanyongyu](https://github.com/yanyongyu) ([#1208](https://github.com/nonebot/nonebot2/pull/1208))
|
||||||
|
- Fix: 修复当消息与不支持的类型相加时抛出的异常类型错误 [@mnixry](https://github.com/mnixry) ([#1166](https://github.com/nonebot/nonebot2/pull/1166))
|
||||||
|
|
||||||
|
### 💫 杂项
|
||||||
|
|
||||||
|
- Fix: 修正 GenshinUID 的发布类型 [@A-kirami](https://github.com/A-kirami) ([#1243](https://github.com/nonebot/nonebot2/pull/1243))
|
||||||
|
- Remove: 移除未使用的导入 [@A-kirami](https://github.com/A-kirami) ([#1236](https://github.com/nonebot/nonebot2/pull/1236))
|
||||||
|
- Plugin: 更新插件米游社辅助工具 tag [@Ljzd-PRO](https://github.com/Ljzd-PRO) ([#1221](https://github.com/nonebot/nonebot2/pull/1221))
|
||||||
|
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1180](https://github.com/nonebot/nonebot2/pull/1180))
|
||||||
|
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1159](https://github.com/nonebot/nonebot2/pull/1159))
|
||||||
|
- Plugin: 修改 QQ 续火花插件信息 [@GC-ZF](https://github.com/GC-ZF) ([#1158](https://github.com/nonebot/nonebot2/pull/1158))
|
||||||
|
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1154](https://github.com/nonebot/nonebot2/pull/1154))
|
||||||
|
|
||||||
|
### 🍻 插件发布
|
||||||
|
|
||||||
|
- Plugin: 文字识别 [@yanyongyu](https://github.com/yanyongyu) ([#1295](https://github.com/nonebot/nonebot2/pull/1295))
|
||||||
|
- Plugin: 在线编曲 [@yanyongyu](https://github.com/yanyongyu) ([#1293](https://github.com/nonebot/nonebot2/pull/1293))
|
||||||
|
- Plugin: 图灵机器人 [@yanyongyu](https://github.com/yanyongyu) ([#1289](https://github.com/nonebot/nonebot2/pull/1289))
|
||||||
|
- Plugin: PicStatus [@yanyongyu](https://github.com/yanyongyu) ([#1287](https://github.com/nonebot/nonebot2/pull/1287))
|
||||||
|
- Plugin: 阿里云盘福利码自动兑换 [@yanyongyu](https://github.com/yanyongyu) ([#1283](https://github.com/nonebot/nonebot2/pull/1283))
|
||||||
|
- Plugin: gal 角色语音生成 [@yanyongyu](https://github.com/yanyongyu) ([#1281](https://github.com/nonebot/nonebot2/pull/1281))
|
||||||
|
- Plugin: 漂流瓶 [@yanyongyu](https://github.com/yanyongyu) ([#1279](https://github.com/nonebot/nonebot2/pull/1279))
|
||||||
|
- Plugin: BWIKI 助手移植版 [@yanyongyu](https://github.com/yanyongyu) ([#1274](https://github.com/nonebot/nonebot2/pull/1274))
|
||||||
|
- Plugin: nonebot 物联网插件 [@yanyongyu](https://github.com/yanyongyu) ([#1265](https://github.com/nonebot/nonebot2/pull/1265))
|
||||||
|
- Plugin: 狼人杀插件 [@yanyongyu](https://github.com/yanyongyu) ([#1252](https://github.com/nonebot/nonebot2/pull/1252))
|
||||||
|
- Plugin: ayaka - 文字游戏开发辅助插件 [@yanyongyu](https://github.com/yanyongyu) ([#1254](https://github.com/nonebot/nonebot2/pull/1254))
|
||||||
|
- Plugin: 图像超分辨率重建 [@yanyongyu](https://github.com/yanyongyu) ([#1250](https://github.com/nonebot/nonebot2/pull/1250))
|
||||||
|
- Plugin: Minecraft Server 聊天同步 [@yanyongyu](https://github.com/yanyongyu) ([#1245](https://github.com/nonebot/nonebot2/pull/1245))
|
||||||
|
- Plugin: 查询 ETH 合并日期 [@yanyongyu](https://github.com/yanyongyu) ([#1232](https://github.com/nonebot/nonebot2/pull/1232))
|
||||||
|
- Plugin: 星际战甲事件查询 [@yanyongyu](https://github.com/yanyongyu) ([#1220](https://github.com/nonebot/nonebot2/pull/1220))
|
||||||
|
- Plugin: 米游社辅助工具 [@yanyongyu](https://github.com/yanyongyu) ([#1218](https://github.com/nonebot/nonebot2/pull/1218))
|
||||||
|
- Plugin: 原神每日材料查询 [@yanyongyu](https://github.com/yanyongyu) ([#1216](https://github.com/nonebot/nonebot2/pull/1216))
|
||||||
|
- Plugin: MC_QQ_MCRcon [@yanyongyu](https://github.com/yanyongyu) ([#1211](https://github.com/nonebot/nonebot2/pull/1211))
|
||||||
|
- Plugin: 原神角色展柜查询 [@yanyongyu](https://github.com/yanyongyu) ([#1209](https://github.com/nonebot/nonebot2/pull/1209))
|
||||||
|
- Plugin: 修仙模拟器 [@yanyongyu](https://github.com/yanyongyu) ([#1202](https://github.com/nonebot/nonebot2/pull/1202))
|
||||||
|
- Plugin: 赛博浅草寺 [@yanyongyu](https://github.com/yanyongyu) ([#1206](https://github.com/nonebot/nonebot2/pull/1206))
|
||||||
|
- Plugin: 不背单词 [@yanyongyu](https://github.com/yanyongyu) ([#1204](https://github.com/nonebot/nonebot2/pull/1204))
|
||||||
|
- Plugin: 自识别 todo [@yanyongyu](https://github.com/yanyongyu) ([#1193](https://github.com/nonebot/nonebot2/pull/1193))
|
||||||
|
- Plugin: 雨课堂自动签到 [@yanyongyu](https://github.com/yanyongyu) ([#1189](https://github.com/nonebot/nonebot2/pull/1189))
|
||||||
|
- Plugin: 反馈及通知 [@yanyongyu](https://github.com/yanyongyu) ([#1187](https://github.com/nonebot/nonebot2/pull/1187))
|
||||||
|
- Plugin: MagiaDice 骰娘及 TRPGLOG [@yanyongyu](https://github.com/yanyongyu) ([#1185](https://github.com/nonebot/nonebot2/pull/1185))
|
||||||
|
- Plugin: 面麻小助手 [@yanyongyu](https://github.com/yanyongyu) ([#1191](https://github.com/nonebot/nonebot2/pull/1191))
|
||||||
|
- Plugin: 话痨排行榜 [@yanyongyu](https://github.com/yanyongyu) ([#1182](https://github.com/nonebot/nonebot2/pull/1182))
|
||||||
|
- Plugin: 保存群聊闪照 [@yanyongyu](https://github.com/yanyongyu) ([#1179](https://github.com/nonebot/nonebot2/pull/1179))
|
||||||
|
- Plugin: 课表查询 [@yanyongyu](https://github.com/yanyongyu) ([#1168](https://github.com/nonebot/nonebot2/pull/1168))
|
||||||
|
- Plugin: 业余无线电助手 [@yanyongyu](https://github.com/yanyongyu) ([#1173](https://github.com/nonebot/nonebot2/pull/1173))
|
||||||
|
- Plugin: NoneBot 树形帮助插件 [@yanyongyu](https://github.com/yanyongyu) ([#1177](https://github.com/nonebot/nonebot2/pull/1177))
|
||||||
|
- Plugin: 工作性价比 [@yanyongyu](https://github.com/yanyongyu) ([#1175](https://github.com/nonebot/nonebot2/pull/1175))
|
||||||
|
- Plugin: 娶群友 [@yanyongyu](https://github.com/yanyongyu) ([#1170](https://github.com/nonebot/nonebot2/pull/1170))
|
||||||
|
- Plugin: PixivBot [@yanyongyu](https://github.com/yanyongyu) ([#1165](https://github.com/nonebot/nonebot2/pull/1165))
|
||||||
|
- Plugin: 日韩中 VITS 模型原神拟声 [@yanyongyu](https://github.com/yanyongyu) ([#1162](https://github.com/nonebot/nonebot2/pull/1162))
|
||||||
|
- Plugin: 每日人品 [@yanyongyu](https://github.com/yanyongyu) ([#1156](https://github.com/nonebot/nonebot2/pull/1156))
|
||||||
|
- Plugin: nonebot-plugin-drawer [@yanyongyu](https://github.com/yanyongyu) ([#1146](https://github.com/nonebot/nonebot2/pull/1146))
|
||||||
|
- Plugin: 小游戏合集 [@yanyongyu](https://github.com/yanyongyu) ([#1150](https://github.com/nonebot/nonebot2/pull/1150))
|
||||||
|
- Plugin: 简易群管(带入群欢迎) [@yanyongyu](https://github.com/yanyongyu) ([#1142](https://github.com/nonebot/nonebot2/pull/1142))
|
||||||
|
- Plugin: wiki 条目搜索、获取简介 [@yanyongyu](https://github.com/yanyongyu) ([#1133](https://github.com/nonebot/nonebot2/pull/1133))
|
||||||
|
- Plugin: bangumi 搜索 [@yanyongyu](https://github.com/yanyongyu) ([#1137](https://github.com/nonebot/nonebot2/pull/1137))
|
||||||
|
- Plugin: 疫情小助手-频道版 [@yanyongyu](https://github.com/yanyongyu) ([#1131](https://github.com/nonebot/nonebot2/pull/1131))
|
||||||
|
- Plugin: MC_QQ 通信 [@yanyongyu](https://github.com/yanyongyu) ([#1127](https://github.com/nonebot/nonebot2/pull/1127))
|
||||||
|
- Plugin: BAWiki [@yanyongyu](https://github.com/yanyongyu) ([#1129](https://github.com/nonebot/nonebot2/pull/1129))
|
||||||
|
|
||||||
|
### 🍻 机器人发布
|
||||||
|
|
||||||
|
- Bot: IdhagnBot [@yanyongyu](https://github.com/yanyongyu) ([#1267](https://github.com/nonebot/nonebot2/pull/1267))
|
||||||
|
- Bot: LittlePaimon [@yanyongyu](https://github.com/yanyongyu) ([#1256](https://github.com/nonebot/nonebot2/pull/1256))
|
||||||
|
- Bot: GenshinUID [@yanyongyu](https://github.com/yanyongyu) ([#1226](https://github.com/nonebot/nonebot2/pull/1226))
|
||||||
|
- Bot: 小白机器人 [@yanyongyu](https://github.com/yanyongyu) ([#1224](https://github.com/nonebot/nonebot2/pull/1224))
|
||||||
|
|
||||||
|
### 🍻 适配器发布
|
||||||
|
|
||||||
|
- Adapter: GitHub [@yanyongyu](https://github.com/yanyongyu) ([#1297](https://github.com/nonebot/nonebot2/pull/1297))
|
||||||
|
- Adapter: Console [@yanyongyu](https://github.com/yanyongyu) ([#1213](https://github.com/nonebot/nonebot2/pull/1213))
|
||||||
|
|
||||||
## v2.0.0-beta.5
|
## v2.0.0-beta.5
|
||||||
|
|
||||||
### 🚀 新功能
|
### 🚀 新功能
|
||||||
|
@@ -78,5 +78,40 @@
|
|||||||
"homepage": "https://onebot.adapters.nonebot.dev/",
|
"homepage": "https://onebot.adapters.nonebot.dev/",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.console",
|
||||||
|
"project_link": "nonebot-adapter-console",
|
||||||
|
"name": "Console",
|
||||||
|
"desc": "基于终端的交互式适配器",
|
||||||
|
"author": "Melodyknit",
|
||||||
|
"homepage": "https://github.com/nonebot/adapter-console",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.github",
|
||||||
|
"project_link": "nonebot-adapter-github",
|
||||||
|
"name": "GitHub",
|
||||||
|
"desc": "GitHub APP & OAuth APP integration",
|
||||||
|
"author": "yanyongyu",
|
||||||
|
"homepage": "https://github.com/nonebot/adapter-github",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.ntchat",
|
||||||
|
"project_link": "nonebot-adapter-ntchat",
|
||||||
|
"name": "Ntchat",
|
||||||
|
"desc": "pc hook的微信客户端适配",
|
||||||
|
"author": "JustUndertaker",
|
||||||
|
"homepage": "https://github.com/JustUndertaker/adapter-ntchat",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "微信",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -123,7 +123,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Inkar Suki",
|
"name": "Inkar Suki",
|
||||||
"desc": "一个十分方便的Bot,支持包括Webhook、群管、互动等一系列功能",
|
"desc": "一个十分方便的Bot,支持包括Webhook、群管、剑网3等一系列功能,持续更新中……",
|
||||||
"author": "HornCopper",
|
"author": "HornCopper",
|
||||||
"homepage": "https://github.com/HornCopper/Inkar-Suki",
|
"homepage": "https://github.com/HornCopper/Inkar-Suki",
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -136,8 +136,8 @@
|
|||||||
"color": "#374fd0"
|
"color": "#374fd0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "群管",
|
"label": "剑网3",
|
||||||
"color": "#4ede39"
|
"color": "#ff0033"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
@@ -233,5 +233,82 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "小白机器人",
|
||||||
|
"desc": "一个高度依赖数据库的群管理机器人",
|
||||||
|
"author": "SDIJF1521",
|
||||||
|
"homepage": "https://github.com/SDIJF1521/qqai",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "群管理",
|
||||||
|
"color": "#ea5252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "新人作品",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LittlePaimon",
|
||||||
|
"desc": "小派蒙,多功能原神机器人。",
|
||||||
|
"author": "CMHopeSunshine",
|
||||||
|
"homepage": "https://github.com/CMHopeSunshine/LittlePaimon",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "原神",
|
||||||
|
"color": "#7a52ea"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IdhagnBot",
|
||||||
|
"desc": "🐱🤖 一个以娱乐功能为主的缝合怪(划掉)QQ机器人,包含一定Furry要素但是不会卖萌(就是逊啦!)",
|
||||||
|
"author": "su226",
|
||||||
|
"homepage": "https://github.com/su226/IdhagnBot",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "a:OneBot",
|
||||||
|
"color": "#ea5252"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hsbot",
|
||||||
|
"desc": "服务于《炉石传说》玩家的机器人,上线至今已有加入十余个个炉石相关群聊,上千名用户使用,响应请求数万次。 数据使用:HSreplay, Fbigame, Hearthstone API",
|
||||||
|
"author": "gzy02",
|
||||||
|
"homepage": "https://github.com/gzy02/hsbot",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "炉石传说",
|
||||||
|
"color": "#526fea"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bread Dog Bot",
|
||||||
|
"desc": "Terraria TShock QQ 机器人",
|
||||||
|
"author": "Qianyiovo",
|
||||||
|
"homepage": "https://github.com/Qianyiovo/bread_dog_bot",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "TShock",
|
||||||
|
"color": "#ea5252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "泰拉瑞亚",
|
||||||
|
"color": "#5dea52"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Terraria",
|
||||||
|
"color": "#5dea52"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,106 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 0
|
|
||||||
description: nonebot.dependencies 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.dependencies
|
|
||||||
|
|
||||||
本模块模块实现了依赖注入的定义与处理。
|
|
||||||
|
|
||||||
## _abstract class_ `Param(default=PydanticUndefined, **kwargs)` {#Param}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
依赖注入的基本单元 —— 参数。
|
|
||||||
|
|
||||||
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `default` (Any)
|
|
||||||
|
|
||||||
- `**kwargs` (Any)
|
|
||||||
|
|
||||||
## _class_ `Dependent(*, call, pre_checkers=None, params=None, parameterless=None, allow_types=None)` {#Dependent}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
依赖注入容器
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `call` ((\*Any, \*\*Any) -> Any): 依赖注入的可调用对象,可以是任何 Callable 对象
|
|
||||||
|
|
||||||
- `pre_checkers` (list[[Param](#Param)] | None): 依赖注入解析前的参数检查
|
|
||||||
|
|
||||||
- `params` (list[pydantic.fields.ModelField] | None): 具名参数列表
|
|
||||||
|
|
||||||
- `parameterless` (list[[Param](#Param)] | None): 匿名参数列表
|
|
||||||
|
|
||||||
- `allow_types` (list[Type[[Param](#Param)]] | None): 允许的参数类型
|
|
||||||
|
|
||||||
### _method_ `append_parameterless(self, value)` {#Dependent-append_parameterless}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (Any)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _classmethod_ `parse(cls, *, call, parameterless=None, allow_types=None)` {#Dependent-parse}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `call` ((\*Any, \*\*Any) -> Any)
|
|
||||||
|
|
||||||
- `parameterless` (list[Any] | None)
|
|
||||||
|
|
||||||
- `allow_types` (list[Type[[Param](#Param)]] | None)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- (~ T)
|
|
||||||
|
|
||||||
### _method_ `parse_param(self, name, param)` {#Dependent-parse_param}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `name` (str)
|
|
||||||
|
|
||||||
- `param` (inspect.Parameter)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Param](#Param)
|
|
||||||
|
|
||||||
### _method_ `parse_parameterless(self, value)` {#Dependent-parse_parameterless}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (Any)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Param](#Param)
|
|
||||||
|
|
||||||
### _method_ `prepend_parameterless(self, value)` {#Dependent-prepend_parameterless}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `value` (Any)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
### _async method_ `solve(self, **params)` {#Dependent-solve}
|
|
||||||
|
|
||||||
- **参数**
|
|
||||||
|
|
||||||
- `**params` (Any)
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- dict[str, Any]
|
|
@@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 4
|
|
||||||
description: nonebot.plugin.export 模块
|
|
||||||
---
|
|
||||||
|
|
||||||
# nonebot.plugin.export
|
|
||||||
|
|
||||||
本模块定义了插件导出的内容对象。
|
|
||||||
|
|
||||||
在新版插件系统中,推荐优先使用直接 import 所需要的插件内容。
|
|
||||||
|
|
||||||
## _class_ `Export()` {#Export}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
插件导出内容以使得其他插件可以获得。
|
|
||||||
|
|
||||||
- **用法**
|
|
||||||
|
|
||||||
```python
|
|
||||||
nonebot.export().default = "bar"
|
|
||||||
|
|
||||||
@nonebot.export()
|
|
||||||
def some_function():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# this doesn't work before python 3.9
|
|
||||||
# use
|
|
||||||
# export = nonebot.export(); @export.sub
|
|
||||||
# instead
|
|
||||||
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
|
|
||||||
@nonebot.export().sub
|
|
||||||
def something_else():
|
|
||||||
pass
|
|
||||||
```
|
|
||||||
|
|
||||||
## _def_ `export()` {#export}
|
|
||||||
|
|
||||||
- **说明**
|
|
||||||
|
|
||||||
获取当前插件的导出内容对象
|
|
||||||
|
|
||||||
- **返回**
|
|
||||||
|
|
||||||
- [Export](#Export)
|
|
@@ -8,7 +8,7 @@ slug: /
|
|||||||
|
|
||||||
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
|
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
|
||||||
|
|
||||||
需要注意的是,NoneBot2 仅支持 **Python 3.7.3 以上版本**
|
需要注意的是,NoneBot2 仅支持 **Python 3.8 以上版本**
|
||||||
|
|
||||||
## 特色
|
## 特色
|
||||||
|
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 376 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user