mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 03:07:07 +00:00
Compare commits
313 Commits
v2.0.0-rc.
...
v2.0.0rc3
Author | SHA1 | Date | |
---|---|---|---|
|
eceef1ebec | ||
|
50aa8c53e0 | ||
|
558f740c13 | ||
|
8bffba7efd | ||
|
ce93ea13e7 | ||
|
174182d62a | ||
|
36f047be7f | ||
|
9d73af0513 | ||
|
ecb0d78011 | ||
|
5920efb6c5 | ||
|
5893fbe57d | ||
|
27557af636 | ||
|
b37c7995cb | ||
|
f46addbb85 | ||
|
6f57a290d7 | ||
|
ae66e45287 | ||
|
03cf7f290a | ||
|
f203aaf4ca | ||
|
9a2edbbeb1 | ||
|
bd9ca99f63 | ||
|
8be262d305 | ||
|
b92d47b362 | ||
|
bdf8cb0d57 | ||
|
0cb65214c6 | ||
|
ccc2c5676a | ||
|
6daec67ebd | ||
|
051851faed | ||
|
8d2fca3e12 | ||
|
76f37c485c | ||
|
0c7af0873f | ||
|
31fa4ec5f4 | ||
|
fda490d252 | ||
|
40e443fd1a | ||
|
4a17e581d2 | ||
|
081d212487 | ||
|
3d6774136f | ||
|
fa934a156a | ||
|
bac5356a90 | ||
|
b289065f71 | ||
|
09cf0f29ba | ||
|
244837dd7c | ||
|
a0bc113912 | ||
|
6f6a296105 | ||
|
a0d316127f | ||
|
f0c0d7788f | ||
|
3f7e2604f1 | ||
|
f43c0087f7 | ||
|
e71d841045 | ||
|
a3af8da331 | ||
|
8bdfdaef91 | ||
|
afd13ed65d | ||
|
d83751d0ca | ||
|
63dd3b8fa7 | ||
|
ae689605a5 | ||
|
bbaba1c955 | ||
|
f1ee54e5c9 | ||
|
6f8e532afe | ||
|
6f68ff61e5 | ||
|
a930fc0997 | ||
|
65da0947fe | ||
|
1b64a54421 | ||
|
d4e1bb7bf3 | ||
|
d737679ccd | ||
|
4cef5512ee | ||
|
1d5d1602f0 | ||
|
87e767fa25 | ||
|
c38437a22f | ||
|
cafb7bedb4 | ||
|
ace053f387 | ||
|
d6e176d03b | ||
|
2fca5b9664 | ||
|
cd93ace0dd | ||
|
b118cb6f22 | ||
|
a69ccb4e6c | ||
|
d5ec31d0a0 | ||
|
62560635b2 | ||
|
c00430c53f | ||
|
1dcda4bd77 | ||
|
b60035f0e6 | ||
|
8551b13eab | ||
|
b448a6e083 | ||
|
956b202087 | ||
|
b95d49cd9c | ||
|
006b9dd816 | ||
|
a9125cd696 | ||
|
ee5dcf0d42 | ||
|
f13c1cc980 | ||
|
16c0a87929 | ||
|
39d1554905 | ||
|
37067229b0 | ||
|
5ca708d3f4 | ||
|
53dded52a7 | ||
|
f8cee790e7 | ||
|
10447ff3c4 | ||
|
f08aec7894 | ||
|
69edb98835 | ||
|
c73ca2b43f | ||
|
848c6c5061 | ||
|
58f82bf881 | ||
|
9b3e670cee | ||
|
f0bebb65b4 | ||
|
bc845c94e2 | ||
|
f4668bf0bc | ||
|
3493d69fcd | ||
|
125bcb943f | ||
|
e65eb3fb18 | ||
|
8045420f97 | ||
|
03a90006e5 | ||
|
adbe341076 | ||
|
2a623b1c81 | ||
|
c91926aea6 | ||
|
63329257de | ||
|
c9dc6e648e | ||
|
78a818547e | ||
|
41eed9d0e9 | ||
|
e32019f15d | ||
|
59c033e2dd | ||
|
49cf1ec5d3 | ||
|
e5ad15d6d6 | ||
|
4cf9790a95 | ||
|
7467e66dab | ||
|
516c1c220c | ||
|
136778ae5b | ||
|
17538f7a66 | ||
|
d1da0be0da | ||
|
e92bc24631 | ||
|
73d1e5dd88 | ||
|
ea83ba78ec | ||
|
712f80a307 | ||
|
5c4ef8fc00 | ||
|
2b0973c9f5 | ||
|
767a8ecb08 | ||
|
b342940b69 | ||
|
7880f07db4 | ||
|
ac702d7eb7 | ||
|
9f3b3b2c32 | ||
|
2d08465426 | ||
|
827d8fbc0e | ||
|
0320be1947 | ||
|
aca65954bd | ||
|
1da9376fc8 | ||
|
909b811f68 | ||
|
ceecf9c692 | ||
|
3de2922773 | ||
|
a5d26b7747 | ||
|
932f50d1fb | ||
|
c69619f142 | ||
|
5a49d1e0e2 | ||
|
4f9f3f449c | ||
|
60733c97be | ||
|
7a1aa0c204 | ||
|
6d1383a10c | ||
|
86006fafdc | ||
|
44ca4f729a | ||
|
cf29209a55 | ||
|
5e78e2bb5d | ||
|
440e15e204 | ||
|
6711a84cab | ||
|
5c2d2141e3 | ||
|
8ec1552fd6 | ||
|
c1dca723ae | ||
|
b6cd0424fa | ||
|
1beee94c1d | ||
|
f2a618f663 | ||
|
c4286f1f39 | ||
|
cd9e30bd52 | ||
|
24ae0dfa15 | ||
|
19a20a3407 | ||
|
36d7b44741 | ||
|
8176cd189c | ||
|
b25de93eb1 | ||
|
79e636ad89 | ||
|
9c9973d8d8 | ||
|
04d4954d50 | ||
|
2c81dc1975 | ||
|
850096ceaa | ||
|
6bb15f6533 | ||
|
92c6a8dd6e | ||
|
723eef10bb | ||
|
06c33ad6ea | ||
|
9826bc29ca | ||
|
f35b5b57b7 | ||
|
433b50c79c | ||
|
89f3496a2a | ||
|
df7e8f6d83 | ||
|
b57f17d447 | ||
|
665de1da3e | ||
|
091232e996 | ||
|
9ee2d94f3c | ||
|
78bdfe65ba | ||
|
5006cf7be6 | ||
|
a818e0056e | ||
|
3efae8bfbc | ||
|
024d97b997 | ||
|
c90ab949d2 | ||
|
e8ffa63b78 | ||
|
6c27ec7357 | ||
|
9bf08593d7 | ||
|
b016a59a38 | ||
|
794395e737 | ||
|
a758e6f06e | ||
|
11feb2c0d0 | ||
|
59a2ed7c2e | ||
|
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 |
@@ -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)",
|
||||
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
"name": "Default Linux Universal",
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/poetry:1": {}
|
||||
},
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && yarn install",
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"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.typeCheckingMode": "basic",
|
||||
"[python]": {
|
||||
@@ -50,7 +31,6 @@
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"lldb.executable": "/usr/bin/lldb",
|
||||
"files.exclude": {
|
||||
"**/__pycache__": true
|
||||
},
|
||||
@@ -59,10 +39,7 @@
|
||||
"**/__pycache__": true
|
||||
}
|
||||
},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-python.isort",
|
||||
@@ -72,27 +49,5 @@
|
||||
"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"
|
||||
|
||||
- id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
|
19
.github/actions/setup-python/action.yml
vendored
19
.github/actions/setup-python/action.yml
vendored
@@ -10,22 +10,15 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- id: python
|
||||
uses: actions/setup-python@v2
|
||||
- name: Install poetry
|
||||
run: pipx install poetry
|
||||
shell: bash
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
architecture: "x64"
|
||||
|
||||
- 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') }}
|
||||
cache: "poetry"
|
||||
|
||||
- run: poetry install -E all
|
||||
shell: bash
|
||||
|
6
.github/workflows/codecov.yml
vendored
6
.github/workflows/codecov.yml
vendored
@@ -5,6 +5,10 @@ on:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
paths:
|
||||
- "nonebot/**"
|
||||
- "packages/**"
|
||||
- "tests/**"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -15,7 +19,7 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10"]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
fail-fast: false
|
||||
env:
|
||||
|
35
.github/workflows/publish-bot.yml
vendored
35
.github/workflows/publish-bot.yml
vendored
@@ -5,17 +5,45 @@ on:
|
||||
types: [opened, reopened, edited]
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
plugin_test:
|
||||
runs-on: ubuntu-latest
|
||||
name: nonebot2 plugin test
|
||||
if: github.event_name != 'issue_comment' || !github.event.issue.pull_request
|
||||
permissions:
|
||||
issues: read
|
||||
outputs:
|
||||
result: ${{ steps.plugin-test.outputs.RESULT }}
|
||||
output: ${{ steps.plugin-test.outputs.OUTPUT }}
|
||||
steps:
|
||||
- name: Install Poetry
|
||||
if: ${{ !startsWith(github.event_name, 'pull_request') }}
|
||||
run: pipx install poetry
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Test Plugin
|
||||
id: plugin-test
|
||||
run: |
|
||||
curl -sSL https://raw.githubusercontent.com/nonebot/nonebot2-publish-bot/main/plugin_test.py -o plugin_test.py
|
||||
python plugin_test.py
|
||||
publish_bot:
|
||||
runs-on: ubuntu-latest
|
||||
name: nonebot2 publish bot
|
||||
needs: plugin_test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: NoneBot2 Publish Bot
|
||||
uses: docker://ghcr.io/nonebot/nonebot2-publish-bot:main
|
||||
with:
|
||||
@@ -27,3 +55,6 @@ jobs:
|
||||
"bot_path": "website/static/bots.json",
|
||||
"adapter_path": "website/static/adapters.json"
|
||||
}
|
||||
env:
|
||||
PLUGIN_TEST_RESULT: ${{ needs.plugin_test.outputs.result }}
|
||||
PLUGIN_TEST_OUTPUT: ${{ needs.plugin_test.outputs.output }}
|
||||
|
2
.github/workflows/website-deploy.yml
vendored
2
.github/workflows/website-deploy.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v1
|
||||
uses: nwtgck/actions-netlify@v2
|
||||
with:
|
||||
publish-dir: "./website/build"
|
||||
production-deploy: true
|
||||
|
2
.github/workflows/website-preview.yml
vendored
2
.github/workflows/website-preview.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
echo "DEPLOY_NAME=deploy-preview-${{ github.event.number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v1
|
||||
uses: nwtgck/actions-netlify@v2
|
||||
with:
|
||||
publish-dir: "./website/build"
|
||||
production-deploy: false
|
||||
|
@@ -6,23 +6,29 @@ ci:
|
||||
autoupdate_schedule: monthly
|
||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||
repos:
|
||||
- repo: https://github.com/hadialqattan/pycln
|
||||
rev: v2.1.2
|
||||
hooks:
|
||||
- id: pycln
|
||||
args: [--config, pyproject.toml]
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.11.4
|
||||
hooks:
|
||||
- id: isort
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.8.0
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0-alpha.0
|
||||
rev: v3.0.0-alpha.4
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml]
|
||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/nonebot/nonemoji
|
||||
|
@@ -33,7 +33,7 @@ pre-commit install
|
||||
|
||||
### 使用 GitHub Codespaces(Dev Container)
|
||||
|
||||
使用 GitHub Codespaces 选择 `NoneBot2` 项目,然后选择 `.devcontainer/devcontainer.json` 配置即可。
|
||||
[](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=289605524)
|
||||
|
||||
### Commit 规范
|
||||
|
||||
|
24
README.md
24
README.md
@@ -97,7 +97,7 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
||||
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
||||
|
||||
| 协议名称 | 状态 | 注释 |
|
||||
| :---------------------------------------------------: | :--: | :----------------------------------------------------------------: |
|
||||
| :-----------------------------------------------------------------------: | :--: | :----------------------------------------------------------------: |
|
||||
| [OneBot 协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号等[平台](https://onebot.dev/ecosystem.html) |
|
||||
| [Telegram](https://core.telegram.org/bots/api) | ✅ | |
|
||||
| [飞书](https://open.feishu.cn/document/home/index) | ✅ | |
|
||||
@@ -107,6 +107,9 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
||||
| Console | ✅ | 控制台交互 |
|
||||
| [开黑啦](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
|
||||
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | 由社区贡献 |
|
||||
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | ↗️ | 由社区贡献 |
|
||||
| [MineCraft (Spigot)](https://github.com/17TheWord/nonebot-adapter-spigot) | ↗️ | 由社区贡献 |
|
||||
| [BiliBili Live](https://github.com/wwweww/adapter-bilibili) | ↗️ | 由社区贡献 |
|
||||
|
||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||
|
||||
@@ -134,12 +137,17 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
|
||||
|
||||
懒得看文档?下面是快速安装指南:
|
||||
|
||||
1. (**强烈建议**)使用你喜欢的 Python 环境管理工具创建新的虚拟环境。
|
||||
|
||||
2. 使用 `pip` (或其他) 安装 NoneBot 脚手架。
|
||||
1. 安装 [pipx](https://pypa.github.io/pipx/)
|
||||
|
||||
```bash
|
||||
pip install nb-cli
|
||||
python -m pip install --user pipx
|
||||
python -m pipx ensurepath
|
||||
```
|
||||
|
||||
2. 安装脚手架
|
||||
|
||||
```bash
|
||||
pipx install nb-cli
|
||||
```
|
||||
|
||||
3. 使用脚手架创建项目
|
||||
@@ -148,6 +156,12 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
|
||||
nb create
|
||||
```
|
||||
|
||||
4. 运行项目
|
||||
|
||||
```bash
|
||||
nb run
|
||||
```
|
||||
|
||||
## 社区资源
|
||||
|
||||
### 常见问题
|
||||
|
@@ -37,26 +37,24 @@ FrontMatter:
|
||||
description: nonebot 模块
|
||||
"""
|
||||
|
||||
import os
|
||||
import importlib
|
||||
from importlib.metadata import version
|
||||
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.utils import escape_tag
|
||||
from nonebot.config import Env, Config
|
||||
from nonebot.log import logger as logger
|
||||
from nonebot.drivers import Driver, ReverseDriver, combine_driver
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
|
||||
_dist: pkg_resources.Distribution = pkg_resources.get_distribution("nonebot2")
|
||||
__version__ = _dist.version
|
||||
VERSION = _dist.parsed_version
|
||||
__version__ = version("nonebot2")
|
||||
except Exception: # pragma: no cover
|
||||
__version__ = None
|
||||
VERSION = None
|
||||
|
||||
_driver: Optional[Driver] = None
|
||||
|
||||
@@ -217,7 +215,7 @@ def _log_patcher(record: "loguru.Record"):
|
||||
)
|
||||
|
||||
|
||||
def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
||||
def init(*, _env_file: Optional[DotenvType] = None, **kwargs: Any) -> None:
|
||||
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
|
||||
|
||||
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
|
||||
@@ -237,10 +235,12 @@ def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
||||
if not _driver:
|
||||
logger.success("NoneBot is initializing...")
|
||||
env = Env()
|
||||
_env_file = _env_file or f".env.{env.environment}"
|
||||
config = Config(
|
||||
**kwargs,
|
||||
_common_config=env.dict(),
|
||||
_env_file=_env_file or f".env.{env.environment}",
|
||||
_env_file=(".env", _env_file)
|
||||
if isinstance(_env_file, (str, os.PathLike))
|
||||
else _env_file,
|
||||
)
|
||||
|
||||
logger.configure(
|
||||
|
@@ -7,21 +7,6 @@ FrontMatter:
|
||||
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 Event as Event
|
||||
from nonebot.internal.adapter import Adapter as Adapter
|
||||
|
@@ -9,19 +9,18 @@ FrontMatter:
|
||||
description: nonebot.config 模块
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import timedelta
|
||||
from ipaddress import IPv4Address
|
||||
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 (
|
||||
DotenvType,
|
||||
SettingsError,
|
||||
EnvSettingsSource,
|
||||
InitSettingsSource,
|
||||
SettingsSourceCallable,
|
||||
read_env_file,
|
||||
env_file_sentinel,
|
||||
)
|
||||
|
||||
from nonebot.log import logger
|
||||
@@ -32,32 +31,14 @@ class CustomEnvSettings(EnvSettingsSource):
|
||||
"""
|
||||
Build environment variables suitable for passing to the Model.
|
||||
"""
|
||||
d: Dict[str, Optional[str]] = {}
|
||||
d: Dict[str, Any] = {}
|
||||
|
||||
if settings.__config__.case_sensitive:
|
||||
env_vars: Mapping[str, Optional[str]] = os.environ # pragma: no cover
|
||||
else:
|
||||
env_vars = {k.lower(): v for k, v in os.environ.items()}
|
||||
|
||||
env_file_vars: Dict[str, Optional[str]] = {}
|
||||
env_file = (
|
||||
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_file_vars = self._read_env_files(settings.__config__.case_sensitive)
|
||||
env_vars = {**env_file_vars, **env_vars}
|
||||
|
||||
for field in settings.__fields__.values():
|
||||
@@ -69,24 +50,38 @@ class CustomEnvSettings(EnvSettingsSource):
|
||||
if env_val is not None:
|
||||
break
|
||||
|
||||
is_complex, allow_parse_failure = self.field_is_complex(field)
|
||||
if is_complex:
|
||||
if env_val is None:
|
||||
continue
|
||||
|
||||
if field.is_complex():
|
||||
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__.json_loads(env_val)
|
||||
except ValueError as e: # pragma: no cover
|
||||
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 JSON for "{env_name}"' # type: ignore
|
||||
f'error parsing env var "{env_name}"' # type: ignore
|
||||
) from e
|
||||
|
||||
if isinstance(env_val, dict):
|
||||
d[field.alias] = deep_update(
|
||||
env_val, self.explode_env_vars(field, env_vars)
|
||||
)
|
||||
else:
|
||||
d[field.alias] = env_val
|
||||
elif env_val is not None:
|
||||
# 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:
|
||||
for env_name in env_file_vars.keys():
|
||||
# remain user custom config
|
||||
for env_name in env_file_vars:
|
||||
env_val = env_vars[env_name]
|
||||
if env_val and (val_striped := env_val.strip()):
|
||||
# there's a value, decode that as JSON
|
||||
try:
|
||||
env_val = settings.__config__.json_loads(val_striped)
|
||||
env_val = settings.__config__.parse_env_var(env_name, val_striped)
|
||||
except ValueError as e:
|
||||
logger.trace(
|
||||
"Error while parsing JSON for "
|
||||
@@ -94,6 +89,17 @@ class CustomEnvSettings(EnvSettingsSource):
|
||||
"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
|
||||
|
||||
return d
|
||||
@@ -106,6 +112,9 @@ class BaseConfig(BaseSettings):
|
||||
return self.__dict__.get(name)
|
||||
|
||||
class Config:
|
||||
extra = Extra.allow
|
||||
env_nested_delimiter = "__"
|
||||
|
||||
@classmethod
|
||||
def customise_sources(
|
||||
cls,
|
||||
@@ -117,7 +126,10 @@ class BaseConfig(BaseSettings):
|
||||
return (
|
||||
init_settings,
|
||||
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),
|
||||
file_secret_settings,
|
||||
@@ -137,7 +149,6 @@ class Env(BaseConfig):
|
||||
"""
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
env_file = ".env"
|
||||
|
||||
|
||||
@@ -150,8 +161,7 @@ class Config(BaseConfig):
|
||||
配置方法参考: [配置](https://v2.nonebot.dev/docs/tutorial/configuration)
|
||||
"""
|
||||
|
||||
_env_file: str = ".env"
|
||||
_common_config: Dict[str, Any] = {}
|
||||
_env_file: DotenvType = ".env", ".env.prod"
|
||||
|
||||
# nonebot configs
|
||||
driver: str = "~fastapi"
|
||||
@@ -231,8 +241,7 @@ class Config(BaseConfig):
|
||||
# or from env file using json loads
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
env_file = ".env.prod"
|
||||
env_file = ".env", ".env.prod"
|
||||
|
||||
|
||||
__autodoc__ = {
|
||||
|
@@ -38,7 +38,17 @@ SHELL_ARGV: Literal["_argv"] = "_argv"
|
||||
|
||||
REGEX_MATCHED: Literal["_matched"] = "_matched"
|
||||
"""正则匹配结果存储 key"""
|
||||
REGEX_STR: Literal["_matched_str"] = "_matched_str"
|
||||
"""正则匹配文本存储 key"""
|
||||
REGEX_GROUP: Literal["_matched_groups"] = "_matched_groups"
|
||||
"""正则匹配 group 元组存储 key"""
|
||||
REGEX_DICT: Literal["_matched_dict"] = "_matched_dict"
|
||||
"""正则匹配 group 字典存储 key"""
|
||||
STARTSWITH_KEY: Literal["_startswith"] = "_startswith"
|
||||
"""响应触发前缀 key"""
|
||||
ENDSWITH_KEY: Literal["_endswith"] = "_endswith"
|
||||
"""响应触发后缀 key"""
|
||||
FULLMATCH_KEY: Literal["_fullmatch"] = "_fullmatch"
|
||||
"""响应触发完整消息 key"""
|
||||
KEYWORD_KEY: Literal["_keyword"] = "_keyword"
|
||||
"""响应触发关键字 key"""
|
||||
|
@@ -4,11 +4,11 @@ FrontMatter:
|
||||
description: nonebot.dependencies.utils 模块
|
||||
"""
|
||||
import inspect
|
||||
from typing import Any, Dict, TypeVar, Callable
|
||||
from typing import Any, Dict, TypeVar, Callable, ForwardRef
|
||||
|
||||
from loguru import logger
|
||||
from pydantic.fields import ModelField
|
||||
from pydantic.typing import ForwardRef, evaluate_forwardref
|
||||
from pydantic.typing import evaluate_forwardref
|
||||
|
||||
from nonebot.exception import TypeMisMatch
|
||||
|
||||
|
@@ -21,7 +21,7 @@ from contextlib import asynccontextmanager
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.drivers import Request, Response
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers._block_driver import BlockDriver
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_driver
|
||||
|
||||
@@ -56,7 +56,13 @@ class Mixin(ForwardMixin):
|
||||
files = aiohttp.FormData()
|
||||
for name, file in setup.files:
|
||||
files.add_field(name, file[1], content_type=file[2], filename=file[0])
|
||||
async with aiohttp.ClientSession(version=version, trust_env=True) as session:
|
||||
|
||||
cookies = {
|
||||
cookie.name: cookie.value for cookie in setup.cookies if cookie.value
|
||||
}
|
||||
async with aiohttp.ClientSession(
|
||||
cookies=cookies, version=version, trust_env=True
|
||||
) as session:
|
||||
async with session.request(
|
||||
setup.method,
|
||||
setup.url,
|
||||
@@ -66,13 +72,12 @@ class Mixin(ForwardMixin):
|
||||
timeout=timeout,
|
||||
proxy=setup.proxy,
|
||||
) as response:
|
||||
res = Response(
|
||||
return Response(
|
||||
response.status,
|
||||
headers=response.headers.copy(),
|
||||
content=await response.read(),
|
||||
request=setup,
|
||||
)
|
||||
return res
|
||||
|
||||
@overrides(ForwardMixin)
|
||||
@asynccontextmanager
|
||||
@@ -92,8 +97,7 @@ class Mixin(ForwardMixin):
|
||||
headers=setup.headers,
|
||||
proxy=setup.proxy,
|
||||
) as ws:
|
||||
websocket = WebSocket(request=setup, session=session, websocket=ws)
|
||||
yield websocket
|
||||
yield WebSocket(request=setup, session=session, websocket=ws)
|
||||
|
||||
|
||||
class WebSocket(BaseWebSocket):
|
||||
@@ -166,5 +170,5 @@ class WebSocket(BaseWebSocket):
|
||||
await self.websocket.send_bytes(data)
|
||||
|
||||
|
||||
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
|
||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
||||
"""AIOHTTP Driver"""
|
||||
|
@@ -1,5 +1,11 @@
|
||||
"""[FastAPI](https://fastapi.tiangolo.com/) 驱动适配
|
||||
|
||||
```bash
|
||||
nb driver install fastapi
|
||||
# 或者
|
||||
pip install nonebot2[fastapi]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持服务端连接
|
||||
:::
|
||||
@@ -13,13 +19,9 @@ FrontMatter:
|
||||
import logging
|
||||
import contextlib
|
||||
from functools import wraps
|
||||
from typing import Any, List, Tuple, Union, Callable, Optional
|
||||
from typing import Any, Dict, List, Tuple, Union, Callable, Optional
|
||||
|
||||
import uvicorn
|
||||
from pydantic import BaseSettings
|
||||
from fastapi.responses import Response
|
||||
from fastapi import FastAPI, Request, UploadFile, status
|
||||
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
||||
|
||||
from nonebot.config import Env
|
||||
from nonebot.typing import overrides
|
||||
@@ -30,6 +32,16 @@ from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
try:
|
||||
import uvicorn
|
||||
from fastapi.responses import Response
|
||||
from fastapi import FastAPI, Request, UploadFile, status
|
||||
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
||||
except ImportError: # pragma: no cover
|
||||
raise ImportError(
|
||||
"Please install FastAPI by using `pip install nonebot2[fastapi]`"
|
||||
) from None
|
||||
|
||||
|
||||
def catch_closed(func):
|
||||
@wraps(func)
|
||||
@@ -65,6 +77,8 @@ class Config(BaseSettings):
|
||||
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_excludes: Optional[List[str]] = None
|
||||
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
fastapi_extra: Dict[str, Any] = {}
|
||||
"""传递给 `FastAPI` 的其他参数。"""
|
||||
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
@@ -82,6 +96,7 @@ class Driver(ReverseDriver):
|
||||
openapi_url=self.fastapi_config.fastapi_openapi_url,
|
||||
docs_url=self.fastapi_config.fastapi_docs_url,
|
||||
redoc_url=self.fastapi_config.fastapi_redoc_url,
|
||||
**self.fastapi_config.fastapi_extra,
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -262,7 +277,7 @@ class FastAPIWebSocket(BaseWebSocket):
|
||||
async def close(
|
||||
self, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = ""
|
||||
) -> None:
|
||||
await self.websocket.close(code)
|
||||
await self.websocket.close(code, reason)
|
||||
|
||||
@overrides(BaseWebSocket)
|
||||
async def receive(self) -> Union[str, bytes]:
|
||||
|
@@ -18,7 +18,7 @@ from typing import Type, AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.drivers._block_driver import BlockDriver
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.drivers import (
|
||||
Request,
|
||||
Response,
|
||||
@@ -48,17 +48,18 @@ class Mixin(ForwardMixin):
|
||||
@overrides(ForwardMixin)
|
||||
async def request(self, setup: Request) -> Response:
|
||||
async with httpx.AsyncClient(
|
||||
cookies=setup.cookies.jar,
|
||||
http2=setup.version == HTTPVersion.H2,
|
||||
proxies=setup.proxy, # type: ignore
|
||||
proxies=setup.proxy,
|
||||
follow_redirects=True,
|
||||
) as client:
|
||||
response = await client.request(
|
||||
setup.method,
|
||||
str(setup.url),
|
||||
content=setup.content, # type: ignore
|
||||
data=setup.data, # type: ignore
|
||||
content=setup.content,
|
||||
data=setup.data,
|
||||
json=setup.json,
|
||||
files=setup.files, # type: ignore
|
||||
files=setup.files,
|
||||
headers=tuple(setup.headers.items()),
|
||||
timeout=setup.timeout,
|
||||
)
|
||||
@@ -76,5 +77,5 @@ class Mixin(ForwardMixin):
|
||||
yield ws
|
||||
|
||||
|
||||
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
|
||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
||||
"""HTTPX Driver"""
|
||||
|
@@ -1,12 +1,24 @@
|
||||
"""None 驱动适配
|
||||
|
||||
:::tip 提示
|
||||
本驱动不支持任何服务器或客户端连接
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 6
|
||||
description: nonebot.drivers.none 模块
|
||||
"""
|
||||
|
||||
|
||||
import signal
|
||||
import asyncio
|
||||
import threading
|
||||
from typing import Set, Union, Callable, Awaitable, cast
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.config import Env, Config
|
||||
from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.utils import run_sync, is_coroutine_callable
|
||||
|
||||
HOOK_FUNC = Union[Callable[[], None], Callable[[], Awaitable[None]]]
|
||||
@@ -16,7 +28,9 @@ HANDLED_SIGNALS = (
|
||||
)
|
||||
|
||||
|
||||
class BlockDriver(Driver):
|
||||
class Driver(BaseDriver):
|
||||
"""None 驱动框架"""
|
||||
|
||||
def __init__(self, env: Env, config: Config):
|
||||
super().__init__(env, config)
|
||||
self.startup_funcs: Set[HOOK_FUNC] = set()
|
||||
@@ -25,18 +39,18 @@ class BlockDriver(Driver):
|
||||
self.force_exit: bool = False
|
||||
|
||||
@property
|
||||
@overrides(Driver)
|
||||
@overrides(BaseDriver)
|
||||
def type(self) -> str:
|
||||
"""驱动名称: `block_driver`"""
|
||||
return "block_driver"
|
||||
"""驱动名称: `none`"""
|
||||
return "none"
|
||||
|
||||
@property
|
||||
@overrides(Driver)
|
||||
@overrides(BaseDriver)
|
||||
def logger(self):
|
||||
"""block driver 使用的 logger"""
|
||||
"""none driver 使用的 logger"""
|
||||
return logger
|
||||
|
||||
@overrides(Driver)
|
||||
@overrides(BaseDriver)
|
||||
def on_startup(self, func: HOOK_FUNC) -> HOOK_FUNC:
|
||||
"""
|
||||
注册一个启动时执行的函数
|
||||
@@ -44,7 +58,7 @@ class BlockDriver(Driver):
|
||||
self.startup_funcs.add(func)
|
||||
return func
|
||||
|
||||
@overrides(Driver)
|
||||
@overrides(BaseDriver)
|
||||
def on_shutdown(self, func: HOOK_FUNC) -> HOOK_FUNC:
|
||||
"""
|
||||
注册一个停止时执行的函数
|
||||
@@ -52,22 +66,22 @@ class BlockDriver(Driver):
|
||||
self.shutdown_funcs.add(func)
|
||||
return func
|
||||
|
||||
@overrides(Driver)
|
||||
@overrides(BaseDriver)
|
||||
def run(self, *args, **kwargs):
|
||||
"""启动 block driver"""
|
||||
"""启动 none driver"""
|
||||
super().run(*args, **kwargs)
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(self.serve())
|
||||
loop.run_until_complete(self._serve())
|
||||
|
||||
async def serve(self):
|
||||
self.install_signal_handlers()
|
||||
await self.startup()
|
||||
async def _serve(self):
|
||||
self._install_signal_handlers()
|
||||
await self._startup()
|
||||
if self.should_exit.is_set():
|
||||
return
|
||||
await self.main_loop()
|
||||
await self.shutdown()
|
||||
await self._main_loop()
|
||||
await self._shutdown()
|
||||
|
||||
async def startup(self):
|
||||
async def _startup(self):
|
||||
# run startup
|
||||
cors = [
|
||||
cast(Callable[..., Awaitable[None]], startup)()
|
||||
@@ -86,10 +100,10 @@ class BlockDriver(Driver):
|
||||
|
||||
logger.info("Application startup completed.")
|
||||
|
||||
async def main_loop(self):
|
||||
async def _main_loop(self):
|
||||
await self.should_exit.wait()
|
||||
|
||||
async def shutdown(self):
|
||||
async def _shutdown(self):
|
||||
logger.info("Shutting down")
|
||||
|
||||
logger.info("Waiting for application shutdown.")
|
||||
@@ -130,7 +144,7 @@ class BlockDriver(Driver):
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.stop()
|
||||
|
||||
def install_signal_handlers(self) -> None:
|
||||
def _install_signal_handlers(self) -> None:
|
||||
if threading.current_thread() is not threading.main_thread():
|
||||
# Signals can only be listened to from the main thread.
|
||||
return
|
||||
@@ -139,13 +153,13 @@ class BlockDriver(Driver):
|
||||
|
||||
try:
|
||||
for sig in HANDLED_SIGNALS:
|
||||
loop.add_signal_handler(sig, self.handle_exit, sig, None)
|
||||
loop.add_signal_handler(sig, self._handle_exit, sig, None)
|
||||
except NotImplementedError:
|
||||
# Windows
|
||||
for sig in HANDLED_SIGNALS:
|
||||
signal.signal(sig, self.handle_exit)
|
||||
signal.signal(sig, self._handle_exit)
|
||||
|
||||
def handle_exit(self, sig, frame):
|
||||
def _handle_exit(self, sig, frame):
|
||||
if self.should_exit.is_set():
|
||||
self.force_exit = True
|
||||
else:
|
@@ -17,9 +17,8 @@ FrontMatter:
|
||||
|
||||
import asyncio
|
||||
from functools import wraps
|
||||
from typing import List, Tuple, Union, TypeVar, Callable, Optional, Coroutine
|
||||
from typing import Any, Dict, List, Tuple, Union, TypeVar, Callable, Optional, Coroutine
|
||||
|
||||
import uvicorn
|
||||
from pydantic import BaseSettings
|
||||
|
||||
from nonebot.config import Env
|
||||
@@ -32,6 +31,7 @@ from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
try:
|
||||
import uvicorn
|
||||
from quart import request as _request
|
||||
from quart import websocket as _websocket
|
||||
from quart import Quart, Request, Response
|
||||
@@ -69,6 +69,8 @@ class Config(BaseSettings):
|
||||
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
quart_reload_excludes: Optional[List[str]] = None
|
||||
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
quart_extra: Dict[str, Any] = {}
|
||||
"""传递给 `Quart` 的其他参数。"""
|
||||
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
@@ -82,7 +84,9 @@ class Driver(ReverseDriver):
|
||||
|
||||
self.quart_config = Config(**config.dict())
|
||||
|
||||
self._server_app = Quart(self.__class__.__qualname__)
|
||||
self._server_app = Quart(
|
||||
self.__class__.__qualname__, **self.quart_config.quart_extra
|
||||
)
|
||||
|
||||
@property
|
||||
@overrides(ReverseDriver)
|
||||
|
@@ -23,7 +23,7 @@ from nonebot.typing import overrides
|
||||
from nonebot.log import LoguruHandler
|
||||
from nonebot.drivers import Request, Response
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers._block_driver import BlockDriver
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
|
||||
|
||||
@@ -70,7 +70,7 @@ class Mixin(ForwardMixin):
|
||||
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
||||
connection = Connect(
|
||||
str(setup.url),
|
||||
extra_headers=setup.headers.items(),
|
||||
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
|
||||
open_timeout=setup.timeout,
|
||||
)
|
||||
async with connection as ws:
|
||||
@@ -101,8 +101,7 @@ class WebSocket(BaseWebSocket):
|
||||
@overrides(BaseWebSocket)
|
||||
@catch_closed
|
||||
async def receive(self) -> Union[str, bytes]:
|
||||
msg = await self.websocket.recv()
|
||||
return msg
|
||||
return await self.websocket.recv()
|
||||
|
||||
@overrides(BaseWebSocket)
|
||||
@catch_closed
|
||||
@@ -129,5 +128,5 @@ class WebSocket(BaseWebSocket):
|
||||
await self.websocket.send(data)
|
||||
|
||||
|
||||
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
|
||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
||||
"""Websockets Driver"""
|
||||
|
@@ -37,6 +37,9 @@ from pydantic.fields import ModelField
|
||||
class NoneBotException(Exception):
|
||||
"""所有 NoneBot 发生的异常基类。"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
# Rule Exception
|
||||
class ParserExit(NoneBotException):
|
||||
@@ -53,9 +56,6 @@ class ParserExit(NoneBotException):
|
||||
+ ")"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
# Processor Exception
|
||||
class ProcessException(NoneBotException):
|
||||
@@ -75,9 +75,6 @@ class IgnoredException(ProcessException):
|
||||
def __repr__(self) -> str:
|
||||
return f"IgnoredException(reason={self.reason!r})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class SkippedException(ProcessException):
|
||||
"""指示 NoneBot 立即结束当前 `Dependent` 的运行。
|
||||
@@ -109,9 +106,6 @@ class TypeMisMatch(SkippedException):
|
||||
f"type={self.param._type_display()}, value={self.value!r}>"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class MockApiException(ProcessException):
|
||||
"""指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。可由 api hook 抛出。
|
||||
@@ -126,9 +120,6 @@ class MockApiException(ProcessException):
|
||||
def __repr__(self) -> str:
|
||||
return f"MockApiException(result={self.result!r})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class StopPropagation(ProcessException):
|
||||
"""指示 NoneBot 终止事件向下层传播。
|
||||
@@ -244,6 +235,3 @@ class WebSocketClosed(DriverException):
|
||||
+ (f", reason={self.reason!r}" if self.reason else "")
|
||||
+ ")"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
@@ -43,6 +43,10 @@ class Bot(abc.ABC):
|
||||
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)
|
||||
|
||||
@property
|
||||
|
@@ -49,11 +49,11 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
) -> None:
|
||||
...
|
||||
|
||||
def __init__( # type:ignore
|
||||
self, template, factory=str
|
||||
) -> None: # TODO: fix type hint here
|
||||
self.template: TF = template
|
||||
self.factory: Type[TF] = factory
|
||||
def __init__(
|
||||
self, template: Union[str, TM], factory: Union[Type[str], Type[TM]] = str
|
||||
) -> None:
|
||||
self.template: TF = template # type: ignore
|
||||
self.factory: Type[TF] = factory # type: ignore
|
||||
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -98,7 +98,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
else:
|
||||
raise TypeError("template must be a string or instance of Message!")
|
||||
|
||||
self.check_unused_args(list(used_args), args, kwargs)
|
||||
self.check_unused_args(used_args, args, kwargs)
|
||||
return cast(TF, full_message)
|
||||
|
||||
def vformat(
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import abc
|
||||
import urllib.request
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
@@ -105,12 +106,9 @@ class Request:
|
||||
self.url: URL = url
|
||||
|
||||
# headers
|
||||
self.headers: CIMultiDict[str]
|
||||
if headers is not None:
|
||||
self.headers = CIMultiDict(headers)
|
||||
else:
|
||||
self.headers = CIMultiDict()
|
||||
|
||||
self.headers: CIMultiDict[str] = (
|
||||
CIMultiDict(headers) if headers is not None else CIMultiDict()
|
||||
)
|
||||
# cookies
|
||||
self.cookies = Cookies(cookies)
|
||||
|
||||
@@ -147,12 +145,9 @@ class Response:
|
||||
self.status_code: int = status_code
|
||||
|
||||
# headers
|
||||
self.headers: CIMultiDict[str]
|
||||
if headers is not None:
|
||||
self.headers = CIMultiDict(headers)
|
||||
else:
|
||||
self.headers = CIMultiDict()
|
||||
|
||||
self.headers: CIMultiDict[str] = (
|
||||
CIMultiDict(headers) if headers is not None else CIMultiDict()
|
||||
)
|
||||
# body
|
||||
self.content: ContentTypes = content
|
||||
|
||||
@@ -308,6 +303,11 @@ class Cookies(MutableMapping):
|
||||
for cookie in cookies.jar:
|
||||
self.jar.set_cookie(cookie)
|
||||
|
||||
def as_header(self, request: Request) -> Dict[str, str]:
|
||||
urllib_request = self._CookieCompatRequest(request)
|
||||
self.jar.add_cookie_header(urllib_request)
|
||||
return urllib_request.added_headers
|
||||
|
||||
def __setitem__(self, name: str, value: str) -> None:
|
||||
return self.set(name, value)
|
||||
|
||||
@@ -333,6 +333,20 @@ class Cookies(MutableMapping):
|
||||
)
|
||||
return f"{self.__class__.__name__}({cookies_repr})"
|
||||
|
||||
class _CookieCompatRequest(urllib.request.Request):
|
||||
def __init__(self, request: Request) -> None:
|
||||
super().__init__(
|
||||
url=str(request.url),
|
||||
headers=dict(request.headers),
|
||||
method=request.method,
|
||||
)
|
||||
self.request = request
|
||||
self.added_headers: Dict[str, str] = {}
|
||||
|
||||
def add_unredirected_header(self, key: str, value: str) -> None:
|
||||
super().add_unredirected_header(key, value)
|
||||
self.added_headers[key] = value
|
||||
|
||||
|
||||
@dataclass
|
||||
class HTTPServerSetup:
|
||||
|
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,12 +1,10 @@
|
||||
from types import ModuleType
|
||||
from contextvars import ContextVar
|
||||
from collections import defaultdict
|
||||
from contextlib import AsyncExitStack
|
||||
from datetime import datetime, timedelta
|
||||
from contextlib import AsyncExitStack, contextmanager
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Union,
|
||||
@@ -19,7 +17,16 @@ from typing import (
|
||||
)
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.internal.rule import Rule
|
||||
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 (
|
||||
ARG_KEY,
|
||||
RECEIVE_KEY,
|
||||
@@ -42,11 +49,7 @@ from nonebot.exception import (
|
||||
FinishedException,
|
||||
RejectedException,
|
||||
)
|
||||
|
||||
from .rule import Rule
|
||||
from .permission import USER, User, Permission
|
||||
from .adapter import Bot, Event, Message, MessageSegment, MessageTemplate
|
||||
from .params import (
|
||||
from nonebot.internal.params import (
|
||||
Depends,
|
||||
ArgParam,
|
||||
BotParam,
|
||||
@@ -57,13 +60,13 @@ from .params import (
|
||||
MatcherParam,
|
||||
)
|
||||
|
||||
from . import matchers
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.plugin import Plugin
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
|
||||
"""用于存储当前所有的事件响应器"""
|
||||
current_bot: ContextVar[Bot] = ContextVar("current_bot")
|
||||
current_event: ContextVar[Event] = ContextVar("current_event")
|
||||
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
||||
@@ -244,6 +247,11 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
return NewMatcher
|
||||
|
||||
@classmethod
|
||||
def destroy(cls) -> None:
|
||||
"""销毁当前的事件响应器"""
|
||||
matchers[cls.priority].remove(cls)
|
||||
|
||||
@classmethod
|
||||
async def check_perm(
|
||||
cls,
|
||||
@@ -661,6 +669,18 @@ class Matcher(metaclass=MatcherMeta):
|
||||
if REJECT_CACHE_TARGET in self.state:
|
||||
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(
|
||||
self,
|
||||
bot: Bot,
|
||||
@@ -673,9 +693,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
f"{self} run with incoming args: "
|
||||
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)
|
||||
|
||||
with self.ensure_context(bot, event):
|
||||
try:
|
||||
# Refresh preprocess state
|
||||
self.state.update(state)
|
||||
@@ -699,9 +718,6 @@ class Matcher(metaclass=MatcherMeta):
|
||||
self.block = True
|
||||
finally:
|
||||
logger.info(f"{self} running complete")
|
||||
current_bot.reset(b_t)
|
||||
current_event.reset(e_t)
|
||||
current_matcher.reset(m_t)
|
||||
|
||||
# 运行handlers
|
||||
async def run(
|
||||
@@ -756,14 +772,3 @@ class Matcher(metaclass=MatcherMeta):
|
||||
)
|
||||
except FinishedException:
|
||||
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
|
||||
"""默认存储器类型"""
|
@@ -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 current_bot as current_bot
|
||||
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_matcher as current_matcher
|
||||
from nonebot.internal.matcher import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
|
||||
|
||||
__autodoc__ = {
|
||||
"Matcher": True,
|
||||
"matchers": True,
|
||||
"MatcherManager": True,
|
||||
"MatcherProvider": True,
|
||||
"DEFAULT_PROVIDER_CLASS": True,
|
||||
}
|
||||
|
@@ -112,7 +112,6 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
||||
|
||||
|
||||
async def _check_matcher(
|
||||
priority: int,
|
||||
Matcher: Type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
@@ -122,7 +121,7 @@ async def _check_matcher(
|
||||
) -> None:
|
||||
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
||||
with contextlib.suppress(Exception):
|
||||
matchers[priority].remove(Matcher)
|
||||
Matcher.destroy()
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -138,7 +137,7 @@ async def _check_matcher(
|
||||
|
||||
if Matcher.temp:
|
||||
with contextlib.suppress(Exception):
|
||||
matchers[priority].remove(Matcher)
|
||||
Matcher.destroy()
|
||||
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
|
||||
|
||||
|
||||
@@ -167,6 +166,8 @@ async def _run_matcher(
|
||||
)
|
||||
for proc in _run_preprocessors
|
||||
]:
|
||||
# ensure matcher function can be correctly called
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except IgnoredException:
|
||||
@@ -205,6 +206,8 @@ async def _run_matcher(
|
||||
)
|
||||
for proc in _run_postprocessors
|
||||
]:
|
||||
# ensure matcher function can be correctly called
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
@@ -290,7 +293,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
|
||||
pending_tasks = [
|
||||
_check_matcher(
|
||||
priority, matcher, bot, event, state.copy(), stack, dependency_cache
|
||||
matcher, bot, event, state.copy(), stack, dependency_cache
|
||||
)
|
||||
for matcher in matchers[priority]
|
||||
]
|
||||
@@ -308,6 +311,9 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
if show_log:
|
||||
logger.debug("Checking for matchers completed")
|
||||
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
|
@@ -5,6 +5,7 @@ FrontMatter:
|
||||
description: nonebot.params 模块
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
|
||||
from nonebot.typing import T_State
|
||||
@@ -24,15 +25,20 @@ from nonebot.internal.params import MatcherParam as MatcherParam
|
||||
from nonebot.internal.params import ExceptionParam as ExceptionParam
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
REGEX_STR,
|
||||
PREFIX_KEY,
|
||||
REGEX_DICT,
|
||||
SHELL_ARGS,
|
||||
SHELL_ARGV,
|
||||
CMD_ARG_KEY,
|
||||
KEYWORD_KEY,
|
||||
RAW_CMD_KEY,
|
||||
REGEX_GROUP,
|
||||
ENDSWITH_KEY,
|
||||
CMD_START_KEY,
|
||||
FULLMATCH_KEY,
|
||||
REGEX_MATCHED,
|
||||
STARTSWITH_KEY,
|
||||
)
|
||||
|
||||
|
||||
@@ -132,10 +138,25 @@ def _regex_matched(state: T_State) -> str:
|
||||
|
||||
def RegexMatched() -> str:
|
||||
"""正则匹配结果"""
|
||||
warnings.warn(
|
||||
'"RegexMatched()" will be changed to "re.Match" object, '
|
||||
'use "RegexStr()" instead. '
|
||||
"See https://github.com/nonebot/nonebot2/pull/1453 .",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return Depends(_regex_matched, use_cache=False)
|
||||
|
||||
|
||||
def _regex_group(state: T_State):
|
||||
def _regex_str(state: T_State) -> str:
|
||||
return state[REGEX_STR]
|
||||
|
||||
|
||||
def RegexStr() -> str:
|
||||
"""正则匹配结果文本"""
|
||||
return Depends(_regex_str, use_cache=False)
|
||||
|
||||
|
||||
def _regex_group(state: T_State) -> Tuple[Any, ...]:
|
||||
return state[REGEX_GROUP]
|
||||
|
||||
|
||||
@@ -144,7 +165,7 @@ def RegexGroup() -> Tuple[Any, ...]:
|
||||
return Depends(_regex_group, use_cache=False)
|
||||
|
||||
|
||||
def _regex_dict(state: T_State):
|
||||
def _regex_dict(state: T_State) -> Dict[str, Any]:
|
||||
return state[REGEX_DICT]
|
||||
|
||||
|
||||
@@ -153,10 +174,46 @@ def RegexDict() -> Dict[str, Any]:
|
||||
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:
|
||||
"""`receive` 事件参数"""
|
||||
|
||||
def _received(matcher: "Matcher"):
|
||||
def _received(matcher: "Matcher") -> Any:
|
||||
return matcher.get_receive(id or "", default)
|
||||
|
||||
return Depends(_received, use_cache=False)
|
||||
|
@@ -65,6 +65,8 @@ def _revert_plugin(plugin: "Plugin") -> None:
|
||||
if plugin.name not in _plugins:
|
||||
raise RuntimeError("Plugin not found!")
|
||||
del _plugins[plugin.name]
|
||||
if parent_plugin := plugin.parent_plugin:
|
||||
parent_plugin.sub_plugins.remove(plugin)
|
||||
|
||||
|
||||
def get_plugin(name: str) -> Optional["Plugin"]:
|
||||
|
@@ -14,7 +14,7 @@ from itertools import chain
|
||||
from types import ModuleType
|
||||
from importlib.abc import MetaPathFinder
|
||||
from importlib.machinery import PathFinder, SourceFileLoader
|
||||
from typing import Set, Dict, List, Union, Iterable, Optional, Sequence
|
||||
from typing import Set, Dict, List, Iterable, Optional, Sequence
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag, path_to_module_name
|
||||
@@ -174,7 +174,7 @@ class PluginFinder(MetaPathFinder):
|
||||
def find_spec(
|
||||
self,
|
||||
fullname: str,
|
||||
path: Optional[Sequence[Union[bytes, str]]],
|
||||
path: Optional[Sequence[str]],
|
||||
target: Optional[ModuleType] = None,
|
||||
):
|
||||
if _managers:
|
||||
|
@@ -439,7 +439,7 @@ def on_regex(
|
||||
|
||||
|
||||
def on_type(
|
||||
types: Union[Type[Event], Tuple[Type[Event]]],
|
||||
types: Union[Type[Event], Tuple[Type[Event], ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
*,
|
||||
_depth: int = 0,
|
||||
|
@@ -154,7 +154,7 @@ def on_regex(
|
||||
state: Optional[T_State] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_type(
|
||||
types: Union[Type[Event], Tuple[Type[Event]]],
|
||||
types: Union[Type[Event], Tuple[Type[Event], ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||
|
@@ -12,7 +12,8 @@ from pydantic import BaseModel
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
from . import _plugins as plugins # FIXME: backport for nonebug
|
||||
# FIXME: backport for nonebug
|
||||
from . import _plugins as plugins # nopycln: import
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .manager import PluginManager
|
||||
|
@@ -12,13 +12,13 @@ import re
|
||||
import shlex
|
||||
from argparse import Action
|
||||
from argparse import ArgumentError
|
||||
from contextvars import ContextVar
|
||||
from itertools import chain, product
|
||||
from argparse import Namespace as Namespace
|
||||
from argparse import ArgumentParser as ArgParser
|
||||
from typing import (
|
||||
IO,
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
@@ -43,15 +43,20 @@ from nonebot.params import Command, EventToMe, CommandArg
|
||||
from nonebot.adapters import Bot, Event, Message, MessageSegment
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
REGEX_STR,
|
||||
PREFIX_KEY,
|
||||
REGEX_DICT,
|
||||
SHELL_ARGS,
|
||||
SHELL_ARGV,
|
||||
CMD_ARG_KEY,
|
||||
KEYWORD_KEY,
|
||||
RAW_CMD_KEY,
|
||||
REGEX_GROUP,
|
||||
ENDSWITH_KEY,
|
||||
CMD_START_KEY,
|
||||
FULLMATCH_KEY,
|
||||
REGEX_MATCHED,
|
||||
STARTSWITH_KEY,
|
||||
)
|
||||
|
||||
T = TypeVar("T")
|
||||
@@ -70,6 +75,8 @@ TRIE_VALUE = NamedTuple(
|
||||
"TRIE_VALUE", [("command_start", str), ("command", Tuple[str, ...])]
|
||||
)
|
||||
|
||||
parser_message: ContextVar[str] = ContextVar("parser_message")
|
||||
|
||||
|
||||
class TrieRule:
|
||||
prefix: CharTrie = CharTrie()
|
||||
@@ -136,20 +143,21 @@ class StartswithRule:
|
||||
def __hash__(self) -> int:
|
||||
return hash((frozenset(self.msg), self.ignorecase))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
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
|
||||
return bool(
|
||||
re.match(
|
||||
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:
|
||||
@@ -192,20 +200,21 @@ class EndswithRule:
|
||||
def __hash__(self) -> int:
|
||||
return hash((frozenset(self.msg), self.ignorecase))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
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
|
||||
return bool(
|
||||
re.search(
|
||||
f"(?:{'|'.join(re.escape(prefix) for prefix in self.msg)})$",
|
||||
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:
|
||||
@@ -248,14 +257,20 @@ class FullmatchRule:
|
||||
def __hash__(self) -> int:
|
||||
return hash((frozenset(self.msg), self.ignorecase))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
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
|
||||
return (text.casefold() if self.ignorecase else text) in self.msg
|
||||
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:
|
||||
@@ -294,14 +309,19 @@ class KeywordsRule:
|
||||
def __hash__(self) -> int:
|
||||
return hash(frozenset(self.keywords))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
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
|
||||
return bool(text and any(keyword in text for keyword in self.keywords))
|
||||
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:
|
||||
@@ -430,13 +450,15 @@ class ArgumentParser(ArgParser):
|
||||
)
|
||||
|
||||
def _print_message(self, message: str, file: Optional[IO[str]] = None):
|
||||
if message:
|
||||
setattr(self, "_message", getattr(self, "_message", "") + message)
|
||||
if (msg := parser_message.get(None)) is not None:
|
||||
parser_message.set(msg + message)
|
||||
else:
|
||||
super()._print_message(message, file)
|
||||
|
||||
def exit(self, status: int = 0, message: Optional[str] = None):
|
||||
if message:
|
||||
self._print_message(message)
|
||||
raise ParserExit(status=status, message=getattr(self, "_message", None))
|
||||
raise ParserExit(status=status, message=parser_message.get(None))
|
||||
|
||||
|
||||
class ShellCommandRule:
|
||||
@@ -483,6 +505,7 @@ class ShellCommandRule:
|
||||
)
|
||||
|
||||
if self.parser:
|
||||
t = parser_message.set("")
|
||||
try:
|
||||
args = self.parser.parse_args(state[SHELL_ARGV])
|
||||
state[SHELL_ARGS] = args
|
||||
@@ -490,6 +513,8 @@ class ShellCommandRule:
|
||||
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
|
||||
except ParserExit as e:
|
||||
state[SHELL_ARGS] = e
|
||||
finally:
|
||||
parser_message.reset(t)
|
||||
return True
|
||||
|
||||
|
||||
@@ -592,6 +617,7 @@ class RegexRule:
|
||||
return False
|
||||
if matched := re.search(self.regex, str(msg), self.flags):
|
||||
state[REGEX_MATCHED] = matched.group()
|
||||
state[REGEX_STR] = matched.group()
|
||||
state[REGEX_GROUP] = matched.groups()
|
||||
state[REGEX_DICT] = matched.groupdict()
|
||||
return True
|
||||
@@ -602,7 +628,7 @@ class RegexRule:
|
||||
def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
|
||||
"""匹配符合正则表达式的消息字符串。
|
||||
|
||||
可以通过 {ref}`nonebot.params.RegexMatched` 获取匹配成功的字符串,
|
||||
可以通过 {ref}`nonebot.params.RegexStr` 获取匹配成功的字符串,
|
||||
通过 {ref}`nonebot.params.RegexGroup` 获取匹配成功的 group 元组,
|
||||
通过 {ref}`nonebot.params.RegexDict` 获取匹配成功的 group 字典。
|
||||
|
||||
|
@@ -11,7 +11,7 @@
|
||||
"start": "yarn workspace nonebot start",
|
||||
"serve": "yarn workspace nonebot serve",
|
||||
"clear": "yarn workspace nonebot clear",
|
||||
"prettier": "prettier --config ./.prettierrc --write \"./website/**/*.md\""
|
||||
"prettier": "prettier --config ./.prettierrc --write \"./website/\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
|
3240
poetry.lock
generated
3240
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot2"
|
||||
version = "2.0.0-rc.1"
|
||||
version = "2.0.0rc3"
|
||||
description = "An asynchronous python bot framework."
|
||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
@@ -26,33 +26,40 @@ python = "^3.8"
|
||||
yarl = "^1.7.2"
|
||||
loguru = "^0.6.0"
|
||||
pygtrie = "^2.4.1"
|
||||
fastapi = "^0.79.0"
|
||||
tomlkit = ">=0.10.0,<1.0.0"
|
||||
typing-extensions = ">=3.10.0,<5.0.0"
|
||||
Quart = { version = "^0.17.0", optional = true }
|
||||
websockets = { version="^10.0", optional = true }
|
||||
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 }
|
||||
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
websockets = { version = "^10.0", optional = true }
|
||||
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
||||
fastapi = { version = ">=0.87.0,!=0.89.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 }
|
||||
uvicorn = { version = ">=0.20.0,<1.0.0", extras = ["standard"], optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pycln = "^2.1.2"
|
||||
isort = "^5.10.1"
|
||||
black = "^22.1.0"
|
||||
nonemoji = "^0.1.2"
|
||||
pytest-cov = "^3.0.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" }
|
||||
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
nb-autodoc = { git = "https://github.com/nonebot/nb-autodoc.git" }
|
||||
|
||||
[tool.poetry.extras]
|
||||
quart = ["quart"]
|
||||
httpx = ["httpx"]
|
||||
aiohttp = ["aiohttp"]
|
||||
websockets = ["websockets"]
|
||||
all = ["quart", "aiohttp", "httpx", "websockets"]
|
||||
quart = ["quart", "uvicorn"]
|
||||
fastapi = ["fastapi", "uvicorn"]
|
||||
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
@@ -64,7 +71,7 @@ filterwarnings = [
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39", "py310"]
|
||||
target-version = ["py38", "py39", "py310", "py311"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
@@ -78,6 +85,20 @@ force_sort_within_sections = true
|
||||
src_paths = ["nonebot", "tests"]
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[tool.pycln]
|
||||
path = "."
|
||||
all = false
|
||||
|
||||
[tool.pyright]
|
||||
reportShadowedImports = false
|
||||
pythonVersion = "3.8"
|
||||
pythonPlatform = "All"
|
||||
executionEnvironments = [
|
||||
{ root = "./tests", extraPaths = ["./"] },
|
||||
{ root = "./" },
|
||||
]
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry_core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
@@ -1,2 +1,3 @@
|
||||
ENVIRONMENT=test
|
||||
COMMON_CONFIG=common
|
||||
COMMON_OVERRIDE=old
|
||||
|
@@ -1,5 +1,13 @@
|
||||
LOG_LEVEL=TRACE
|
||||
NICKNAME=["test"]
|
||||
SUPERUSERS=["test", "fake:faketest"]
|
||||
COMMON_OVERRIDE=new
|
||||
CONFIG_FROM_ENV=
|
||||
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 +1 @@
|
||||
from .nested_subplugin2 import a
|
||||
from .nested_subplugin2 import a # nopycln: import
|
||||
|
@@ -4,10 +4,15 @@ from nonebot.typing import T_State
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import (
|
||||
Command,
|
||||
Keyword,
|
||||
Endswith,
|
||||
RegexStr,
|
||||
Fullmatch,
|
||||
RegexDict,
|
||||
CommandArg,
|
||||
RawCommand,
|
||||
RegexGroup,
|
||||
Startswith,
|
||||
CommandStart,
|
||||
RegexMatched,
|
||||
ShellCommandArgs,
|
||||
@@ -65,3 +70,23 @@ async def regex_group(regex_group: Tuple = RegexGroup()) -> Tuple:
|
||||
|
||||
async def regex_matched(regex_matched: str = RegexMatched()) -> str:
|
||||
return regex_matched
|
||||
|
||||
|
||||
async def regex_str(regex_matched: str = RegexStr()) -> str:
|
||||
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,3 +1,5 @@
|
||||
import json
|
||||
import asyncio
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
@@ -79,6 +81,62 @@ async def test_reverse_driver(app: App):
|
||||
|
||||
await ws.close()
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"nonebug_init",
|
||||
[
|
||||
pytest.param({"driver": "nonebot.drivers.httpx:Driver"}, id="httpx"),
|
||||
pytest.param({"driver": "nonebot.drivers.aiohttp:Driver"}, id="aiohttp"),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_http_driver(app: App):
|
||||
import nonebot
|
||||
from nonebot.drivers import Request, ForwardDriver
|
||||
|
||||
driver = cast(ForwardDriver, nonebot.get_driver())
|
||||
|
||||
request = Request(
|
||||
"POST",
|
||||
"https://httpbin.org/post",
|
||||
params={"param": "test"},
|
||||
headers={"X-Test": "test"},
|
||||
cookies={"session": "test"},
|
||||
content="test",
|
||||
)
|
||||
response = await driver.request(request)
|
||||
assert response.status_code == 200 and response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["args"] == {"param": "test"}
|
||||
assert data["headers"].get("X-Test") == "test"
|
||||
assert data["headers"].get("Cookie") == "session=test"
|
||||
assert data["data"] == "test"
|
||||
|
||||
request = Request("POST", "https://httpbin.org/post", data={"form": "test"})
|
||||
response = await driver.request(request)
|
||||
assert response.status_code == 200 and response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["form"] == {"form": "test"}
|
||||
|
||||
request = Request("POST", "https://httpbin.org/post", json={"json": "test"})
|
||||
response = await driver.request(request)
|
||||
assert response.status_code == 200 and response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["json"] == {"json": "test"}
|
||||
|
||||
request = Request(
|
||||
"POST", "https://httpbin.org/post", files={"test": ("test.txt", b"test")}
|
||||
)
|
||||
response = await driver.request(request)
|
||||
assert response.status_code == 200 and response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["files"] == {"test": "test"}
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
@@ -91,7 +149,7 @@ async def test_reverse_driver(app: App):
|
||||
),
|
||||
pytest.param(
|
||||
{"driver": "~httpx:Driver+~websockets"},
|
||||
"block_driver+httpx+websockets",
|
||||
"none+httpx+websockets",
|
||||
id="httpx+websockets",
|
||||
),
|
||||
],
|
||||
|
@@ -29,6 +29,10 @@ async def test_init(nonebug_init):
|
||||
assert config.config_override == "new"
|
||||
assert config.config_from_init == "init"
|
||||
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
|
||||
|
@@ -187,25 +187,19 @@ async def test_expire(app: App, load_plugin):
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_temp_matcher in matchers[test_temp_matcher.priority]
|
||||
await _check_matcher(
|
||||
test_temp_matcher.priority, test_temp_matcher, bot, event, {}
|
||||
)
|
||||
await _check_matcher(test_temp_matcher, bot, event, {})
|
||||
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
|
||||
|
||||
event = make_fake_event()()
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
|
||||
await _check_matcher(
|
||||
test_datetime_matcher.priority, test_datetime_matcher, bot, event, {}
|
||||
)
|
||||
await _check_matcher(test_datetime_matcher, bot, event, {})
|
||||
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
|
||||
|
||||
event = make_fake_event()()
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
|
||||
await _check_matcher(
|
||||
test_timedelta_matcher.priority, test_timedelta_matcher, bot, event, {}
|
||||
)
|
||||
await _check_matcher(test_timedelta_matcher, bot, event, {})
|
||||
assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
|
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
|
@@ -163,20 +163,30 @@ async def test_state(app: App, load_plugin):
|
||||
from nonebot.params import StateParam, DependParam
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
REGEX_STR,
|
||||
PREFIX_KEY,
|
||||
REGEX_DICT,
|
||||
SHELL_ARGS,
|
||||
SHELL_ARGV,
|
||||
CMD_ARG_KEY,
|
||||
KEYWORD_KEY,
|
||||
RAW_CMD_KEY,
|
||||
REGEX_GROUP,
|
||||
ENDSWITH_KEY,
|
||||
CMD_START_KEY,
|
||||
FULLMATCH_KEY,
|
||||
REGEX_MATCHED,
|
||||
STARTSWITH_KEY,
|
||||
)
|
||||
from plugins.param.param_state import (
|
||||
state,
|
||||
command,
|
||||
keyword,
|
||||
endswith,
|
||||
fullmatch,
|
||||
regex_str,
|
||||
regex_dict,
|
||||
startswith,
|
||||
command_arg,
|
||||
raw_command,
|
||||
regex_group,
|
||||
@@ -199,8 +209,13 @@ async def test_state(app: App, load_plugin):
|
||||
SHELL_ARGV: ["-h"],
|
||||
SHELL_ARGS: {"help": True},
|
||||
REGEX_MATCHED: "[cq:test,arg=value]",
|
||||
REGEX_STR: "[cq:test,arg=value]",
|
||||
REGEX_GROUP: ("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:
|
||||
@@ -259,6 +274,12 @@ async def test_state(app: App, load_plugin):
|
||||
ctx.pass_params(state=fake_state)
|
||||
ctx.should_return(fake_state[REGEX_MATCHED])
|
||||
|
||||
async with app.test_dependent(
|
||||
regex_str, allow_types=[StateParam, DependParam]
|
||||
) as ctx:
|
||||
ctx.pass_params(state=fake_state)
|
||||
ctx.should_return(fake_state[REGEX_STR])
|
||||
|
||||
async with app.test_dependent(
|
||||
regex_group, allow_types=[StateParam, DependParam]
|
||||
) as ctx:
|
||||
@@ -271,6 +292,30 @@ async def test_state(app: App, load_plugin):
|
||||
ctx.pass_params(state=fake_state)
|
||||
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
|
||||
async def test_matcher(app: App, load_plugin):
|
||||
|
@@ -65,19 +65,24 @@ async def test_startswith(
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
from nonebot.consts import STARTSWITH_KEY
|
||||
from nonebot.rule import StartswithRule, startswith
|
||||
|
||||
test_startswith = startswith(msg, ignorecase)
|
||||
dependent = list(test_startswith.checkers)[0]
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
|
||||
assert isinstance(checker, StartswithRule)
|
||||
assert checker.msg == (msg,) if isinstance(msg, str) else msg
|
||||
assert checker.msg == msg
|
||||
assert checker.ignorecase == ignorecase
|
||||
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
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
|
||||
@@ -103,19 +108,24 @@ async def test_endswith(
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
from nonebot.consts import ENDSWITH_KEY
|
||||
from nonebot.rule import EndswithRule, endswith
|
||||
|
||||
test_endswith = endswith(msg, ignorecase)
|
||||
dependent = list(test_endswith.checkers)[0]
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
|
||||
assert isinstance(checker, EndswithRule)
|
||||
assert checker.msg == (msg,) if isinstance(msg, str) else msg
|
||||
assert checker.msg == msg
|
||||
assert checker.ignorecase == ignorecase
|
||||
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
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
|
||||
@@ -141,19 +151,24 @@ async def test_fullmatch(
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
from nonebot.consts import FULLMATCH_KEY
|
||||
from nonebot.rule import FullmatchRule, fullmatch
|
||||
|
||||
test_fullmatch = fullmatch(msg, ignorecase)
|
||||
dependent = list(test_fullmatch.checkers)[0]
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
|
||||
assert isinstance(checker, FullmatchRule)
|
||||
assert checker.msg == ((msg,) if isinstance(msg, str) else msg)
|
||||
assert checker.msg == msg
|
||||
assert checker.ignorecase == ignorecase
|
||||
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
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
|
||||
@@ -164,6 +179,7 @@ async def test_fullmatch(
|
||||
(("key", "foo"), "message", "_foo_", True),
|
||||
(("key",), "message", None, False),
|
||||
(("key",), "notice", "foo", False),
|
||||
(("key",), "message", "foo", False),
|
||||
],
|
||||
)
|
||||
async def test_keyword(
|
||||
@@ -173,6 +189,7 @@ async def test_keyword(
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
from nonebot.consts import KEYWORD_KEY
|
||||
from nonebot.rule import KeywordsRule, keyword
|
||||
|
||||
test_keyword = keyword(*kws)
|
||||
@@ -184,7 +201,9 @@ async def test_keyword(
|
||||
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
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
|
||||
@@ -277,6 +296,7 @@ async def test_shell_command(app: App):
|
||||
assert state[SHELL_ARGV] == []
|
||||
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||
assert state[SHELL_ARGS].status != 0
|
||||
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
|
||||
|
||||
test_message_parser = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_message_parser.checkers)[0]
|
||||
@@ -308,7 +328,7 @@ async def test_shell_command(app: App):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"pattern,type,text,expected,matched,group,dict",
|
||||
"pattern,type,text,expected,matched,string,group,dict",
|
||||
[
|
||||
(
|
||||
r"(?P<key>key\d)",
|
||||
@@ -316,11 +336,12 @@ async def test_shell_command(app: App):
|
||||
"_key1_",
|
||||
True,
|
||||
"key1",
|
||||
"key1",
|
||||
("key1",),
|
||||
{"key": "key1"},
|
||||
),
|
||||
(r"foo", "message", None, False, None, None, None),
|
||||
(r"foo", "notice", "foo", False, None, None, None),
|
||||
(r"foo", "message", None, False, None, None, None, None),
|
||||
(r"foo", "notice", "foo", False, None, None, None, None),
|
||||
],
|
||||
)
|
||||
async def test_regex(
|
||||
@@ -330,12 +351,13 @@ async def test_regex(
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
matched: Optional[str],
|
||||
string: 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
|
||||
from nonebot.consts import REGEX_STR, REGEX_DICT, REGEX_GROUP, REGEX_MATCHED
|
||||
|
||||
test_regex = regex(pattern)
|
||||
dependent = list(test_regex.checkers)[0]
|
||||
@@ -349,6 +371,7 @@ async def test_regex(
|
||||
state = {}
|
||||
assert await dependent(event=event, state=state) == expected
|
||||
assert state.get(REGEX_MATCHED) == matched
|
||||
assert state.get(REGEX_STR) == string
|
||||
assert state.get(REGEX_GROUP) == group
|
||||
assert state.get(REGEX_DICT) == dict
|
||||
|
||||
|
@@ -20,7 +20,11 @@ options:
|
||||
|
||||
您可以选择自己喜欢的方式将插件发布到 [**PyPI**](https://pypi.org/),如使用 [**setuptools**](https://pypi.org/project/setuptools/) 或 [**Poetry**](https://pypi.org/project/poetry/)。
|
||||
|
||||
发布时,请您为自己的插件取一个清晰易懂的名字。通常而言,一款 NoneBot2 插件名称使用 `nonebot-plugin-` 作为前缀(如`nonebot-plugin-foo`),以 `nonebot_plugin_` 作为包名的前缀(如`nonebot_plugin_foo`),这并非强制规范,而是为了防止与其他 PyPI 包产生冲突,所以我们推荐您在没有特殊需求的情况下这样做。
|
||||
发布时,请您为自己的插件取一个清晰易懂的名字。通常而言,一款 NoneBot2 插件名称使用 `nonebot-plugin-` 作为 PyPI 项目名前缀(如`nonebot-plugin-foo`),以 `nonebot_plugin_` 作为 Python 包名的前缀(如`nonebot_plugin_foo`),这并非强制规范,而是为了防止与其他 PyPI 包产生冲突,所以我们推荐您在没有特殊需求的情况下这样做。
|
||||
|
||||
:::warning
|
||||
虽然在 NoneBot 2 载入插件时,插件的 Python 包名中可以使用 `-`,但是在 Python 的 import 语句中,`-` 不会被解析为包名的一部分。如果插件需要向外界提供 import 语法导入的支持,应在 Python 包名中使用 `_` 代替 `-`。
|
||||
:::
|
||||
|
||||
发布后,请确保您的插件已能公开的从 PyPI 访问到,试着检查您的插件在 PyPI 的地址,如 `https://pypi.org/project/<您的 NoneBot2 插件项目名>`。
|
||||
|
||||
@@ -32,7 +36,7 @@ options:
|
||||
|
||||
### 申请发布到 NoneBot2 插件商店
|
||||
|
||||
完成在 PyPI 的插件发布流程与源代码托管流程后,请您前往 [**NoneBot2 商店**](https://v2.nonebot.dev/store.html)页面,切换到**插件**页签,点击**发布插件**按钮。
|
||||
完成在 PyPI 的插件发布流程与源代码托管流程后,请您前往 [**NoneBot2 商店**](https://v2.nonebot.dev/store)页面,切换到**插件**页签,点击**发布插件**按钮。
|
||||
|
||||

|
||||
|
||||
@@ -61,12 +65,12 @@ import 包名:您的插件通过 Python 导入时使用的包名,如 nonebot
|
||||
|
||||
之后,NoneBot2 的维护者们将会对插件进行进一步的检查,以确保用户能够正常安装并使用该插件。
|
||||
|
||||
完成这些步骤后,您的插件将会被合并到 [**NoneBot2 商店**](https://v2.nonebot.dev/store.html),而您也将成为 [**NoneBot2 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)中的一员。
|
||||
完成这些步骤后,您的插件将会被合并到 [**NoneBot2 商店**](https://v2.nonebot.dev/store),而您也将成为 [**NoneBot2 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)中的一员。
|
||||
|
||||
## 完成
|
||||
|
||||
恭喜您,经过上述的发布流程,您的插件已经成功发布到 NoneBot2 商店了。
|
||||
|
||||
此时,您可以在 [**NoneBot2 商店**](https://v2.nonebot.dev/store.html)的插件页签查找到您的插件。同时,欢迎您成为 [**NoneBot2 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)!
|
||||
此时,您可以在 [**NoneBot2 商店**](https://v2.nonebot.dev/store)的插件页签查找到您的插件。同时,欢迎您成为 [**NoneBot2 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)!
|
||||
|
||||
**Congratulations!**
|
||||
|
@@ -49,8 +49,8 @@ async def init_adapter(app: App, import_hook):
|
||||
|
||||
1. 反向 HTTP(WebHook)
|
||||
2. 反向 WebSocket
|
||||
3. ~~正向 HTTP(尚未实现)~~
|
||||
4. ~~正向 WebSocket(尚未实现)~~
|
||||
3. 正向 HTTP
|
||||
4. 正向 WebSocket
|
||||
|
||||
NoneBug 的 `test_server` 方法可以供我们测试反向连接方式。
|
||||
|
||||
|
@@ -24,7 +24,7 @@ nb adapter list
|
||||
或者自行输入命令安装:
|
||||
|
||||
```bash
|
||||
nb adapter install <adapter-name>
|
||||
nb adapter install <adapter-package>
|
||||
```
|
||||
|
||||
或者使用交互模式安装:
|
||||
@@ -36,7 +36,7 @@ nb adapter install
|
||||
也可以使用 pip 安装
|
||||
|
||||
```bash
|
||||
pip install <adapter-name>
|
||||
pip install <adapter-package>
|
||||
```
|
||||
|
||||
<Asciinema
|
||||
|
@@ -7,7 +7,7 @@ import Asciinema from "@site/src/components/Asciinema";
|
||||
|
||||
# 安装驱动器
|
||||
|
||||
NoneBot 在默认安装情况下内置了 `fastapi` 服务端驱动器,其他驱动器如 `httpx`、`aiohttp` 则需要额外安装。
|
||||
NoneBot 在默认安装情况下内置了 `none` 驱动器,其他驱动器如 `fastapi`、`httpx`、`aiohttp` 则需要额外安装。
|
||||
|
||||
## 查看
|
||||
|
||||
@@ -38,7 +38,7 @@ nb driver install
|
||||
也可以使用 pip 安装
|
||||
|
||||
```bash
|
||||
pip install <driver-name>
|
||||
pip install <driver-package>
|
||||
```
|
||||
|
||||
<Asciinema
|
||||
|
@@ -24,7 +24,7 @@ nb plugin list
|
||||
或者自行输入命令安装:
|
||||
|
||||
```bash
|
||||
nb plugin install <plugin-name>
|
||||
nb plugin install <plugin-package>
|
||||
```
|
||||
|
||||
或者使用交互模式安装:
|
||||
@@ -36,7 +36,7 @@ nb plugin install
|
||||
也可以使用 pip 安装
|
||||
|
||||
```bash
|
||||
pip install <plugin-name>
|
||||
pip install <plugin-package>
|
||||
```
|
||||
|
||||
<Asciinema
|
||||
|
@@ -27,13 +27,21 @@ pip uninstall nonebot
|
||||
|
||||
## 通过脚手架安装(推荐)
|
||||
|
||||
1. (可选)使用你喜欢的 Python 环境管理工具(如 Poetry、venv、Conda 等)创建新的虚拟环境
|
||||
2. 使用 pip 或其他包管理工具安装 nb-cli,NoneBot2 会作为其依赖被一起安装
|
||||
1. 安装 [pipx](https://pypa.github.io/pipx/)
|
||||
|
||||
```bash
|
||||
pip install nb-cli
|
||||
python -m pip install --user pipx
|
||||
python -m pipx ensurepath
|
||||
```
|
||||
|
||||
2. 安装脚手架
|
||||
|
||||
```bash
|
||||
pipx install nb-cli
|
||||
```
|
||||
|
||||
安装完成后,你可以在命令行使用 `nb` 命令来使用脚手架。如果出现无法找到命令的情况(例如:`Command not found`),请参考 [pipx 文档](https://pypa.github.io/pipx/) 检查你的环境变量。
|
||||
|
||||
<Asciinema
|
||||
url="https://asciinema.org/a/464654.cast"
|
||||
options={{ theme: "monokai", poster: "npt:2.8" }}
|
||||
@@ -48,9 +56,9 @@ nb-cli 的使用方法详见[使用脚手架](./nb-cli.md)
|
||||
如果你不想使用脚手架,可以直接安装 NoneBot2,并自行完成开发配置。
|
||||
|
||||
```bash
|
||||
pip install nonebot2
|
||||
pip install nonebot2[fastapi]
|
||||
# 也可以通过 Poetry 安装
|
||||
poetry add nonebot2
|
||||
poetry add nonebot2[fastapi]
|
||||
```
|
||||
|
||||
## 从 GitHub 安装
|
||||
|
@@ -10,10 +10,12 @@ options:
|
||||
|
||||
# 使用脚手架
|
||||
|
||||
`nb-cli` 详细参考文档已移至 <https://cli.nonebot.dev>。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pip install nb-cli
|
||||
pipx install nb-cli
|
||||
```
|
||||
|
||||
## 初次使用
|
||||
@@ -21,14 +23,11 @@ pip install nb-cli
|
||||
在安装完成之后,即可在命令行使用 nb-cli 的命令 `nb` 进行开发:
|
||||
|
||||
```bash
|
||||
# 直接使用 nb 命令
|
||||
nb
|
||||
# 或使用 Python 执行 module
|
||||
python -m nb_cli
|
||||
```
|
||||
|
||||
:::warning 注意
|
||||
通常情况下,你可以直接在命令行使用 `nb` 命令,但如果命令行出现 `Command not found` 错误,这是由于环境变量 `PATH` 没有正确配置或未配置导致的,可以使用第二种方式代替。
|
||||
通常情况下,你可以直接在命令行使用 `nb` 命令。如果出现无法找到命令的情况(例如:`Command not found`),请参考 [pipx 文档](https://pypa.github.io/pipx/) 检查你的环境变量。
|
||||
:::
|
||||
|
||||
## 使用方式
|
||||
|
@@ -14,16 +14,36 @@ options:
|
||||
|
||||
NoneBot 提供了两种方式来调用机器人平台 API,两种方式都需要首先获得 Bot 实例,然后调用相应的方法。
|
||||
|
||||
例如,如果需要调用机器人平台的 `get_user_info` API,可以这样做:
|
||||
## 获取 Bot 实例
|
||||
|
||||
```python
|
||||
from nonebot import get_bot
|
||||
|
||||
bot = get_bot("bot_id")
|
||||
bot = get_bot() # 获取第一个已连接的 bot 实例
|
||||
bot = get_bot("bot_id") # 获取指定 bot_id 的 bot 实例
|
||||
```
|
||||
|
||||
在事件处理依赖中,我们可以使用更为简便的办法来获取 bot 实例,详情可以参考 [获取上下文信息-Bot](https://v2.nonebot.dev/docs/tutorial/plugin/create-handler#bot)
|
||||
|
||||
```python
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
async def handle_func(bot: Bot): # 通过依赖注入获取 bot 实例
|
||||
...
|
||||
```
|
||||
|
||||
## 调用 API
|
||||
|
||||
如果需要调用某个机器人平台的 `get_user_info` API,我们可以使用以下任意一种方式:
|
||||
|
||||
```python
|
||||
# 通过 bot 实例上的魔术方法直接使用.操作符调用 API
|
||||
result = await bot.get_user_info(user_id=12345678)
|
||||
await bot.call_api("get_user_info", user_id=12345678)
|
||||
|
||||
# 通过 bot 实例上的 call_api 方法调用 API
|
||||
result = await bot.call_api("get_user_info", user_id=12345678)
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
API 由平台提供,请参考平台文档。
|
||||
实际可用的 API 由平台提供,请参考平台文档。
|
||||
:::
|
||||
|
@@ -146,6 +146,12 @@ nonebot.run(app="bot:app")
|
||||
默认值:`None`
|
||||
说明:不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
##### `fastapi_extra`
|
||||
|
||||
类型:`Dist[str, Any]`
|
||||
默认值:`{}`
|
||||
说明:传递给 `FastAPI` 的其他参数
|
||||
|
||||
### Quart
|
||||
|
||||
类型:`ReverseDriver`
|
||||
@@ -199,6 +205,12 @@ nonebot.run(app="bot:app")
|
||||
默认值:`None`
|
||||
说明:不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
##### `quart_extra`
|
||||
|
||||
类型:`Dist[str, Any]`
|
||||
默认值:`{}`
|
||||
说明:传递给 `Quart` 的其他参数
|
||||
|
||||
### HTTPX
|
||||
|
||||
类型:`ForwardDriver`
|
||||
|
@@ -224,8 +224,8 @@ NICKNAME=["bot"]
|
||||
命令消息的起始符和分隔符。用于 [`command`](../api/rule.md#command) 规则。
|
||||
|
||||
```env
|
||||
COMMAND_START={"/", "!"}
|
||||
COMMAND_SEP={".", "/"}
|
||||
COMMAND_START=["/", "!"]
|
||||
COMMAND_SEP=[".", "/"]
|
||||
```
|
||||
|
||||
### Session Expire Timeout
|
||||
|
@@ -11,7 +11,7 @@ NoneBot 使用 [Loguru](https://loguru.readthedocs.io/) 进行日志记录,并
|
||||
|
||||
NoneBot 启动时会添加一个默认的日志 handler。此 handler 将会将日志输出到 **stdout**,并且根据配置的日志级别进行过滤。
|
||||
|
||||
[默认格式](../api/log.md#default_format)):
|
||||
[默认格式](../api/log.md#default_format):
|
||||
|
||||
```python
|
||||
default_format: str = (
|
||||
|
@@ -321,18 +321,18 @@ matcher = on_shell_command("cmd")
|
||||
async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||
```
|
||||
|
||||
### RegexMatched
|
||||
### RegexStr
|
||||
|
||||
获取正则匹配结果。
|
||||
获取正则匹配结果的文本。
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_regex
|
||||
from nonebot.params import RegexMatched
|
||||
from nonebot.params import RegexStr
|
||||
|
||||
matcher = on_regex("regex")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: str = RegexMatched()): ...
|
||||
async def _(foo: str = RegexStr()): ...
|
||||
```
|
||||
|
||||
### RegexGroup
|
||||
@@ -363,6 +363,62 @@ matcher = on_regex("regex")
|
||||
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
|
||||
|
||||
获取当前事件响应器实例。
|
||||
|
@@ -17,14 +17,16 @@ export type Obj = {
|
||||
};
|
||||
|
||||
export function filterObjs(filter: string, objs: Obj[]): Obj[] {
|
||||
let filterLower = filter.toLowerCase();
|
||||
return objs.filter((o) => {
|
||||
return (
|
||||
o.module_name?.includes(filter) ||
|
||||
o.project_link?.includes(filter) ||
|
||||
o.name.includes(filter) ||
|
||||
o.desc.includes(filter) ||
|
||||
o.author.includes(filter) ||
|
||||
o.tags.filter((t) => t.label.includes(filter)).length > 0
|
||||
o.module_name?.toLowerCase().includes(filterLower) ||
|
||||
o.project_link?.toLowerCase().includes(filterLower) ||
|
||||
o.name.toLowerCase().includes(filterLower) ||
|
||||
o.desc.toLowerCase().includes(filterLower) ||
|
||||
o.author.toLowerCase().includes(filterLower) ||
|
||||
o.tags.filter((t) => t.label.toLowerCase().includes(filterLower)).length >
|
||||
0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -5,6 +5,207 @@ toc_max_heading_level: 2
|
||||
|
||||
# 更新日志
|
||||
|
||||
## v2.0.0rc3
|
||||
|
||||
### 🚀 新功能
|
||||
|
||||
- Feature: 添加事件响应器检查完成日志 [@A-kirami](https://github.com/A-kirami) ([#1578](https://github.com/nonebot/nonebot2/pull/1578))
|
||||
- Remove: 移除默认安装 FastAPI [@yanyongyu](https://github.com/yanyongyu) ([#1557](https://github.com/nonebot/nonebot2/pull/1557))
|
||||
- Feature: 支持给 `FastAPI` 和 `Quart` 传递额外的参数 [@A-kirami](https://github.com/A-kirami) ([#1543](https://github.com/nonebot/nonebot2/pull/1543))
|
||||
- Feature: 添加 `logger` 重导出 [@A-kirami](https://github.com/A-kirami) ([#1526](https://github.com/nonebot/nonebot2/pull/1526))
|
||||
- Feature: 将 block driver 转正为 none 驱动器 [@he0119](https://github.com/he0119) ([#1522](https://github.com/nonebot/nonebot2/pull/1522))
|
||||
- Develop: 使用 pycln 自动移除未使用的 import [@yanyongyu](https://github.com/yanyongyu) ([#1481](https://github.com/nonebot/nonebot2/pull/1481))
|
||||
- Feature: 添加正则匹配文本注入 [@A-kirami](https://github.com/A-kirami) ([#1457](https://github.com/nonebot/nonebot2/pull/1457))
|
||||
- Feature: 支持主动销毁事件响应器 [@A-kirami](https://github.com/A-kirami) ([#1444](https://github.com/nonebot/nonebot2/pull/1444))
|
||||
|
||||
### 🐛 Bug 修复
|
||||
|
||||
- Fix: 屏蔽 fastapi 0.89.0 [@yanyongyu](https://github.com/yanyongyu) ([#1574](https://github.com/nonebot/nonebot2/pull/1574))
|
||||
- Fix: 修复子插件加载失败时没有从父插件中移除的问题 [@A-kirami](https://github.com/A-kirami) ([#1559](https://github.com/nonebot/nonebot2/pull/1559))
|
||||
- Fix: 修复客户端请求未处理 cookies [@yanyongyu](https://github.com/yanyongyu) ([#1491](https://github.com/nonebot/nonebot2/pull/1491))
|
||||
- Fix: `on_type` typing error [@yanyongyu](https://github.com/yanyongyu) ([#1482](https://github.com/nonebot/nonebot2/pull/1482))
|
||||
- Fix: 修复 ArgumentParser 错误信息叠加问题 [@yanyongyu](https://github.com/yanyongyu) ([#1426](https://github.com/nonebot/nonebot2/pull/1426))
|
||||
|
||||
### 📝 文档
|
||||
|
||||
- Docs: 修改更新部分文档 [@yanyongyu](https://github.com/yanyongyu) ([#1615](https://github.com/nonebot/nonebot2/pull/1615))
|
||||
- Docs: 商店搜索大小写不敏感 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#1609](https://github.com/nonebot/nonebot2/pull/1609))
|
||||
- Docs: 更新测试文档中的连接方式\&细化插件发布描述 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#1504](https://github.com/nonebot/nonebot2/pull/1504))
|
||||
- Docs: 修复文档中部分超链接跳转到 `/store.html` 的问题 [@yzyyz1387](https://github.com/yzyyz1387) ([#1470](https://github.com/nonebot/nonebot2/pull/1470))
|
||||
- Fix: 补充 `params` 模块的类型注解 [@A-kirami](https://github.com/A-kirami) ([#1458](https://github.com/nonebot/nonebot2/pull/1458))
|
||||
- Docs: 移除文档 `自定义日志` 中多余的符号 [@A-kirami](https://github.com/A-kirami) ([#1448](https://github.com/nonebot/nonebot2/pull/1448))
|
||||
- Docs: 完善 `调用平台 API` 部分 [@A-kirami](https://github.com/A-kirami) ([#1447](https://github.com/nonebot/nonebot2/pull/1447))
|
||||
- Docs: 修正文档中部分配置文件示例的符号误用 [@MingxuanGame](https://github.com/MingxuanGame) ([#1432](https://github.com/nonebot/nonebot2/pull/1432))
|
||||
|
||||
### 💫 杂项
|
||||
|
||||
- Plugin: 移除 nonebot-plugin-puppet [@j1g5awi](https://github.com/j1g5awi) ([#1605](https://github.com/nonebot/nonebot2/pull/1605))
|
||||
- Plugin: 更新 MC 的插件信息 [@nikissXI](https://github.com/nikissXI) ([#1589](https://github.com/nonebot/nonebot2/pull/1589))
|
||||
- Plugin: 移除 `nonebot-plugin-aidraw` [@A-kirami](https://github.com/A-kirami) ([#1588](https://github.com/nonebot/nonebot2/pull/1588))
|
||||
- Plugins: 更新 ayaka_games 插件名和描述 [@bridgeL](https://github.com/bridgeL) ([#1586](https://github.com/nonebot/nonebot2/pull/1586))
|
||||
- Plugin: 更新 tts_gal 插件名和描述 [@dpm12345](https://github.com/dpm12345) ([#1581](https://github.com/nonebot/nonebot2/pull/1581))
|
||||
- Plugin: 移除 `nonebot_plugin_super_resolution` [@A-kirami](https://github.com/A-kirami) ([#1561](https://github.com/nonebot/nonebot2/pull/1561))
|
||||
- Plugin: 更新 OlivOS.nb2 import 包名 [@j1g5awi](https://github.com/j1g5awi) ([#1560](https://github.com/nonebot/nonebot2/pull/1560))
|
||||
- Develop: 添加 pyright 环境配置 [@yanyongyu](https://github.com/yanyongyu) ([#1554](https://github.com/nonebot/nonebot2/pull/1554))
|
||||
- CI: 优化触发条件减少无效运行 [@he0119](https://github.com/he0119) ([#1545](https://github.com/nonebot/nonebot2/pull/1545))
|
||||
- Plugin: 删除 ayaka_who_is_suspect 插件 [@bridgeL](https://github.com/bridgeL) ([#1525](https://github.com/nonebot/nonebot2/pull/1525))
|
||||
- Fix: 修复异常在 traceback 中无法正常显示信息 [@he0119](https://github.com/he0119) ([#1521](https://github.com/nonebot/nonebot2/pull/1521))
|
||||
- CI: 添加插件加载测试 [@he0119](https://github.com/he0119) ([#1519](https://github.com/nonebot/nonebot2/pull/1519))
|
||||
- Plugin: 移除 `nonebot-plugin-filehost` [@mnixry](https://github.com/mnixry) ([#1516](https://github.com/nonebot/nonebot2/pull/1516))
|
||||
- Plugin: 更新 `abstain_diary` 插件名和描述 [@Ikaros-521](https://github.com/Ikaros-521) ([#1509](https://github.com/nonebot/nonebot2/pull/1509))
|
||||
- Plugin: 更新 gpt3 插件模块名 [@chrisyy2003](https://github.com/chrisyy2003) ([#1501](https://github.com/nonebot/nonebot2/pull/1501))
|
||||
- Plugin: 更新 随机禁言 插件功能描述 [@Ikaros-521](https://github.com/Ikaros-521) ([#1495](https://github.com/nonebot/nonebot2/pull/1495))
|
||||
- Plugin: 更新 multi chatgpt 插件仓库地址 [@chrisyy2003](https://github.com/chrisyy2003) ([#1487](https://github.com/nonebot/nonebot2/pull/1487))
|
||||
- Plugin: 更新 ayaka_games 介绍 [@bridgeL](https://github.com/bridgeL) ([#1431](https://github.com/nonebot/nonebot2/pull/1431))
|
||||
- Plugin: 修改 novelai send magiadice 插件模块名 [@sena-nana](https://github.com/sena-nana) ([#1423](https://github.com/nonebot/nonebot2/pull/1423))
|
||||
|
||||
### 🍻 插件发布
|
||||
|
||||
- Plugin: 反向词典 [@yanyongyu](https://github.com/yanyongyu) ([#1619](https://github.com/nonebot/nonebot2/pull/1619))
|
||||
- Plugin: PicMCStat [@yanyongyu](https://github.com/yanyongyu) ([#1614](https://github.com/nonebot/nonebot2/pull/1614))
|
||||
- Plugin: 犯人在跳舞 [@yanyongyu](https://github.com/yanyongyu) ([#1608](https://github.com/nonebot/nonebot2/pull/1608))
|
||||
- Plugin: 喵喵自记菜谱 [@yanyongyu](https://github.com/yanyongyu) ([#1599](https://github.com/nonebot/nonebot2/pull/1599))
|
||||
- Plugin: 语音功能 [@yanyongyu](https://github.com/yanyongyu) ([#1597](https://github.com/nonebot/nonebot2/pull/1597))
|
||||
- Plugin: OrangeDice! [@yanyongyu](https://github.com/yanyongyu) ([#1595](https://github.com/nonebot/nonebot2/pull/1595))
|
||||
- Plugin: 简易谷歌翻译插件 [@yanyongyu](https://github.com/yanyongyu) ([#1593](https://github.com/nonebot/nonebot2/pull/1593))
|
||||
- Plugin: 哔哩哔哩 q 群登录 [@yanyongyu](https://github.com/yanyongyu) ([#1591](https://github.com/nonebot/nonebot2/pull/1591))
|
||||
- Plugin: 原神实时公告 [@yanyongyu](https://github.com/yanyongyu) ([#1585](https://github.com/nonebot/nonebot2/pull/1585))
|
||||
- Plugin: 心灵鸡汤 [@yanyongyu](https://github.com/yanyongyu) ([#1580](https://github.com/nonebot/nonebot2/pull/1580))
|
||||
- Plugin: Bing 每日图片获取 [@yanyongyu](https://github.com/yanyongyu) ([#1577](https://github.com/nonebot/nonebot2/pull/1577))
|
||||
- Plugin: 星座运势 [@yanyongyu](https://github.com/yanyongyu) ([#1572](https://github.com/nonebot/nonebot2/pull/1572))
|
||||
- Plugin: 回声洞 [@yanyongyu](https://github.com/yanyongyu) ([#1573](https://github.com/nonebot/nonebot2/pull/1573))
|
||||
- Plugin: 整点报时 [@yanyongyu](https://github.com/yanyongyu) ([#1569](https://github.com/nonebot/nonebot2/pull/1569))
|
||||
- Plugin: Hypixel 数据查询 [@yanyongyu](https://github.com/yanyongyu) ([#1556](https://github.com/nonebot/nonebot2/pull/1556))
|
||||
- Plugin: 查找图片出处 [@yanyongyu](https://github.com/yanyongyu) ([#1553](https://github.com/nonebot/nonebot2/pull/1553))
|
||||
- Plugin: 云签到 [@yanyongyu](https://github.com/yanyongyu) ([#1551](https://github.com/nonebot/nonebot2/pull/1551))
|
||||
- Plugin: 图像标注 [@yanyongyu](https://github.com/yanyongyu) ([#1550](https://github.com/nonebot/nonebot2/pull/1550))
|
||||
- Plugin: 对对联 [@yanyongyu](https://github.com/yanyongyu) ([#1542](https://github.com/nonebot/nonebot2/pull/1542))
|
||||
- Plugin: 群聊学习 [@yanyongyu](https://github.com/yanyongyu) ([#1540](https://github.com/nonebot/nonebot2/pull/1540))
|
||||
- Plugin: 求生之路 2——服务器操作 [@yanyongyu](https://github.com/yanyongyu) ([#1538](https://github.com/nonebot/nonebot2/pull/1538))
|
||||
- Plugin: setu_customization [@yanyongyu](https://github.com/yanyongyu) ([#1537](https://github.com/nonebot/nonebot2/pull/1537))
|
||||
- Plugin: 主动消息撤回 [@yanyongyu](https://github.com/yanyongyu) ([#1536](https://github.com/nonebot/nonebot2/pull/1536))
|
||||
- Plugin: HttpCat🐱 猫猫 http 状态码 [@yanyongyu](https://github.com/yanyongyu) ([#1529](https://github.com/nonebot/nonebot2/pull/1529))
|
||||
- Plugin: 命令探查 [@yanyongyu](https://github.com/yanyongyu) ([#1524](https://github.com/nonebot/nonebot2/pull/1524))
|
||||
- Plugin: AnimalVoice_Convert [@yanyongyu](https://github.com/yanyongyu) ([#1518](https://github.com/nonebot/nonebot2/pull/1518))
|
||||
- Plugin: 服务状态查询 [@yanyongyu](https://github.com/yanyongyu) ([#1513](https://github.com/nonebot/nonebot2/pull/1513))
|
||||
- Plugin: 腾讯云图像变换 [@yanyongyu](https://github.com/yanyongyu) ([#1515](https://github.com/nonebot/nonebot2/pull/1515))
|
||||
- Plugin: Ping [@yanyongyu](https://github.com/yanyongyu) ([#1508](https://github.com/nonebot/nonebot2/pull/1508))
|
||||
- Plugin: 群友召唤术 [@yanyongyu](https://github.com/yanyongyu) ([#1503](https://github.com/nonebot/nonebot2/pull/1503))
|
||||
- Plugin: 战地群聊天插件 [@yanyongyu](https://github.com/yanyongyu) ([#1506](https://github.com/nonebot/nonebot2/pull/1506))
|
||||
- Plugin: 不要复读 [@yanyongyu](https://github.com/yanyongyu) ([#1500](https://github.com/nonebot/nonebot2/pull/1500))
|
||||
- Plugin: JAVA MC 服务器信息查询 [@yanyongyu](https://github.com/yanyongyu) ([#1497](https://github.com/nonebot/nonebot2/pull/1497))
|
||||
- Plugin: 防撤回 [@yanyongyu](https://github.com/yanyongyu) ([#1489](https://github.com/nonebot/nonebot2/pull/1489))
|
||||
- Plugin: 随机禁言 [@yanyongyu](https://github.com/yanyongyu) ([#1486](https://github.com/nonebot/nonebot2/pull/1486))
|
||||
- Plugin: 只因进化录 [@yanyongyu](https://github.com/yanyongyu) ([#1484](https://github.com/nonebot/nonebot2/pull/1484))
|
||||
- Plugin: GPT3 [@yanyongyu](https://github.com/yanyongyu) ([#1480](https://github.com/nonebot/nonebot2/pull/1480))
|
||||
- Plugin: 熊老板 [@yanyongyu](https://github.com/yanyongyu) ([#1472](https://github.com/nonebot/nonebot2/pull/1472))
|
||||
- Plugin: QQ 群文件备份 [@yanyongyu](https://github.com/yanyongyu) ([#1478](https://github.com/nonebot/nonebot2/pull/1478))
|
||||
- Plugin: 戒色打卡日记 [@yanyongyu](https://github.com/yanyongyu) ([#1475](https://github.com/nonebot/nonebot2/pull/1475))
|
||||
- Plugin: nonebot_plugin_idiom [@yanyongyu](https://github.com/yanyongyu) ([#1469](https://github.com/nonebot/nonebot2/pull/1469))
|
||||
- Plugin: 随机配色方案 [@yanyongyu](https://github.com/yanyongyu) ([#1466](https://github.com/nonebot/nonebot2/pull/1466))
|
||||
- Plugin: multi-ChatGPT [@yanyongyu](https://github.com/yanyongyu) ([#1462](https://github.com/nonebot/nonebot2/pull/1462))
|
||||
- Plugin: 权限控制 [@yanyongyu](https://github.com/yanyongyu) ([#1464](https://github.com/nonebot/nonebot2/pull/1464))
|
||||
- Plugin: 汇率换算 [@yanyongyu](https://github.com/yanyongyu) ([#1452](https://github.com/nonebot/nonebot2/pull/1452))
|
||||
- Plugin: 全群广播 [@yanyongyu](https://github.com/yanyongyu) ([#1450](https://github.com/nonebot/nonebot2/pull/1450))
|
||||
- Plugin: 图片背景消除 [@yanyongyu](https://github.com/yanyongyu) ([#1446](https://github.com/nonebot/nonebot2/pull/1446))
|
||||
- Plugin: 雀魂信息查询 [@yanyongyu](https://github.com/yanyongyu) ([#1443](https://github.com/nonebot/nonebot2/pull/1443))
|
||||
- Plugin: ChatGPT [@yanyongyu](https://github.com/yanyongyu) ([#1439](https://github.com/nonebot/nonebot2/pull/1439))
|
||||
- Plugin: 免费快捷点歌插件 [@yanyongyu](https://github.com/yanyongyu) ([#1436](https://github.com/nonebot/nonebot2/pull/1436))
|
||||
- Plugin: 动画截图追溯来源 [@yanyongyu](https://github.com/yanyongyu) ([#1434](https://github.com/nonebot/nonebot2/pull/1434))
|
||||
- Plugin: b 站图片下载 [@yanyongyu](https://github.com/yanyongyu) ([#1430](https://github.com/nonebot/nonebot2/pull/1430))
|
||||
- Plugin: 记事本 [@yanyongyu](https://github.com/yanyongyu) ([#1420](https://github.com/nonebot/nonebot2/pull/1420))
|
||||
- Plugin: 原神前瞻直播兑换码查询 [@yanyongyu](https://github.com/yanyongyu) ([#1422](https://github.com/nonebot/nonebot2/pull/1422))
|
||||
|
||||
### 🍻 机器人发布
|
||||
|
||||
- Bot: SuzunoBot [@yanyongyu](https://github.com/yanyongyu) ([#1601](https://github.com/nonebot/nonebot2/pull/1601))
|
||||
- Bot: 辞辞(cici)Bot [@yanyongyu](https://github.com/yanyongyu) ([#1583](https://github.com/nonebot/nonebot2/pull/1583))
|
||||
- Bot: RanBot [@yanyongyu](https://github.com/yanyongyu) ([#1511](https://github.com/nonebot/nonebot2/pull/1511))
|
||||
|
||||
### 🍻 适配器发布
|
||||
|
||||
- Adapter: BilibiliLive [@yanyongyu](https://github.com/yanyongyu) ([#1617](https://github.com/nonebot/nonebot2/pull/1617))
|
||||
- Adapter: Spigot [@yanyongyu](https://github.com/yanyongyu) ([#1612](https://github.com/nonebot/nonebot2/pull/1612))
|
||||
|
||||
## 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
|
||||
|
||||
### 💥 破坏性变更
|
||||
|
@@ -98,5 +98,45 @@
|
||||
"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
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.spigot",
|
||||
"project_link": "nonebot-adapter-spigot",
|
||||
"name": "Spigot",
|
||||
"desc": "MineCraft通信适配",
|
||||
"author": "17TheWord",
|
||||
"homepage": "https://github.com/17TheWord/nonebot-adapter-spigot",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Minecraft",
|
||||
"color": "#4ef0ea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapter.bilibili",
|
||||
"project_link": "nonebot-adapter-bilibili",
|
||||
"name": "BilibiliLive",
|
||||
"desc": "b站直播间ws协议",
|
||||
"author": "wwweww",
|
||||
"homepage": "https://github.com/wwweww/adapter-bilibili",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
@@ -123,7 +123,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Inkar Suki",
|
||||
"desc": "一个十分方便的Bot,支持包括Webhook、群管、互动等一系列功能",
|
||||
"desc": "一个十分方便的Bot,支持包括Webhook、群管、剑网3等一系列功能,持续更新中……",
|
||||
"author": "HornCopper",
|
||||
"homepage": "https://github.com/HornCopper/Inkar-Suki",
|
||||
"tags": [
|
||||
@@ -136,8 +136,8 @@
|
||||
"color": "#374fd0"
|
||||
},
|
||||
{
|
||||
"label": "群管",
|
||||
"color": "#4ede39"
|
||||
"label": "剑网3",
|
||||
"color": "#ff0033"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
@@ -276,5 +276,89 @@
|
||||
}
|
||||
],
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "RanBot",
|
||||
"desc": "不@会很安静的Bot",
|
||||
"author": "IAXRetailer",
|
||||
"homepage": "https://github.com/Hecatia-Hell-Workshop/RanBot",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "辞辞(cici)Bot",
|
||||
"desc": "一个集成娱乐和群管为一体的机器人",
|
||||
"author": "mengxinyuan638",
|
||||
"homepage": "https://github.com/mengxinyuan638/cici-bot",
|
||||
"tags": [
|
||||
{
|
||||
"label": "辞辞Bot",
|
||||
"color": "#04de4d"
|
||||
},
|
||||
{
|
||||
"label": "萌新源",
|
||||
"color": "#fd1c06"
|
||||
},
|
||||
{
|
||||
"label": "群管",
|
||||
"color": "#06b8fd"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "SuzunoBot",
|
||||
"desc": "多功能音游bot,主要服务maimaiDX、Arcaea",
|
||||
"author": "Rinfair-CSP-A016",
|
||||
"homepage": "https://github.com/Rinfair-CSP-A016/SuzunoBot-AGLAS",
|
||||
"tags": [
|
||||
{
|
||||
"label": "t:maimaiDX",
|
||||
"color": "#189ede"
|
||||
},
|
||||
{
|
||||
"label": "t:Arcaea",
|
||||
"color": "#d551ef"
|
||||
},
|
||||
{
|
||||
"label": "t:coc",
|
||||
"color": "#7fe4d0"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
@@ -1,7 +1,17 @@
|
||||
[
|
||||
{
|
||||
"module_name": "nonebot.drivers.fastapi",
|
||||
"module_name": "~none",
|
||||
"project_link": "",
|
||||
"name": "None",
|
||||
"desc": "None 驱动器",
|
||||
"author": "yanyongyu",
|
||||
"homepage": "/docs/tutorial/choose-driver",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "~fastapi",
|
||||
"project_link": "nonebot2[fastapi]",
|
||||
"name": "FastAPI",
|
||||
"desc": "FastAPI 驱动器",
|
||||
"author": "yanyongyu",
|
||||
@@ -10,8 +20,8 @@
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.drivers.quart",
|
||||
"project_link": "quart",
|
||||
"module_name": "~quart",
|
||||
"project_link": "nonebot2[quart]",
|
||||
"name": "Quart",
|
||||
"desc": "Quart 驱动器",
|
||||
"author": "yanyongyu",
|
||||
@@ -20,8 +30,8 @@
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.drivers.httpx",
|
||||
"project_link": "httpx",
|
||||
"module_name": "~httpx",
|
||||
"project_link": "nonebot2[httpx]",
|
||||
"name": "HTTPX",
|
||||
"desc": "HTTPX 驱动器",
|
||||
"author": "yanyongyu",
|
||||
@@ -30,8 +40,8 @@
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.drivers.websockets",
|
||||
"project_link": "websockets",
|
||||
"module_name": "~websockets",
|
||||
"project_link": "nonebot2[websockets]",
|
||||
"name": "websockets",
|
||||
"desc": "websockets 驱动器",
|
||||
"author": "yanyongyu",
|
||||
@@ -40,8 +50,8 @@
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.drivers.aiohttp",
|
||||
"project_link": "aiohttp",
|
||||
"module_name": "~aiohttp",
|
||||
"project_link": "nonebot2[aiohttp]",
|
||||
"name": "AIOHTTP",
|
||||
"desc": "AIOHTTP 驱动器",
|
||||
"author": "yanyongyu",
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
||||
---
|
||||
sidebar_position: 8
|
||||
description: 调用机器人平台 API,完成更多的功能
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 29
|
||||
category: guide
|
||||
---
|
||||
|
||||
# 调用平台 API
|
||||
|
||||
在使用机器人功能时,除了发送消息以外,还可能需要调用机器人平台的 API 来完成更多的功能。
|
||||
|
||||
NoneBot 提供了两种方式来调用机器人平台 API,两种方式都需要首先获得 Bot 实例,然后调用相应的方法。
|
||||
|
||||
例如,如果需要调用机器人平台的 `get_user_info` API,可以这样做:
|
||||
|
||||
```python
|
||||
from nonebot import get_bot
|
||||
|
||||
bot = get_bot("bot_id")
|
||||
result = await bot.get_user_info(user_id=12345678)
|
||||
await bot.call_api("get_user_info", user_id=12345678)
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
API 由平台提供,请参考平台文档。
|
||||
:::
|
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 |
@@ -20,7 +20,11 @@ options:
|
||||
|
||||
您可以选择自己喜欢的方式将插件发布到 [**PyPI**](https://pypi.org/),如使用 [**setuptools**](https://pypi.org/project/setuptools/) 或 [**Poetry**](https://pypi.org/project/poetry/)。
|
||||
|
||||
发布时,请您为自己的插件取一个清晰易懂的名字。通常而言,一款 NoneBot2 插件名称使用 `nonebot-plugin-` 作为前缀(如`nonebot-plugin-foo`),以 `nonebot_plugin_` 作为包名的前缀(如`nonebot_plugin_foo`),这并非强制规范,而是为了防止与其他 PyPI 包产生冲突,所以我们推荐您在没有特殊需求的情况下这样做。
|
||||
发布时,请您为自己的插件取一个清晰易懂的名字。通常而言,一款 NoneBot2 插件名称使用 `nonebot-plugin-` 作为 PyPI 项目名前缀(如`nonebot-plugin-foo`),以 `nonebot_plugin_` 作为 Python 包名的前缀(如`nonebot_plugin_foo`),这并非强制规范,而是为了防止与其他 PyPI 包产生冲突,所以我们推荐您在没有特殊需求的情况下这样做。
|
||||
|
||||
:::warning
|
||||
虽然在 NoneBot 2 载入插件时,插件的 Python 包名中可以使用 `-`,但是在 Python 的 import 语句中,`-` 不会被解析为包名的一部分。如果插件需要向外界提供 import 语法导入的支持,应在 Python 包名中使用 `_` 代替 `-`。
|
||||
:::
|
||||
|
||||
发布后,请确保您的插件已能公开的从 PyPI 访问到,试着检查您的插件在 PyPI 的地址,如 `https://pypi.org/project/<您的 NoneBot2 插件项目名>`。
|
||||
|
||||
@@ -32,7 +36,7 @@ options:
|
||||
|
||||
### 申请发布到 NoneBot2 插件商店
|
||||
|
||||
完成在 PyPI 的插件发布流程与源代码托管流程后,请您前往 [**NoneBot2 商店**](https://v2.nonebot.dev/store.html)页面,切换到**插件**页签,点击**发布插件**按钮。
|
||||
完成在 PyPI 的插件发布流程与源代码托管流程后,请您前往 [**NoneBot2 商店**](https://v2.nonebot.dev/store)页面,切换到**插件**页签,点击**发布插件**按钮。
|
||||
|
||||

|
||||
|
||||
@@ -61,12 +65,12 @@ import 包名:您的插件通过 Python 导入时使用的包名,如 nonebot
|
||||
|
||||
之后,NoneBot2 的维护者们将会对插件进行进一步的检查,以确保用户能够正常安装并使用该插件。
|
||||
|
||||
完成这些步骤后,您的插件将会被合并到 [**NoneBot2 商店**](https://v2.nonebot.dev/store.html),而您也将成为 [**NoneBot2 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)中的一员。
|
||||
完成这些步骤后,您的插件将会被合并到 [**NoneBot2 商店**](https://v2.nonebot.dev/store),而您也将成为 [**NoneBot2 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)中的一员。
|
||||
|
||||
## 完成
|
||||
|
||||
恭喜您,经过上述的发布流程,您的插件已经成功发布到 NoneBot2 商店了。
|
||||
|
||||
此时,您可以在 [**NoneBot2 商店**](https://v2.nonebot.dev/store.html)的插件页签查找到您的插件。同时,欢迎您成为 [**NoneBot2 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)!
|
||||
此时,您可以在 [**NoneBot2 商店**](https://v2.nonebot.dev/store)的插件页签查找到您的插件。同时,欢迎您成为 [**NoneBot2 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)!
|
||||
|
||||
**Congratulations!**
|
@@ -49,8 +49,8 @@ async def init_adapter(app: App, import_hook):
|
||||
|
||||
1. 反向 HTTP(WebHook)
|
||||
2. 反向 WebSocket
|
||||
3. ~~正向 HTTP(尚未实现)~~
|
||||
4. ~~正向 WebSocket(尚未实现)~~
|
||||
3. 正向 HTTP
|
||||
4. 正向 WebSocket
|
||||
|
||||
NoneBug 的 `test_server` 方法可以供我们测试反向连接方式。
|
||||
|
@@ -546,9 +546,9 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **参数**
|
||||
|
||||
- `template`: 模板
|
||||
- `template` (str | (~ TM)): 模板
|
||||
|
||||
- `factory`: 消息类型工厂,默认为 `str`
|
||||
- `factory` (Type[str] | Type[(~ TM)]): 消息类型工厂,默认为 `str`
|
||||
|
||||
### _method_ `add_format_spec(self, spec, name=None)` {#MessageTemplate-add_format_spec}
|
||||
|
@@ -21,7 +21,7 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
|
||||
- **参数**
|
||||
|
||||
- `_env_file` (str | os.PathLike | NoneType)
|
||||
- `_env_file` (str | os.PathLike | list[str | os.PathLike] | tuple[str | os.PathLike, ...] | NoneType)
|
||||
|
||||
- `_env_file_encoding` (str | None)
|
||||
|
||||
@@ -56,7 +56,7 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
|
||||
- **参数**
|
||||
|
||||
- `_env_file` (str | os.PathLike | NoneType)
|
||||
- `_env_file` (str | os.PathLike | list[str | os.PathLike] | tuple[str | os.PathLike, ...] | NoneType)
|
||||
|
||||
- `_env_file_encoding` (str | None)
|
||||
|
||||
@@ -72,7 +72,7 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
|
||||
- `log_level` (int | str)
|
||||
|
||||
- `api_timeout` (float)
|
||||
- `api_timeout` (float | None)
|
||||
|
||||
- `superusers` (set[str])
|
||||
|
@@ -85,6 +85,12 @@ description: nonebot.consts 模块
|
||||
|
||||
- **说明:** 正则匹配结果存储 key
|
||||
|
||||
## _var_ `REGEX_STR` {#REGEX_STR}
|
||||
|
||||
- **类型:** Literal['_matched_str']
|
||||
|
||||
- **说明:** 正则匹配文本存储 key
|
||||
|
||||
## _var_ `REGEX_GROUP` {#REGEX_GROUP}
|
||||
|
||||
- **类型:** Literal['_matched_groups']
|
||||
@@ -96,3 +102,27 @@ description: nonebot.consts 模块
|
||||
- **类型:** Literal['_matched_dict']
|
||||
|
||||
- **说明:** 正则匹配 group 字典存储 key
|
||||
|
||||
## _var_ `STARTSWITH_KEY` {#STARTSWITH_KEY}
|
||||
|
||||
- **类型:** Literal['_startswith']
|
||||
|
||||
- **说明:** 响应触发前缀 key
|
||||
|
||||
## _var_ `ENDSWITH_KEY` {#ENDSWITH_KEY}
|
||||
|
||||
- **类型:** Literal['_endswith']
|
||||
|
||||
- **说明:** 响应触发后缀 key
|
||||
|
||||
## _var_ `FULLMATCH_KEY` {#FULLMATCH_KEY}
|
||||
|
||||
- **类型:** Literal['_fullmatch']
|
||||
|
||||
- **说明:** 响应触发完整消息 key
|
||||
|
||||
## _var_ `KEYWORD_KEY` {#KEYWORD_KEY}
|
||||
|
||||
- **类型:** Literal['_keyword']
|
||||
|
||||
- **说明:** 响应触发关键字 key
|
@@ -7,11 +7,17 @@ description: nonebot.drivers.fastapi 模块
|
||||
|
||||
[FastAPI](https://fastapi.tiangolo.com/) 驱动适配
|
||||
|
||||
```bash
|
||||
nb driver install fastapi
|
||||
# 或者
|
||||
pip install nonebot2[fastapi]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持服务端连接
|
||||
:::
|
||||
|
||||
## _class_ `Config(_env_file='<object object>', _env_file_encoding=None, _env_nested_delimiter=None, _secrets_dir=None, *, fastapi_openapi_url=None, fastapi_docs_url=None, fastapi_redoc_url=None, fastapi_include_adapter_schema=True, fastapi_reload=False, fastapi_reload_dirs=None, fastapi_reload_delay=0.25, fastapi_reload_includes=None, fastapi_reload_excludes=None)` {#Config}
|
||||
## _class_ `Config(_env_file='<object object>', _env_file_encoding=None, _env_nested_delimiter=None, _secrets_dir=None, *, fastapi_openapi_url=None, fastapi_docs_url=None, fastapi_redoc_url=None, fastapi_include_adapter_schema=True, fastapi_reload=False, fastapi_reload_dirs=None, fastapi_reload_delay=0.25, fastapi_reload_includes=None, fastapi_reload_excludes=None, fastapi_extra={})` {#Config}
|
||||
|
||||
- **说明**
|
||||
|
||||
@@ -19,7 +25,7 @@ description: nonebot.drivers.fastapi 模块
|
||||
|
||||
- **参数**
|
||||
|
||||
- `_env_file` (str | os.PathLike | NoneType)
|
||||
- `_env_file` (str | os.PathLike | list[str | os.PathLike] | tuple[str | os.PathLike, ...] | NoneType)
|
||||
|
||||
- `_env_file_encoding` (str | None)
|
||||
|
||||
@@ -27,23 +33,25 @@ description: nonebot.drivers.fastapi 模块
|
||||
|
||||
- `_secrets_dir` (str | os.PathLike | NoneType)
|
||||
|
||||
- `fastapi_openapi_url` (str)
|
||||
- `fastapi_openapi_url` (str | None)
|
||||
|
||||
- `fastapi_docs_url` (str)
|
||||
- `fastapi_docs_url` (str | None)
|
||||
|
||||
- `fastapi_redoc_url` (str)
|
||||
- `fastapi_redoc_url` (str | None)
|
||||
|
||||
- `fastapi_include_adapter_schema` (bool)
|
||||
|
||||
- `fastapi_reload` (bool)
|
||||
|
||||
- `fastapi_reload_dirs` (list[str])
|
||||
- `fastapi_reload_dirs` (list[str] | None)
|
||||
|
||||
- `fastapi_reload_delay` (float)
|
||||
|
||||
- `fastapi_reload_includes` (list[str])
|
||||
- `fastapi_reload_includes` (list[str] | None)
|
||||
|
||||
- `fastapi_reload_excludes` (list[str])
|
||||
- `fastapi_reload_excludes` (list[str] | None)
|
||||
|
||||
- `fastapi_extra` (dict[str, Any])
|
||||
|
||||
### _class-var_ `fastapi_openapi_url` {#Config-fastapi_openapi_url}
|
||||
|
||||
@@ -99,6 +107,12 @@ description: nonebot.drivers.fastapi 模块
|
||||
|
||||
- **说明:** 不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
|
||||
### _class-var_ `fastapi_extra` {#Config-fastapi_extra}
|
||||
|
||||
- **类型:** dict[str, Any]
|
||||
|
||||
- **说明:** 传递给 `FastAPI` 的其他参数。
|
||||
|
||||
## _class_ `Driver(env, config)` {#Driver}
|
||||
|
||||
- **说明**
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user