mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 19:26:44 +00:00
Compare commits
142 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1e8c2cfc9f | ||
|
5ce0238ace | ||
|
4e6b52b85c | ||
|
05fe7bb715 | ||
|
c555e2fac6 | ||
|
fd126ae154 | ||
|
6c7b6a9575 | ||
|
c4716e3e17 | ||
|
3601a33f20 | ||
|
451023518b | ||
|
2bd377a221 | ||
|
66384adad4 | ||
|
ec1f7ba5bc | ||
|
e7fc5b7b7e | ||
|
11477ea9d7 | ||
|
6adf40f45d | ||
|
1bdf169980 | ||
|
81cb356503 | ||
|
805778794c | ||
|
28cd8dd08a | ||
|
139b39984e | ||
|
f9b5fece80 | ||
|
8076c6bc0a | ||
|
44b89d13f8 | ||
|
fbc4225110 | ||
|
f07f35ccc1 | ||
|
111dfbf164 | ||
|
c713c7723b | ||
|
4fa2af41b0 | ||
|
39c09d22d1 | ||
|
4819b21f52 | ||
|
6ef6721527 | ||
|
14cb447874 | ||
|
1b2b89074d | ||
|
75c5678782 | ||
|
45ec5cdfb4 | ||
|
f6dd98825b | ||
|
f59271bd47 | ||
|
79f833b946 | ||
|
9ad562bbfd | ||
|
267b49247d | ||
|
dbda4150fb | ||
|
a4e17f0c49 | ||
|
8d8d1169d1 | ||
|
7bc9e61985 | ||
|
35cc6011b5 | ||
|
086af8fd22 | ||
|
a60d1520e6 | ||
|
30c22ba25a | ||
|
41fbaec42c | ||
|
562ec79e3b | ||
|
f620bd8eb2 | ||
|
13e40458d7 | ||
|
dc4ac6d8d7 | ||
|
41498bdf21 | ||
|
b8eae2eb82 | ||
|
039c2b5509 | ||
|
2e635370bb | ||
|
807a86371d | ||
|
c66953779c | ||
|
117ef18f1c | ||
|
520dd03d77 | ||
|
63f3ca2f6f | ||
|
2e8230e9f4 | ||
|
4bfea99e54 | ||
|
f58eba7975 | ||
|
53d1de4aec | ||
|
00f18c1bd8 | ||
|
ba4fbb2ec3 | ||
|
b3722bd637 | ||
|
012bd6d4fb | ||
|
9c4ca28d61 | ||
|
53bcae04ff | ||
|
754c54e268 | ||
|
f97fbc814e | ||
|
b8856a0577 | ||
|
1c0e88907b | ||
|
31b6df5b39 | ||
|
bca9e4fd08 | ||
|
026ceb5028 | ||
|
47d5a647b7 | ||
|
37d7230949 | ||
|
be458b1d5e | ||
|
f375a4a723 | ||
|
3edce9a630 | ||
|
c525bda1e0 | ||
|
417f586e0d | ||
|
80d7e68835 | ||
|
a284e6df5c | ||
|
7176a69f81 | ||
|
e3a1c02e8a | ||
|
5e789ae4e0 | ||
|
bb684e20cb | ||
|
e11293e46b | ||
|
e0d74a1657 | ||
|
fdd36565b1 | ||
|
28c53fe0d7 | ||
|
26539bf2b1 | ||
|
347889c822 | ||
|
91849b762c | ||
|
5d1319ddb9 | ||
|
d98228926e | ||
|
493997d998 | ||
|
3098b7c153 | ||
|
2b0a050226 | ||
|
1f3abc2bb9 | ||
|
dd5541e658 | ||
|
a76bf27f60 | ||
|
d70ce366cc | ||
|
f94b802c9b | ||
|
17d7bd4e31 | ||
|
76a40b60ff | ||
|
469efedab2 | ||
|
383699a8b4 | ||
|
1b9a07b923 | ||
|
15b76c266c | ||
|
dfdecaddb1 | ||
|
5de9de903d | ||
|
327f3fa441 | ||
|
08fde7580c | ||
|
4ca91ecc7e | ||
|
885db90bc0 | ||
|
c43d631eb5 | ||
|
cfda433d14 | ||
|
ea4a27bf89 | ||
|
23944833f2 | ||
|
4a40782be0 | ||
|
babafcaa87 | ||
|
9b164a6f5a | ||
|
4a07981972 | ||
|
6bb2c46f8a | ||
|
2054655912 | ||
|
062af45367 | ||
|
83c3ed5966 | ||
|
a2f2b818a7 | ||
|
e7941efd9a | ||
|
aa6faba9ae | ||
|
8ca72f3c64 | ||
|
45e10e7139 | ||
|
73d1b19669 | ||
|
ad4cf86a96 | ||
|
48b3e3aaf3 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
website/versioned_*/** linguist-documentation
|
4
.github/actions/setup-node/action.yml
vendored
4
.github/actions/setup-node/action.yml
vendored
@@ -4,9 +4,9 @@ description: Setup Node
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "18"
|
||||
|
||||
- id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
@@ -4,3 +4,18 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/.github/actions/build-api-doc"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/.github/actions/setup-node"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/.github/actions/setup-python"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
2
.github/workflows/codecov.yml
vendored
2
.github/workflows/codecov.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
4
.github/workflows/noneflow.yml
vendored
4
.github/workflows/noneflow.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Test Plugin
|
||||
id: plugin-test
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
private_key: ${{ secrets.APP_KEY }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
|
2
.github/workflows/pyright.yml
vendored
2
.github/workflows/pyright.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
name: Pyright Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
4
.github/workflows/release-drafter.yml
vendored
4
.github/workflows/release-drafter.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
|
2
.github/workflows/ruff.yml
vendored
2
.github/workflows/ruff.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
name: Ruff Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Ruff Lint
|
||||
uses: chartboost/ruff-action@v1
|
||||
|
4
.github/workflows/website-deploy.yml
vendored
4
.github/workflows/website-deploy.yml
vendored
@@ -13,7 +13,9 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
3
.github/workflows/website-preview.yml
vendored
3
.github/workflows/website-preview.yml
vendored
@@ -11,9 +11,10 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
@@ -7,7 +7,7 @@ ci:
|
||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.0.276
|
||||
rev: v0.0.287
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
@@ -20,13 +20,13 @@ repos:
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.3.0
|
||||
rev: 23.7.0
|
||||
hooks:
|
||||
- id: black
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0-alpha.9-for-vscode
|
||||
rev: v3.0.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
||||
|
@@ -10,12 +10,10 @@
|
||||
|
||||
### 报告问题、故障与漏洞
|
||||
|
||||
NoneBot2 仍然是一个不够稳定的开发中项目,如果你在使用过程中发现问题并确信是由 NoneBot2 引起的,欢迎提交 Issue。
|
||||
如果你在使用过程中发现问题并确信是由 NoneBot2 引起的,欢迎提交 Issue。
|
||||
|
||||
### 建议功能
|
||||
|
||||
NoneBot2 还未进入正式版,欢迎在 Issue 中提议要加入哪些新功能。
|
||||
|
||||
为了让开发者更好地理解你的意图,请认真描述你所需要的特性,可能的话可以提出你认为可行的解决方案。
|
||||
|
||||
## Pull Request
|
||||
|
@@ -111,7 +111,7 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
||||
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
||||
|
||||
| 协议名称 | 状态 | 注释 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
|
||||
| OneBot([仓库](https://github.com/nonebot/adapter-onebot),[协议](https://onebot.dev/)) | ✅ | 支持 QQ、TG、微信公众号、KOOK 等[平台](https://onebot.dev/ecosystem.html) |
|
||||
| Telegram([仓库](https://github.com/nonebot/adapter-telegram),[协议](https://core.telegram.org/bots/api)) | ✅ | |
|
||||
| 飞书([仓库](https://github.com/nonebot/adapter-feishu),[协议](https://open.feishu.cn/document/home/index)) | ✅ | |
|
||||
@@ -119,13 +119,15 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
||||
| QQ 频道([仓库](https://github.com/nonebot/adapter-qqguild),[协议](https://bot.q.qq.com/wiki/)) | ✅ | 官方接口调整较多 |
|
||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||
| Console([仓库](https://github.com/nonebot/adapter-console)) | ✅ | 控制台交互 |
|
||||
| Red ([仓库](https://github.com/nonebot/adapter-red),[协议](https://chrononeko.github.io/QQNTRedProtocol/)) | ✅ | QQ 协议 |
|
||||
| Discord ([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||
| Mirai([仓库](https://github.com/ieew/nonebot_adapter_mirai2),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa)) | ↗️ | 米游社大别野 Bot 协议,由社区贡献 |
|
||||
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa),[协议](https://webstatic.mihoyo.com/vila/bot/doc/)) | ↗️ | 米游社大别野 Bot 协议,由社区贡献 |
|
||||
|
||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||
|
||||
|
@@ -53,7 +53,7 @@ from nonebot.config import Env, Config
|
||||
from nonebot.log import logger as logger
|
||||
from nonebot.adapters import Bot, Adapter
|
||||
from nonebot.utils import escape_tag, resolve_dot_notation
|
||||
from nonebot.drivers import Driver, ReverseDriver, combine_driver
|
||||
from nonebot.drivers import Driver, ASGIMixin, combine_driver
|
||||
|
||||
try:
|
||||
__version__ = version("nonebot2")
|
||||
@@ -149,13 +149,13 @@ def get_adapters() -> Dict[str, Adapter]:
|
||||
|
||||
|
||||
def get_app() -> Any:
|
||||
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应的 Server App 对象。
|
||||
"""获取全局 {ref}`nonebot.drivers.ASGIMixin` 对应的 Server App 对象。
|
||||
|
||||
返回:
|
||||
Server App 对象
|
||||
|
||||
异常:
|
||||
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
|
||||
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ASGIMixin` 类型
|
||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||
|
||||
@@ -165,21 +165,19 @@ def get_app() -> Any:
|
||||
```
|
||||
"""
|
||||
driver = get_driver()
|
||||
assert isinstance(
|
||||
driver, ReverseDriver
|
||||
), "app object is only available for reverse driver"
|
||||
assert isinstance(driver, ASGIMixin), "app object is only available for asgi driver"
|
||||
return driver.server_app
|
||||
|
||||
|
||||
def get_asgi() -> Any:
|
||||
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应
|
||||
"""获取全局 {ref}`nonebot.drivers.ASGIMixin` 对应
|
||||
[ASGI](https://asgi.readthedocs.io/) 对象。
|
||||
|
||||
返回:
|
||||
ASGI 对象
|
||||
|
||||
异常:
|
||||
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
|
||||
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ASGIMixin` 类型
|
||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
|
||||
({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||
|
||||
@@ -190,7 +188,7 @@ def get_asgi() -> Any:
|
||||
"""
|
||||
driver = get_driver()
|
||||
assert isinstance(
|
||||
driver, ReverseDriver
|
||||
driver, ASGIMixin
|
||||
), "asgi object is only available for reverse driver"
|
||||
return driver.asgi
|
||||
|
||||
|
@@ -45,6 +45,10 @@ class Param(abc.ABC, FieldInfo):
|
||||
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
|
||||
"""
|
||||
|
||||
def __init__(self, *args, validate: bool = False, **kwargs: Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.validate = validate
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
|
||||
@@ -97,6 +101,7 @@ class Dependent(Generic[R]):
|
||||
)
|
||||
|
||||
async def __call__(self, **kwargs: Any) -> R:
|
||||
try:
|
||||
# do pre-check
|
||||
await self.check(**kwargs)
|
||||
|
||||
@@ -108,11 +113,14 @@ class Dependent(Generic[R]):
|
||||
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
|
||||
else:
|
||||
return await run_sync(cast(Callable[..., R], self.call))(**values)
|
||||
except SkippedException as e:
|
||||
logger.trace(f"{self} skipped due to {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def parse_params(
|
||||
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
|
||||
) -> Tuple[ModelField]:
|
||||
) -> Tuple[ModelField, ...]:
|
||||
fields: List[ModelField] = []
|
||||
params = get_typed_signature(call).parameters.values()
|
||||
|
||||
@@ -191,25 +199,18 @@ class Dependent(Generic[R]):
|
||||
return cls(call, params, parameterless_params)
|
||||
|
||||
async def check(self, **params: Any) -> None:
|
||||
try:
|
||||
await asyncio.gather(*(param._check(**params) for param in self.parameterless))
|
||||
await asyncio.gather(
|
||||
*(param._check(**params) for param in self.parameterless)
|
||||
*(cast(Param, param.field_info)._check(**params) for param in self.params)
|
||||
)
|
||||
await asyncio.gather(
|
||||
*(
|
||||
cast(Param, param.field_info)._check(**params)
|
||||
for param in self.params
|
||||
)
|
||||
)
|
||||
except SkippedException as e:
|
||||
logger.trace(f"{self} skipped due to {e}")
|
||||
raise
|
||||
|
||||
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
||||
value = await cast(Param, field.field_info)._solve(**params)
|
||||
param = cast(Param, field.field_info)
|
||||
value = await param._solve(**params)
|
||||
if value is Undefined:
|
||||
value = field.get_default()
|
||||
return check_field_type(field, value)
|
||||
v = check_field_type(field, value)
|
||||
return v if param.validate else value
|
||||
|
||||
async def solve(self, **params: Any) -> Dict[str, Any]:
|
||||
# solve parameterless
|
||||
|
@@ -5,7 +5,7 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
import inspect
|
||||
from typing import Any, Dict, TypeVar, Callable, ForwardRef
|
||||
from typing import Any, Dict, Callable, ForwardRef
|
||||
|
||||
from loguru import logger
|
||||
from pydantic.fields import ModelField
|
||||
@@ -13,8 +13,6 @@ from pydantic.typing import evaluate_forwardref
|
||||
|
||||
from nonebot.exception import TypeMisMatch
|
||||
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||
"""获取可调用对象签名"""
|
||||
@@ -49,10 +47,10 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) ->
|
||||
return annotation
|
||||
|
||||
|
||||
def check_field_type(field: ModelField, value: V) -> V:
|
||||
def check_field_type(field: ModelField, value: Any) -> Any:
|
||||
"""检查字段类型是否匹配"""
|
||||
|
||||
_, errs_ = field.validate(value, {}, loc=())
|
||||
v, errs_ = field.validate(value, {}, loc=())
|
||||
if errs_:
|
||||
raise TypeMisMatch(field, value)
|
||||
return value
|
||||
return v
|
||||
|
@@ -8,30 +8,40 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
from nonebot.internal.driver import URL as URL
|
||||
from nonebot.internal.driver import Mixin as Mixin
|
||||
from nonebot.internal.driver import Driver as Driver
|
||||
from nonebot.internal.driver import Cookies as Cookies
|
||||
from nonebot.internal.driver import Request as Request
|
||||
from nonebot.internal.driver import Response as Response
|
||||
from nonebot.internal.driver import ASGIMixin as ASGIMixin
|
||||
from nonebot.internal.driver import WebSocket as WebSocket
|
||||
from nonebot.internal.driver import HTTPVersion as HTTPVersion
|
||||
from nonebot.internal.driver import ForwardMixin as ForwardMixin
|
||||
from nonebot.internal.driver import ReverseMixin as ReverseMixin
|
||||
from nonebot.internal.driver import ForwardDriver as ForwardDriver
|
||||
from nonebot.internal.driver import ReverseDriver as ReverseDriver
|
||||
from nonebot.internal.driver import combine_driver as combine_driver
|
||||
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
|
||||
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
|
||||
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
|
||||
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
||||
|
||||
__autodoc__ = {
|
||||
"URL": True,
|
||||
"Driver": True,
|
||||
"Cookies": True,
|
||||
"Request": True,
|
||||
"Response": True,
|
||||
"WebSocket": True,
|
||||
"HTTPVersion": True,
|
||||
"Driver": True,
|
||||
"Mixin": True,
|
||||
"ForwardMixin": True,
|
||||
"ForwardDriver": True,
|
||||
"HTTPClientMixin": True,
|
||||
"WebSocketClientMixin": True,
|
||||
"ReverseMixin": True,
|
||||
"ReverseDriver": True,
|
||||
"ASGIMixin": True,
|
||||
"combine_driver": True,
|
||||
"HTTPServerSetup": True,
|
||||
"WebSocketServerSetup": True,
|
||||
|
@@ -16,14 +16,19 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
from typing_extensions import override
|
||||
from typing import Type, AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import TYPE_CHECKING, AsyncGenerator
|
||||
|
||||
from nonebot.drivers import Request, Response
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_driver
|
||||
from nonebot.drivers import (
|
||||
HTTPVersion,
|
||||
HTTPClientMixin,
|
||||
WebSocketClientMixin,
|
||||
combine_driver,
|
||||
)
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
@@ -34,7 +39,7 @@ except ModuleNotFoundError as e: # pragma: no cover
|
||||
) from e
|
||||
|
||||
|
||||
class Mixin(ForwardMixin):
|
||||
class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
||||
"""AIOHTTP Mixin"""
|
||||
|
||||
@property
|
||||
@@ -172,5 +177,11 @@ class WebSocket(BaseWebSocket):
|
||||
await self.websocket.send_bytes(data)
|
||||
|
||||
|
||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
||||
if TYPE_CHECKING:
|
||||
|
||||
class Driver(Mixin, NoneDriver):
|
||||
...
|
||||
|
||||
else:
|
||||
Driver = combine_driver(NoneDriver, Mixin)
|
||||
"""AIOHTTP Driver"""
|
||||
|
@@ -25,12 +25,14 @@ from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
from pydantic import BaseSettings
|
||||
|
||||
from nonebot.config import Env
|
||||
from nonebot.drivers import ASGIMixin
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.internal.driver import FileTypes
|
||||
from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
|
||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
from ._lifespan import LIFESPAN_FUNC, Lifespan
|
||||
|
||||
@@ -87,7 +89,7 @@ class Config(BaseSettings):
|
||||
extra = "ignore"
|
||||
|
||||
|
||||
class Driver(ReverseDriver):
|
||||
class Driver(BaseDriver, ASGIMixin):
|
||||
"""FastAPI 驱动框架。"""
|
||||
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
@@ -179,7 +181,7 @@ class Driver(ReverseDriver):
|
||||
**kwargs,
|
||||
):
|
||||
"""使用 `uvicorn` 启动 FastAPI"""
|
||||
super().run(host, port, app, **kwargs)
|
||||
super().run(host, port, app=app, **kwargs)
|
||||
LOGGING_CONFIG = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
|
@@ -15,18 +15,15 @@ FrontMatter:
|
||||
description: nonebot.drivers.httpx 模块
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing_extensions import override
|
||||
from typing import Type, AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.drivers import (
|
||||
Request,
|
||||
Response,
|
||||
WebSocket,
|
||||
HTTPVersion,
|
||||
ForwardMixin,
|
||||
ForwardDriver,
|
||||
HTTPClientMixin,
|
||||
combine_driver,
|
||||
)
|
||||
|
||||
@@ -39,7 +36,7 @@ except ModuleNotFoundError as e: # pragma: no cover
|
||||
) from e
|
||||
|
||||
|
||||
class Mixin(ForwardMixin):
|
||||
class Mixin(HTTPClientMixin):
|
||||
"""HTTPX Mixin"""
|
||||
|
||||
@property
|
||||
@@ -72,12 +69,12 @@ class Mixin(ForwardMixin):
|
||||
request=setup,
|
||||
)
|
||||
|
||||
@override
|
||||
@asynccontextmanager
|
||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
||||
async with super(Mixin, self).websocket(setup) as ws:
|
||||
yield ws
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
||||
class Driver(Mixin, NoneDriver):
|
||||
...
|
||||
|
||||
else:
|
||||
Driver = combine_driver(NoneDriver, Mixin)
|
||||
"""HTTPX Driver"""
|
||||
|
@@ -34,12 +34,14 @@ from typing import (
|
||||
from pydantic import BaseSettings
|
||||
|
||||
from nonebot.config import Env
|
||||
from nonebot.drivers import ASGIMixin
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.internal.driver import FileTypes
|
||||
from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
|
||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
try:
|
||||
import uvicorn
|
||||
@@ -89,7 +91,7 @@ class Config(BaseSettings):
|
||||
extra = "ignore"
|
||||
|
||||
|
||||
class Driver(ReverseDriver):
|
||||
class Driver(BaseDriver, ASGIMixin):
|
||||
"""Quart 驱动框架"""
|
||||
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
|
@@ -19,14 +19,14 @@ import logging
|
||||
from functools import wraps
|
||||
from contextlib import asynccontextmanager
|
||||
from typing_extensions import ParamSpec, override
|
||||
from typing import Type, Union, TypeVar, Callable, Awaitable, AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Union, TypeVar, Callable, Awaitable, AsyncGenerator
|
||||
|
||||
from nonebot.drivers import Request
|
||||
from nonebot.log import LoguruHandler
|
||||
from nonebot.drivers import Request, Response
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
|
||||
from nonebot.drivers import WebSocketClientMixin, combine_driver
|
||||
|
||||
try:
|
||||
from websockets.exceptions import ConnectionClosed
|
||||
@@ -58,7 +58,7 @@ def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
||||
return decorator
|
||||
|
||||
|
||||
class Mixin(ForwardMixin):
|
||||
class Mixin(WebSocketClientMixin):
|
||||
"""Websockets Mixin"""
|
||||
|
||||
@property
|
||||
@@ -66,10 +66,6 @@ class Mixin(ForwardMixin):
|
||||
def type(self) -> str:
|
||||
return "websockets"
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
return await super(Mixin, self).request(setup)
|
||||
|
||||
@override
|
||||
@asynccontextmanager
|
||||
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
||||
@@ -133,5 +129,11 @@ class WebSocket(BaseWebSocket):
|
||||
await self.websocket.send(data)
|
||||
|
||||
|
||||
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
|
||||
if TYPE_CHECKING:
|
||||
|
||||
class Driver(Mixin, NoneDriver):
|
||||
...
|
||||
|
||||
else:
|
||||
Driver = combine_driver(NoneDriver, Mixin)
|
||||
"""Websockets Driver"""
|
||||
|
@@ -7,10 +7,11 @@ from nonebot.internal.driver import (
|
||||
Driver,
|
||||
Request,
|
||||
Response,
|
||||
ASGIMixin,
|
||||
WebSocket,
|
||||
ForwardDriver,
|
||||
ReverseDriver,
|
||||
HTTPClientMixin,
|
||||
HTTPServerSetup,
|
||||
WebSocketClientMixin,
|
||||
WebSocketServerSetup,
|
||||
)
|
||||
|
||||
@@ -72,26 +73,26 @@ class Adapter(abc.ABC):
|
||||
|
||||
def setup_http_server(self, setup: HTTPServerSetup):
|
||||
"""设置一个 HTTP 服务器路由配置"""
|
||||
if not isinstance(self.driver, ReverseDriver):
|
||||
if not isinstance(self.driver, ASGIMixin):
|
||||
raise TypeError("Current driver does not support http server")
|
||||
self.driver.setup_http_server(setup)
|
||||
|
||||
def setup_websocket_server(self, setup: WebSocketServerSetup):
|
||||
"""设置一个 WebSocket 服务器路由配置"""
|
||||
if not isinstance(self.driver, ReverseDriver):
|
||||
if not isinstance(self.driver, ASGIMixin):
|
||||
raise TypeError("Current driver does not support websocket server")
|
||||
self.driver.setup_websocket_server(setup)
|
||||
|
||||
async def request(self, setup: Request) -> Response:
|
||||
"""进行一个 HTTP 客户端请求"""
|
||||
if not isinstance(self.driver, ForwardDriver):
|
||||
if not isinstance(self.driver, HTTPClientMixin):
|
||||
raise TypeError("Current driver does not support http client")
|
||||
return await self.driver.request(setup)
|
||||
|
||||
@asynccontextmanager
|
||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
||||
"""建立一个 WebSocket 客户端连接请求"""
|
||||
if not isinstance(self.driver, ForwardDriver):
|
||||
if not isinstance(self.driver, WebSocketClientMixin):
|
||||
raise TypeError("Current driver does not support websocket client")
|
||||
async with self.driver.websocket(setup) as ws:
|
||||
yield ws
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from .model import URL as URL
|
||||
from .driver import Mixin as Mixin
|
||||
from .model import RawURL as RawURL
|
||||
from .driver import Driver as Driver
|
||||
from .model import Cookies as Cookies
|
||||
@@ -8,6 +9,7 @@ from .model import Response as Response
|
||||
from .model import DataTypes as DataTypes
|
||||
from .model import FileTypes as FileTypes
|
||||
from .model import WebSocket as WebSocket
|
||||
from .driver import ASGIMixin as ASGIMixin
|
||||
from .model import FilesTypes as FilesTypes
|
||||
from .model import QueryTypes as QueryTypes
|
||||
from .model import CookieTypes as CookieTypes
|
||||
@@ -17,9 +19,12 @@ from .model import HeaderTypes as HeaderTypes
|
||||
from .model import SimpleQuery as SimpleQuery
|
||||
from .model import ContentTypes as ContentTypes
|
||||
from .driver import ForwardMixin as ForwardMixin
|
||||
from .driver import ReverseMixin as ReverseMixin
|
||||
from .model import QueryVariable as QueryVariable
|
||||
from .driver import ForwardDriver as ForwardDriver
|
||||
from .driver import ReverseDriver as ReverseDriver
|
||||
from .driver import combine_driver as combine_driver
|
||||
from .model import HTTPServerSetup as HTTPServerSetup
|
||||
from .driver import HTTPClientMixin as HTTPClientMixin
|
||||
from .model import WebSocketServerSetup as WebSocketServerSetup
|
||||
from .driver import WebSocketClientMixin as WebSocketClientMixin
|
||||
|
@@ -1,7 +1,19 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from typing_extensions import TypeAlias
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, AsyncGenerator
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
Type,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
AsyncGenerator,
|
||||
overload,
|
||||
)
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.config import Env, Config
|
||||
@@ -21,11 +33,15 @@ if TYPE_CHECKING:
|
||||
from nonebot.internal.adapter import Bot, Adapter
|
||||
|
||||
|
||||
D = TypeVar("D", bound="Driver")
|
||||
|
||||
BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
|
||||
|
||||
|
||||
class Driver(abc.ABC):
|
||||
"""Driver 基类。
|
||||
"""驱动器基类。
|
||||
|
||||
驱动器控制框架的启动和停止,适配器的注册,以及机器人生命周期管理。
|
||||
|
||||
参数:
|
||||
env: 包含环境信息的 Env 对象
|
||||
@@ -45,6 +61,7 @@ class Driver(abc.ABC):
|
||||
self.config: Config = config
|
||||
"""全局配置对象"""
|
||||
self._bots: Dict[str, "Bot"] = {}
|
||||
self._bot_tasks: Set[asyncio.Task] = set()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@@ -94,6 +111,8 @@ class Driver(abc.ABC):
|
||||
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
|
||||
)
|
||||
|
||||
self.on_shutdown(self._cleanup)
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_startup(self, func: Callable) -> Callable:
|
||||
"""注册一个在驱动器启动时执行的函数"""
|
||||
@@ -156,7 +175,9 @@ class Driver(abc.ABC):
|
||||
"</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
asyncio.create_task(_run_hook(bot))
|
||||
task = asyncio.create_task(_run_hook(bot))
|
||||
task.add_done_callback(self._bot_tasks.discard)
|
||||
self._bot_tasks.add(task)
|
||||
|
||||
def _bot_disconnect(self, bot: "Bot") -> None:
|
||||
"""在连接断开后,调用该函数来注销 bot 对象"""
|
||||
@@ -183,23 +204,49 @@ class Driver(abc.ABC):
|
||||
"</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
asyncio.create_task(_run_hook(bot))
|
||||
task = asyncio.create_task(_run_hook(bot))
|
||||
task.add_done_callback(self._bot_tasks.discard)
|
||||
self._bot_tasks.add(task)
|
||||
|
||||
async def _cleanup(self) -> None:
|
||||
"""清理驱动器资源"""
|
||||
if self._bot_tasks:
|
||||
logger.opt(colors=True).debug(
|
||||
"<y>Waiting for running bot connection hooks...</y>"
|
||||
)
|
||||
await asyncio.gather(*self._bot_tasks, return_exceptions=True)
|
||||
|
||||
|
||||
class ForwardMixin(abc.ABC):
|
||||
"""客户端混入基类。"""
|
||||
class Mixin(abc.ABC):
|
||||
"""可与其他驱动器共用的混入基类。"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def type(self) -> str:
|
||||
"""客户端驱动类型名称"""
|
||||
"""混入驱动类型名称"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ForwardMixin(Mixin):
|
||||
"""客户端混入基类。"""
|
||||
|
||||
|
||||
class ReverseMixin(Mixin):
|
||||
"""服务端混入基类。"""
|
||||
|
||||
|
||||
class HTTPClientMixin(ForwardMixin):
|
||||
"""HTTP 客户端混入基类。"""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def request(self, setup: Request) -> Response:
|
||||
"""发送一个 HTTP 请求"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class WebSocketClientMixin(ForwardMixin):
|
||||
"""WebSocket 客户端混入基类。"""
|
||||
|
||||
@abc.abstractmethod
|
||||
@asynccontextmanager
|
||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
||||
@@ -208,12 +255,11 @@ class ForwardMixin(abc.ABC):
|
||||
yield # used for static type checking's generator detection
|
||||
|
||||
|
||||
class ForwardDriver(Driver, ForwardMixin):
|
||||
"""客户端基类。将客户端框架封装,以满足适配器使用。"""
|
||||
class ASGIMixin(ReverseMixin):
|
||||
"""ASGI 服务端基类。
|
||||
|
||||
|
||||
class ReverseDriver(Driver):
|
||||
"""服务端基类。将后端框架封装,以满足适配器使用。"""
|
||||
将后端框架封装,以满足适配器使用。
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
@@ -238,18 +284,49 @@ class ReverseDriver(Driver):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Driver]:
|
||||
ForwardDriver: TypeAlias = ForwardMixin
|
||||
"""支持客户端请求的驱动器。
|
||||
|
||||
**Deprecated**,请使用 {ref}`nonebot.drivers.ForwardMixin` 或其子类代替。
|
||||
"""
|
||||
|
||||
ReverseDriver: TypeAlias = ReverseMixin
|
||||
"""支持服务端请求的驱动器。
|
||||
|
||||
**Deprecated**,请使用 {ref}`nonebot.drivers.ReverseMixin` 或其子类代替。
|
||||
"""
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
class CombinedDriver(Driver, Mixin):
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def combine_driver(driver: Type[D]) -> Type[D]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]:
|
||||
...
|
||||
|
||||
|
||||
def combine_driver(
|
||||
driver: Type[D], *mixins: Type[Mixin]
|
||||
) -> Union[Type[D], Type["CombinedDriver"]]:
|
||||
"""将一个驱动器和多个混入类合并。"""
|
||||
# check first
|
||||
assert issubclass(driver, Driver), "`driver` must be subclass of Driver"
|
||||
assert all(
|
||||
issubclass(m, ForwardMixin) for m in mixins
|
||||
), "`mixins` must be subclass of ForwardMixin"
|
||||
issubclass(m, Mixin) for m in mixins
|
||||
), "`mixins` must be subclass of Mixin"
|
||||
|
||||
if not mixins:
|
||||
return driver
|
||||
|
||||
def type_(self: ForwardDriver) -> str:
|
||||
def type_(self: "CombinedDriver") -> str:
|
||||
return (
|
||||
driver.type.__get__(self)
|
||||
+ "+"
|
||||
@@ -257,5 +334,5 @@ def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Dr
|
||||
)
|
||||
|
||||
return type(
|
||||
"CombinedDriver", (*mixins, driver, ForwardDriver), {"type": property(type_)}
|
||||
"CombinedDriver", (*mixins, driver), {"type": property(type_)}
|
||||
) # type: ignore
|
||||
|
@@ -125,7 +125,7 @@ class Request:
|
||||
files_ = files.items() if isinstance(files, dict) else files
|
||||
for name, file_info in files_:
|
||||
if not isinstance(file_info, tuple):
|
||||
self.files.append((name, (None, file_info, None)))
|
||||
self.files.append((name, (name, file_info, None)))
|
||||
elif len(file_info) == 2:
|
||||
self.files.append((name, (file_info[0], file_info[1], None)))
|
||||
else:
|
||||
|
@@ -6,6 +6,7 @@ matchers = MatcherManager()
|
||||
|
||||
from .matcher import Matcher as Matcher
|
||||
from .matcher import current_bot as current_bot
|
||||
from .matcher import MatcherSource as MatcherSource
|
||||
from .matcher import current_event as current_event
|
||||
from .matcher import current_handler as current_handler
|
||||
from .matcher import current_matcher as current_matcher
|
||||
|
@@ -1,6 +1,5 @@
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
@@ -53,7 +52,7 @@ class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
||||
def __delitem__(self, key: int) -> None:
|
||||
del self.provider[key]
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, MatcherManager) and self.provider == other.provider
|
||||
|
||||
def keys(self) -> KeysView[int]:
|
||||
|
@@ -1,4 +1,9 @@
|
||||
import sys
|
||||
import inspect
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from dataclasses import dataclass
|
||||
from contextvars import ContextVar
|
||||
from typing_extensions import Self
|
||||
from datetime import datetime, timedelta
|
||||
@@ -8,6 +13,7 @@ from typing import (
|
||||
Any,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
@@ -20,7 +26,8 @@ from typing import (
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.internal.rule import Rule
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.utils import classproperty
|
||||
from nonebot.dependencies import Param, Dependent
|
||||
from nonebot.internal.permission import User, Permission
|
||||
from nonebot.internal.adapter import (
|
||||
Bot,
|
||||
@@ -74,15 +81,51 @@ current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
||||
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatcherSource:
|
||||
"""Matcher 源代码上下文信息"""
|
||||
|
||||
plugin_name: Optional[str] = None
|
||||
"""事件响应器所在插件名称"""
|
||||
module_name: Optional[str] = None
|
||||
"""事件响应器所在插件模块的路径名"""
|
||||
lineno: Optional[int] = None
|
||||
"""事件响应器所在行号"""
|
||||
|
||||
@property
|
||||
def plugin(self) -> Optional["Plugin"]:
|
||||
"""事件响应器所在插件"""
|
||||
from nonebot.plugin import get_plugin
|
||||
|
||||
if self.plugin_name is not None:
|
||||
return get_plugin(self.plugin_name)
|
||||
|
||||
@property
|
||||
def module(self) -> Optional[ModuleType]:
|
||||
if self.module_name is not None:
|
||||
return sys.modules.get(self.module_name)
|
||||
|
||||
@property
|
||||
def file(self) -> Optional[Path]:
|
||||
if self.module is not None and (file := inspect.getsourcefile(self.module)):
|
||||
return Path(file).absolute()
|
||||
|
||||
|
||||
class MatcherMeta(type):
|
||||
if TYPE_CHECKING:
|
||||
module_name: Optional[str]
|
||||
type: str
|
||||
_source: Optional[MatcherSource]
|
||||
module_name: Optional[str]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__name__}(type={self.type!r}"
|
||||
+ (f", module={self.module_name}" if self.module_name else "")
|
||||
+ (
|
||||
f", lineno={self._source.lineno}"
|
||||
if self._source and self._source.lineno is not None
|
||||
else ""
|
||||
)
|
||||
+ ")"
|
||||
)
|
||||
|
||||
@@ -90,14 +133,7 @@ class MatcherMeta(type):
|
||||
class Matcher(metaclass=MatcherMeta):
|
||||
"""事件响应器类"""
|
||||
|
||||
plugin: ClassVar[Optional["Plugin"]] = None
|
||||
"""事件响应器所在插件"""
|
||||
module: ClassVar[Optional[ModuleType]] = None
|
||||
"""事件响应器所在插件模块"""
|
||||
plugin_name: ClassVar[Optional[str]] = None
|
||||
"""事件响应器所在插件名"""
|
||||
module_name: ClassVar[Optional[str]] = None
|
||||
"""事件响应器所在点分割插件模块路径"""
|
||||
_source: ClassVar[Optional[MatcherSource]] = None
|
||||
|
||||
type: ClassVar[str] = ""
|
||||
"""事件响应器类型"""
|
||||
@@ -124,7 +160,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
_default_permission_updater: ClassVar[Optional[Dependent[Permission]]] = None
|
||||
"""事件响应器权限更新函数"""
|
||||
|
||||
HANDLER_PARAM_TYPES = (
|
||||
HANDLER_PARAM_TYPES: ClassVar[Tuple[Type[Param], ...]] = (
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -142,6 +178,11 @@ class Matcher(metaclass=MatcherMeta):
|
||||
return (
|
||||
f"{self.__class__.__name__}(type={self.type!r}"
|
||||
+ (f", module={self.module_name}" if self.module_name else "")
|
||||
+ (
|
||||
f", lineno={self._source.lineno}"
|
||||
if self._source and self._source.lineno is not None
|
||||
else ""
|
||||
)
|
||||
+ ")"
|
||||
)
|
||||
|
||||
@@ -158,6 +199,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
*,
|
||||
plugin: Optional["Plugin"] = None,
|
||||
module: Optional[ModuleType] = None,
|
||||
source: Optional[MatcherSource] = None,
|
||||
expire_time: Optional[Union[datetime, timedelta]] = None,
|
||||
default_state: Optional[T_State] = None,
|
||||
default_type_updater: Optional[Union[T_TypeUpdater, Dependent[str]]] = None,
|
||||
@@ -176,22 +218,47 @@ class Matcher(metaclass=MatcherMeta):
|
||||
temp: 是否为临时事件响应器,即触发一次后删除
|
||||
priority: 响应优先级
|
||||
block: 是否阻止事件向更低优先级的响应器传播
|
||||
plugin: 事件响应器所在插件
|
||||
module: 事件响应器所在模块
|
||||
default_state: 默认状态 `state`
|
||||
plugin: **Deprecated.** 事件响应器所在插件
|
||||
module: **Deprecated.** 事件响应器所在模块
|
||||
source: 事件响应器源代码上下文信息
|
||||
expire_time: 事件响应器最终有效时间点,过时即被删除
|
||||
default_state: 默认状态 `state`
|
||||
default_type_updater: 默认事件类型更新函数
|
||||
default_permission_updater: 默认会话权限更新函数
|
||||
|
||||
返回:
|
||||
Type[Matcher]: 新的事件响应器类
|
||||
"""
|
||||
if plugin is not None:
|
||||
warnings.warn(
|
||||
(
|
||||
"Pass `plugin` context info to create Matcher is deprecated. "
|
||||
"Use `source` instead."
|
||||
),
|
||||
DeprecationWarning,
|
||||
)
|
||||
if module is not None:
|
||||
warnings.warn(
|
||||
(
|
||||
"Pass `module` context info to create Matcher is deprecated. "
|
||||
"Use `source` instead."
|
||||
),
|
||||
DeprecationWarning,
|
||||
)
|
||||
source = source or (
|
||||
MatcherSource(
|
||||
plugin_name=plugin and plugin.name,
|
||||
module_name=module and module.__name__,
|
||||
)
|
||||
if plugin is not None or module is not None
|
||||
else None
|
||||
)
|
||||
|
||||
NewMatcher = type(
|
||||
cls.__name__,
|
||||
(cls,),
|
||||
{
|
||||
"plugin": plugin,
|
||||
"module": module,
|
||||
"plugin_name": plugin and plugin.name,
|
||||
"module_name": module and module.__name__,
|
||||
"_source": source,
|
||||
"type": type_,
|
||||
"rule": rule or Rule(),
|
||||
"permission": permission or Permission(),
|
||||
@@ -253,6 +320,26 @@ class Matcher(metaclass=MatcherMeta):
|
||||
"""销毁当前的事件响应器"""
|
||||
matchers[cls.priority].remove(cls)
|
||||
|
||||
@classproperty
|
||||
def plugin(cls) -> Optional["Plugin"]:
|
||||
"""事件响应器所在插件"""
|
||||
return cls._source and cls._source.plugin
|
||||
|
||||
@classproperty
|
||||
def module(cls) -> Optional[ModuleType]:
|
||||
"""事件响应器所在插件模块"""
|
||||
return cls._source and cls._source.module
|
||||
|
||||
@classproperty
|
||||
def plugin_name(cls) -> Optional[str]:
|
||||
"""事件响应器所在插件名"""
|
||||
return cls._source and cls._source.plugin_name
|
||||
|
||||
@classproperty
|
||||
def module_name(cls) -> Optional[str]:
|
||||
"""事件响应器所在插件模块路径"""
|
||||
return cls._source and cls._source.module_name
|
||||
|
||||
@classmethod
|
||||
async def check_perm(
|
||||
cls,
|
||||
@@ -773,8 +860,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
plugin=self.plugin,
|
||||
module=self.module,
|
||||
source=self.__class__._source,
|
||||
expire_time=bot.config.session_expire_timeout,
|
||||
default_state=self.state,
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
@@ -794,8 +880,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
plugin=self.plugin,
|
||||
module=self.module,
|
||||
source=self.__class__._source,
|
||||
expire_time=bot.config.session_expire_timeout,
|
||||
default_state=self.state,
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
|
@@ -1,11 +1,21 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
from typing_extensions import Annotated
|
||||
from typing_extensions import Self, Annotated, override
|
||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||
from typing import TYPE_CHECKING, Any, Type, Tuple, Literal, Callable, Optional, cast
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Literal,
|
||||
Callable,
|
||||
Optional,
|
||||
cast,
|
||||
)
|
||||
|
||||
from pydantic.typing import get_args, get_origin
|
||||
from pydantic.fields import Required, Undefined, ModelField
|
||||
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
|
||||
|
||||
from nonebot.dependencies.utils import check_field_type
|
||||
from nonebot.dependencies import Param, Dependent, CustomConfig
|
||||
@@ -24,6 +34,23 @@ if TYPE_CHECKING:
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Bot, Event
|
||||
|
||||
EXTRA_FIELD_INFO = (
|
||||
"gt",
|
||||
"lt",
|
||||
"ge",
|
||||
"le",
|
||||
"multiple_of",
|
||||
"allow_inf_nan",
|
||||
"max_digits",
|
||||
"decimal_places",
|
||||
"min_items",
|
||||
"max_items",
|
||||
"unique_items",
|
||||
"min_length",
|
||||
"max_length",
|
||||
"regex",
|
||||
)
|
||||
|
||||
|
||||
class DependsInner:
|
||||
def __init__(
|
||||
@@ -31,26 +58,31 @@ class DependsInner:
|
||||
dependency: Optional[T_Handler] = None,
|
||||
*,
|
||||
use_cache: bool = True,
|
||||
validate: Union[bool, FieldInfo] = False,
|
||||
) -> None:
|
||||
self.dependency = dependency
|
||||
self.use_cache = use_cache
|
||||
self.validate = validate
|
||||
|
||||
def __repr__(self) -> str:
|
||||
dep = get_name(self.dependency)
|
||||
cache = "" if self.use_cache else ", use_cache=False"
|
||||
return f"DependsInner({dep}{cache})"
|
||||
validate = f", validate={self.validate}" if self.validate else ""
|
||||
return f"DependsInner({dep}{cache}{validate})"
|
||||
|
||||
|
||||
def Depends(
|
||||
dependency: Optional[T_Handler] = None,
|
||||
*,
|
||||
use_cache: bool = True,
|
||||
validate: Union[bool, FieldInfo] = False,
|
||||
) -> Any:
|
||||
"""子依赖装饰器
|
||||
|
||||
参数:
|
||||
dependency: 依赖函数。默认为参数的类型注释。
|
||||
use_cache: 是否使用缓存。默认为 `True`。
|
||||
validate: 是否使用 Pydantic 类型校验。默认为 `False`。
|
||||
|
||||
用法:
|
||||
```python
|
||||
@@ -70,7 +102,7 @@ def Depends(
|
||||
...
|
||||
```
|
||||
"""
|
||||
return DependsInner(dependency, use_cache=use_cache)
|
||||
return DependsInner(dependency, use_cache=use_cache, validate=validate)
|
||||
|
||||
|
||||
class DependParam(Param):
|
||||
@@ -85,23 +117,44 @@ class DependParam(Param):
|
||||
return f"Depends({self.extra['dependent']})"
|
||||
|
||||
@classmethod
|
||||
def _from_field(
|
||||
cls, sub_dependent: Dependent, use_cache: bool, validate: Union[bool, FieldInfo]
|
||||
) -> Self:
|
||||
kwargs = {}
|
||||
if isinstance(validate, FieldInfo):
|
||||
kwargs.update((k, getattr(validate, k)) for k in EXTRA_FIELD_INFO)
|
||||
|
||||
return cls(
|
||||
Required,
|
||||
validate=bool(validate),
|
||||
**kwargs,
|
||||
dependent=sub_dependent,
|
||||
use_cache=use_cache,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["DependParam"]:
|
||||
) -> Optional[Self]:
|
||||
type_annotation, depends_inner = param.annotation, None
|
||||
# extract type annotation and dependency from Annotated
|
||||
if get_origin(param.annotation) is Annotated:
|
||||
type_annotation, *extra_args = get_args(param.annotation)
|
||||
depends_inner = next(
|
||||
(x for x in extra_args if isinstance(x, DependsInner)), None
|
||||
)
|
||||
|
||||
# param default value takes higher priority
|
||||
depends_inner = (
|
||||
param.default if isinstance(param.default, DependsInner) else depends_inner
|
||||
)
|
||||
# not a dependent
|
||||
if depends_inner is None:
|
||||
return
|
||||
|
||||
dependency: T_Handler
|
||||
# sub dependency is not specified, use type annotation
|
||||
if depends_inner.dependency is None:
|
||||
assert (
|
||||
type_annotation is not inspect.Signature.empty
|
||||
@@ -109,13 +162,18 @@ class DependParam(Param):
|
||||
dependency = type_annotation
|
||||
else:
|
||||
dependency = depends_inner.dependency
|
||||
# parse sub dependency
|
||||
sub_dependent = Dependent[Any].parse(
|
||||
call=dependency,
|
||||
allow_types=allow_types,
|
||||
)
|
||||
return cls(Required, use_cache=depends_inner.use_cache, dependent=sub_dependent)
|
||||
|
||||
return cls._from_field(
|
||||
sub_dependent, depends_inner.use_cache, depends_inner.validate
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_parameterless(
|
||||
cls, value: Any, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["Param"]:
|
||||
@@ -124,8 +182,9 @@ class DependParam(Param):
|
||||
dependent = Dependent[Any].parse(
|
||||
call=value.dependency, allow_types=allow_types
|
||||
)
|
||||
return cls(Required, use_cache=value.use_cache, dependent=dependent)
|
||||
return cls._from_field(dependent, value.use_cache, value.validate)
|
||||
|
||||
@override
|
||||
async def _solve(
|
||||
self,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
@@ -169,6 +228,7 @@ class DependParam(Param):
|
||||
dependency_cache[call] = task
|
||||
return await task
|
||||
|
||||
@override
|
||||
async def _check(self, **kwargs: Any) -> None:
|
||||
# run sub dependent pre-checkers
|
||||
sub_dependent: Dependent = self.extra["dependent"]
|
||||
@@ -195,9 +255,10 @@ class BotParam(Param):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["BotParam"]:
|
||||
) -> Optional[Self]:
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
# param type is Bot(s) or subclass(es) of Bot or None
|
||||
@@ -217,9 +278,11 @@ class BotParam(Param):
|
||||
elif param.annotation == param.empty and param.name == "bot":
|
||||
return cls(Required)
|
||||
|
||||
@override
|
||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||
return bot
|
||||
|
||||
@override
|
||||
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
||||
if checker := self.extra.get("checker"):
|
||||
check_field_type(checker, bot)
|
||||
@@ -245,9 +308,10 @@ class EventParam(Param):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["EventParam"]:
|
||||
) -> Optional[Self]:
|
||||
from nonebot.adapters import Event
|
||||
|
||||
# param type is Event(s) or subclass(es) of Event or None
|
||||
@@ -267,9 +331,11 @@ class EventParam(Param):
|
||||
elif param.annotation == param.empty and param.name == "event":
|
||||
return cls(Required)
|
||||
|
||||
@override
|
||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||
return event
|
||||
|
||||
@override
|
||||
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
||||
if checker := self.extra.get("checker", None):
|
||||
check_field_type(checker, event)
|
||||
@@ -287,9 +353,10 @@ class StateParam(Param):
|
||||
return "StateParam()"
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["StateParam"]:
|
||||
) -> Optional[Self]:
|
||||
# param type is T_State
|
||||
if param.annotation is T_State:
|
||||
return cls(Required)
|
||||
@@ -297,6 +364,7 @@ class StateParam(Param):
|
||||
elif param.annotation == param.empty and param.name == "state":
|
||||
return cls(Required)
|
||||
|
||||
@override
|
||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||
return state
|
||||
|
||||
@@ -313,9 +381,10 @@ class MatcherParam(Param):
|
||||
return "MatcherParam()"
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["MatcherParam"]:
|
||||
) -> Optional[Self]:
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
# param type is Matcher(s) or subclass(es) of Matcher or None
|
||||
@@ -335,9 +404,11 @@ class MatcherParam(Param):
|
||||
elif param.annotation == param.empty and param.name == "matcher":
|
||||
return cls(Required)
|
||||
|
||||
@override
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
return matcher
|
||||
|
||||
@override
|
||||
async def _check(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
if checker := self.extra.get("checker", None):
|
||||
check_field_type(checker, matcher)
|
||||
@@ -382,9 +453,10 @@ class ArgParam(Param):
|
||||
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["ArgParam"]:
|
||||
) -> Optional[Self]:
|
||||
if isinstance(param.default, ArgInner):
|
||||
return cls(
|
||||
Required, key=param.default.key or param.name, type=param.default.type
|
||||
@@ -419,9 +491,10 @@ class ExceptionParam(Param):
|
||||
return "ExceptionParam()"
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["ExceptionParam"]:
|
||||
) -> Optional[Self]:
|
||||
# param type is Exception(s) or subclass(es) of Exception or None
|
||||
if generic_check_issubclass(param.annotation, Exception):
|
||||
return cls(Required)
|
||||
@@ -429,6 +502,7 @@ class ExceptionParam(Param):
|
||||
elif param.annotation == param.empty and param.name == "exception":
|
||||
return cls(Required)
|
||||
|
||||
@override
|
||||
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
||||
return exception
|
||||
|
||||
@@ -445,12 +519,14 @@ class DefaultParam(Param):
|
||||
return f"DefaultParam(default={self.default!r})"
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["DefaultParam"]:
|
||||
) -> Optional[Self]:
|
||||
if param.default != param.empty:
|
||||
return cls(param.default)
|
||||
|
||||
@override
|
||||
async def _solve(self, **kwargs: Any) -> Any:
|
||||
return Undefined
|
||||
|
||||
|
@@ -8,6 +8,7 @@ FrontMatter:
|
||||
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 MatcherSource as MatcherSource
|
||||
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
|
||||
|
@@ -7,14 +7,15 @@ FrontMatter:
|
||||
|
||||
import re
|
||||
import inspect
|
||||
import warnings
|
||||
from types import ModuleType
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.matcher import Matcher, MatcherSource
|
||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
||||
from nonebot.rule import (
|
||||
Rule,
|
||||
@@ -45,25 +46,39 @@ def store_matcher(matcher: Type[Matcher]) -> None:
|
||||
plugin_chain[-1].matcher.add(matcher)
|
||||
|
||||
|
||||
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]:
|
||||
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]: # pragma: no cover
|
||||
"""获取事件响应器定义所在插件。
|
||||
|
||||
**Deprecated**, 请使用 {ref}`nonebot.plugin.on.get_matcher_source` 获取信息。
|
||||
|
||||
参数:
|
||||
depth: 调用栈深度
|
||||
"""
|
||||
# matcher defined when plugin loading
|
||||
if plugin_chain := _current_plugin_chain.get():
|
||||
return plugin_chain[-1]
|
||||
|
||||
# matcher defined when plugin running
|
||||
if module := get_matcher_module(depth + 1):
|
||||
if plugin := get_plugin_by_module_name(module.__name__):
|
||||
return plugin
|
||||
warnings.warn(
|
||||
"`get_matcher_plugin` is deprecated, please use `get_matcher_source` instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return (source := get_matcher_source(depth + 1)) and source.plugin
|
||||
|
||||
|
||||
def get_matcher_module(depth: int = 1) -> Optional[ModuleType]:
|
||||
def get_matcher_module(depth: int = 1) -> Optional[ModuleType]: # pragma: no cover
|
||||
"""获取事件响应器定义所在模块。
|
||||
|
||||
**Deprecated**, 请使用 {ref}`nonebot.plugin.on.get_matcher_source` 获取信息。
|
||||
|
||||
参数:
|
||||
depth: 调用栈深度
|
||||
"""
|
||||
warnings.warn(
|
||||
"`get_matcher_module` is deprecated, please use `get_matcher_source` instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return (source := get_matcher_source(depth + 1)) and source.module
|
||||
|
||||
|
||||
def get_matcher_source(depth: int = 1) -> Optional[MatcherSource]:
|
||||
"""获取事件响应器定义所在源码信息。
|
||||
|
||||
参数:
|
||||
depth: 调用栈深度
|
||||
"""
|
||||
@@ -71,7 +86,22 @@ def get_matcher_module(depth: int = 1) -> Optional[ModuleType]:
|
||||
if current_frame is None:
|
||||
return None
|
||||
frame = inspect.getouterframes(current_frame)[depth + 1].frame
|
||||
return inspect.getmodule(frame)
|
||||
|
||||
module_name = (module := inspect.getmodule(frame)) and module.__name__
|
||||
|
||||
plugin: Optional["Plugin"] = None
|
||||
# matcher defined when plugin loading
|
||||
if plugin_chain := _current_plugin_chain.get():
|
||||
plugin = plugin_chain[-1]
|
||||
# matcher defined when plugin running
|
||||
elif module_name:
|
||||
plugin = get_plugin_by_module_name(module_name)
|
||||
|
||||
return MatcherSource(
|
||||
plugin_name=plugin and plugin.name,
|
||||
module_name=module_name,
|
||||
lineno=frame.f_lineno,
|
||||
)
|
||||
|
||||
|
||||
def on(
|
||||
@@ -109,8 +139,7 @@ def on(
|
||||
priority=priority,
|
||||
block=block,
|
||||
handlers=handlers,
|
||||
plugin=get_matcher_plugin(_depth + 1),
|
||||
module=get_matcher_module(_depth + 1),
|
||||
source=get_matcher_source(_depth + 1),
|
||||
default_state=state,
|
||||
)
|
||||
store_matcher(matcher)
|
||||
|
@@ -4,10 +4,10 @@ from types import ModuleType
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.rule import Rule, ArgumentParser
|
||||
from nonebot.matcher import Matcher, MatcherSource
|
||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
||||
|
||||
from .plugin import Plugin
|
||||
@@ -15,6 +15,7 @@ from .plugin import Plugin
|
||||
def store_matcher(matcher: type[Matcher]) -> None: ...
|
||||
def get_matcher_plugin(depth: int = ...) -> Plugin | None: ...
|
||||
def get_matcher_module(depth: int = ...) -> ModuleType | None: ...
|
||||
def get_matcher_source(depth: int = ...) -> MatcherSource | None: ...
|
||||
def on(
|
||||
type: str = "",
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
|
@@ -21,6 +21,7 @@ from typing import (
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Optional,
|
||||
@@ -220,6 +221,16 @@ def resolve_dot_notation(
|
||||
return instance
|
||||
|
||||
|
||||
class classproperty(Generic[T]):
|
||||
"""类属性装饰器"""
|
||||
|
||||
def __init__(self, func: Callable[[Any], T]) -> None:
|
||||
self.func = func
|
||||
|
||||
def __get__(self, instance: Any, owner: Optional[Type[Any]] = None) -> T:
|
||||
return self.func(type(instance) if owner is None else owner)
|
||||
|
||||
|
||||
class DataclassEncoder(json.JSONEncoder):
|
||||
"""可以序列化 {ref}`nonebot.adapters.Message`(List[Dataclass]) 的 `JSONEncoder`"""
|
||||
|
||||
|
639
poetry.lock
generated
639
poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiodns"
|
||||
@@ -16,13 +16,13 @@ pycares = ">=4.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
version = "23.1.0"
|
||||
version = "23.2.1"
|
||||
description = "File support for asyncio."
|
||||
optional = true
|
||||
python-versions = ">=3.7,<4.0"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"},
|
||||
{file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"},
|
||||
{file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"},
|
||||
{file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -204,13 +204,13 @@ requests = ">=2.21,<3.0"
|
||||
|
||||
[[package]]
|
||||
name = "async-timeout"
|
||||
version = "4.0.2"
|
||||
version = "4.0.3"
|
||||
description = "Timeout context manager for asyncio programs"
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
|
||||
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
|
||||
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
||||
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -233,33 +233,13 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "23.7.0"
|
||||
version = "23.9.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"},
|
||||
{file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"},
|
||||
{file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"},
|
||||
{file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"},
|
||||
{file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"},
|
||||
{file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"},
|
||||
{file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"},
|
||||
{file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"},
|
||||
{file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"},
|
||||
{file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"},
|
||||
{file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"},
|
||||
{file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"},
|
||||
{file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"},
|
||||
{file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"},
|
||||
{file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"},
|
||||
{file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"},
|
||||
{file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"},
|
||||
{file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"},
|
||||
{file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"},
|
||||
{file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"},
|
||||
{file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"},
|
||||
{file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"},
|
||||
{file = "black-23.9.0-py3-none-any.whl", hash = "sha256:9366c1f898981f09eb8da076716c02fd021f5a0e63581c66501d68a2e4eab844"},
|
||||
{file = "black-23.9.0.tar.gz", hash = "sha256:3511c8a7e22ce653f89ae90dfddaf94f3bb7e2587a245246572d3b9c92adf066"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -269,7 +249,7 @@ packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
@@ -290,93 +270,94 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "1.0.9"
|
||||
version = "1.1.0"
|
||||
description = "Python bindings for the Brotli compression library"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"},
|
||||
{file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"},
|
||||
{file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"},
|
||||
{file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"},
|
||||
{file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"},
|
||||
{file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"},
|
||||
{file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"},
|
||||
{file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"},
|
||||
{file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"},
|
||||
{file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"},
|
||||
{file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"},
|
||||
{file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"},
|
||||
{file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"},
|
||||
{file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"},
|
||||
{file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"},
|
||||
{file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"},
|
||||
{file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"},
|
||||
{file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"},
|
||||
{file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"},
|
||||
{file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"},
|
||||
{file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"},
|
||||
{file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"},
|
||||
{file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"},
|
||||
{file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"},
|
||||
{file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"},
|
||||
{file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"},
|
||||
{file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"},
|
||||
{file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"},
|
||||
{file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"},
|
||||
{file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"},
|
||||
{file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"},
|
||||
{file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"},
|
||||
{file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"},
|
||||
{file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"},
|
||||
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -506,13 +487,13 @@ pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.3.1"
|
||||
version = "3.4.0"
|
||||
description = "Validate configuration and produce human readable error messages."
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
|
||||
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
|
||||
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
|
||||
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -601,13 +582,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.6"
|
||||
version = "8.1.7"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"},
|
||||
{file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"},
|
||||
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -626,71 +607,63 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.2.7"
|
||||
version = "7.3.1"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"},
|
||||
{file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"},
|
||||
{file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"},
|
||||
{file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"},
|
||||
{file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"},
|
||||
{file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"},
|
||||
{file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"},
|
||||
{file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"},
|
||||
{file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"},
|
||||
{file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"},
|
||||
{file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"},
|
||||
{file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"},
|
||||
{file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"},
|
||||
{file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"},
|
||||
{file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"},
|
||||
{file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -728,13 +701,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"},
|
||||
{file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"},
|
||||
{file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"},
|
||||
{file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -756,17 +729,18 @@ testing = ["hatch", "pre-commit", "pytest", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.100.0"
|
||||
version = "0.103.1"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = true
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"},
|
||||
{file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"},
|
||||
{file = "fastapi-0.103.1-py3-none-any.whl", hash = "sha256:5e5f17e826dbd9e9b5a5145976c5cd90bcaa61f2bf9a69aca423f2bcebe44d83"},
|
||||
{file = "fastapi-0.103.1.tar.gz", hash = "sha256:345844e6a82062f06a096684196aaf96c1198b25c06b72c1311b882aa2d8a35d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0"
|
||||
anyio = ">=3.7.1,<4.0.0"
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||
starlette = ">=0.27.0,<0.28.0"
|
||||
typing-extensions = ">=4.5.0"
|
||||
|
||||
@@ -775,18 +749,21 @@ all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)"
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.12.2"
|
||||
version = "3.12.3"
|
||||
description = "A platform independent file lock."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"},
|
||||
{file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"},
|
||||
{file = "filelock-3.12.3-py3-none-any.whl", hash = "sha256:f067e40ccc40f2b48395a80fcbd4728262fab54e232e090a4063ab804179efeb"},
|
||||
{file = "filelock-3.12.3.tar.gz", hash = "sha256:0ecc1dd2ec4672a10c8550a8182f1bd0c0a5088470ecd5a125e45f49472fac3d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
|
||||
docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "frozenlist"
|
||||
@@ -1024,13 +1001,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.5.26"
|
||||
version = "2.5.27"
|
||||
description = "File identification library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"},
|
||||
{file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"},
|
||||
{file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"},
|
||||
{file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -1124,13 +1101,13 @@ i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
description = "Python logging made (stupidly) simple"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"},
|
||||
{file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"},
|
||||
{file = "loguru-0.7.1-py3-none-any.whl", hash = "sha256:046bf970cb3cad77a28d607cbf042ac25a407db987a1e801c7f7e692469982f9"},
|
||||
{file = "loguru-0.7.1.tar.gz", hash = "sha256:7ba2a7d81b79a412b0ded69bd921e012335e80fd39937a633570f273a343579e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1138,7 +1115,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
|
||||
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"]
|
||||
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "pre-commit (==3.3.1)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
@@ -1324,13 +1301,13 @@ setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
name = "nonebug"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
description = "nonebot2 test framework"
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
files = [
|
||||
{file = "nonebug-0.3.4-py3-none-any.whl", hash = "sha256:d6ebbde934d463141497e3162e26371b7e266d39f8cac0aa1bccc0e4542dd48b"},
|
||||
{file = "nonebug-0.3.4.tar.gz", hash = "sha256:11d106dff3fe0d5fa029b9745f701770bcc484be048e72722bb17bb00f84753d"},
|
||||
{file = "nonebug-0.3.5-py3-none-any.whl", hash = "sha256:588831b08b3ea42d058874214bedae646e2ab8c1ec4ae1540ff789873107a8fa"},
|
||||
{file = "nonebug-0.3.5.tar.gz", hash = "sha256:4d4bf9448cd1cbfaaabaab73dbe4ac8757e86dd92a41ef79cdece8dd61e724e2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1381,39 +1358,39 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
|
||||
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
|
||||
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
|
||||
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "3.9.1"
|
||||
version = "3.10.0"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"},
|
||||
{file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"},
|
||||
{file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"},
|
||||
{file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"]
|
||||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"},
|
||||
{file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"},
|
||||
{file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
|
||||
{file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -1422,13 +1399,13 @@ testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "3.3.3"
|
||||
version = "3.4.0"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"},
|
||||
{file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"},
|
||||
{file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"},
|
||||
{file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1543,47 +1520,47 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.11"
|
||||
version = "1.10.12"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"},
|
||||
{file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"},
|
||||
{file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"},
|
||||
{file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"},
|
||||
{file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"},
|
||||
{file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"},
|
||||
{file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"},
|
||||
{file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"},
|
||||
{file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"},
|
||||
{file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"},
|
||||
{file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"},
|
||||
{file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"},
|
||||
{file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"},
|
||||
{file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"},
|
||||
{file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"},
|
||||
{file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"},
|
||||
{file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"},
|
||||
{file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"},
|
||||
{file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"},
|
||||
{file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"},
|
||||
{file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"},
|
||||
{file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"},
|
||||
{file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"},
|
||||
{file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"},
|
||||
{file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"},
|
||||
{file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"},
|
||||
{file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"},
|
||||
{file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"},
|
||||
{file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"},
|
||||
{file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"},
|
||||
{file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"},
|
||||
{file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"},
|
||||
{file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"},
|
||||
{file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"},
|
||||
{file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"},
|
||||
{file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"},
|
||||
{file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"},
|
||||
{file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1607,13 +1584,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.0"
|
||||
version = "7.4.2"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"},
|
||||
{file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"},
|
||||
{file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"},
|
||||
{file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1709,6 +1686,7 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
|
||||
@@ -1716,8 +1694,15 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
|
||||
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
|
||||
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
|
||||
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
|
||||
@@ -1734,6 +1719,7 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
|
||||
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
|
||||
@@ -1741,6 +1727,7 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
|
||||
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
|
||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||
@@ -1795,45 +1782,45 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.280"
|
||||
version = "0.0.287"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.0.280-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:48ed5aca381050a4e2f6d232db912d2e4e98e61648b513c350990c351125aaec"},
|
||||
{file = "ruff-0.0.280-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:ef6ee3e429fd29d6a5ceed295809e376e6ece5b0f13c7e703efaf3d3bcb30b96"},
|
||||
{file = "ruff-0.0.280-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d878370f7e9463ac40c253724229314ff6ebe4508cdb96cb536e1af4d5a9cd4f"},
|
||||
{file = "ruff-0.0.280-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83e8f372fa5627eeda5b83b5a9632d2f9c88fc6d78cead7e2a1f6fb05728d137"},
|
||||
{file = "ruff-0.0.280-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7008fc6ca1df18b21fa98bdcfc711dad5f94d0fc3c11791f65e460c48ef27c82"},
|
||||
{file = "ruff-0.0.280-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe7118c1eae3fda17ceb409629c7f3b5a22dffa7caf1f6796776936dca1fe653"},
|
||||
{file = "ruff-0.0.280-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37359cd67d2af8e09110a546507c302cbea11c66a52d2a9b6d841d465f9962d4"},
|
||||
{file = "ruff-0.0.280-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd58af46b0221efb95966f1f0f7576df711cb53e50d2fdb0e83c2f33360116a4"},
|
||||
{file = "ruff-0.0.280-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e7c15828d09f90e97bea8feefcd2907e8c8ce3a1f959c99f9b4b3469679f33c"},
|
||||
{file = "ruff-0.0.280-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2dae8f2d9c44c5c49af01733c2f7956f808db682a4193180dedb29dd718d7bbe"},
|
||||
{file = "ruff-0.0.280-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5f972567163a20fb8c2d6afc60c2ea5ef8b68d69505760a8bd0377de8984b4f6"},
|
||||
{file = "ruff-0.0.280-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ffa7347ad11643f29de100977c055e47c988cd6d9f5f5ff83027600b11b9189"},
|
||||
{file = "ruff-0.0.280-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a37dab70114671d273f203268f6c3366c035fe0c8056614069e90a65e614bfc"},
|
||||
{file = "ruff-0.0.280-py3-none-win32.whl", hash = "sha256:7784e3606352fcfb193f3cd22b2e2117c444cb879ef6609ec69deabd662b0763"},
|
||||
{file = "ruff-0.0.280-py3-none-win_amd64.whl", hash = "sha256:4a7d52457b5dfcd3ab24b0b38eefaead8e2dca62b4fbf10de4cd0938cf20ce30"},
|
||||
{file = "ruff-0.0.280-py3-none-win_arm64.whl", hash = "sha256:b7de5b8689575918e130e4384ed9f539ce91d067c0a332aedef6ca7188adac2d"},
|
||||
{file = "ruff-0.0.280.tar.gz", hash = "sha256:581c43e4ac5e5a7117ad7da2120d960a4a99e68ec4021ec3cd47fe1cf78f8380"},
|
||||
{file = "ruff-0.0.287-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:1e0f9ee4c3191444eefeda97d7084721d9b8e29017f67997a20c153457f2eafd"},
|
||||
{file = "ruff-0.0.287-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e9843e5704d4fb44e1a8161b0d31c1a38819723f0942639dfeb53d553be9bfb5"},
|
||||
{file = "ruff-0.0.287-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca1ed11d759a29695aed2bfc7f914b39bcadfe2ef08d98ff69c873f639ad3a8"},
|
||||
{file = "ruff-0.0.287-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf4d5ad3073af10f186ea22ce24bc5a8afa46151f6896f35c586e40148ba20b"},
|
||||
{file = "ruff-0.0.287-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d9d58bcb29afd72d2afe67120afcc7d240efc69a235853813ad556443dc922"},
|
||||
{file = "ruff-0.0.287-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:06ac5df7dd3ba8bf83bba1490a72f97f1b9b21c7cbcba8406a09de1a83f36083"},
|
||||
{file = "ruff-0.0.287-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2bfb478e1146a60aa740ab9ebe448b1f9e3c0dfb54be3cc58713310eef059c30"},
|
||||
{file = "ruff-0.0.287-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00d579a011949108c4b4fa04c4f1ee066dab536a9ba94114e8e580c96be2aeb4"},
|
||||
{file = "ruff-0.0.287-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a810a79b8029cc92d06c36ea1f10be5298d2323d9024e1d21aedbf0a1a13e5"},
|
||||
{file = "ruff-0.0.287-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:150007028ad4976ce9a7704f635ead6d0e767f73354ce0137e3e44f3a6c0963b"},
|
||||
{file = "ruff-0.0.287-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a24a280db71b0fa2e0de0312b4aecb8e6d08081d1b0b3c641846a9af8e35b4a7"},
|
||||
{file = "ruff-0.0.287-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2918cb7885fa1611d542de1530bea3fbd63762da793751cc8c8d6e4ba234c3d8"},
|
||||
{file = "ruff-0.0.287-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:33d7b251afb60bec02a64572b0fd56594b1923ee77585bee1e7e1daf675e7ae7"},
|
||||
{file = "ruff-0.0.287-py3-none-win32.whl", hash = "sha256:022f8bed2dcb5e5429339b7c326155e968a06c42825912481e10be15dafb424b"},
|
||||
{file = "ruff-0.0.287-py3-none-win_amd64.whl", hash = "sha256:26bd0041d135a883bd6ab3e0b29c42470781fb504cf514e4c17e970e33411d90"},
|
||||
{file = "ruff-0.0.287-py3-none-win_arm64.whl", hash = "sha256:44bceb3310ac04f0e59d4851e6227f7b1404f753997c7859192e41dbee9f5c8d"},
|
||||
{file = "ruff-0.0.287.tar.gz", hash = "sha256:02dc4f5bf53ef136e459d467f3ce3e04844d509bc46c025a05b018feb37bbc39"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "68.0.0"
|
||||
version = "68.2.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"},
|
||||
{file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"},
|
||||
{file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"},
|
||||
{file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
@@ -1905,13 +1892,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.23.1"
|
||||
version = "0.23.2"
|
||||
description = "The lightning-fast ASGI server."
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "uvicorn-0.23.1-py3-none-any.whl", hash = "sha256:1d55d46b83ee4ce82b4e82f621f2050adb3eb7b5481c13f9af1744951cae2f1f"},
|
||||
{file = "uvicorn-0.23.1.tar.gz", hash = "sha256:da9b0c8443b2d7ee9db00a345f1eee6db7317432c9d4400f5049cc8d358383be"},
|
||||
{file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"},
|
||||
{file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1975,53 +1962,53 @@ test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "my
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.24.1"
|
||||
version = "20.24.5"
|
||||
description = "Virtual Python Environment builder"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "virtualenv-20.24.1-py3-none-any.whl", hash = "sha256:01aacf8decd346cf9a865ae85c0cdc7f64c8caa07ff0d8b1dfc1733d10677442"},
|
||||
{file = "virtualenv-20.24.1.tar.gz", hash = "sha256:2ef6a237c31629da6442b0bcaa3999748108c7166318d1f55cc9f8d7294e97bd"},
|
||||
{file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"},
|
||||
{file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
distlib = ">=0.3.6,<1"
|
||||
filelock = ">=3.12,<4"
|
||||
platformdirs = ">=3.5.1,<4"
|
||||
distlib = ">=0.3.7,<1"
|
||||
filelock = ">=3.12.2,<4"
|
||||
platformdirs = ">=3.9.1,<4"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"]
|
||||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
description = "Simple, modern and high performance file watching and code reload in python."
|
||||
optional = true
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"},
|
||||
{file = "watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"},
|
||||
{file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"},
|
||||
{file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"},
|
||||
{file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"},
|
||||
{file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"},
|
||||
{file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"},
|
||||
{file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"},
|
||||
{file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"},
|
||||
{file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"},
|
||||
{file = "watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:3796312bd3587e14926013612b23066912cf45a14af71cf2b20db1c12dadf4e9"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:d0002d81c89a662b595645fb684a371b98ff90a9c7d8f8630c82f0fde8310458"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:570848706440373b4cd8017f3e850ae17f76dbdf1e9045fc79023b11e1afe490"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a0351d20d03c6f7ad6b2e8a226a5efafb924c7755ee1e34f04c77c3682417fa"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:007dcc4a401093010b389c044e81172c8a2520dba257c88f8828b3d460c6bb38"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d82dbc1832da83e441d112069833eedd4cf583d983fb8dd666fbefbea9d99c0"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99f4c65fd2fce61a571b2a6fcf747d6868db0bef8a934e8ca235cc8533944d95"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5392dd327a05f538c56edb1c6ebba6af91afc81b40822452342f6da54907bbdf"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:08dc702529bb06a2b23859110c214db245455532da5eaea602921687cfcd23db"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7d4e66a857621584869cfbad87039e65dadd7119f0d9bb9dbc957e089e32c164"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-win32.whl", hash = "sha256:a03d1e6feb7966b417f43c3e3783188167fd69c2063e86bad31e62c4ea794cc5"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-win_amd64.whl", hash = "sha256:eccc8942bcdc7d638a01435d915b913255bbd66f018f1af051cd8afddb339ea3"},
|
||||
{file = "watchfiles-0.20.0-cp37-abi3-win_arm64.whl", hash = "sha256:b17d4176c49d207865630da5b59a91779468dd3e08692fe943064da260de2c7c"},
|
||||
{file = "watchfiles-0.20.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d97db179f7566dcf145c5179ddb2ae2a4450e3a634eb864b09ea04e68c252e8e"},
|
||||
{file = "watchfiles-0.20.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:835df2da7a5df5464c4a23b2d963e1a9d35afa422c83bf4ff4380b3114603644"},
|
||||
{file = "watchfiles-0.20.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:608cd94a8767f49521901aff9ae0c92cc8f5a24d528db7d6b0295290f9d41193"},
|
||||
{file = "watchfiles-0.20.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89d1de8218874925bce7bb2ae9657efc504411528930d7a83f98b1749864f2ef"},
|
||||
{file = "watchfiles-0.20.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:13f995d5152a8ba4ed7c2bbbaeee4e11a5944defc7cacd0ccb4dcbdcfd78029a"},
|
||||
{file = "watchfiles-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9b5c8d3be7b502f8c43a33c63166ada8828dbb0c6d49c8f9ce990a96de2f5a49"},
|
||||
{file = "watchfiles-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e43af4464daa08723c04b43cf978ab86cc55c684c16172622bdac64b34e36af0"},
|
||||
{file = "watchfiles-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d9e1f75c4f86c93d73b5bd1ebe667558357548f11b4f8af4e0e272f79413ce"},
|
||||
{file = "watchfiles-0.20.0.tar.gz", hash = "sha256:728575b6b94c90dd531514677201e8851708e6e4b5fe7028ac506a200b622019"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2119,13 +2106,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "2.3.6"
|
||||
version = "2.3.7"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"},
|
||||
{file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"},
|
||||
{file = "werkzeug-2.3.7-py3-none-any.whl", hash = "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"},
|
||||
{file = "werkzeug-2.3.7.tar.gz", hash = "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2275,4 +2262,4 @@ websockets = ["websockets"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "e125baa8903fb84e5955cf4e3d6f666496fa1d2c2b77b36309f81e4a6c02e242"
|
||||
content-hash = "0fe5200eab7eb8fe06e86f9aa727297ba96646093dfe4940f8c0d93949956582"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot2"
|
||||
version = "2.0.1"
|
||||
version = "2.1.0"
|
||||
description = "An asynchronous python bot framework."
|
||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
@@ -14,11 +14,9 @@ classifiers = [
|
||||
"Framework :: Robot Framework",
|
||||
"Framework :: Robot Framework :: Library",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3"
|
||||
]
|
||||
packages = [
|
||||
{ include = "nonebot" },
|
||||
"Programming Language :: Python :: 3",
|
||||
]
|
||||
packages = [{ include = "nonebot" }]
|
||||
include = ["nonebot/py.typed"]
|
||||
|
||||
[tool.poetry.urls]
|
||||
@@ -31,7 +29,7 @@ python = "^3.8"
|
||||
yarl = "^1.7.2"
|
||||
pygtrie = "^2.4.1"
|
||||
loguru = ">=0.6.0,<1.0.0"
|
||||
typing-extensions = ">=4.0.0,<5.0.0"
|
||||
typing-extensions = ">=4.4.0,<5.0.0"
|
||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
||||
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
|
||||
|
||||
@@ -40,7 +38,9 @@ Quart = { version = ">=0.18.0,<1.0.0", optional = true }
|
||||
fastapi = { version = ">=0.93.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 }
|
||||
uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
||||
"standard",
|
||||
], optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
isort = "^5.10.1"
|
||||
@@ -71,10 +71,7 @@ all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "strict"
|
||||
addopts = "--cov=nonebot --cov-append --cov-report=term-missing"
|
||||
filterwarnings = [
|
||||
"error",
|
||||
"ignore::DeprecationWarning",
|
||||
]
|
||||
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
@@ -94,7 +91,7 @@ extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[tool.ruff]
|
||||
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
|
||||
ignore = ["E402", "C901"]
|
||||
ignore = ["E402", "C901", "UP037"]
|
||||
|
||||
line-length = 88
|
||||
target-version = "py38"
|
||||
@@ -107,7 +104,9 @@ mark-parentheses = false
|
||||
pythonVersion = "3.8"
|
||||
pythonPlatform = "All"
|
||||
executionEnvironments = [
|
||||
{ root = "./tests", extraPaths = ["./"] },
|
||||
{ root = "./tests", extraPaths = [
|
||||
"./",
|
||||
] },
|
||||
{ root = "./" },
|
||||
]
|
||||
|
||||
|
@@ -11,6 +11,7 @@ exclude_lines =
|
||||
if (typing\.)?TYPE_CHECKING( is True)?:
|
||||
@(abc\.)?abstractmethod
|
||||
raise NotImplementedError
|
||||
warnings\.warn
|
||||
\.\.\.
|
||||
pass
|
||||
if __name__ == .__main__.:
|
||||
|
@@ -8,8 +8,10 @@ from nonebug import NONEBOT_INIT_KWARGS
|
||||
from werkzeug.serving import BaseWSGIServer, make_server
|
||||
|
||||
import nonebot
|
||||
from nonebot.drivers import URL
|
||||
from nonebot.config import Env
|
||||
from fake_server import request_handler
|
||||
from nonebot.drivers import URL, Driver
|
||||
from nonebot import _resolve_combine_expr
|
||||
|
||||
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
|
||||
os.environ["CONFIG_OVERRIDE"] = "new"
|
||||
@@ -22,6 +24,17 @@ def pytest_configure(config: pytest.Config) -> None:
|
||||
config.stash[NONEBOT_INIT_KWARGS] = {"config_from_init": "init"}
|
||||
|
||||
|
||||
@pytest.fixture(name="driver")
|
||||
def load_driver(request: pytest.FixtureRequest) -> Driver:
|
||||
driver_name = getattr(request, "param", None)
|
||||
global_driver = nonebot.get_driver()
|
||||
if driver_name is None:
|
||||
return global_driver
|
||||
|
||||
DriverClass = _resolve_combine_expr(driver_name)
|
||||
return DriverClass(Env(environment=global_driver.env), global_driver.config)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def load_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
# preload global plugins
|
||||
|
3
tests/plugins/matcher/matcher_info.py
Normal file
3
tests/plugins/matcher/matcher_info.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from nonebot import on
|
||||
|
||||
matcher = on("message", temp=False, expire_time=None, priority=1, block=True)
|
@@ -1,7 +1,10 @@
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.params import Depends
|
||||
|
||||
test_depends = on_message()
|
||||
@@ -33,6 +36,14 @@ class ClassDependency:
|
||||
y: int = Depends(gen_async)
|
||||
|
||||
|
||||
class FooBot(Bot):
|
||||
...
|
||||
|
||||
|
||||
async def sub_bot(b: FooBot) -> FooBot:
|
||||
return b
|
||||
|
||||
|
||||
# test parameterless
|
||||
@test_depends.handle(parameterless=[Depends(parameterless)])
|
||||
async def depends(x: int = Depends(dependency)):
|
||||
@@ -46,19 +57,46 @@ async def depends_cache(y: int = Depends(dependency, use_cache=True)):
|
||||
return y
|
||||
|
||||
|
||||
# test class dependency
|
||||
async def class_depend(c: ClassDependency = Depends()):
|
||||
return c
|
||||
|
||||
|
||||
# test annotated dependency
|
||||
async def annotated_depend(x: Annotated[int, Depends(dependency)]):
|
||||
return x
|
||||
|
||||
|
||||
# test annotated class dependency
|
||||
async def annotated_class_depend(c: Annotated[ClassDependency, Depends()]):
|
||||
return c
|
||||
|
||||
|
||||
# test dependency priority
|
||||
async def annotated_prior_depend(
|
||||
x: Annotated[int, Depends(lambda: 2)] = Depends(dependency)
|
||||
):
|
||||
return x
|
||||
|
||||
|
||||
# test sub dependency type mismatch
|
||||
async def sub_type_mismatch(b: FooBot = Depends(sub_bot)):
|
||||
return b
|
||||
|
||||
|
||||
# test type validate
|
||||
async def validate(x: int = Depends(lambda: "1", validate=True)):
|
||||
return x
|
||||
|
||||
|
||||
async def validate_fail(x: int = Depends(lambda: "not_number", validate=True)):
|
||||
return x
|
||||
|
||||
|
||||
# test FieldInfo validate
|
||||
async def validate_field(x: int = Depends(lambda: "1", validate=Field(gt=0))):
|
||||
return x
|
||||
|
||||
|
||||
async def validate_field_fail(x: int = Depends(lambda: "0", validate=Field(gt=0))):
|
||||
return x
|
||||
|
211
tests/test_adapters/test_adapter.py
Normal file
211
tests/test_adapters/test_adapter.py
Normal file
@@ -0,0 +1,211 @@
|
||||
from typing import Optional
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
from utils import FakeAdapter
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.drivers import (
|
||||
URL,
|
||||
Driver,
|
||||
Request,
|
||||
Response,
|
||||
WebSocket,
|
||||
HTTPServerSetup,
|
||||
WebSocketServerSetup,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_adapter_connect(app: App, driver: Driver):
|
||||
last_connect_bot: Optional[Bot] = None
|
||||
last_disconnect_bot: Optional[Bot] = None
|
||||
|
||||
def _fake_bot_connect(bot: Bot):
|
||||
nonlocal last_connect_bot
|
||||
last_connect_bot = bot
|
||||
|
||||
def _fake_bot_disconnect(bot: Bot):
|
||||
nonlocal last_disconnect_bot
|
||||
last_disconnect_bot = bot
|
||||
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
m.setattr(driver, "_bot_connect", _fake_bot_connect)
|
||||
m.setattr(driver, "_bot_disconnect", _fake_bot_disconnect)
|
||||
|
||||
adapter = FakeAdapter(driver)
|
||||
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot(adapter=adapter)
|
||||
assert last_connect_bot is bot
|
||||
assert adapter.bots[bot.self_id] is bot
|
||||
|
||||
assert last_disconnect_bot is bot
|
||||
assert bot.self_id not in adapter.bots
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
pytest.param("nonebot.drivers.fastapi:Driver", id="fastapi"),
|
||||
pytest.param("nonebot.drivers.quart:Driver", id="quart"),
|
||||
pytest.param(
|
||||
"nonebot.drivers.httpx:Driver",
|
||||
id="httpx",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a server", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"nonebot.drivers.websockets:Driver",
|
||||
id="websockets",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a server", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"nonebot.drivers.aiohttp:Driver",
|
||||
id="aiohttp",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a server", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_adapter_server(driver: Driver):
|
||||
last_http_setup: Optional[HTTPServerSetup] = None
|
||||
last_ws_setup: Optional[WebSocketServerSetup] = None
|
||||
|
||||
def _fake_setup_http_server(setup: HTTPServerSetup):
|
||||
nonlocal last_http_setup
|
||||
last_http_setup = setup
|
||||
|
||||
def _fake_setup_websocket_server(setup: WebSocketServerSetup):
|
||||
nonlocal last_ws_setup
|
||||
last_ws_setup = setup
|
||||
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
m.setattr(driver, "setup_http_server", _fake_setup_http_server, raising=False)
|
||||
m.setattr(
|
||||
driver,
|
||||
"setup_websocket_server",
|
||||
_fake_setup_websocket_server,
|
||||
raising=False,
|
||||
)
|
||||
|
||||
async def handle_http(request: Request):
|
||||
return Response(200, content="test")
|
||||
|
||||
async def handle_ws(ws: WebSocket):
|
||||
...
|
||||
|
||||
adapter = FakeAdapter(driver)
|
||||
|
||||
setup = HTTPServerSetup(URL("/test"), "GET", "test", handle_http)
|
||||
adapter.setup_http_server(setup)
|
||||
assert last_http_setup is setup
|
||||
|
||||
setup = WebSocketServerSetup(URL("/test"), "test", handle_ws)
|
||||
adapter.setup_websocket_server(setup)
|
||||
assert last_ws_setup is setup
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
pytest.param(
|
||||
"nonebot.drivers.fastapi:Driver",
|
||||
id="fastapi",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a http client", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"nonebot.drivers.quart:Driver",
|
||||
id="quart",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a http client", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
||||
pytest.param(
|
||||
"nonebot.drivers.websockets:Driver",
|
||||
id="websockets",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a http client", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_adapter_http_client(driver: Driver):
|
||||
last_request: Optional[Request] = None
|
||||
|
||||
async def _fake_request(request: Request):
|
||||
nonlocal last_request
|
||||
last_request = request
|
||||
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
m.setattr(driver, "request", _fake_request, raising=False)
|
||||
|
||||
adapter = FakeAdapter(driver)
|
||||
|
||||
request = Request("GET", URL("/test"))
|
||||
await adapter.request(request)
|
||||
assert last_request is request
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
pytest.param(
|
||||
"nonebot.drivers.fastapi:Driver",
|
||||
id="fastapi",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a websocket client", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"nonebot.drivers.quart:Driver",
|
||||
id="quart",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a websocket client", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"nonebot.drivers.httpx:Driver",
|
||||
id="httpx",
|
||||
marks=pytest.mark.xfail(
|
||||
reason="not a websocket client", raises=TypeError, strict=True
|
||||
),
|
||||
),
|
||||
pytest.param("nonebot.drivers.websockets:Driver", id="websockets"),
|
||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_adapter_websocket_client(driver: Driver):
|
||||
_fake_ws = object()
|
||||
_last_request: Optional[Request] = None
|
||||
|
||||
@asynccontextmanager
|
||||
async def _fake_websocket(setup: Request):
|
||||
nonlocal _last_request
|
||||
_last_request = setup
|
||||
yield _fake_ws
|
||||
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
m.setattr(driver, "websocket", _fake_websocket, raising=False)
|
||||
|
||||
adapter = FakeAdapter(driver)
|
||||
|
||||
request = Request("GET", URL("/test"))
|
||||
async with adapter.websocket(request) as ws:
|
||||
assert _last_request is request
|
||||
assert ws is _fake_ws
|
@@ -1,15 +1,12 @@
|
||||
import json
|
||||
import asyncio
|
||||
from typing import Any, Set, Optional, cast
|
||||
from typing import Any, Set, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
import nonebot
|
||||
from nonebot.config import Env
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.params import Depends
|
||||
from nonebot import _resolve_combine_expr
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers._lifespan import Lifespan
|
||||
@@ -18,25 +15,15 @@ from nonebot.drivers import (
|
||||
Driver,
|
||||
Request,
|
||||
Response,
|
||||
ASGIMixin,
|
||||
WebSocket,
|
||||
ForwardDriver,
|
||||
ReverseDriver,
|
||||
HTTPClientMixin,
|
||||
HTTPServerSetup,
|
||||
WebSocketClientMixin,
|
||||
WebSocketServerSetup,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="driver")
|
||||
def load_driver(request: pytest.FixtureRequest) -> Driver:
|
||||
driver_name = getattr(request, "param", None)
|
||||
global_driver = nonebot.get_driver()
|
||||
if driver_name is None:
|
||||
return global_driver
|
||||
|
||||
DriverClass = _resolve_combine_expr(driver_name)
|
||||
return DriverClass(Env(environment=global_driver.env), global_driver.config)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_lifespan():
|
||||
lifespan = Lifespan()
|
||||
@@ -80,7 +67,7 @@ async def test_lifespan():
|
||||
indirect=True,
|
||||
)
|
||||
async def test_http_server(app: App, driver: Driver):
|
||||
driver = cast(ReverseDriver, driver)
|
||||
assert isinstance(driver, ASGIMixin)
|
||||
|
||||
async def _handle_http(request: Request) -> Response:
|
||||
assert request.content in (b"test", "test")
|
||||
@@ -108,7 +95,7 @@ async def test_http_server(app: App, driver: Driver):
|
||||
indirect=True,
|
||||
)
|
||||
async def test_websocket_server(app: App, driver: Driver):
|
||||
driver = cast(ReverseDriver, driver)
|
||||
assert isinstance(driver, ASGIMixin)
|
||||
|
||||
async def _handle_ws(ws: WebSocket) -> None:
|
||||
await ws.accept()
|
||||
@@ -164,7 +151,7 @@ async def test_websocket_server(app: App, driver: Driver):
|
||||
indirect=True,
|
||||
)
|
||||
async def test_cross_context(app: App, driver: Driver):
|
||||
driver = cast(ReverseDriver, driver)
|
||||
assert isinstance(driver, ASGIMixin)
|
||||
|
||||
ws: Optional[WebSocket] = None
|
||||
ws_ready = asyncio.Event()
|
||||
@@ -221,7 +208,7 @@ async def test_cross_context(app: App, driver: Driver):
|
||||
indirect=True,
|
||||
)
|
||||
async def test_http_client(driver: Driver, server_url: URL):
|
||||
driver = cast(ForwardDriver, driver)
|
||||
assert isinstance(driver, HTTPClientMixin)
|
||||
|
||||
# simple post with query, headers, cookies and content
|
||||
request = Request(
|
||||
@@ -233,6 +220,23 @@ async def test_http_client(driver: Driver, server_url: URL):
|
||||
content="test",
|
||||
)
|
||||
response = await driver.request(request)
|
||||
assert server_url.host is not None
|
||||
request_raw_url = Request(
|
||||
"POST",
|
||||
(
|
||||
server_url.scheme.encode("ascii"),
|
||||
server_url.host.encode("ascii"),
|
||||
server_url.port,
|
||||
server_url.path.encode("ascii"),
|
||||
),
|
||||
params={"param": "test"},
|
||||
headers={"X-Test": "test"},
|
||||
cookies={"session": "test"},
|
||||
content="test",
|
||||
)
|
||||
assert (
|
||||
request.url == request_raw_url.url
|
||||
), "request.url should be equal to request_raw_url.url"
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
@@ -265,7 +269,11 @@ async def test_http_client(driver: Driver, server_url: URL):
|
||||
"POST",
|
||||
server_url,
|
||||
data={"form": "test"},
|
||||
files={"test": ("test.txt", b"test")},
|
||||
files=[
|
||||
("test1", b"test"),
|
||||
("test2", ("test.txt", b"test")),
|
||||
("test3", ("test.txt", b"test", "text/plain")),
|
||||
],
|
||||
)
|
||||
response = await driver.request(request)
|
||||
assert response.status_code == 200
|
||||
@@ -273,11 +281,28 @@ async def test_http_client(driver: Driver, server_url: URL):
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["form"] == {"form": "test"}
|
||||
assert data["files"] == {"test": "test"}
|
||||
assert data["files"] == {
|
||||
"test1": "test",
|
||||
"test2": "test",
|
||||
"test3": "test",
|
||||
}, "file parsing error"
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
pytest.param("nonebot.drivers.websockets:Driver", id="websockets"),
|
||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_websocket_client(driver: Driver):
|
||||
assert isinstance(driver, WebSocketClientMixin)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("driver", "driver_type"),
|
||||
|
@@ -2,7 +2,7 @@ import pytest
|
||||
from nonebug import App
|
||||
|
||||
import nonebot
|
||||
from nonebot.drivers import Driver, ReverseDriver
|
||||
from nonebot.drivers import Driver, ASGIMixin, ReverseDriver
|
||||
from nonebot import (
|
||||
get_app,
|
||||
get_bot,
|
||||
@@ -47,6 +47,7 @@ async def test_get_driver(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
async def test_get_asgi(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
driver = get_driver()
|
||||
assert isinstance(driver, ReverseDriver)
|
||||
assert isinstance(driver, ASGIMixin)
|
||||
assert get_asgi() == driver.asgi
|
||||
|
||||
|
||||
@@ -54,6 +55,7 @@ async def test_get_asgi(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
async def test_get_app(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
driver = get_driver()
|
||||
assert isinstance(driver, ReverseDriver)
|
||||
assert isinstance(driver, ASGIMixin)
|
||||
assert get_app() == driver.server_app
|
||||
|
||||
|
||||
|
@@ -1,12 +1,45 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
from nonebot import get_plugin
|
||||
from nonebot.permission import User
|
||||
from nonebot.matcher import Matcher, matchers
|
||||
from utils import FakeMessage, make_fake_event
|
||||
from nonebot.message import check_and_run_matcher
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_info(app: App):
|
||||
from plugins.matcher.matcher_info import matcher
|
||||
|
||||
assert issubclass(matcher, Matcher)
|
||||
assert matcher.type == "message"
|
||||
assert matcher.priority == 1
|
||||
assert matcher.temp is False
|
||||
assert matcher.expire_time is None
|
||||
assert matcher.block is True
|
||||
|
||||
assert matcher._source
|
||||
|
||||
assert matcher._source.module_name == "plugins.matcher.matcher_info"
|
||||
assert matcher.module is sys.modules["plugins.matcher.matcher_info"]
|
||||
assert matcher.module_name == "plugins.matcher.matcher_info"
|
||||
|
||||
assert matcher._source.plugin_name == "matcher_info"
|
||||
assert matcher.plugin is get_plugin("matcher_info")
|
||||
assert matcher.plugin_name == "matcher_info"
|
||||
|
||||
assert (
|
||||
matcher._source.file
|
||||
== (Path(__file__).parent.parent / "plugins/matcher/matcher_info.py").absolute()
|
||||
)
|
||||
|
||||
assert matcher._source.lineno == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_handle(app: App):
|
||||
from plugins.matcher.matcher_process import test_handle
|
||||
|
@@ -42,9 +42,14 @@ async def test_depend(app: App):
|
||||
ClassDependency,
|
||||
runned,
|
||||
depends,
|
||||
validate,
|
||||
class_depend,
|
||||
test_depends,
|
||||
validate_fail,
|
||||
validate_field,
|
||||
annotated_depend,
|
||||
sub_type_mismatch,
|
||||
validate_field_fail,
|
||||
annotated_class_depend,
|
||||
annotated_prior_depend,
|
||||
)
|
||||
@@ -62,8 +67,7 @@ async def test_depend(app: App):
|
||||
event_next = make_fake_event()()
|
||||
ctx.receive_event(bot, event_next)
|
||||
|
||||
assert len(runned) == 2
|
||||
assert runned[0] == runned[1] == 1
|
||||
assert runned == [1, 1]
|
||||
|
||||
runned.clear()
|
||||
|
||||
@@ -84,6 +88,29 @@ async def test_depend(app: App):
|
||||
) as ctx:
|
||||
ctx.should_return(ClassDependency(x=1, y=2))
|
||||
|
||||
with pytest.raises(TypeMisMatch): # noqa: PT012
|
||||
async with app.test_dependent(
|
||||
sub_type_mismatch, allow_types=[DependParam, BotParam]
|
||||
) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
ctx.pass_params(bot=bot)
|
||||
|
||||
async with app.test_dependent(validate, allow_types=[DependParam]) as ctx:
|
||||
ctx.should_return(1)
|
||||
|
||||
with pytest.raises(TypeMisMatch):
|
||||
async with app.test_dependent(validate_fail, allow_types=[DependParam]) as ctx:
|
||||
...
|
||||
|
||||
async with app.test_dependent(validate_field, allow_types=[DependParam]) as ctx:
|
||||
ctx.should_return(1)
|
||||
|
||||
with pytest.raises(TypeMisMatch):
|
||||
async with app.test_dependent(
|
||||
validate_field_fail, allow_types=[DependParam]
|
||||
) as ctx:
|
||||
...
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot(app: App):
|
||||
|
@@ -1,8 +1,9 @@
|
||||
from typing_extensions import override
|
||||
from typing import Type, Union, Mapping, Iterable, Optional
|
||||
|
||||
from pydantic import Extra, create_model
|
||||
|
||||
from nonebot.adapters import Event, Message, MessageSegment
|
||||
from nonebot.adapters import Bot, Event, Adapter, Message, MessageSegment
|
||||
|
||||
|
||||
def escape_text(s: str, *, escape_comma: bool = True) -> str:
|
||||
@@ -12,11 +13,24 @@ def escape_text(s: str, *, escape_comma: bool = True) -> str:
|
||||
return s
|
||||
|
||||
|
||||
class FakeAdapter(Adapter):
|
||||
@classmethod
|
||||
@override
|
||||
def get_name(cls) -> str:
|
||||
return "fake"
|
||||
|
||||
@override
|
||||
async def _call_api(self, bot: Bot, api: str, **data):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class FakeMessageSegment(MessageSegment["FakeMessage"]):
|
||||
@classmethod
|
||||
@override
|
||||
def get_message_class(cls):
|
||||
return FakeMessage
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
return self.data["text"] if self.type == "text" else f"[fake:{self.type}]"
|
||||
|
||||
@@ -32,16 +46,19 @@ class FakeMessageSegment(MessageSegment["FakeMessage"]):
|
||||
def nested(content: "FakeMessage"):
|
||||
return FakeMessageSegment("node", {"content": content})
|
||||
|
||||
@override
|
||||
def is_text(self) -> bool:
|
||||
return self.type == "text"
|
||||
|
||||
|
||||
class FakeMessage(Message[FakeMessageSegment]):
|
||||
@classmethod
|
||||
@override
|
||||
def get_segment_class(cls):
|
||||
return FakeMessageSegment
|
||||
|
||||
@staticmethod
|
||||
@override
|
||||
def _construct(msg: Union[str, Iterable[Mapping]]):
|
||||
if isinstance(msg, str):
|
||||
yield FakeMessageSegment.text(msg)
|
||||
@@ -50,6 +67,7 @@ class FakeMessage(Message[FakeMessageSegment]):
|
||||
yield FakeMessageSegment(**seg)
|
||||
return
|
||||
|
||||
@override
|
||||
def __add__(
|
||||
self, other: Union[str, FakeMessageSegment, Iterable[FakeMessageSegment]]
|
||||
):
|
||||
@@ -71,30 +89,37 @@ def make_fake_event(
|
||||
Base = _base or Event
|
||||
|
||||
class FakeEvent(Base, extra=Extra.forbid):
|
||||
@override
|
||||
def get_type(self) -> str:
|
||||
return _type
|
||||
|
||||
@override
|
||||
def get_event_name(self) -> str:
|
||||
return _name
|
||||
|
||||
@override
|
||||
def get_event_description(self) -> str:
|
||||
return _description
|
||||
|
||||
@override
|
||||
def get_user_id(self) -> str:
|
||||
if _user_id is not None:
|
||||
return _user_id
|
||||
raise NotImplementedError
|
||||
|
||||
@override
|
||||
def get_session_id(self) -> str:
|
||||
if _session_id is not None:
|
||||
return _session_id
|
||||
raise NotImplementedError
|
||||
|
||||
@override
|
||||
def get_message(self) -> "Message":
|
||||
if _message is not None:
|
||||
return _message
|
||||
raise NotImplementedError
|
||||
|
||||
@override
|
||||
def is_tome(self) -> bool:
|
||||
return _to_me
|
||||
|
||||
|
@@ -219,7 +219,7 @@ async def _(e: Union[ActionFailed, NetworkError]): ...
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
|
||||
```python {4,16}
|
||||
```python {5,15}
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot import on_command
|
||||
@@ -241,7 +241,7 @@ async def _(event: Annotated[Event, Depends(check)]):
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {2,14}
|
||||
```python {3,13}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
@@ -267,7 +267,7 @@ async def _(event: Event = Depends(check)):
|
||||
|
||||
特别的,我们可以为 `Dependent` 对象定义一系列前置子依赖,它们会在参数执行前被顺序执行,且返回值将会被忽略,例如:
|
||||
|
||||
```python {2,14}
|
||||
```python {11}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
@@ -353,6 +353,80 @@ async def _(x: int = Depends(random_result, use_cache=False)):
|
||||
缓存的生命周期与当前接收到的事件相同。接收到事件后,子依赖在首次执行时缓存,在该事件处理完成后,缓存就会被清除。
|
||||
:::
|
||||
|
||||
### 类型转换与校验
|
||||
|
||||
在依赖注入系统中,我们可以对子依赖的返回值进行自动类型转换与校验。这个功能由 Pydantic 支持,因此我们通过参数类型注解自动使用 Pydantic 支持的类型转换。例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
|
||||
```python {6,9}
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot.params import Depends
|
||||
from nonebot.adapters import Event
|
||||
|
||||
def get_user_id(event: Event) -> str:
|
||||
return event.get_user_id()
|
||||
|
||||
async def _(user_id: Annotated[int, Depends(get_user_id, validate=True)]):
|
||||
print(user_id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {4,7}
|
||||
from nonebot.params import Depends
|
||||
from nonebot.adapters import Event
|
||||
|
||||
def get_user_id(event: Event) -> str:
|
||||
return event.get_user_id()
|
||||
|
||||
async def _(user_id: int = Depends(get_user_id, validate=True)):
|
||||
print(user_id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
在进行类型自动转换的同时,Pydantic 还支持对数据进行更多的限制,如:大于、小于、长度等。使用方法如下:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
|
||||
```python {7,10}
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
from nonebot.params import Depends
|
||||
from nonebot.adapters import Event
|
||||
|
||||
def get_user_id(event: Event) -> str:
|
||||
return event.get_user_id()
|
||||
|
||||
async def _(user_id: Annotated[int, Depends(get_user_id, validate=Field(gt=100))]):
|
||||
print(user_id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {5,8}
|
||||
from pydantic import Field
|
||||
from nonebot.params import Depends
|
||||
from nonebot.adapters import Event
|
||||
|
||||
def get_user_id(event: Event) -> str:
|
||||
return event.get_user_id()
|
||||
|
||||
async def _(user_id: int = Depends(get_user_id, validate=Field(gt=100))):
|
||||
print(user_id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### 类作为依赖
|
||||
|
||||
在前面的事例中,我们使用了函数作为子依赖。实际上,我们还可以使用类作为依赖。当我们在实例化一个类的时候,其实我们就在调用它,类本身也是一个可调用对象。例如:
|
||||
|
@@ -22,21 +22,22 @@ options:
|
||||
|
||||
## 驱动器类型
|
||||
|
||||
驱动器的类型有两种:
|
||||
驱动器类型大体上可以分为两种:
|
||||
|
||||
- `ForwardDriver`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。
|
||||
- `ReverseDriver`:即服务端型驱动器,多用于使用 WebHook,接收 WebSocket 客户端连接等情形。
|
||||
- `Forward`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。
|
||||
- `Reverse`:即服务端型驱动器,多用于使用 WebHook,接收 WebSocket 客户端连接等情形。
|
||||
|
||||
客户端型驱动器具有以下两种功能:
|
||||
客户端型驱动器可以分为以下两种:
|
||||
|
||||
1. 异步发送 HTTP 请求,自定义 `HTTP Method`、`URL`、`Header`、`Body`、`Cookie`、`Proxy`、`Timeout` 等。
|
||||
2. 异步建立 WebSocket 连接上下文,自定义 `WebSocket URL`、`Header`、`Cookie`、`Proxy`、`Timeout` 等。
|
||||
|
||||
服务端型驱动器通常为 ASGI 应用框架,具有以下功能:
|
||||
服务端型驱动器目前有:
|
||||
|
||||
1. 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
|
||||
2. 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
|
||||
3. 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。
|
||||
1. ASGI 应用框架,具有以下功能:
|
||||
- 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
|
||||
- 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
|
||||
- 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。
|
||||
|
||||
## 配置驱动器
|
||||
|
||||
@@ -79,7 +80,7 @@ DRIVER=~none
|
||||
|
||||
### FastAPI(默认)
|
||||
|
||||
**类型:**服务端驱动器
|
||||
**类型:**ASGI 服务端驱动器
|
||||
|
||||
> FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
|
||||
|
||||
@@ -185,7 +186,7 @@ nonebot.run(app="bot:app")
|
||||
|
||||
### Quart
|
||||
|
||||
**类型:**`ReverseDriver`
|
||||
**类型:**ASGI 服务端驱动器
|
||||
|
||||
> Quart is an asyncio reimplementation of the popular Flask microframework API.
|
||||
|
||||
@@ -249,7 +250,7 @@ nonebot.run(app="bot:app")
|
||||
|
||||
### HTTPX
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
**类型:**HTTP 客户端驱动器
|
||||
|
||||
:::warning 注意
|
||||
本驱动器仅支持 HTTP 请求,不支持 WebSocket 连接请求。
|
||||
@@ -263,7 +264,7 @@ DRIVER=~httpx
|
||||
|
||||
### websockets
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
**类型:**WebSocket 客户端驱动器
|
||||
|
||||
:::warning 注意
|
||||
本驱动器仅支持 WebSocket 连接请求,不支持 HTTP 请求。
|
||||
@@ -277,7 +278,7 @@ DRIVER=~websockets
|
||||
|
||||
### AIOHTTP
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
**类型:**HTTP/WebSocket 客户端驱动器
|
||||
|
||||
> [AIOHTTP](https://docs.aiohttp.org/): Asynchronous HTTP Client/Server for asyncio and Python.
|
||||
|
||||
|
@@ -333,4 +333,6 @@ matcher2 = group.on_message()
|
||||
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
|
||||
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
|
||||
|
||||
详情请阅读最佳实践中的 [命令解析拓展](../best-practice/alconna.md) 章节。
|
||||
该插件同时通过提供 `UniMessage` (通用消息模型) 实现了**跨平台接收和发送消息**的功能。
|
||||
|
||||
详情请阅读最佳实践中的 [命令解析拓展](../best-practice/alconna/README.mdx) 章节。
|
||||
|
@@ -12,7 +12,7 @@ options:
|
||||
|
||||
在[驱动器](./driver.md)一节中,我们了解了驱动器的两种类型。既然驱动器可以作为服务端运行,那么我们就可以向驱动器添加路由规则,从而实现自定义的 API 接口等功能。在添加路由规则时,我们需要注意驱动器的类型,详情可以参考[选择驱动器](./driver.md#配置驱动器)。
|
||||
|
||||
NoneBot 中,我们可以通过两种途径向驱动器添加路由规则:
|
||||
NoneBot 中,我们可以通过两种途径向 ASGI 驱动器添加路由规则:
|
||||
|
||||
1. 通过 NoneBot 的兼容层建立路由规则。
|
||||
2. 直接向 ASGI 应用添加路由规则。
|
||||
@@ -23,9 +23,9 @@ NoneBot 中,我们可以通过两种途径向驱动器添加路由规则:
|
||||
|
||||
```python {3}
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import ReverseDriver
|
||||
from nonebot.drivers import ASGIMixin
|
||||
|
||||
can_use = isinstance(get_driver(), ReverseDriver)
|
||||
can_use = isinstance(get_driver(), ASGIMixin)
|
||||
```
|
||||
|
||||
## 通过兼容层添加路由
|
||||
@@ -45,12 +45,12 @@ NoneBot 兼容层定义了两个数据类 `HTTPServerSetup` 和 `WebSocketServer
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import URL, Request, Response, HTTPServerSetup
|
||||
from nonebot.drivers import URL, Request, Response, ASGIMixin, HTTPServerSetup
|
||||
|
||||
async def hello(request: Request) -> Response:
|
||||
return Response(200, content="Hello, world!")
|
||||
|
||||
if isinstance((driver := get_driver()), ReverseDriver):
|
||||
if isinstance((driver := get_driver()), ASGIMixin):
|
||||
driver.setup_http_server(
|
||||
HTTPServerSetup(
|
||||
path=URL("/hello"),
|
||||
@@ -75,7 +75,7 @@ if isinstance((driver := get_driver()), ReverseDriver):
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import URL, WebSocket, WebSocketServerSetup
|
||||
from nonebot.drivers import URL, ASGIMixin, WebSocket, WebSocketServerSetup
|
||||
|
||||
async def ws_handler(ws: WebSocket):
|
||||
await ws.accept()
|
||||
@@ -91,7 +91,7 @@ async def ws_handler(ws: WebSocket):
|
||||
await websocket.close()
|
||||
# do some cleanup
|
||||
|
||||
if isinstance((driver := get_driver()), ReverseDriver):
|
||||
if isinstance((driver := get_driver()), ASGIMixin):
|
||||
driver.setup_websocket_server(
|
||||
WebSocketServerSetup(
|
||||
path=URL("/ws"),
|
||||
|
@@ -482,7 +482,7 @@ nonebot.init(superusers={"123123123"})
|
||||
- **类型**: `set[str]`
|
||||
- **默认值**: `set()`
|
||||
|
||||
机器人昵称,通常协议适配器会根据用户是否 @user 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。
|
||||
机器人昵称,通常协议适配器会根据用户是否 @bot 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
@@ -374,6 +374,14 @@ async def _(matcher: Matcher):
|
||||
|
||||
`get_last_receive` 操作接受一个可选的 default 参数。当事件不存在时,将返回 default 或 `None`。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@matcher.handle()
|
||||
async def _(matcher: Matcher):
|
||||
event = matcher.get_last_receive(default=None)
|
||||
```
|
||||
|
||||
### set_receive
|
||||
|
||||
设置 / 覆盖一个 `receive` 接收的事件。
|
||||
|
@@ -1,288 +0,0 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
description: Alconna 命令解析拓展
|
||||
---
|
||||
|
||||
# Alconna 命令解析
|
||||
|
||||
[`nonebot-plugin-alconna`](https://github.com/nonebot/plugin-alconna) 是一类提供了拓展响应规则的插件。
|
||||
该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
|
||||
是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||
|
||||
特点包括:
|
||||
|
||||
- 高效
|
||||
- 直观的命令组件创建方式
|
||||
- 强大的类型解析与类型转换功能
|
||||
- 自定义的帮助信息格式
|
||||
- 多语言支持
|
||||
- 易用的快捷命令创建与使用
|
||||
- 可创建命令补全会话, 以实现多轮连续的补全提示
|
||||
- 可嵌套的多级子命令
|
||||
- 正则匹配支持
|
||||
|
||||
该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。
|
||||
|
||||
同时,基于 [Annotated 支持](https://github.com/nonebot/nonebot2/pull/1832), 添加了两类注解 `AlcMatches` 与 `AlcResult`
|
||||
|
||||
该插件还可以通过 `handle(parameterless)` 来控制一个具体的响应函数是否在不满足条件时跳过响应:
|
||||
|
||||
- `pip.handle([Check(assign("add.name", "nb"))])` 表示仅在命令为 `role-group add` 并且 name 为 `nb` 时响应
|
||||
- `pip.handle([Check(assign("list"))])` 表示仅在命令为 `role-group list` 时响应
|
||||
- `pip.handle([Check(assign("add"))])` 表示仅在命令为 `role-group add` 时响应
|
||||
|
||||
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
|
||||
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
|
||||
|
||||
## 安装插件
|
||||
|
||||
在使用前请先安装 `nonebot-plugin-alconna` 插件至项目环境中,可参考[获取商店插件](../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如:
|
||||
|
||||
在**项目目录**下执行以下命令:
|
||||
|
||||
```shell
|
||||
nb plugin install nonebot-plugin-alconna
|
||||
```
|
||||
|
||||
或
|
||||
|
||||
```shell
|
||||
pip install nonebot-plugin-alconna
|
||||
```
|
||||
|
||||
## 使用插件
|
||||
|
||||
以下为一个简单的使用示例:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.adapters import At
|
||||
from nonebot.adapters.onebot.v12 import Message
|
||||
from nonebot_plugin_alconna.adapters.onebot12 import Image
|
||||
from nonebot_plugin_alconna import AlconnaMatches, on_alconna
|
||||
from nonebot.adapters.onebot.v12 import MessageSegment as Ob12MS
|
||||
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
|
||||
|
||||
alc = Alconna(
|
||||
["/", "!"],
|
||||
"role-group",
|
||||
Subcommand(
|
||||
"add",
|
||||
Args["name", str],
|
||||
Option("member", Args["target", MultiVar(At)]),
|
||||
),
|
||||
Option("list"),
|
||||
)
|
||||
rg = on_alconna(alc, auto_send_output=True)
|
||||
|
||||
|
||||
@rg.handle()
|
||||
async def _(result: Arparma = AlconnaMatches()):
|
||||
if result.find("list"):
|
||||
img = await gen_role_group_list_image()
|
||||
await rg.finish(Message([Image(img)]))
|
||||
if result.find("add"):
|
||||
group = await create_role_group(result["add.name"])
|
||||
if result.find("add.member"):
|
||||
ats: tuple[Ob12MS, ...] = result["add.member.target"]
|
||||
group.extend(member.data["user_id"] for member in ats)
|
||||
await rg.finish("添加成功")
|
||||
```
|
||||
|
||||
### 导入插件
|
||||
|
||||
由于 `nonebot-plugin-alconna` 作为插件,因此需要在使用前对其进行**加载**并**导入**其中的 `on_alconna` 来使用命令拓展。使用 `require` 方法可轻松完成这一过程,可参考 [跨插件访问](../advanced/requiring.md) 一节进行了解。
|
||||
|
||||
```python
|
||||
from nonebot import require
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import on_alconna
|
||||
```
|
||||
|
||||
### 命令编写
|
||||
|
||||
我们可以看到主要的两大组件:`Option` 与 `Subcommand`。
|
||||
|
||||
`Option` 可以传入一组别名,如 `Option("--foo|-F|--FOO|-f")` 或 `Option("--foo", alias=["-F"]`
|
||||
|
||||
`Subcommand` 则可以传入自己的 `Option` 与 `Subcommand`:
|
||||
|
||||
他们拥有如下共同参数:
|
||||
|
||||
- `help_text`: 传入该组件的帮助信息
|
||||
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
|
||||
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
|
||||
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
|
||||
|
||||
然后是 `Args` 与 `MultiVar`,他们是用于解析参数的组件。
|
||||
|
||||
`Args` 是参数解析的基础组件,构造方法形如 `Args["foo", str]["bar", int]["baz", bool, False]`,
|
||||
与函数签名类似,但是允许含有默认值的参数在前;同时支持 keyword-only 参数不依照构造顺序传入 (但是仍需要在非 keyword-only 参数之后)。
|
||||
|
||||
`MultiVar` 则是一个特殊的标注,用于告知解析器该参数可以接受多个值,其构造方法形如 `MultiVar(str)`。
|
||||
同样的还有 `KeyWordVar`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数。
|
||||
|
||||
:::tip
|
||||
`MultiVar` 与 `KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,其构造方法形如 `MultiVar(KeyWordVar(str))`
|
||||
|
||||
`MultiVar` 与 `KeyWordVar` 也可以传入 `default` 参数,用于指定默认值。
|
||||
|
||||
`MultiVar` 不能在 `KeyWordVar` 之后传入。
|
||||
:::
|
||||
|
||||
### 参数标注
|
||||
|
||||
`Args` 的参数类型表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例。
|
||||
|
||||
```python
|
||||
from arclet.alconna import Args
|
||||
from nepattern import BasePattern
|
||||
|
||||
# 表示 foo 参数需要匹配一个 @number 样式的字符串
|
||||
args = Args["foo", BasePattern("@\d+")]
|
||||
```
|
||||
|
||||
示例中传入的 `str` 是因为 `str` 已经注册在了 `nepattern.global_patterns` 中,因此会替换为 `nepattern.global_patterns[str]`。
|
||||
|
||||
默认支持的类型有:
|
||||
|
||||
- `str`: 匹配任意字符串
|
||||
- `int`: 匹配整数
|
||||
- `float`: 匹配浮点数
|
||||
- `bool`: 匹配 `True` 与 `False` 以及他们小写形式
|
||||
- `hex`: 匹配 `0x` 开头的十六进制字符串
|
||||
- `url`: 匹配网址
|
||||
- `email`: 匹配 `xxxx@xxx` 的字符串
|
||||
- `ipv4`: 匹配 `xxx.xxx.xxx.xxx` 的字符串
|
||||
- `list`: 匹配类似 `["foo","bar","baz"]` 的字符串
|
||||
- `dict`: 匹配类似 `{"foo":"bar","baz":"qux"}` 的字符串
|
||||
- `datetime`: 传入一个 `datetime` 支持的格式字符串,或时间戳
|
||||
- `Any`: 匹配任意类型
|
||||
- `AnyString`: 匹配任意类型,转为 `str`
|
||||
- `Number`: 匹配 `int` 与 `float`,转为 `int`
|
||||
|
||||
同时可以使用 typing 中的类型:
|
||||
|
||||
- `Literal[X]`: 匹配其中的任意一个值
|
||||
- `Union[X, Y]`: 匹配其中的任意一个类型
|
||||
- `Optional[xxx]`: 会自动将默认值设为 `None`,并在解析失败时使用默认值
|
||||
- `List[X]`: 匹配一个列表,其中的元素为 `X` 类型
|
||||
- `Dict[X, Y]`: 匹配一个字典,其中的 key 为 `X` 类型,value 为 `Y` 类型
|
||||
- ...
|
||||
|
||||
:::tip
|
||||
几类特殊的传入标记:
|
||||
|
||||
- `"foo"`: 匹配字符串 "foo" (若没有某个 `BasePattern` 与之关联)
|
||||
- `RawStr("foo")`: 匹配字符串 "foo" (不会被 `BasePattern` 替换)
|
||||
- `"foo|bar|baz"`: 匹配 "foo" 或 "bar" 或 "baz"
|
||||
- `[foo, bar, Baz, ...]`: 匹配其中的任意一个值或类型
|
||||
- `Callable[[X], Y]`: 匹配一个参数为 `X` 类型的值,并返回通过该函数调用得到的 `Y` 类型的值
|
||||
- `"re:xxx"`: 匹配一个正则表达式 `xxx`,会返回 Match[0]
|
||||
- `"rep:xxx"`: 匹配一个正则表达式 `xxx`,会返回 `re.Match` 对象
|
||||
- `{foo: bar, baz: qux}`: 匹配字典中的任意一个键, 并返回对应的值 (特殊的键 ... 会匹配任意的值)
|
||||
- ...
|
||||
|
||||
:::
|
||||
|
||||
### 消息段标注
|
||||
|
||||
示例中使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
|
||||
|
||||
消息段标注会匹配特定的 `MessageSegment`:
|
||||
|
||||
```python
|
||||
...
|
||||
ats: tuple[Ob12MS, ...] = result["add.member.target"]
|
||||
group.extend(member.data["user_id"] for member in ats)
|
||||
```
|
||||
|
||||
:::tip
|
||||
通用标注与适配器标注的区别在于,通用标注会匹配多个适配器中相似类型的消息段。
|
||||
|
||||
通用标注返回的是 `nonebot_plugin_alconna.adapters` 中定义的 `Segment` 模型:
|
||||
|
||||
```python
|
||||
class Segment:
|
||||
"""基类标注"""
|
||||
origin: MessageSegment
|
||||
|
||||
class At(Segment):
|
||||
"""At对象, 表示一类提醒某用户的元素"""
|
||||
target: str
|
||||
|
||||
class Emoji(Segment):
|
||||
"""Emoji对象, 表示一类表情元素"""
|
||||
id: str
|
||||
name: Optional[str]
|
||||
|
||||
class Media(Segment):
|
||||
url: Optional[str]
|
||||
id: Optional[str]
|
||||
|
||||
class Image(Media):
|
||||
"""Image对象, 表示一类图片元素"""
|
||||
|
||||
class Audio(Media):
|
||||
"""Audio对象, 表示一类音频元素"""
|
||||
|
||||
class Voice(Media):
|
||||
"""Voice对象, 表示一类语音元素"""
|
||||
|
||||
class Video(Media):
|
||||
"""Video对象, 表示一类视频元素"""
|
||||
|
||||
class File(Segment):
|
||||
"""File对象, 表示一类文件元素"""
|
||||
id: str
|
||||
name: Optional[str] = field(default=None)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 响应器使用
|
||||
|
||||
`on_alconna` 的所有参数如下:
|
||||
|
||||
- `command: Alconna | str`: Alconna 命令
|
||||
- `skip_for_unmatch: bool = True`: 是否在命令不匹配时跳过该响应
|
||||
- `auto_send_output: bool = False`: 是否自动发送输出信息并跳过响应
|
||||
- `output_converter: TConvert | None = None`: 输出信息字符串转换为消息序列方法
|
||||
- `aliases: set[str | tuple[str, ...]] | None = None`: 命令别名, 作用类似于 `on_command` 中的 aliases
|
||||
- `comp_config: CompConfig | None = None`: 补全会话配置, 不传入则不启用补全会话
|
||||
|
||||
`AlconnaMatches` 是一个依赖注入函数,可注入 `Alconna` 命令解析结果。
|
||||
|
||||
### 配置项
|
||||
|
||||
#### alconna_auto_send_output
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
"是否全局启用输出信息自动发送,不启用则会在触特殊内置选项后仍然将解析结果传递至响应器。
|
||||
|
||||
#### alconna_use_command_start
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否读取 Nonebot 的配置项 `COMMAND_START` 来作为全局的 Alconna 命令前缀
|
||||
|
||||
#### alconna_auto_completion
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否全局启用命令自动补全,启用后会在参数缺失或触发 `--comp` 选项时自自动启用交互式补全。
|
||||
|
||||
## 文档参考
|
||||
|
||||
插件文档: [📦 这里](https://github.com/nonebot/plugin-alconna/blob/master/docs.md)
|
||||
|
||||
官方文档: [👉 指路](https://arclet.top/)
|
||||
|
||||
QQ 交流群: [🔗 链接](https://jq.qq.com/?_wv=1027&k=PUPOnCSH)
|
||||
|
||||
友链: [📚 文档](https://graiax.cn/guide/message_parser/alconna.html)
|
126
website/docs/best-practice/alconna/README.mdx
Normal file
126
website/docs/best-practice/alconna/README.mdx
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: Alconna 命令解析拓展
|
||||
|
||||
slug: /best-practice/alconna/
|
||||
---
|
||||
|
||||
# Alconna 插件
|
||||
|
||||
[`nonebot-plugin-alconna`](https://github.com/nonebot/plugin-alconna) 是一类提供了拓展响应规则的插件。
|
||||
该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
|
||||
是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||
|
||||
该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。
|
||||
|
||||
同时,基于 [Annotated 支持](https://github.com/nonebot/nonebot2/pull/1832), 添加了两类注解 `AlcMatches` 与 `AlcResult`
|
||||
|
||||
该插件声明了一个 `Matcher` 的子类 `AlconnaMatcher`,并在 `AlconnaMatcher` 中添加了一些新的方法,例如:
|
||||
|
||||
- `assign`:基于 `Alconna` 解析结果,执行满足目标路径的处理函数
|
||||
- `dispatch`:类似 `CommandGroup`,对目标路径创建一个新的 `AlconnaMatcher`,并将解析结果分配给该 `AlconnaMatcher`
|
||||
- `got_path`:类似 `got`,但是可以指定目标路径,并且能够验证解析结果是否可用
|
||||
- ...
|
||||
|
||||
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
|
||||
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
|
||||
|
||||
该插件同时通过提供 `UniMessage` (通用消息模型) 实现了**跨平台接收和发送消息**的功能。
|
||||
|
||||
## 安装插件
|
||||
|
||||
在使用前请先安装 `nonebot-plugin-alconna` 插件至项目环境中,可参考[获取商店插件](../../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如:
|
||||
|
||||
在**项目目录**下执行以下命令:
|
||||
|
||||
```shell
|
||||
nb plugin install nonebot-plugin-alconna
|
||||
```
|
||||
|
||||
或
|
||||
|
||||
```shell
|
||||
pip install nonebot-plugin-alconna
|
||||
```
|
||||
|
||||
## 导入插件
|
||||
|
||||
由于 `nonebot-plugin-alconna` 作为插件,因此需要在使用前对其进行**加载**并**导入**其中的 `on_alconna` 来使用命令拓展。使用 `require` 方法可轻松完成这一过程,可参考 [跨插件访问](../../advanced/requiring.md) 一节进行了解。
|
||||
|
||||
```python
|
||||
from nonebot import require
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import on_alconna
|
||||
```
|
||||
|
||||
## 使用插件
|
||||
|
||||
在前面的[深入指南](../../appendices/session-control.mdx)中,我们已经得到了一个天气插件。
|
||||
现在我们将使用 `Alconna` 来改写这个插件。
|
||||
|
||||
<details>
|
||||
<summary>插件示例</summary>
|
||||
|
||||
```python title=weather/__init__.py
|
||||
from nonebot import on_command
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import CommandArg, ArgPlainText
|
||||
|
||||
weather = on_command("天气", rule=to_me(), aliases={"weather", "天气预报"})
|
||||
|
||||
@weather.handle()
|
||||
async def handle_function(matcher: Matcher, args: Message = CommandArg()):
|
||||
if args.extract_plain_text():
|
||||
matcher.set_arg("location", args)
|
||||
|
||||
@weather.got("location", prompt="请输入地名")
|
||||
async def got_location(location: str = ArgPlainText()):
|
||||
if location not in ["北京", "上海", "广州", "深圳"]:
|
||||
await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
|
||||
await weather.finish(f"今天{location}的天气是...")
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
```python {5-10,14-16,18-19}
|
||||
from nonebot.rule import to_me
|
||||
from arclet.alconna import Alconna, Args
|
||||
from nonebot_plugin_alconna import Match, AlconnaMatcher, on_alconna
|
||||
|
||||
weather = on_alconna(
|
||||
Alconna("天气", Args["location?", str]),
|
||||
rule=to_me(),
|
||||
)
|
||||
weather.shortcut("weather", {"command": "天气"})
|
||||
weather.shortcut("天气预报", {"command": "天气"})
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def handle_function(matcher: AlconnaMatcher, location: Match[str]):
|
||||
if location.available:
|
||||
matcher.set_path_arg("location", location.result)
|
||||
|
||||
@weather.got_path("location", prompt="请输入地名")
|
||||
async def got_location(location: str):
|
||||
if location not in ["北京", "上海", "广州", "深圳"]:
|
||||
await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
|
||||
await weather.finish(f"今天{location}的天气是...")
|
||||
```
|
||||
|
||||
在上面的代码中,我们使用 `Alconna` 来解析命令,`on_alconna` 用来创建响应器,使用 `Match` 来获取解析结果。
|
||||
command
|
||||
关于更多 `Alconna` 的使用方法,可参考 [Alconna 文档](https://arclet.top/docs/tutorial/alconna),
|
||||
或阅读 [Alconna 基本介绍](./command.md) 一节。
|
||||
|
||||
关于更多 `on_alconna` 的使用方法,可参考 [插件文档](https://github.com/nonebot/plugin-alconna/blob/master/docs.md),
|
||||
或阅读 [响应规则的使用](./matcher.md) 一节。
|
||||
|
||||
## 交流与反馈
|
||||
|
||||
QQ 交流群: [🔗 链接](https://jq.qq.com/?_wv=1027&k=PUPOnCSH)
|
||||
|
||||
友链: [📚 文档](https://graiax.cn/guide/message_parser/alconna.html)
|
4
website/docs/best-practice/alconna/_category_.json
Normal file
4
website/docs/best-practice/alconna/_category_.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Alconna 命令解析拓展",
|
||||
"position": 6
|
||||
}
|
484
website/docs/best-practice/alconna/command.md
Normal file
484
website/docs/best-practice/alconna/command.md
Normal file
@@ -0,0 +1,484 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: Alconna 基本介绍
|
||||
---
|
||||
|
||||
# Alconna 命令解析
|
||||
|
||||
[Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
|
||||
是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||
|
||||
特点包括:
|
||||
|
||||
- 高效
|
||||
- 直观的命令组件创建方式
|
||||
- 强大的类型解析与类型转换功能
|
||||
- 自定义的帮助信息格式
|
||||
- 多语言支持
|
||||
- 易用的快捷命令创建与使用
|
||||
- 可创建命令补全会话, 以实现多轮连续的补全提示
|
||||
- 可嵌套的多级子命令
|
||||
- 正则匹配支持
|
||||
|
||||
## 命令编写
|
||||
|
||||
### 命令头
|
||||
|
||||
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 `!help` 中的 `!` 与 `help`。
|
||||
|
||||
在 Alconna 中,你可以传入多种类型的命令头,例如:
|
||||
|
||||
| 前缀 | 命令名 | 匹配内容 | 说明 |
|
||||
| :--------------------------: | :--------: | :---------------------------------------------------------: | :--------------: |
|
||||
| - | "foo" | `"foo"` | 无前缀的纯文字头 |
|
||||
| - | 123 | `123` | 无前缀的元素头 |
|
||||
| - | "re:\d{2}" | `"32"` | 无前缀的正则头 |
|
||||
| - | int | `123` 或 `"456"` | 无前缀的类型头 |
|
||||
| [int, bool] | - | `True` 或 `123` | 无名的元素类头 |
|
||||
| ["foo", "bar"] | - | `"foo"` 或 `"bar"` | 无名的纯文字头 |
|
||||
| ["foo", "bar"] | "baz" | `"foobaz"` 或 `"barbaz"` | 纯文字头 |
|
||||
| [int, bool] | "foo" | `[123, "foo"]` 或 `[False, "foo"]` | 类型头 |
|
||||
| [123, 4567] | "foo" | `[123, "foo"]` 或 `[4567, "foo"]` | 元素头 |
|
||||
| [nepattern.NUMBER] | "bar" | `[123, "bar"]` 或 `[123.456, "bar"]` | 表达式头 |
|
||||
| [123, "foo"] | "bar" | `[123, "bar"]` 或 `"foobar"` 或 `["foo", "bar"]` | 混合头 |
|
||||
| [(int, "foo"), (456, "bar")] | "baz" | `[123, "foobaz"]` 或 `[456, "foobaz"]` 或 `[456, "barbaz"]` | 对头 |
|
||||
|
||||
其中
|
||||
|
||||
- 元素头:只会匹配对应的值,例如 `[123, 456]` 只会匹配 `123` 或 `456`,不会匹配 `789`。
|
||||
- 纯文字头:只会匹配对应的字符串,例如 `["foo", "bar"]` 只会匹配 `"foo"` 或 `"bar"`,不会匹配 `"baz"`。
|
||||
- 正则头:`re:xxx` 会将 `xxx` 转为正则表达式,然后匹配对应的字符串,例如 `re:\d{2}` 只会匹配 `"12"` 或 `"34"`,不会匹配 `"foo"`。
|
||||
**正则只在命令名上生效,命令前缀中的正则会被转义**。
|
||||
- 类型头:只会匹配对应的类型,例如 `[int, bool]` 只会匹配 `123` 或 `True`,不会匹配 `"foo"`。
|
||||
- 无前缀的类型头:此时会将传入的值尝试转为 BasePattern,例如 `int` 会转为 `nepattern.INTEGER`。此时命令头会匹配对应的类型,
|
||||
例如 `int` 会匹配 `123` 或 `"456"`,但不会匹配 `"foo"`。同时,Alconna 会将命令头匹配到的值转为对应的类型,例如 `int` 会将 `"123"` 转为 `123`。
|
||||
- 表达式头:只会匹配对应的表达式,例如 `[nepattern.NUMBER]` 只会匹配 `123` 或 `123.456`,不会匹配 `"foo"`。
|
||||
- 混合头:
|
||||
|
||||
除了通过传入 `re:xxx` 来使用正则表达式外,Alconna 还提供了一种更加简洁的方式来使用正则表达式,那就是 Bracket Header。
|
||||
|
||||
```python
|
||||
from alconna import Alconna
|
||||
|
||||
alc = Alconna(".rd{roll:int}")
|
||||
assert alc.parse(".rd123").header["roll"] == 123
|
||||
```
|
||||
|
||||
Bracket Header 类似 python 里的 f-string 写法,通过 "{}" 声明匹配类型
|
||||
|
||||
"{}" 中的内容为 "name:type or pat":
|
||||
|
||||
- "{}", "{:}": 占位符,等价于 "(.+)"
|
||||
- "{foo}": 等价于 "(?P<foo>.+)"
|
||||
- "{:\d+}": 等价于 "(\d+)"
|
||||
- "{foo:int}": 等价于 "(?P<foo>\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
|
||||
|
||||
### 组件
|
||||
|
||||
我们可以看到主要的两大组件:`Option` 与 `Subcommand`。
|
||||
|
||||
`Option` 可以传入一组 `alias`,如 `Option("--foo|-F|--FOO|-f")` 或 `Option("--foo", alias=["-F"])`
|
||||
|
||||
传入别名后,Option 会选择其中长度最长的作为选项名称。若传入为 "--foo|-f",则命令名称为 "--foo"。
|
||||
|
||||
`Subcommand` 则可以传入自己的 **Option** 与 **Subcommand**。
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Option, Subcommand
|
||||
|
||||
alc = Alconna(
|
||||
"command_name",
|
||||
Option("opt1"),
|
||||
Option("--opt2"),
|
||||
Subcommand(
|
||||
"sub1",
|
||||
Option("sub1_opt1"),
|
||||
Option("-SO2"),
|
||||
Subcommand(
|
||||
"sub1_sub1"
|
||||
)
|
||||
),
|
||||
Subcommand(
|
||||
"sub2"
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
他们拥有如下共同参数:
|
||||
|
||||
- `help_text`: 传入该组件的帮助信息
|
||||
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
|
||||
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
|
||||
对于命令 `test foo bar baz qux <a:int>` 来讲,因为`foo bar baz` 仅需要判断是否相等, 所以可以这么编写:
|
||||
|
||||
```python
|
||||
Alconna("test", Option("qux", Args.a[int], requires=["foo", "bar", "baz"]))
|
||||
```
|
||||
|
||||
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
|
||||
|
||||
特别的,使用 `OptionResult` 或 `SubcomanndResult` 可以设置包括参数字典在内的默认值:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Option, OptionResult
|
||||
|
||||
opt1 = Option("--foo", default=False)
|
||||
opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
||||
```
|
||||
|
||||
### 选项操作
|
||||
|
||||
`Option` 可以特别设置传入一类 `Action`,作为解析操作
|
||||
|
||||
`Action` 分为三类:
|
||||
|
||||
- `store`: 无 Args 时, 仅存储一个值, 默认为 Ellipsis; 有 Args 时, 后续的解析结果会覆盖之前的值
|
||||
- `append`: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis; 有 Args 时, 每个解析结果会追加到列表中
|
||||
|
||||
当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性
|
||||
|
||||
- `count`: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同
|
||||
|
||||
当存在默认值并且不为数字时, 会自动将默认值变成 1, 以保证计数器的正确性。
|
||||
|
||||
`Alconna` 提供了预制的几类 `action`:
|
||||
|
||||
- `store`,`store_value`,`store_true`,`store_false`
|
||||
- `append`,`append_value`
|
||||
- `count`
|
||||
|
||||
### 参数声明
|
||||
|
||||
`Args` 是用于声明命令参数的组件。
|
||||
|
||||
`Args` 是参数解析的基础组件,构造方法形如 `Args["foo", str]["bar", int]["baz", bool, False]`,
|
||||
与函数签名类似,但是允许含有默认值的参数在前;同时支持 keyword-only 参数不依照构造顺序传入 (但是仍需要在非 keyword-only 参数之后)。
|
||||
|
||||
`Args` 中的 `name` 是用以标记解析出来的参数并存放于 **Arparma** 中,以方便用户调用。
|
||||
|
||||
其有三种为 Args 注解的标识符: `?`、`/` 与 `!`。标识符与 key 之间建议以 `;` 分隔:
|
||||
|
||||
- `!` 标识符表示该处传入的参数应不是规定的类型,或不在指定的值中。
|
||||
- `?` 标识符表示该参数为可选参数,会在无参数匹配时跳过。
|
||||
- `/` 标识符表示该参数的类型注解需要隐藏。
|
||||
|
||||
另外,对于参数的注释也可以标记在 `name` 中,其与 name 或者标识符 以 `#` 分割:
|
||||
|
||||
`foo#这是注释;?` 或 `foo?#这是注释`
|
||||
|
||||
:::tip
|
||||
`Args` 中的 `name` 在实际命令中并不需要传入(keyword 参数除外):
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args
|
||||
|
||||
alc = Alconna("test", Args["foo", str])
|
||||
alc.parse("test --foo abc") # 错误
|
||||
alc.parse("test abc") # 之前
|
||||
```
|
||||
|
||||
若需要 `test --foo abc`,你应该使用 `Option`:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args, Option
|
||||
|
||||
alc = Alconna("test", Option("--foo", Args["foo", str]))
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
`Args` 的参数类型表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例。
|
||||
|
||||
```python
|
||||
from arclet.alconna import Args
|
||||
from nepattern import BasePattern
|
||||
|
||||
# 表示 foo 参数需要匹配一个 @number 样式的字符串
|
||||
args = Args["foo", BasePattern("@\d+")]
|
||||
```
|
||||
|
||||
示例中传入的 `str` 是因为 `str` 已经注册在了 `nepattern.global_patterns` 中,因此会替换为 `nepattern.global_patterns[str]`。
|
||||
|
||||
默认支持的类型有:
|
||||
|
||||
- `str`: 匹配任意字符串
|
||||
- `int`: 匹配整数
|
||||
- `float`: 匹配浮点数
|
||||
- `bool`: 匹配 `True` 与 `False` 以及他们小写形式
|
||||
- `hex`: 匹配 `0x` 开头的十六进制字符串
|
||||
- `url`: 匹配网址
|
||||
- `email`: 匹配 `xxxx@xxx` 的字符串
|
||||
- `ipv4`: 匹配 `xxx.xxx.xxx.xxx` 的字符串
|
||||
- `list`: 匹配类似 `["foo","bar","baz"]` 的字符串
|
||||
- `dict`: 匹配类似 `{"foo":"bar","baz":"qux"}` 的字符串
|
||||
- `datetime`: 传入一个 `datetime` 支持的格式字符串,或时间戳
|
||||
- `Any`: 匹配任意类型
|
||||
- `AnyString`: 匹配任意类型,转为 `str`
|
||||
- `Number`: 匹配 `int` 与 `float`,转为 `int`
|
||||
|
||||
同时可以使用 typing 中的类型:
|
||||
|
||||
- `Literal[X]`: 匹配其中的任意一个值
|
||||
- `Union[X, Y]`: 匹配其中的任意一个类型
|
||||
- `Optional[xxx]`: 会自动将默认值设为 `None`,并在解析失败时使用默认值
|
||||
- `List[X]`: 匹配一个列表,其中的元素为 `X` 类型
|
||||
- `Dict[X, Y]`: 匹配一个字典,其中的 key 为 `X` 类型,value 为 `Y` 类型
|
||||
- ...
|
||||
|
||||
:::tip
|
||||
几类特殊的传入标记:
|
||||
|
||||
- `"foo"`: 匹配字符串 "foo" (若没有某个 `BasePattern` 与之关联)
|
||||
- `RawStr("foo")`: 匹配字符串 "foo" (不会被 `BasePattern` 替换)
|
||||
- `"foo|bar|baz"`: 匹配 "foo" 或 "bar" 或 "baz"
|
||||
- `[foo, bar, Baz, ...]`: 匹配其中的任意一个值或类型
|
||||
- `Callable[[X], Y]`: 匹配一个参数为 `X` 类型的值,并返回通过该函数调用得到的 `Y` 类型的值
|
||||
- `"re:xxx"`: 匹配一个正则表达式 `xxx`,会返回 Match[0]
|
||||
- `"rep:xxx"`: 匹配一个正则表达式 `xxx`,会返回 `re.Match` 对象
|
||||
- `{foo: bar, baz: qux}`: 匹配字典中的任意一个键, 并返回对应的值 (特殊的键 ... 会匹配任意的值)
|
||||
- ...
|
||||
|
||||
:::
|
||||
|
||||
`MultiVar` 则是一个特殊的标注,用于告知解析器该参数可以接受多个值,其构造方法形如 `MultiVar(str)`。
|
||||
同样的还有 `KeyWordVar`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数。
|
||||
|
||||
:::tip
|
||||
`MultiVar` 与 `KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,其构造方法形如 `MultiVar(KeyWordVar(str))`
|
||||
|
||||
`MultiVar` 与 `KeyWordVar` 也可以传入 `default` 参数,用于指定默认值。
|
||||
|
||||
`MultiVar` 不能在 `KeyWordVar` 之后传入。
|
||||
:::
|
||||
|
||||
### 紧凑命令
|
||||
|
||||
`Alconna`, `Option` 与 `Subcommand` 可以设置 `compact=True` 使得解析命令时允许名称与后随参数之间没有分隔:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Option, CommandMeta, Args
|
||||
|
||||
alc = Alconna("test", Args["foo", int], Option("BAR", Args["baz", str], compact=True), meta=CommandMeta(compact=True))
|
||||
|
||||
assert alc.parse("test123 BARabc").matched
|
||||
```
|
||||
|
||||
这使得我们可以实现如下命令:
|
||||
|
||||
```python
|
||||
>>> from arclet.alconna import Alconna, Option, Args, append
|
||||
>>> alc = Alconna("gcc", Option("--flag|-F", Args["content", str], action=append, compact=True))
|
||||
>>> alc.parse("gcc -Fabc -Fdef -Fxyz").query[list[str]]("flag.content")
|
||||
['abc', 'def', 'xyz']
|
||||
```
|
||||
|
||||
当 `Option` 的 `action` 为 `count` 时,其自动支持 `compact` 特性:
|
||||
|
||||
```python
|
||||
>>> from arclet.alconna import Alconna, Option, Args, count
|
||||
>>> alc = Alconna("pp", Option("--verbose|-v", action=count, default=0))
|
||||
>>> alc.parse("pp -vvv").query[int]("verbose.value")
|
||||
3
|
||||
```
|
||||
|
||||
## 命令特性
|
||||
|
||||
### 配置
|
||||
|
||||
`arclet.alconna.Namespace` 表示某一命名空间下的默认配置:
|
||||
|
||||
```python
|
||||
from arclet.alconna import config, namespace, Namespace
|
||||
from arclet.alconna.tools import ShellTextFormatter
|
||||
|
||||
|
||||
np = Namespace("foo", prefixes=["/"]) # 创建 Namespace 对象,并进行初始配置
|
||||
|
||||
with namespace("bar") as np1:
|
||||
np1.prefixes = ["!"] # 以上下文管理器方式配置命名空间,此时配置会自动注入上下文内创建的命令
|
||||
np1.formatter_type = ShellTextFormatter # 设置此命名空间下的命令的 formatter 默认为 ShellTextFormatter
|
||||
np1.builtin_option_name["help"] = {"帮助", "-h"} # 设置此命名空间下的命令的帮助选项名称
|
||||
|
||||
config.namespaces["foo"] = np # 将命名空间挂载到 config 上
|
||||
```
|
||||
|
||||
同时也提供了默认命名空间配置与修改方法:
|
||||
|
||||
```python
|
||||
from arclet.alconna import config, namespace, Namespace
|
||||
|
||||
|
||||
config.default_namespace.prefixes = [...] # 直接修改默认配置
|
||||
|
||||
np = Namespace("xxx", prefixes=[...])
|
||||
config.default_namespace = np # 更换默认的命名空间
|
||||
|
||||
with namespace(config.default_namespace.name) as np:
|
||||
np.prefixes = [...]
|
||||
```
|
||||
|
||||
### 半自动补全
|
||||
|
||||
半自动补全为用户提供了推荐后续输入的功能。
|
||||
|
||||
补全默认通过 `--comp` 或 `-cp` 或 `?` 触发:(命名空间配置可修改名称)
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args, Option
|
||||
|
||||
alc = Alconna("test", Args["abc", int]) + Option("foo") + Option("bar")
|
||||
alc.parse("test ?")
|
||||
|
||||
'''
|
||||
output
|
||||
|
||||
以下是建议的输入:
|
||||
* <abc: int>
|
||||
* --help
|
||||
* -h
|
||||
* -sct
|
||||
* --shortcut
|
||||
* foo
|
||||
* bar
|
||||
'''
|
||||
```
|
||||
|
||||
### 快捷指令
|
||||
|
||||
快捷指令顾名思义,可以为基础指令创建便捷的触发方式
|
||||
|
||||
一般情况下你可以通过 `Alconna.shortcut` 进行快捷指令操作 (创建,删除);
|
||||
|
||||
```python
|
||||
>>> from arclet.alconna import Alconna, Args
|
||||
>>> alc = Alconna("setu", Args["count", int])
|
||||
>>> alc.shortcut("涩图(\d+)张", {"args": ["{0}"]})
|
||||
'Alconna::setu 的快捷指令: "涩图(\\d+)张" 添加成功'
|
||||
>>> alc.parse("涩图3张").query("count")
|
||||
3
|
||||
```
|
||||
|
||||
`shortcut` 的第一个参数为快捷指令名称,第二个参数为 `ShortcutArgs`,作为快捷指令的配置
|
||||
|
||||
```python
|
||||
class ShortcutArgs(TypedDict):
|
||||
"""快捷指令参数"""
|
||||
|
||||
command: NotRequired[DataCollection[Any]]
|
||||
"""快捷指令的命令"""
|
||||
args: NotRequired[list[Any]]
|
||||
"""快捷指令的附带参数"""
|
||||
fuzzy: NotRequired[bool]
|
||||
"""是否允许命令后随参数"""
|
||||
prefix: NotRequired[bool]
|
||||
"""是否调用时保留指令前缀"""
|
||||
```
|
||||
|
||||
当 `fuzzy` 为 False 时,传入 `"涩图1张 abc"` 之类的快捷指令将视为解析失败
|
||||
|
||||
快捷指令允许三类特殊的 placeholder:
|
||||
|
||||
- `{%X}`: 如 `setu {%0}`,表示此处填入快捷指令后随的第 X 个参数。
|
||||
|
||||
例如,若快捷指令为 `涩图`, 配置为 `{"command": "setu {%0}"}`, 则指令 `涩图 1` 相当于 `setu 1`
|
||||
|
||||
- `{*}`: 表示此处填入所有后随参数,并且可以通过 `{*X}` 的方式指定组合参数之间的分隔符。
|
||||
- `{X}`: 表示此处填入可能的正则匹配的组:
|
||||
- 若 `command` 中存在匹配组 `(xxx)`,则 `{X}` 表示第 X 个匹配组的内容
|
||||
- 若 `command` 中存储匹配组 `(?P<xxx>...)`, 则 `{X}` 表示名字为 X 的匹配结果
|
||||
|
||||
除此之外, 通过内置选项 `--shortcut` 可以动态操作快捷指令。
|
||||
|
||||
例如:
|
||||
|
||||
- `cmd --shortcut <key> <cmd>` 来增加一个快捷指令
|
||||
- `cmd --shortcut list` 来列出当前指令的所有快捷指令
|
||||
- `cmd --shortcut delete key` 来删除一个快捷指令
|
||||
|
||||
### 使用模糊匹配
|
||||
|
||||
模糊匹配通过在 Alconna 中设置其 CommandMeta 开启。
|
||||
|
||||
模糊匹配会应用在任意需要进行名称判断的地方,如**命令名称**,**选项名称**和**参数名称**(如指定需要传入参数名称)。
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, CommandMeta
|
||||
|
||||
alc = Alconna("test_fuzzy", meta=CommandMeta(fuzzy_match=True))
|
||||
alc.parse("test_fuzy")
|
||||
# output: test_fuzy is not matched. Do you mean "test_fuzzy"?
|
||||
```
|
||||
|
||||
## 解析结果
|
||||
|
||||
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果。
|
||||
|
||||
`Arpamar` 会有如下参数:
|
||||
|
||||
- 调试类
|
||||
|
||||
- matched: 是否匹配成功
|
||||
- error_data: 解析失败时剩余的数据
|
||||
- error_info: 解析失败时的异常内容
|
||||
- origin: 原始命令,可以类型标注
|
||||
|
||||
- 分析类
|
||||
- header_match: 命令头部的解析结果,包括原始头部、解析后头部、解析结果与可能的正则匹配组
|
||||
- main_args: 命令的主参数的解析结果
|
||||
- options: 命令所有选项的解析结果
|
||||
- subcommands: 命令所有子命令的解析结果
|
||||
- other_args: 除主参数外的其他解析结果
|
||||
- all_matched_args: 所有 Args 的解析结果
|
||||
|
||||
`Arparma` 同时提供了便捷的查询方法 `query[type]()`,会根据传入的 `path` 查找参数并返回
|
||||
|
||||
`path` 支持如下:
|
||||
|
||||
- `main_args`, `options`, ...: 返回对应的属性
|
||||
- `args`: 返回 all_matched_args
|
||||
- `main_args.xxx`, `options.xxx`, ...: 返回字典中 `xxx`键对应的值
|
||||
- `args.xxx`: 返回 all_matched_args 中 `xxx`键对应的值
|
||||
- `options.foo`, `foo`: 返回选项 `foo` 的解析结果 (OptionResult)
|
||||
- `options.foo.value`, `foo.value`: 返回选项 `foo` 的解析值
|
||||
- `options.foo.args`, `foo.args`: 返回选项 `foo` 的解析参数字典
|
||||
- `options.foo.args.bar`, `foo.bar`: 返回选项 `foo` 的参数字典中 `bar` 键对应的值
|
||||
...
|
||||
|
||||
同样, `Arparma["foo.bar"]` 的表现与 `query()` 一致
|
||||
|
||||
## Duplication
|
||||
|
||||
**Duplication** 用来提供更好的自动补全,类似于 **ArgParse** 的 **Namespace**,经测试表现良好(好耶)。
|
||||
|
||||
普通情况下使用,需要利用到 **ArgsStub**、**OptionStub** 和 **SubcommandStub** 三个部分,
|
||||
|
||||
以 pip 为例,其对应的 Duplication 应如下构造:
|
||||
|
||||
```python
|
||||
from arclet.alconna import OptionResult, Duplication, SubcommandStub
|
||||
|
||||
class MyDup(Duplication):
|
||||
verbose: OptionResult
|
||||
install: SubcommandStub # 选项与子命令对应的stub的变量名必须与其名字相同
|
||||
```
|
||||
|
||||
并在解析时传入 Duplication:
|
||||
|
||||
```python
|
||||
result = alc.parse("pip -v install ...", duplication=MyDup)
|
||||
>>> type(result)
|
||||
<class MyDup>
|
||||
```
|
||||
|
||||
**Duplication** 也可以如 **Namespace** 一样直接标明参数名称和类型:
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
from arclet.alconna import Duplication
|
||||
|
||||
|
||||
class MyDup(Duplication):
|
||||
package: str
|
||||
file: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
```
|
41
website/docs/best-practice/alconna/config.md
Normal file
41
website/docs/best-practice/alconna/config.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
description: 配置项
|
||||
---
|
||||
|
||||
# 配置项
|
||||
|
||||
## alconna_auto_send_output
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否全局启用输出信息自动发送,不启用则会在触特殊内置选项后仍然将解析结果传递至响应器。
|
||||
|
||||
## alconna_use_command_start
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否读取 Nonebot 的配置项 `COMMAND_START` 来作为全局的 Alconna 命令前缀
|
||||
|
||||
## alconna_auto_completion
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否全局启用命令自动补全,启用后会在参数缺失或触发 `--comp` 选项时自自动启用交互式补全。
|
||||
|
||||
## alconna_use_origin
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否全局使用原始消息 (即未经过 to_me 等处理的), 该选项会影响到 Alconna 的匹配行为。
|
||||
|
||||
## alconna_use_param
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `True`
|
||||
|
||||
是否使用特制的 Param 提供更好的依赖注入,该选项不会对使用依赖注入函数形式造成影响
|
285
website/docs/best-practice/alconna/matcher.md
Normal file
285
website/docs/best-practice/alconna/matcher.md
Normal file
@@ -0,0 +1,285 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
description: 响应规则的使用
|
||||
---
|
||||
|
||||
# Alconna 响应规则
|
||||
|
||||
以下为一个简单的使用示例:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.adapters.onebot12 import Image
|
||||
from nonebot_plugin_alconna import At, AlconnaMatches, on_alconna
|
||||
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
|
||||
|
||||
alc = Alconna(
|
||||
["/", "!"],
|
||||
"role-group",
|
||||
Subcommand(
|
||||
"add",
|
||||
Args["name", str],
|
||||
Option("member", Args["target", MultiVar(At)]),
|
||||
),
|
||||
Option("list"),
|
||||
Option("icon", Args["icon", Image])
|
||||
)
|
||||
rg = on_alconna(alc, auto_send_output=True)
|
||||
|
||||
|
||||
@rg.handle()
|
||||
async def _(result: Arparma = AlconnaMatches()):
|
||||
if result.find("list"):
|
||||
img = await gen_role_group_list_image()
|
||||
await rg.finish(Image(img))
|
||||
if result.find("add"):
|
||||
group = await create_role_group(result.query[str]("add.name"))
|
||||
if result.find("add.member"):
|
||||
ats = result.query[tuple[At, ...]]("add.member.target")
|
||||
group.extend(member.target for member in ats)
|
||||
await rg.finish("添加成功")
|
||||
```
|
||||
|
||||
## 响应器使用
|
||||
|
||||
`on_alconna` 的所有参数如下:
|
||||
|
||||
- `command: Alconna | str`: Alconna 命令
|
||||
- `skip_for_unmatch: bool = True`: 是否在命令不匹配时跳过该响应
|
||||
- `auto_send_output: bool = False`: 是否自动发送输出信息并跳过响应
|
||||
- `output_converter: TConvert | None = None`: 输出信息字符串转换为消息序列方法
|
||||
- `aliases: set[str | tuple[str, ...]] | None = None`: 命令别名, 作用类似于 `on_command` 中的 aliases
|
||||
- `comp_config: CompConfig | None = None`: 补全会话配置, 不传入则不启用补全会话
|
||||
- `use_origin: bool = False`: 是否使用未经 to_me 等处理过的消息
|
||||
- `use_cmd_start`: 是否使用 COMMAND_START 作为命令前缀
|
||||
|
||||
`on_alconna` 返回的是 `Matcher` 的子类 `AlconnaMatcher`,其拓展了如下方法:
|
||||
|
||||
- `.assign(path, value, or_not)`: 用于对包含多个选项/子命令的命令的分派处理
|
||||
- `.got_path(path, prompt, middleware)`: 在 `got` 方法的基础上,会以 path 对应的参数为准,读取传入 message 的最后一个消息段并验证转换
|
||||
- `.set_path_arg(key, value)`, `.get_path_arg(key)`: 类似 `set_arg` 和 `got_arg`,为 `got_path` 的特化版本
|
||||
- `.reject_path(path[, prompt, fallback])`: 类似于 `reject_arg`,对应 `got_path`
|
||||
- `.dispatch`: 同样的分派处理,但是是类似 `CommandGroup` 一样返回新的 `AlconnaMatcher`
|
||||
- `.got`, `send`, `reject`, ...: 拓展了 prompt 类型,即支持使用 `UniMessage` 作为 prompt
|
||||
|
||||
用例:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Option, Args
|
||||
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match, AlconnaMatcher, AlconnaArg
|
||||
|
||||
login = on_alconna(Alconna(["/"], "login", Args["password?", str], Option("-r|--recall")))
|
||||
|
||||
@login.assign("recall")
|
||||
async def login_exit():
|
||||
await login.finish("已退出")
|
||||
|
||||
@login.assign("password")
|
||||
async def login_handle(matcher: AlconnaMatcher, pw: Match[str] = AlconnaMatch("password")):
|
||||
matcher.set_path_arg("password", pw.result)
|
||||
|
||||
@login.got_path("password", prompt="请输入密码")
|
||||
async def login_got(password: str = AlconnaArg("password")):
|
||||
assert password
|
||||
await login.send("登录成功")
|
||||
```
|
||||
|
||||
## 依赖注入
|
||||
|
||||
`Alconna` 的解析结果会放入 `Arparma` 类中,或用户指定的 `Duplication` 类。
|
||||
|
||||
`nonebot_plugin_alconna` 提供了一系列的依赖注入函数,他们包括:
|
||||
|
||||
- `AlconnaResult`: `CommandResult` 类型的依赖注入函数
|
||||
- `AlconnaMatches`: `Arparma` 类型的依赖注入函数
|
||||
- `AlconnaDuplication`: `Duplication` 类型的依赖注入函数
|
||||
- `AlconnaMatch`: `Match` 类型的依赖注入函数,其能够额外传入一个 middleware 函数来处理得到的参数
|
||||
- `AlconnaQuery`: `Query` 类型的依赖注入函数,其能够额外传入一个 middleware 函数来处理得到的参数
|
||||
- `AlconnaExecResult`: 提供挂载在命令上的 callback 的返回结果 (`Dict[str, Any]`) 的依赖注入函数
|
||||
|
||||
可以看到,本插件提供了几类额外的模型:
|
||||
|
||||
- `CommandResult`: 解析结果,包括了源命令 `source: Alconna` ,解析结果 `result: Arparma`,以及可能的输出信息 `output: str | None` 字段
|
||||
- `Match`: 匹配项,表示参数是否存在于 `all_matched_args` 内,可用 `Match.available` 判断是否匹配,`Match.result` 获取匹配的值
|
||||
- `Query`: 查询项,表示参数是否可由 `Arparma.query` 查询并获得结果,可用 `Query.available` 判断是否查询成功,`Query.result` 获取查询结果
|
||||
|
||||
同时,基于 [`Annotated` 支持](https://github.com/nonebot/nonebot2/pull/1832), 添加了三类注解:
|
||||
|
||||
- `AlcMatches`:同 `AlconnaMatches`
|
||||
- `AlcResult`:同 `AlconnaResult`
|
||||
- `AlcExecResult`: 同 `AlconnaExecResult`
|
||||
|
||||
而若设置配置项 **ALCONNA_USE_PARAM** (默认为 True) 为 True,则上述依赖注入的目标参数皆不需要使用依赖注入函数:
|
||||
|
||||
```python
|
||||
async def handle(
|
||||
result: CommandResult,
|
||||
arp: Arparma,
|
||||
dup: Duplication,
|
||||
source: Alconna,
|
||||
abc: str, # 类似 Match, 但是若匹配结果不存在对应字段则跳过该 handler
|
||||
foo: Match[str],
|
||||
bar: Query[int] = Query("ttt.bar", 0) # Query 仍然需要一个默认值来传递 path 参数
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
该效果对于 `got_path` 下的 Arg 同样有效
|
||||
|
||||
实例:
|
||||
|
||||
```python
|
||||
...
|
||||
from nonebot import require
|
||||
require("nonebot_plugin_alconna")
|
||||
...
|
||||
|
||||
from nonebot_plugin_alconna import (
|
||||
on_alconna,
|
||||
Match,
|
||||
Query,
|
||||
AlconnaQuery,
|
||||
AlcResult
|
||||
)
|
||||
from arclet.alconna import Alconna, Args, Option, Arparma
|
||||
|
||||
test = on_alconna(
|
||||
Alconna(
|
||||
"test",
|
||||
Option("foo", Args["bar", int]),
|
||||
Option("baz", Args["qux", bool, False])
|
||||
),
|
||||
auto_send_output=True
|
||||
)
|
||||
|
||||
|
||||
@test.handle()
|
||||
async def handle_test1(result: AlcResult):
|
||||
await test.send(f"matched: {result.matched}")
|
||||
await test.send(f"maybe output: {result.output}")
|
||||
|
||||
@test.handle()
|
||||
async def handle_test2(result: Arparma):
|
||||
await test.send(f"head result: {result.header_result}")
|
||||
await test.send(f"args: {result.all_matched_args}")
|
||||
|
||||
@test.handle()
|
||||
async def handle_test3(bar: Match[int]):
|
||||
if bar.available:
|
||||
await test.send(f"foo={bar.result}")
|
||||
|
||||
@test.handle()
|
||||
async def handle_test4(qux: Query[bool] = AlconnaQuery("baz.qux", False)):
|
||||
if qux.available:
|
||||
await test.send(f"baz.qux={qux.result}")
|
||||
```
|
||||
|
||||
## 消息段标注
|
||||
|
||||
示例中使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
|
||||
|
||||
适配器下的消息段标注会匹配特定的 `MessageSegment`:
|
||||
|
||||
而通用标注与适配器标注的区别在于,通用标注会匹配多个适配器中相似类型的消息段,并返回
|
||||
`nonebot_plugin_alconna.uniseg` 中定义的 [`Segment` 模型](./utils.md#通用消息段)
|
||||
|
||||
例如:
|
||||
|
||||
```python
|
||||
...
|
||||
ats = result.query[tuple[At, ...]]("add.member.target")
|
||||
group.extend(member.target for member in ats)
|
||||
```
|
||||
|
||||
这样插件使用者就不用考虑平台之间字段的差异
|
||||
|
||||
本插件为以下适配器提供了专门的适配器标注:
|
||||
|
||||
| 协议名称 | 路径 |
|
||||
| ------------------------------------------------------------------- | ------------------------------------ |
|
||||
| [OneBot 协议](https://github.com/nonebot/adapter-onebot) | adapters.onebot11, adapters.onebot12 |
|
||||
| [Telegram](https://github.com/nonebot/adapter-telegram) | adapters.telegram |
|
||||
| [飞书](https://github.com/nonebot/adapter-feishu) | adapters.feishu |
|
||||
| [GitHub](https://github.com/nonebot/adapter-github) | adapters.github |
|
||||
| [QQ 频道](https://github.com/nonebot/adapter-qqguild) | adapters.qqguild |
|
||||
| [钉钉](https://github.com/nonebot/adapter-ding) | adapters.ding |
|
||||
| [Console](https://github.com/nonebot/adapter-console) | adapters.console |
|
||||
| [开黑啦](https://github.com/Tian-que/nonebot-adapter-kaiheila) | adapters.kook |
|
||||
| [Mirai](https://github.com/ieew/nonebot_adapter_mirai2) | adapters.mirai |
|
||||
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | adapters.ntchat |
|
||||
| [MineCraft](https://github.com/17TheWord/nonebot-adapter-minecraft) | adapters.minecraft |
|
||||
| [BiliBili Live](https://github.com/wwweww/adapter-bilibili) | adapters.bilibili |
|
||||
| [Walle-Q](https://github.com/onebot-walle/nonebot_adapter_walleq) | adapters.onebot12 |
|
||||
| [Villa](https://github.com/CMHopeSunshine/nonebot-adapter-villa) | adapters.villa |
|
||||
| [Discord](https://github.com/nonebot/adapter-discord) | adapters.discord |
|
||||
| [Red 协议](https://github.com/nonebot/adapter-red) | adapters.red |
|
||||
|
||||
## 条件控制
|
||||
|
||||
本插件可以通过 `handle(parameterless)` 来控制一个具体的响应函数是否在不满足条件时跳过响应。
|
||||
|
||||
```python
|
||||
...
|
||||
from nonebot import require
|
||||
require("nonebot_plugin_alconna")
|
||||
...
|
||||
|
||||
from arclet.alconna import Alconna, Subcommand, Option, Args
|
||||
from nonebot_plugin_alconna import assign, on_alconna, CommandResult, Check
|
||||
|
||||
pip = Alconna(
|
||||
"pip",
|
||||
Subcommand(
|
||||
"install", Args["pak", str],
|
||||
Option("--upgrade"),
|
||||
Option("--force-reinstall")
|
||||
),
|
||||
Subcommand("list", Option("--out-dated"))
|
||||
)
|
||||
|
||||
pip_cmd = on_alconna(pip)
|
||||
|
||||
# 仅在命令为 `pip install` 并且 pak 为 `pip` 时响应
|
||||
@pip_cmd.handle([Check(assign("install.pak", "pip"))])
|
||||
async def update(arp: CommandResult):
|
||||
...
|
||||
|
||||
# 仅在命令为 `pip list` 时响应
|
||||
@pip_cmd.handle([Check(assign("list"))])
|
||||
async def list_(arp: CommandResult):
|
||||
...
|
||||
|
||||
# 仅在命令为 `pip install` 时响应
|
||||
@pip_cmd.handle([Check(assign("install"))])
|
||||
async def install(arp: CommandResult):
|
||||
...
|
||||
```
|
||||
|
||||
或者使用 `AlconnaMatcher.assign`:
|
||||
|
||||
```python
|
||||
@pip_cmd.assign("install.pak", "pip")
|
||||
async def update(arp: CommandResult):
|
||||
...
|
||||
|
||||
# 仅在命令为 `pip list` 时响应
|
||||
@pip_cmd.assign("list")
|
||||
async def list_(arp: CommandResult):
|
||||
...
|
||||
|
||||
# 仅在命令为 `pip install` 时响应
|
||||
@pip_cmd.assign("install")
|
||||
async def install(arp: CommandResult):
|
||||
...
|
||||
```
|
||||
|
||||
或者使用 `AlconnaMatcher.dispatch`:
|
||||
|
||||
此外,还能像 `CommandGroup` 一样为每个分发设置独立的 matcher:
|
||||
|
||||
```python
|
||||
update_cmd = pip_cmd.dispatch("install.pak", "pip")
|
||||
|
||||
@update_cmd.handle()
|
||||
async def update(arp: CommandResult = AlconnaResult()):
|
||||
...
|
||||
```
|
317
website/docs/best-practice/alconna/utils.md
Normal file
317
website/docs/best-practice/alconna/utils.md
Normal file
@@ -0,0 +1,317 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
description: 杂项
|
||||
---
|
||||
|
||||
# 杂项
|
||||
|
||||
## 通用消息段
|
||||
|
||||
`nonebot-plugin-alconna` 提供了类似 `MessageSegment` 的通用消息段,并可在 `Alconna` 下直接标注使用:
|
||||
|
||||
```python
|
||||
class Segment:
|
||||
"""基类标注"""
|
||||
|
||||
class Text(Segment):
|
||||
"""Text对象, 表示一类文本元素"""
|
||||
text: str
|
||||
style: Optional[str]
|
||||
|
||||
class At(Segment):
|
||||
"""At对象, 表示一类提醒某用户的元素"""
|
||||
type: Literal["user", "role", "channel"]
|
||||
target: str
|
||||
|
||||
class AtAll(Segment):
|
||||
"""AtAll对象, 表示一类提醒所有人的元素"""
|
||||
|
||||
class Emoji(Segment):
|
||||
"""Emoji对象, 表示一类表情元素"""
|
||||
id: str
|
||||
name: Optional[str]
|
||||
|
||||
class Media(Segment):
|
||||
url: Optional[str]
|
||||
id: Optional[str]
|
||||
path: Optional[str]
|
||||
raw: Optional[bytes]
|
||||
|
||||
class Image(Media):
|
||||
"""Image对象, 表示一类图片元素"""
|
||||
|
||||
class Audio(Media):
|
||||
"""Audio对象, 表示一类音频元素"""
|
||||
|
||||
class Voice(Media):
|
||||
"""Voice对象, 表示一类语音元素"""
|
||||
|
||||
class Video(Media):
|
||||
"""Video对象, 表示一类视频元素"""
|
||||
|
||||
class File(Segment):
|
||||
"""File对象, 表示一类文件元素"""
|
||||
id: str
|
||||
name: Optional[str]
|
||||
|
||||
class Reply(Segment):
|
||||
"""Reply对象,表示一类回复消息"""
|
||||
origin: Any
|
||||
id: str
|
||||
msg: Optional[Union[Message, str]]
|
||||
|
||||
class Card(Segment):
|
||||
type: Literal["xml", "json"]
|
||||
raw: str
|
||||
|
||||
class Other(Segment):
|
||||
"""其他 Segment"""
|
||||
```
|
||||
|
||||
来自各自适配器的消息序列都会经过这些通用消息段对应的标注转换,以达到跨平台接收消息的作用
|
||||
|
||||
## 通用消息序列
|
||||
|
||||
除了通用消息段外,`nonebot-plugin-alconna` 还提供了一个类似于 `Message` 的 `UniMessage` 类型,其元素为经过通用标注转换后的通用消息段。
|
||||
|
||||
你可以通过提供的 `UniversalMessage` 或 `UniMsg` 依赖注入器来获取 `UniMessage`。
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import UniMsg, At, Reply
|
||||
|
||||
matcher = on_xxx(...)
|
||||
|
||||
@matcher.handle()
|
||||
async def _(msg: UniMsg):
|
||||
reply = msg[Reply, 0]
|
||||
print(reply.origin)
|
||||
if msg.has(At):
|
||||
ats = msg.get(At)
|
||||
print(ats)
|
||||
...
|
||||
```
|
||||
|
||||
### 获取消息纯文本
|
||||
|
||||
类似于 `Message.extract_plain_text()`,用于获取通用消息的纯文本。
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import UniMessage, At
|
||||
# 提取消息纯文本字符串
|
||||
assert UniMessage(
|
||||
[At("user", "1234"), "text"]
|
||||
).extract_plain_text() == "text"
|
||||
```
|
||||
|
||||
### 遍历
|
||||
|
||||
通用消息序列继承自 `List[Segment]` ,因此可以使用 `for` 循环遍历消息段。
|
||||
|
||||
```python
|
||||
for segment in message: # type: Segment
|
||||
...
|
||||
```
|
||||
|
||||
### 检查消息段
|
||||
|
||||
我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
|
||||
|
||||
```python
|
||||
# 是否存在消息段
|
||||
At("user", "1234") in message
|
||||
# 是否存在指定类型的消息段
|
||||
At in message
|
||||
```
|
||||
|
||||
我们还可以使用 `only` 方法来检查消息中是否仅包含指定的消息段。
|
||||
|
||||
```python
|
||||
# 是否都为 "test"
|
||||
message.only("test")
|
||||
# 是否仅包含指定类型的消息段
|
||||
message.only(Text)
|
||||
```
|
||||
|
||||
### 过滤、索引与切片
|
||||
|
||||
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片。
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import UniMessage, At, Text, Reply
|
||||
|
||||
message = UniMessage(
|
||||
[
|
||||
Reply(...),
|
||||
"text1",
|
||||
At("user", "1234"),
|
||||
"text2"
|
||||
]
|
||||
)
|
||||
# 索引
|
||||
message[0] == Reply(...)
|
||||
# 切片
|
||||
message[0:2] == UniMessage([Reply(...), Text("text1")])
|
||||
# 类型过滤
|
||||
message[At] == Message([At("user", "1234")])
|
||||
# 类型索引
|
||||
message[At, 0] == At("user", "1234")
|
||||
# 类型切片
|
||||
message[Text, 0:2] == UniMessage([Text("text1"), Text("text2")])
|
||||
```
|
||||
|
||||
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤。
|
||||
|
||||
```python
|
||||
message.include(Text, At)
|
||||
message.exclude(Reply)
|
||||
```
|
||||
|
||||
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。
|
||||
|
||||
```python
|
||||
# 指定类型首个消息段索引
|
||||
message.index(Text) == 1
|
||||
# 指定类型消息段数量
|
||||
message.count(Text) == 2
|
||||
```
|
||||
|
||||
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。
|
||||
|
||||
```python
|
||||
# 获取指定类型指定个数的消息段
|
||||
message.get(Text, 1) == UniMessage([Text("test1")])
|
||||
```
|
||||
|
||||
### 拼接消息
|
||||
|
||||
`str`、`UniMessage`、`Segment` 对象之间可以直接相加,相加均会返回一个新的 `UniMessage` 对象。
|
||||
|
||||
```python
|
||||
# 消息序列与消息段相加
|
||||
UniMessage("text") + Text("text")
|
||||
# 消息序列与字符串相加
|
||||
UniMessage([Text("text")]) + "text"
|
||||
# 消息序列与消息序列相加
|
||||
UniMessage("text") + UniMessage([Text("text")])
|
||||
# 字符串与消息序列相加
|
||||
"text" + UniMessage([Text("text")])
|
||||
# 消息段与消息段相加
|
||||
Text("text") + Text("text")
|
||||
# 消息段与字符串相加
|
||||
Text("text") + "text"
|
||||
# 消息段与消息序列相加
|
||||
Text("text") + UniMessage([Text("text")])
|
||||
# 字符串与消息段相加
|
||||
"text" + Text("text")
|
||||
```
|
||||
|
||||
如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append`、`Message.extend` 方法,或者使用自加。
|
||||
|
||||
```python
|
||||
msg = UniMessage([Text("text")])
|
||||
# 自加
|
||||
msg += "text"
|
||||
msg += Text("text")
|
||||
msg += UniMessage([Text("text")])
|
||||
# 附加
|
||||
msg.append(Text("text"))
|
||||
# 扩展
|
||||
msg.extend([Text("text")])
|
||||
```
|
||||
|
||||
## 跨平台发送
|
||||
|
||||
`nonebot-plugin-alconna` 不仅支持跨平台接收消息,通过 `UniMessage.export` 方法其同样支持了跨平台发送消息。
|
||||
|
||||
`UniMessage.export` 会通过传入的 `bot: Bot` 参数读取适配器信息,并使用对应的生成方法把通用消息转为适配器对应的消息序列:
|
||||
|
||||
```python
|
||||
from nonebot import Bot, on_command
|
||||
from nonebot_plugin_alconna import Image, UniMessage
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
@test.handle()
|
||||
async def handle_test(bot: Bot):
|
||||
await test.send(await UniMessage(Image(path="path/to/img")).export(bot))
|
||||
```
|
||||
|
||||
而在 `AlconnaMatcher` 下,`got`, `send`, `reject` 等可以发送消息的方法皆支持使用 `UniMessage`,不需要手动调用 export 方法:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args
|
||||
from nonebot_plugin_alconna import At, Match, UniMessage, AlconnaMatcher, on_alconna
|
||||
|
||||
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
|
||||
|
||||
@test_cmd.handle()
|
||||
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
|
||||
if target.available:
|
||||
matcher.set_path_arg("target", target.result)
|
||||
|
||||
@test_cmd.got_path("target", prompt="请输入目标")
|
||||
async def tt(target: At):
|
||||
await test_cmd.send(UniMessage([target, "\ndone."]))
|
||||
```
|
||||
|
||||
## 特殊装饰器
|
||||
|
||||
`nonebot_plugin_alconna` 提供 了一个 `funcommand` 装饰器, 其用于将一个接受任意参数,
|
||||
返回 `str` 或 `Message` 或 `MessageSegment` 的函数转换为命令响应器。
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import funcommand
|
||||
|
||||
@funcommand()
|
||||
async def echo(msg: str):
|
||||
return msg
|
||||
```
|
||||
|
||||
其等同于
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args
|
||||
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match
|
||||
|
||||
echo = on_alconna(Alconna("echo", Args["msg", str]))
|
||||
|
||||
@echo.handle()
|
||||
async def echo_exit(msg: Match[str] = AlconnaMatch("msg")):
|
||||
await echo.finish(msg.result)
|
||||
```
|
||||
|
||||
## 特殊构造器
|
||||
|
||||
`nonebot_plugin_alconna` 提供了一个 `Command` 构造器,其基于 `arclet.alconna.tools` 中的 `AlconnaString`,
|
||||
以类似 `Koishi` 中注册命令的方式来构建一个 AlconnaMatcher:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import Command, Arparma
|
||||
|
||||
book = (
|
||||
Command("book", "测试")
|
||||
.option("writer", "-w <id:int>")
|
||||
.option("writer", "--anonymous", {"id": 0})
|
||||
.usage("book [-w <id:int> | --anonymous]")
|
||||
.shortcut("测试", {"args": ["--anonymous"]})
|
||||
.build()
|
||||
)
|
||||
|
||||
@book.handle()
|
||||
async def _(arp: Arparma):
|
||||
await book.send(str(arp.options))
|
||||
```
|
||||
|
||||
甚至,你可以设置 `action` 来设定响应行为:
|
||||
|
||||
```python
|
||||
book = (
|
||||
Command("book", "测试")
|
||||
.option("writer", "-w <id:int>")
|
||||
.option("writer", "--anonymous", {"id": 0})
|
||||
.usage("book [-w <id:int> | --anonymous]")
|
||||
.shortcut("测试", {"args": ["--anonymous"]})
|
||||
.action(lambda options: str(options)) # 会自动通过 bot.send 发送
|
||||
.build()
|
||||
)
|
||||
```
|
@@ -4,3 +4,578 @@ description: 编写适配器对接新的平台
|
||||
---
|
||||
|
||||
# 编写适配器
|
||||
|
||||
在编写适配器之前,我们需要先了解[适配器的功能与组成](../advanced/adapter#适配器功能与组成),适配器通常由 `Adapter`、`Bot`、`Event` 和 `Message` 四个部分组成,在编写适配器时,我们需要继承 NoneBot 中的基类,并根据实际平台来编写每个部分功能。
|
||||
|
||||
## 组织结构
|
||||
|
||||
NoneBot 适配器项目通常以 `nonebot-adapter-{adapter-name}` 作为项目名,并以**命名空间包**的形式编写,即在 `nonebot/adapters/{adapter-name}` 目录中编写实际代码,例如:
|
||||
|
||||
```tree
|
||||
📦 nonebot-adapter-{adapter-name}
|
||||
├── 📂 nonebot
|
||||
│ ├── 📂 adapters
|
||||
│ │ ├── 📂 {adapter-name}
|
||||
│ │ │ ├── 📜 __init__.py
|
||||
│ │ │ ├── 📜 adapter.py
|
||||
│ │ │ ├── 📜 bot.py
|
||||
│ │ │ ├── 📜 config.py
|
||||
│ │ │ ├── 📜 event.py
|
||||
│ │ │ └── 📜 message.py
|
||||
├── 📜 pyproject.toml
|
||||
└── 📜 README.md
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
|
||||
上述的项目结构仅作推荐,不做强制要求,保证实际可用性即可。
|
||||
|
||||
:::
|
||||
|
||||
### 使用 NB-CLI 创建项目
|
||||
|
||||
我们可以使用脚手架快速创建项目:
|
||||
|
||||
```shell
|
||||
nb adapter create
|
||||
```
|
||||
|
||||
按照指引,输入适配器名称以及存储位置,即可创建一个带有基本结构的适配器项目。
|
||||
|
||||
## 组成部分
|
||||
|
||||
:::tip 提示
|
||||
|
||||
本章节的代码中提到的 `Adapter`、`Bot`、`Event` 和 `Message` 等,均为下文中适配器所编写的类,而非 NoneBot 中的基类。
|
||||
|
||||
:::
|
||||
|
||||
### Log
|
||||
|
||||
适配器在处理时通常需要打印日志,但直接使用 NoneBot 的默认 `logger` 不方便区分适配器输出和其它日志。因此我们可以使用 NoneBot 提供的 `logger_wrapper` 方法,自定义一个 `log` 函数用于快捷打印适配器日志:
|
||||
|
||||
```python {3} title=log.py
|
||||
from nonebot.utils import logger_wrapper
|
||||
|
||||
log = logger_wrapper("your_adapter_name")
|
||||
```
|
||||
|
||||
这个 `log` 函数会在默认 `logger` 中添加适配器名称前缀,它接收三个参数:日志等级、日志内容以及可选的异常,具体用法如下:
|
||||
|
||||
```python
|
||||
from .log import log
|
||||
|
||||
log("DEBUG", "A DEBUG log.")
|
||||
log("INFO", "A INFO log.")
|
||||
|
||||
try:
|
||||
...
|
||||
except Exception as e:
|
||||
log("ERROR", "something error.", e)
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
通常适配器需要一些配置项,例如平台连接密钥等。适配器的配置方法与[插件配置](../appendices/config#%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE)类似,例如:
|
||||
|
||||
```python title=config.py
|
||||
from pydantic import BaseModel, Extra
|
||||
|
||||
class Config(BaseModel, extra=Extra.ignore):
|
||||
xxx_id: str
|
||||
xxx_token: str
|
||||
```
|
||||
|
||||
配置项的读取将在下方 [Adapter](#adapter) 中介绍。
|
||||
|
||||
### Adapter
|
||||
|
||||
Adapter 负责转换事件、调用接口,以及正确创建 Bot 对象并注册到 NoneBot 中。在编写平台相关内容之前,我们需要继承基类,并实现适配器的基本信息:
|
||||
|
||||
```python {9,11,14,18} title=adapter.py
|
||||
from typing import Any
|
||||
from typing_extensions import override
|
||||
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot.adapters import Adapter as BaseAdapter
|
||||
|
||||
from .config import Config
|
||||
|
||||
class Adapter(BaseAdapter):
|
||||
@override
|
||||
def __init__(self, driver: Driver, **kwargs: Any):
|
||||
super().__init__(driver, **kwargs)
|
||||
# 读取适配器所需的配置项
|
||||
self.adapter_config: Config = Config(**self.config.dict())
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def get_name(cls) -> str:
|
||||
"""适配器名称"""
|
||||
return "your_adapter_name"
|
||||
```
|
||||
|
||||
#### 与平台交互
|
||||
|
||||
NoneBot 提供了多种 [Driver](../advanced/driver) 来帮助适配器进行网络通信,主要分为客户端和服务端两种类型。我们需要**根据平台文档和特性**选择合适的通信方式,并编写相关方法用于初始化适配器,与平台建立连接和进行交互:
|
||||
|
||||
##### 客户端通信方式
|
||||
|
||||
```python {12,23,24} title=adapter.py
|
||||
import asyncio
|
||||
from typing_extensions import override
|
||||
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers import Request, WebSocketClientMixin
|
||||
|
||||
class Adapter(BaseAdapter):
|
||||
@override
|
||||
def __init__(self, driver: Driver, **kwargs: Any):
|
||||
super().__init__(driver, **kwargs)
|
||||
self.adapter_config: Config = Config(**self.config.dict())
|
||||
self.task: Optional[asyncio.Task] = None # 存储 ws 任务
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
if not isinstance(self.driver, WebSocketClientMixin):
|
||||
# 判断用户配置的Driver类型是否符合适配器要求,不符合时应抛出异常
|
||||
raise RuntimeError(
|
||||
f"Current driver {self.config.driver} doesn't support websocket client connections!"
|
||||
f"{self.get_name()} Adapter need a WebSocket Client Driver to work."
|
||||
)
|
||||
# 在 NoneBot 启动和关闭时进行相关操作
|
||||
self.driver.on_startup(self.startup)
|
||||
self.driver.on_shutdown(self.shutdown)
|
||||
|
||||
async def startup(self) -> None:
|
||||
"""定义启动时的操作,例如和平台建立连接"""
|
||||
self.task = asyncio.create_task(self._forward_ws()) # 建立 ws 连接
|
||||
|
||||
async def _forward_ws(self):
|
||||
request = Request(
|
||||
method="GET",
|
||||
url="your_platform_websocket_url",
|
||||
headers={"token": "..."}, # 鉴权请求头
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
async with self.websocket(request) as ws:
|
||||
try:
|
||||
# 处理 websocket
|
||||
...
|
||||
except WebSocketClosed as e:
|
||||
log(
|
||||
"ERROR",
|
||||
"<r><bg #f8bbd0>WebSocket Closed</bg #f8bbd0></r>",
|
||||
e,
|
||||
)
|
||||
except Exception as e:
|
||||
log(
|
||||
"ERROR",
|
||||
"<r><bg #f8bbd0>Error while process data from "
|
||||
"websocket platform_websocket_url. "
|
||||
"Trying to reconnect...</bg #f8bbd0></r>",
|
||||
e,
|
||||
)
|
||||
finally:
|
||||
# 这里要断开 Bot 连接
|
||||
except Exception as e:
|
||||
# 尝试重连
|
||||
log(
|
||||
"ERROR",
|
||||
"<r><bg #f8bbd0>Error while setup websocket to "
|
||||
"platform_websocket_url. Trying to reconnect...</bg #f8bbd0></r>",
|
||||
e,
|
||||
)
|
||||
await asyncio.sleep(3) # 重连间隔
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""定义关闭时的操作,例如停止任务、断开连接"""
|
||||
|
||||
# 断开 ws 连接
|
||||
if self.task is not None and not self.task.done():
|
||||
self.task.cancel()
|
||||
```
|
||||
|
||||
##### 服务端通信方式
|
||||
|
||||
```python {30,38} title=adapter.py
|
||||
from nonebot.drivers import (
|
||||
Request,
|
||||
ASGIMixin,
|
||||
WebSocket,
|
||||
HTTPServerSetup,
|
||||
WebSocketServerSetup
|
||||
)
|
||||
|
||||
class Adapter(BaseAdapter):
|
||||
@override
|
||||
def __init__(self, driver: Driver, **kwargs: Any):
|
||||
super().__init__(driver, **kwargs)
|
||||
self.adapter_config: Config = Config(**self.config.dict())
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
if not isinstance(self.driver, ASGIMixin):
|
||||
raise RuntimeError(
|
||||
f"Current driver {self.config.driver} doesn't support asgi server!"
|
||||
f"{self.get_name()} Adapter need a asgi server driver to work."
|
||||
)
|
||||
# 建立服务端路由
|
||||
# HTTP Webhook 路由
|
||||
http_setup = HTTPServerSetup(
|
||||
URL("your_webhook_url"), # 路由地址
|
||||
"POST", # 接收的方法
|
||||
"WEBHOOK name", # 路由名称
|
||||
self._handle_http, # 处理函数
|
||||
)
|
||||
self.setup_http_server(http_setup)
|
||||
|
||||
# 反向 Websocket 路由
|
||||
ws_setup = WebSocketServerSetup(
|
||||
URL("your_websocket_url"), # 路由地址
|
||||
"WebSocket name", # 路由名称
|
||||
self._handle_ws, # 处理函数
|
||||
)
|
||||
self.setup_websocket_server(ws_setup)
|
||||
|
||||
|
||||
async def _handle_http(self, request: Request) -> Response:
|
||||
"""HTTP 路由处理函数,只有一个类型为 Request 的参数,且返回值类型为 Response"""
|
||||
...
|
||||
return Response(
|
||||
status_code=200, # 状态码
|
||||
headers={"something": "something"}, # 响应头
|
||||
content="xxx", # 响应内容
|
||||
)
|
||||
|
||||
async def _handle_ws(self, websocket: WebSocket) -> Any:
|
||||
"""WebSocket 路由处理函数,只有一个类型为 WebSocket 的参数"""
|
||||
...
|
||||
```
|
||||
|
||||
更多通信交互方式可以参考以下适配器:
|
||||
|
||||
- [OneBot](https://github.com/nonebot/adapter-onebot/blob/master/nonebot/adapters/onebot/v11/adapter.py) - `WebSocket 客户端`、`WebSocket 服务端`、`HTTP WEBHOOK`、`HTTP POST`
|
||||
- [QQGuild](https://github.com/nonebot/adapter-qqguild/blob/master/nonebot/adapters/qqguild/adapter.py) - `WebSocket 服务端`
|
||||
- [Telegram](https://github.com/nonebot/adapter-telegram/blob/beta/nonebot/adapters/telegram/adapter.py) - `HTTP WEBHOOK`
|
||||
|
||||
#### 建立 Bot 连接
|
||||
|
||||
在与平台建立连接后,我们需要将 [Bot](#bot) 实例化,并调用适配器提供的的 `bot_connect` 方法告知 NoneBot 建立了 Bot 连接。在与平台断开连接或出现某些异常进行重连时,我们需要调用 `bot_disconnect` 方法告知 NoneBot 断开了 Bot 连接。
|
||||
|
||||
```python {7,8,11} title=adapter.py
|
||||
from .bot import Bot
|
||||
|
||||
class Adapter(BaseAdapter):
|
||||
|
||||
def _handle_connect(self):
|
||||
bot_id = ... # 通过配置或者平台 API 等方式,获取到 Bot 的 ID
|
||||
bot = Bot(self, self_id=bot_id) # 实例化 Bot
|
||||
self.bot_connect(bot) # 建立 Bot 连接
|
||||
|
||||
def _handle_disconnect(self):
|
||||
self.bot_disconnect(bot) # 断开 Bot 连接
|
||||
```
|
||||
|
||||
#### 转换 Event 事件
|
||||
|
||||
在接收到来自平台的事件数据后,我们需要将其转为适配器的 [Event](#event),并调用 Bot 的 `handle_event` 方法来让 Bot 对事件进行处理:
|
||||
|
||||
```python title=adapter.py
|
||||
import asyncio
|
||||
from typing import Any, Dict
|
||||
|
||||
from .bot import Bot
|
||||
from .event import Event
|
||||
from .log import log
|
||||
|
||||
class Adapter(BaseAdapter):
|
||||
|
||||
@classmethod
|
||||
def payload_to_event(cls, payload: Dict[str, Any]) -> Event:
|
||||
"""根据平台事件的特性,转换平台 payload 为具体 Event
|
||||
|
||||
Event 模型继承自 pydantic.BaseModel,具体请参考 pydantic 文档
|
||||
"""
|
||||
|
||||
# 做一层异常处理,以应对平台事件数据的变更
|
||||
try:
|
||||
return your_event_class.parse_obj(payload)
|
||||
except Exception as e:
|
||||
# 无法正常解析为具体 Event 时,给出日志提示
|
||||
log(
|
||||
"WARNING",
|
||||
f"Parse event error: {str(payload)}",
|
||||
)
|
||||
# 也可以尝试转为基础 Event 进行处理
|
||||
return Event.parse_obj(payload)
|
||||
|
||||
|
||||
async def _forward(self, bot: Bot):
|
||||
|
||||
payload: Dict[str, Any] # 接收到的事件数据
|
||||
event = self.payload_to_event(payload)
|
||||
# 让 bot 对事件进行处理
|
||||
asyncio.create_task(bot.handle_event(event))
|
||||
```
|
||||
|
||||
#### 调用平台 API
|
||||
|
||||
我们需要实现 `Adapter` 的 `_call_api` 方法,使开发者能够调用平台提供的 API。如果通过 WebSocket 通信可以通过 `send` 方法来发送数据,如果采用 HTTP 请求,则需要通过 NoneBot 提供的 `Request` 对象,调用 `driver` 的 `request` 方法来发送请求。
|
||||
|
||||
```python {11} title=adapter.py
|
||||
from typing import Any
|
||||
from typing_extensions import override
|
||||
|
||||
from nonebot.drivers import Request, WebSocket
|
||||
|
||||
from .bot import Bot
|
||||
|
||||
class Adapter(BaseAdapter):
|
||||
|
||||
@override
|
||||
async def _call_api(self, bot: Bot, api: str, **data: Any) -> Any:
|
||||
log("DEBUG", f"Calling API <y>{api}</y>") # 给予日志提示
|
||||
platform_data = your_handle_data_method(data) # 自行将数据转为平台所需要的格式
|
||||
|
||||
# 采用 HTTP 请求的方式,需要构造一个 Request 对象
|
||||
request = Request(
|
||||
method="GET", # 请求方法
|
||||
url=api, # 接口地址
|
||||
headers=..., # 请求头,通常需要包含鉴权信息
|
||||
params=platform_data, # 自行处理数据的传输形式
|
||||
# json=platform_data,
|
||||
# data=platform_data,
|
||||
)
|
||||
# 发送请求,返回结果
|
||||
return await self.driver.request(request)
|
||||
|
||||
|
||||
# 采用 WebSocket 通信的方式,可以直接调用 send 方法发送数据
|
||||
# 通过某种方式获取到 bot 对应的 websocket 对象
|
||||
ws: WebSocket = your_get_websocket_method(bot.self_id)
|
||||
|
||||
await ws.send_text(platform_data) # 发送 str 类型的数据
|
||||
await ws.send_bytes(platform_data) # 发送 bytes 类型的数据
|
||||
await ws.send(platform_data) # 是以上两种方式的合体
|
||||
|
||||
# 接收并返回结果,同样的,也有 str 和 bytes 的区别
|
||||
return await ws.receive_text()
|
||||
return await ws.receive_bytes()
|
||||
return await ws.receive()
|
||||
```
|
||||
|
||||
`调用平台 API` 实现方式具体可以参考以下适配器:
|
||||
|
||||
Websocket:
|
||||
|
||||
- [OneBot V11](https://github.com/nonebot/adapter-onebot/blob/master/nonebot/adapters/onebot/v11/adapter.py#L127)
|
||||
- [OneBot V12](https://github.com/nonebot/adapter-onebot/blob/master/nonebot/adapters/onebot/v12/adapter.py#L162)
|
||||
|
||||
HTTP:
|
||||
|
||||
- [QQ 频道](https://github.com/nonebot/adapter-qqguild/blob/master/nonebot/adapters/qqguild/adapter.py#L354)
|
||||
- [Telegram](https://github.com/nonebot/adapter-telegram/blob/beta/nonebot/adapters/telegram/adapter.py#L145)
|
||||
- [飞书](https://github.com/nonebot/adapter-feishu/blob/master/nonebot/adapters/feishu/adapter.py#L158)
|
||||
|
||||
### Bot
|
||||
|
||||
Bot 是机器人开发者能够直接获取并使用的核心对象,负责存储平台机器人相关信息,并提供回复事件、调用 API 的上层方法。我们需要继承基类 `Bot`,并实现相关方法:
|
||||
|
||||
```python {20,25,34} title=bot.py
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
from typing_extensions import override
|
||||
|
||||
from nonebot.message import handle_event
|
||||
from nonebot.adapters import Bot as BaseBot
|
||||
|
||||
from .event import Event
|
||||
from .message import Message, MessageSegment
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .adapter import Adapter
|
||||
|
||||
|
||||
class Bot(BaseBot):
|
||||
"""
|
||||
your_adapter_name 协议 Bot 适配。
|
||||
"""
|
||||
|
||||
@override
|
||||
def __init__(self, adapter: Adapter, self_id: str, **kwargs: Any):
|
||||
super().__init__(adapter, self_id)
|
||||
self.adapter: Adapter = adapter
|
||||
# 一些有关 Bot 的信息也可以在此定义和存储
|
||||
|
||||
async def handle_event(self, event: Event):
|
||||
# 根据需要,对事件进行某些预处理,例如:
|
||||
# 检查事件是否和机器人有关操作,去除事件消息首尾的 @bot
|
||||
# 检查事件是否有回复消息,调用平台 API 获取原始消息的消息内容
|
||||
...
|
||||
# 调用 handle_event 让 NoneBot 对事件进行处理
|
||||
await handle_event(self, event)
|
||||
|
||||
@override
|
||||
async def send(
|
||||
self,
|
||||
event: Event,
|
||||
message: Union[str, Message, MessageSegment],
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
# 根据平台实现 Bot 回复事件的方法
|
||||
|
||||
# 将消息处理为平台所需的格式后,调用发送消息接口进行发送,例如:
|
||||
data = message_to_platform_data(message)
|
||||
await self.send_message(
|
||||
data=data,
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
### Event
|
||||
|
||||
Event 是 NoneBot 中的事件主体对象,所有平台消息在进入处理流程前需要转换为 NoneBot 事件。我们需要继承基类 `Event`,并实现相关方法:
|
||||
|
||||
```python {5,8,13,18,23,28,33} title=event.py
|
||||
from typing_extensions import override
|
||||
|
||||
from nonebot.adapters import Event as BaseEvent
|
||||
|
||||
class Event(BaseEvent):
|
||||
|
||||
@override
|
||||
def get_event_name(self) -> str:
|
||||
# 返回事件的名称,用于日志打印
|
||||
return "event name"
|
||||
|
||||
@override
|
||||
def get_event_description(self) -> str:
|
||||
# 返回事件的描述,用于日志打印,请注意转义 loguru tag
|
||||
return escape_tag(repr(self.dict()))
|
||||
|
||||
@override
|
||||
def get_message(self):
|
||||
# 获取事件消息的方法,根据事件具体实现,如果事件非消息类型事件,则抛出异常
|
||||
raise ValueError("Event has no message!")
|
||||
|
||||
@override
|
||||
def get_user_id(self) -> str:
|
||||
# 获取用户 ID 的方法,根据事件具体实现,如果事件没有用户 ID,则抛出异常
|
||||
raise ValueError("Event has no context!")
|
||||
|
||||
@override
|
||||
def get_session_id(self) -> str:
|
||||
# 获取事件会话 ID 的方法,根据事件具体实现,如果事件没有相关 ID,则抛出异常
|
||||
raise ValueError("Event has no context!")
|
||||
|
||||
@override
|
||||
def is_tome(self) -> bool:
|
||||
# 判断事件是否和机器人有关
|
||||
return False
|
||||
```
|
||||
|
||||
然后根据平台消息的类型,编写各种不同的事件,并且注意要根据事件类型实现 `get_type` 方法,具体请参考[事件类型](../advanced/adapter#事件类型)。消息类型事件还应重写 `get_message` 和 `get_user_id` 等方法,例如:
|
||||
|
||||
```python {7,16,20,25,34,42} title=event.py
|
||||
from .message import Message
|
||||
|
||||
class HeartbeatEvent(Event):
|
||||
"""心跳时间,通常为元事件"""
|
||||
|
||||
@override
|
||||
def get_type(self) -> str:
|
||||
return "meta_event"
|
||||
|
||||
class MessageEvent(Event):
|
||||
"""消息事件"""
|
||||
message_id: str
|
||||
user_id: str
|
||||
|
||||
@override
|
||||
def get_type(self) -> str:
|
||||
return "message"
|
||||
|
||||
@override
|
||||
def get_message(self) -> Message:
|
||||
# 返回事件消息对应的 NoneBot Message 对象
|
||||
return self.message
|
||||
|
||||
@override
|
||||
def get_user_id(self) -> str:
|
||||
return self.user_id
|
||||
|
||||
class JoinRoomEvent(Event):
|
||||
"""加入房间事件,通常为通知事件"""
|
||||
user_id: str
|
||||
room_id: str
|
||||
|
||||
@override
|
||||
def get_type(self) -> str:
|
||||
return "notice"
|
||||
|
||||
class ApplyAddFriendEvent(Event):
|
||||
"""申请添加好友事件,通常为请求事件"""
|
||||
user_id: str
|
||||
|
||||
@override
|
||||
def get_type(self) -> str:
|
||||
return "request"
|
||||
```
|
||||
|
||||
### Message
|
||||
|
||||
Message 负责正确序列化消息,以便机器人插件处理。我们需要继承 `MessageSegment` 和 `Message` 两个类,并实现相关方法:
|
||||
|
||||
```python {9,12,17,22,27,30,36} title=message.py
|
||||
from typing import Type, Iterable
|
||||
from typing_extensions import override
|
||||
|
||||
from nonebot.utils import escape_tag
|
||||
|
||||
from nonebot.adapters import Message as BaseMessage
|
||||
from nonebot.adapters import MessageSegment as BaseMessageSegment
|
||||
|
||||
class MessageSegment(BaseMessageSegment["Message"]):
|
||||
@classmethod
|
||||
@override
|
||||
def get_message_class(cls) -> Type["Message"]:
|
||||
# 返回适配器的 Message 类型本身
|
||||
return Message
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
# 返回该消息段的纯文本表现形式,通常在日志中展示
|
||||
return "text of MessageSegment"
|
||||
|
||||
@override
|
||||
def is_text(self) -> bool:
|
||||
# 判断该消息段是否为纯文本
|
||||
return self.type == "text"
|
||||
|
||||
|
||||
class Message(BaseMessage[MessageSegment]):
|
||||
@classmethod
|
||||
@override
|
||||
def get_segment_class(cls) -> Type[MessageSegment]:
|
||||
# 返回适配器的 MessageSegment 类型本身
|
||||
return MessageSegment
|
||||
|
||||
@staticmethod
|
||||
@override
|
||||
def _construct(msg: str) -> Iterable[MessageSegment]:
|
||||
# 实现从字符串中构造消息数组,如无字符串嵌入格式可直接返回文本类型 MessageSegment
|
||||
...
|
||||
```
|
||||
|
||||
然后根据平台具体的消息类型,来实现各种 `MessageSegment` 消息段,具体可以参考以下适配器:
|
||||
|
||||
- [OneBot](https://github.com/nonebot/adapter-onebot/blob/master/nonebot/adapters/onebot/v11/message.py#L77-L261)
|
||||
- [QQGuild](https://github.com/nonebot/adapter-qqguild/blob/master/nonebot/adapters/qqguild/message.py#L22-L150)
|
||||
- [Telegram](https://github.com/nonebot/adapter-telegram/blob/beta/nonebot/adapters/telegram/message.py#L43-L250)
|
||||
|
||||
## 后续工作
|
||||
|
||||
在完成适配器代码的编写后,如果想要将适配器发布到 NoneBot 商店,我们需要将适配器发布到 PyPI 中,然后前往[商店](/store)页面,切换到适配器页签,点击**发布适配器**按钮,填写适配器相关信息并提交。
|
||||
|
||||
另外建议编写适配器文档或者一些插件开发示例,以便其他开发者使用我们的适配器。
|
||||
|
@@ -38,9 +38,9 @@ options:
|
||||
在以下的示例中,为了更好的理解多种类型的消息组成方式,我们将使用 `Console` 协议适配器来演示消息序列的使用方法。在实际使用中,你需要确保你使用的**消息序列类型**与你所要发送的**平台类型**一致。
|
||||
:::
|
||||
|
||||
通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过依赖注入 [`EventMessage`](../advanced/dependency.mdx#eventmessage), 或者使用 `event.get_message()` 获取。
|
||||
通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过依赖注入 [`EventMessage`](../advanced/dependency.mdx#eventmessage),或者使用 `event.get_message()` 获取。
|
||||
|
||||
由于消息序列是 `List[MessageSegment]` 的子类, 所以你总是可以用和操作 `List` 类似的方式来处理消息序列。例如:
|
||||
由于消息序列是 `List[MessageSegment]` 的子类,所以你总是可以用和操作 `List` 类似的方式来处理消息序列。例如:
|
||||
|
||||
```python
|
||||
>>> from nonebot.adapters.console import Message, MessageSegment
|
||||
@@ -278,14 +278,14 @@ msg == Message(
|
||||
|
||||
### 使用消息模板
|
||||
|
||||
为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列
|
||||
为了提供安全可靠的跨平台模板字符,我们提供了一个消息模板功能来构建消息序列
|
||||
|
||||
它在以下常见场景中尤其有用:
|
||||
它在以下常见场景中尤其有用:
|
||||
|
||||
- 多行富文本编排(包含图片,文字以及表情等)
|
||||
- 多行富文本编排(包含图片,文字以及表情等)
|
||||
- 客制化(由 Bot 最终用户提供消息模板时)
|
||||
|
||||
在事实上, 它的用法和 `str.format` 极为相近, 所以你在使用的时候, 总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果,这里给出几个简单的例子。
|
||||
在事实上,它的用法和 `str.format` 极为相近,所以你在使用的时候,总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果,这里给出几个简单的例子。
|
||||
|
||||
默认情况下,消息模板采用 `str` 纯文本形式的格式化:
|
||||
|
||||
|
@@ -5,6 +5,97 @@ toc_max_heading_level: 2
|
||||
|
||||
# 更新日志
|
||||
|
||||
## v2.1.0
|
||||
|
||||
### 🚀 新功能
|
||||
|
||||
- Feature: 为 Matcher.HANDLER_PARAM_TYPES 补增类型 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2352](https://github.com/nonebot/nonebot2/pull/2352))
|
||||
- Feature: 为事件响应器添加更多源码信息 [@yanyongyu](https://github.com/yanyongyu) ([#2351](https://github.com/nonebot/nonebot2/pull/2351))
|
||||
- Feature: 补充依赖注入部分情况下类型错误时的日志提示 [@A-kirami](https://github.com/A-kirami) ([#2343](https://github.com/nonebot/nonebot2/pull/2343))
|
||||
- Feature: 支持子依赖定义 Pydantic 类型校验 [@yanyongyu](https://github.com/yanyongyu) ([#2310](https://github.com/nonebot/nonebot2/pull/2310))
|
||||
- Feature: 细化 driver 职责类型 [@yanyongyu](https://github.com/yanyongyu) ([#2296](https://github.com/nonebot/nonebot2/pull/2296))
|
||||
|
||||
### 🐛 Bug 修复
|
||||
|
||||
- Fix: 修复依赖注入解析类型标注错误 [@yanyongyu](https://github.com/yanyongyu) ([#2338](https://github.com/nonebot/nonebot2/pull/2338))
|
||||
- Fix: 设置 file request 默认 filename [@eya46](https://github.com/eya46) ([#2284](https://github.com/nonebot/nonebot2/pull/2284))
|
||||
|
||||
### 📝 文档
|
||||
|
||||
- Docs: 更新最佳实践部分的 Alconna 章节 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2349](https://github.com/nonebot/nonebot2/pull/2349))
|
||||
- Docs: 添加 Discord 适配器描述,补充 Villa 适配器协议链接 [@CMHopeSunshine](https://github.com/CMHopeSunshine) ([#2316](https://github.com/nonebot/nonebot2/pull/2316))
|
||||
- Docs: 添加 Red 适配器描述 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2313](https://github.com/nonebot/nonebot2/pull/2313))
|
||||
- Docs: 更新最佳实践部分的 Alconna 章节 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2303](https://github.com/nonebot/nonebot2/pull/2303))
|
||||
- Docs: 修复 Alconna 中 `CommandResult` 描述错误 [@KomoriDev](https://github.com/KomoriDev) ([#2282](https://github.com/nonebot/nonebot2/pull/2282))
|
||||
- Docs: 修复子依赖部分代码行号错误 [@A-kirami](https://github.com/A-kirami) ([#2279](https://github.com/nonebot/nonebot2/pull/2279))
|
||||
- Docs: 补充 `get_last_receive` 示例 [@A-kirami](https://github.com/A-kirami) ([#2278](https://github.com/nonebot/nonebot2/pull/2278))
|
||||
- Docs: 修复文档中错误的标点 [@A-kirami](https://github.com/A-kirami) ([#2275](https://github.com/nonebot/nonebot2/pull/2275))
|
||||
- Docs: 修复配置文档中 `Nickname` 属性的描述错误 [@A-kirami](https://github.com/A-kirami) ([#2271](https://github.com/nonebot/nonebot2/pull/2271))
|
||||
- Docs: 适配器编写教程 [@CMHopeSunshine](https://github.com/CMHopeSunshine) ([#2079](https://github.com/nonebot/nonebot2/pull/2079))
|
||||
- Docs: 更新贡献指南 [@A-kirami](https://github.com/A-kirami) ([#2255](https://github.com/nonebot/nonebot2/pull/2255))
|
||||
- Docs: 修复文档 Last updated author 错误 [@eya46](https://github.com/eya46) ([#2241](https://github.com/nonebot/nonebot2/pull/2241))
|
||||
- Docs: 更新最佳实践部分的 Alconna 章节 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2237](https://github.com/nonebot/nonebot2/pull/2237))
|
||||
|
||||
### 💫 杂项
|
||||
|
||||
- Plugin: 删除插件 nonebot-plugin-heisi [@yzyyz1387](https://github.com/yzyyz1387) ([#2353](https://github.com/nonebot/nonebot2/pull/2353))
|
||||
- CI: 更新到 node 18 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#2344](https://github.com/nonebot/nonebot2/pull/2344))
|
||||
- CI: 插件测试使用最新的稳定版 Python 版本 [@he0119](https://github.com/he0119) ([#2336](https://github.com/nonebot/nonebot2/pull/2336))
|
||||
- Plugin: 删除不再维护的插件 [@ZM25XC](https://github.com/ZM25XC) ([#2330](https://github.com/nonebot/nonebot2/pull/2330))
|
||||
- Plugin: 删除插件 poe ai [@nikissXI](https://github.com/nikissXI) ([#2308](https://github.com/nonebot/nonebot2/pull/2308))
|
||||
- Plugin: 移除不再维护的插件,修改插件信息 [@Well2333](https://github.com/Well2333) ([#2292](https://github.com/nonebot/nonebot2/pull/2292))
|
||||
- Fix: 修复 ruff 发现的问题 [@yanyongyu](https://github.com/yanyongyu) ([#2286](https://github.com/nonebot/nonebot2/pull/2286))
|
||||
- Develop: 添加 dependabot actions 更新检查 [@yanyongyu](https://github.com/yanyongyu) ([#2256](https://github.com/nonebot/nonebot2/pull/2256))
|
||||
- Develop: 添加 git attributes 定义 [@yanyongyu](https://github.com/yanyongyu) ([#2210](https://github.com/nonebot/nonebot2/pull/2210))
|
||||
|
||||
### 🍻 插件发布
|
||||
|
||||
- Plugin: 文心一言 [@noneflow](https://github.com/noneflow) ([#2342](https://github.com/nonebot/nonebot2/pull/2342))
|
||||
- Plugin: nonebot_plugin_group_whitelist [@noneflow](https://github.com/noneflow) ([#2320](https://github.com/nonebot/nonebot2/pull/2320))
|
||||
- Plugin: 森空岛明日方舟签到器 [@noneflow](https://github.com/noneflow) ([#2340](https://github.com/nonebot/nonebot2/pull/2340))
|
||||
- Plugin: 女装 ! [@noneflow](https://github.com/noneflow) ([#2337](https://github.com/nonebot/nonebot2/pull/2337))
|
||||
- Plugin: helper_plus [@noneflow](https://github.com/noneflow) ([#2324](https://github.com/nonebot/nonebot2/pull/2324))
|
||||
- Plugin: nonebot-plugin-souti [@noneflow](https://github.com/noneflow) ([#2334](https://github.com/nonebot/nonebot2/pull/2334))
|
||||
- Plugin: Alconna 帮助工具 [@noneflow](https://github.com/noneflow) ([#2326](https://github.com/nonebot/nonebot2/pull/2326))
|
||||
- Plugin: 消息伪造 [@noneflow](https://github.com/noneflow) ([#2312](https://github.com/nonebot/nonebot2/pull/2312))
|
||||
- Plugin: 二维码 [@noneflow](https://github.com/noneflow) ([#2302](https://github.com/nonebot/nonebot2/pull/2302))
|
||||
- Plugin: httpcat-状态猫 😺 [@noneflow](https://github.com/noneflow) ([#2306](https://github.com/nonebot/nonebot2/pull/2306))
|
||||
- Plugin: 雪豹闭嘴 [@noneflow](https://github.com/noneflow) ([#2300](https://github.com/nonebot/nonebot2/pull/2300))
|
||||
- Plugin: Nonebot Requests [@noneflow](https://github.com/noneflow) ([#2294](https://github.com/nonebot/nonebot2/pull/2294))
|
||||
- Plugin: 双向聊天插件 [@noneflow](https://github.com/noneflow) ([#2291](https://github.com/nonebot/nonebot2/pull/2291))
|
||||
- Plugin: 识别动漫 gal 角色 [@noneflow](https://github.com/noneflow) ([#2288](https://github.com/nonebot/nonebot2/pull/2288))
|
||||
- Plugin: arxiv 订阅 [@noneflow](https://github.com/noneflow) ([#2285](https://github.com/nonebot/nonebot2/pull/2285))
|
||||
- Plugin: SUDO [@noneflow](https://github.com/noneflow) ([#2277](https://github.com/nonebot/nonebot2/pull/2277))
|
||||
- Plugin: 消息推送插件 [@noneflow](https://github.com/noneflow) ([#2273](https://github.com/nonebot/nonebot2/pull/2273))
|
||||
- Plugin: 周易蓍草占卜 [@noneflow](https://github.com/noneflow) ([#2268](https://github.com/nonebot/nonebot2/pull/2268))
|
||||
- Plugin: 欧若可骰娘 [@noneflow](https://github.com/noneflow) ([#2266](https://github.com/nonebot/nonebot2/pull/2266))
|
||||
- Plugin: 科大讯飞星火大模型聊天 [@noneflow](https://github.com/noneflow) ([#2258](https://github.com/nonebot/nonebot2/pull/2258))
|
||||
- Plugin: 剑网三查询和推送 [@noneflow](https://github.com/noneflow) ([#2254](https://github.com/nonebot/nonebot2/pull/2254))
|
||||
- Plugin: Muteme(我禁我自己) [@noneflow](https://github.com/noneflow) ([#2252](https://github.com/nonebot/nonebot2/pull/2252))
|
||||
- Plugin: MC 版本更新检测 [@noneflow](https://github.com/noneflow) ([#2247](https://github.com/nonebot/nonebot2/pull/2247))
|
||||
- Plugin: KanonBot [@noneflow](https://github.com/noneflow) ([#2244](https://github.com/nonebot/nonebot2/pull/2244))
|
||||
- Plugin: CSGO 饰品查询机器人 [@noneflow](https://github.com/noneflow) ([#2225](https://github.com/nonebot/nonebot2/pull/2225))
|
||||
- Plugin: talk with poe ai [@noneflow](https://github.com/noneflow) ([#2230](https://github.com/nonebot/nonebot2/pull/2230))
|
||||
- Plugin: 命运方舟流浪商人卡牌刷新提示 [@noneflow](https://github.com/noneflow) ([#2234](https://github.com/nonebot/nonebot2/pull/2234))
|
||||
- Plugin: Savepic [@noneflow](https://github.com/noneflow) ([#2232](https://github.com/nonebot/nonebot2/pull/2232))
|
||||
- Plugin: 跨平台账户绑定 [@noneflow](https://github.com/noneflow) ([#2227](https://github.com/nonebot/nonebot2/pull/2227))
|
||||
- Plugin: Among US 中的 TOH 模组职业介绍 [@noneflow](https://github.com/noneflow) ([#2221](https://github.com/nonebot/nonebot2/pull/2221))
|
||||
- Plugin: NoneMeme [@noneflow](https://github.com/noneflow) ([#2219](https://github.com/nonebot/nonebot2/pull/2219))
|
||||
- Plugin: The World [@noneflow](https://github.com/noneflow) ([#2216](https://github.com/nonebot/nonebot2/pull/2216))
|
||||
- Plugin: Bot 上下线邮件通知 [@noneflow](https://github.com/noneflow) ([#2214](https://github.com/nonebot/nonebot2/pull/2214))
|
||||
- Plugin: bot 断连通知 [@noneflow](https://github.com/noneflow) ([#2212](https://github.com/nonebot/nonebot2/pull/2212))
|
||||
|
||||
### 🍻 机器人发布
|
||||
|
||||
- Bot: OCNbot [@noneflow](https://github.com/noneflow) ([#2261](https://github.com/nonebot/nonebot2/pull/2261))
|
||||
- Bot: 星见 Kirami [@noneflow](https://github.com/noneflow) ([#2263](https://github.com/nonebot/nonebot2/pull/2263))
|
||||
- Bot: 不正经的妹妹 [@noneflow](https://github.com/noneflow) ([#2249](https://github.com/nonebot/nonebot2/pull/2249))
|
||||
|
||||
### 🍻 适配器发布
|
||||
|
||||
- Adapter: Discord [@noneflow](https://github.com/noneflow) ([#2315](https://github.com/nonebot/nonebot2/pull/2315))
|
||||
- Adapter: RedProtocol [@noneflow](https://github.com/noneflow) ([#2239](https://github.com/nonebot/nonebot2/pull/2239))
|
||||
|
||||
## v2.0.1
|
||||
|
||||
### 🚀 新功能
|
||||
|
@@ -168,5 +168,25 @@
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.red",
|
||||
"project_link": "nonebot-adapter-red",
|
||||
"name": "RedProtocol",
|
||||
"desc": "QQNT RedProtocol 适配",
|
||||
"author": "zhaomaoniu",
|
||||
"homepage": "https://github.com/nonebot/adapter-red",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.discord",
|
||||
"project_link": "nonebot-adapter-discord",
|
||||
"name": "Discord",
|
||||
"desc": "Discord 官方 Bot 协议适配",
|
||||
"author": "CMHopeSunshine",
|
||||
"homepage": "https://github.com/nonebot/adapter-discord",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
}
|
||||
]
|
||||
|
@@ -541,5 +541,38 @@
|
||||
"homepage": "https://github.com/LambdaYH/MigangBot",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "不正经的妹妹",
|
||||
"desc": "一款功能丰富、简单易用、自定义性强、扩展性强的可爱的QQ娱乐机器人",
|
||||
"author": "itsevin",
|
||||
"homepage": "https://github.com/itsevin/sister_bot",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "星见Kirami",
|
||||
"desc": "🌟 读作 Kirami,写作星见,简明轻快的聊天机器人应用。",
|
||||
"author": "A-kirami",
|
||||
"homepage": "https://kiramibot.dev/",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "OCNbot",
|
||||
"desc": "OI Contest Notifier bot,一个可以推送洛谷、cf、atcoder、牛客比赛通知的bot",
|
||||
"author": "ACnoway",
|
||||
"homepage": "https://github.com/ACnoway/OCNbot",
|
||||
"tags": [
|
||||
{
|
||||
"label": "OI",
|
||||
"color": "#2fccff"
|
||||
},
|
||||
{
|
||||
"label": "ACM",
|
||||
"color": "#ff0004"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
||||
|
@@ -303,6 +303,31 @@
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_alconna",
|
||||
"project_link": "nonebot-plugin-alconna",
|
||||
"name": "Alconna 命令工具",
|
||||
"desc": "提供一系列工具以在 nonebot 下使用 Alconna 拓展命令解析",
|
||||
"author": "RF-Tar-Railt",
|
||||
"homepage": "https://github.com/nonebot/plugin-alconna",
|
||||
"tags": [
|
||||
{
|
||||
"label": "matcher",
|
||||
"color": "#5280ea"
|
||||
},
|
||||
{
|
||||
"label": "command",
|
||||
"color": "#ea6f52"
|
||||
},
|
||||
{
|
||||
"label": "alconna",
|
||||
"color": "#5452ea"
|
||||
}
|
||||
],
|
||||
"is_official": true,
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_mcstatus",
|
||||
"project_link": "nonebot-plugin-mcstatus",
|
||||
@@ -565,18 +590,6 @@
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_heisi",
|
||||
"project_link": "nonebot-plugin-heisi",
|
||||
"name": "随机黑丝",
|
||||
"desc": "发送一张黑丝涩图,内置CD",
|
||||
"author": "yzyyz1387",
|
||||
"homepage": "https://github.com/yzyyz1387/nonebot_plugin_heisi",
|
||||
"tags": [],
|
||||
"is_official": false,
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_picsbank",
|
||||
"project_link": "nonebot-plugin-picsbank",
|
||||
@@ -3990,31 +4003,6 @@
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_alconna",
|
||||
"project_link": "nonebot-plugin-alconna",
|
||||
"name": "Alconna 命令工具",
|
||||
"desc": "提供一系列工具以在 nonebot 下使用 Alconna 拓展命令解析",
|
||||
"author": "RF-Tar-Railt",
|
||||
"homepage": "https://github.com/nonebot/plugin-alconna",
|
||||
"tags": [
|
||||
{
|
||||
"label": "matcher",
|
||||
"color": "#5280ea"
|
||||
},
|
||||
{
|
||||
"label": "command",
|
||||
"color": "#ea6f52"
|
||||
},
|
||||
{
|
||||
"label": "alconna",
|
||||
"color": "#5452ea"
|
||||
}
|
||||
],
|
||||
"is_official": true,
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot-plugin-mcport",
|
||||
"project_link": "nonebot-plugin-mcport",
|
||||
@@ -4643,52 +4631,6 @@
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "TeenStudy",
|
||||
"project_link": "teenstudy",
|
||||
"name": "青年大学习提交(Web UI)",
|
||||
"desc": "利用httpx库,向后台提交数据完成大学习,返回完成截图",
|
||||
"author": "ZM25XC",
|
||||
"homepage": "https://github.com/ZM25XC/TeenStudy",
|
||||
"tags": [
|
||||
{
|
||||
"label": "青年大学习",
|
||||
"color": "#e5ea52"
|
||||
},
|
||||
{
|
||||
"label": "httpx",
|
||||
"color": "#52ea5f"
|
||||
},
|
||||
{
|
||||
"label": "Web UI",
|
||||
"color": "#5295ea"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_auto_teenstudy",
|
||||
"project_link": "nonebot-plugin-auto-teenstudy",
|
||||
"name": "青年大学习提交(基础版)",
|
||||
"desc": "使用httpx向各地区大学习发送post请求完成大学习,返回完成截图",
|
||||
"author": "ZM25XC",
|
||||
"homepage": "https://github.com/ZM25XC/nonebot_plugin_auto_teenstudy",
|
||||
"tags": [
|
||||
{
|
||||
"label": "青年大学习",
|
||||
"color": "#e8f710"
|
||||
},
|
||||
{
|
||||
"label": "多地区",
|
||||
"color": "#108ef7"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_60s",
|
||||
"project_link": "nonebot-plugin-60s",
|
||||
@@ -5343,9 +5285,9 @@
|
||||
"module_name": "nonebot_plugin_bilichat",
|
||||
"project_link": "nonebot-plugin-bilichat",
|
||||
"name": "多功能 BiliBili 解析工具",
|
||||
"desc": "视频链接解析,并根据其内容生成基本信息、词云和使用 ChatGPT 进行内容总结",
|
||||
"author": "djkcyl",
|
||||
"homepage": "https://github.com/djkcyl/nonebot-plugin-bilichat",
|
||||
"desc": "多种B站链接解析,视频词云,AI总结,你想要的都在 bilichat",
|
||||
"author": "Well2333",
|
||||
"homepage": "https://github.com/Well2333/nonebot-plugin-bilichat",
|
||||
"tags": [
|
||||
{
|
||||
"label": "哔哩哔哩",
|
||||
@@ -5761,23 +5703,6 @@
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_grouplock",
|
||||
"project_link": "nonebot-plugin-grouplock",
|
||||
"name": "群聊人数锁定",
|
||||
"desc": "一个基于nonebot2和go-cqhttp的群聊人数锁定插件",
|
||||
"author": "ZM25XC",
|
||||
"homepage": "https://github.com/ZM25XC/nonebot-plugin-grouplock",
|
||||
"tags": [
|
||||
{
|
||||
"label": "群管",
|
||||
"color": "#52b5ea"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_callapi",
|
||||
"project_link": "nonebot-plugin-callapi",
|
||||
@@ -5839,18 +5764,6 @@
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_segbuilder",
|
||||
"project_link": "nonebot-plugin-segbuilder",
|
||||
"name": "雷神工业",
|
||||
"desc": "为不同的适配器提供更通用且简易的消息段构建方式",
|
||||
"author": "Well2333",
|
||||
"homepage": "https://github.com/Well2333/nonebot-plugin-segbuilder",
|
||||
"tags": [],
|
||||
"is_official": false,
|
||||
"type": null,
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_arkgacha",
|
||||
"project_link": "nonebot-plugin-arkgacha",
|
||||
@@ -6772,5 +6685,638 @@
|
||||
"is_official": false,
|
||||
"type": "library",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_disconnect_notice",
|
||||
"project_link": "nonebot-plugin-disconnect-notice",
|
||||
"name": "bot断连通知",
|
||||
"desc": "bot断连时的通知插件,当前支持邮件通知",
|
||||
"author": "Cypas",
|
||||
"homepage": "https://github.com/Cypas/nonebot_plugin_disconnect_notice",
|
||||
"tags": [
|
||||
{
|
||||
"label": "掉线通知",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "邮件",
|
||||
"color": "#52eaa4"
|
||||
},
|
||||
{
|
||||
"label": "通知",
|
||||
"color": "#cbea52"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_BotMailNotice",
|
||||
"project_link": "nonebot-plugin-BotMailNotice",
|
||||
"name": "Bot上下线邮件通知",
|
||||
"desc": "Bot上下线发送邮件通知的插件",
|
||||
"author": "ZM25XC",
|
||||
"homepage": "https://github.com/ZM25XC/BotMailNotice",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Mail",
|
||||
"color": "#52e5ea"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_theworld",
|
||||
"project_link": "nonebot-plugin-theworld",
|
||||
"name": "The World",
|
||||
"desc": "「ザ・ワールド」ッ! 時よ止まれ!",
|
||||
"author": "A-kirami",
|
||||
"homepage": "https://github.com/A-kirami/nonebot-plugin-theworld",
|
||||
"tags": [
|
||||
{
|
||||
"label": "JOJO",
|
||||
"color": "#75147c"
|
||||
},
|
||||
{
|
||||
"label": "DIO",
|
||||
"color": "#f9d849"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_nonememe",
|
||||
"project_link": "nonebot-plugin-nonememe",
|
||||
"name": "NoneMeme",
|
||||
"desc": "NoneBot 群大佬的日常",
|
||||
"author": "lgc2333",
|
||||
"homepage": "https://github.com/lgc-NB2Dev/nonebot-plugin-nonememe",
|
||||
"tags": [
|
||||
{
|
||||
"label": "NoneMeme",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": [
|
||||
"nonebot.adapters.kaiheila",
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
"nonebot.adapters.qqguild",
|
||||
"nonebot.adapters.telegram"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_aujob",
|
||||
"project_link": "nonebot-plugin-aujob",
|
||||
"name": "Among US中的TOH模组职业介绍",
|
||||
"desc": "查询TOH模组里职业的玩法描述",
|
||||
"author": "qwqZYLqwq",
|
||||
"homepage": "https://github.com/qwqZYLqwq/nonebot-plugin-aujob",
|
||||
"tags": [
|
||||
{
|
||||
"label": "among us",
|
||||
"color": "#48d5bf"
|
||||
},
|
||||
{
|
||||
"label": "TOH",
|
||||
"color": "#05c4f2"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_bind",
|
||||
"project_link": "nonebot-plugin-bind",
|
||||
"name": "跨平台账户绑定",
|
||||
"desc": "nonebot多适配器通用的跨平台账户绑定插件",
|
||||
"author": "canxin121",
|
||||
"homepage": "https://github.com/canxin121/nonebot_plugin_bind",
|
||||
"tags": [
|
||||
{
|
||||
"label": "跨平台",
|
||||
"color": "#5289ea"
|
||||
},
|
||||
{
|
||||
"label": "账户绑定",
|
||||
"color": "#6eb428"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "library",
|
||||
"supported_adapters": [
|
||||
"nonebot.adapters.feishu",
|
||||
"nonebot.adapters.kaiheila",
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.telegram",
|
||||
"nonebot.adapters.villa"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_savepic",
|
||||
"project_link": "nonebot-plugin-savepic",
|
||||
"name": "Savepic",
|
||||
"desc": "表情包保存",
|
||||
"author": "Yan-Zero",
|
||||
"homepage": "https://github.com/Yan-Zero/nonebot-plugin-savepic",
|
||||
"tags": [
|
||||
{
|
||||
"label": "表情包",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_lostark_wandering_trader",
|
||||
"project_link": "nonebot-plugin-lostark-wandering-trader",
|
||||
"name": "命运方舟流浪商人卡牌刷新提示",
|
||||
"desc": "国服命运方舟流浪商人稀有史诗传说卡牌刷新提示",
|
||||
"author": "EmiyaGm",
|
||||
"homepage": "https://github.com/EmiyaGm/nonebot-plugin-lostark-wandering-trader",
|
||||
"tags": [
|
||||
{
|
||||
"label": "命运方舟",
|
||||
"color": "#5289ea"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_csornament",
|
||||
"project_link": "nonebot-plugin-csornament",
|
||||
"name": "CSGO饰品查询机器人",
|
||||
"desc": "一款模拟查找饰品价格的机器人",
|
||||
"author": "Sydrr0",
|
||||
"homepage": "https://github.com/Sydrr0/nonebot-plugin-csornament",
|
||||
"tags": [
|
||||
{
|
||||
"label": "CS:GO",
|
||||
"color": "#047b97"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_kanonbot",
|
||||
"project_link": "nonebot-plugin-kanonbot",
|
||||
"name": "KanonBot",
|
||||
"desc": "KanonBot for Nonebot2",
|
||||
"author": "SuperGuGuGu",
|
||||
"homepage": "https://github.com/SuperGuGuGu/nonebot_plugin_kanonbot",
|
||||
"tags": [
|
||||
{
|
||||
"label": "KanonBot",
|
||||
"color": "#44ddff"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_mcversion",
|
||||
"project_link": "nonebot-plugin-mcversion",
|
||||
"name": "MC版本更新检测",
|
||||
"desc": "一个用于检测MC最新版本的插件",
|
||||
"author": "CN171-1",
|
||||
"homepage": "https://github.com/CN171-1/nonebot_plugin_mcversion",
|
||||
"tags": [
|
||||
{
|
||||
"label": "版本",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "MC",
|
||||
"color": "#52e7ea"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_muteme",
|
||||
"project_link": "nonebot-plugin-muteme",
|
||||
"name": "Muteme(我禁我自己)",
|
||||
"desc": "高仿@能干辉的muteme,我禁我自己",
|
||||
"author": "XTxiaoting14332",
|
||||
"homepage": "https://github.com/XTxiaoting14332/nonebot-plugin-muteme",
|
||||
"tags": [
|
||||
{
|
||||
"label": "禁言",
|
||||
"color": "#e45b8d"
|
||||
},
|
||||
{
|
||||
"label": "muteme",
|
||||
"color": "#5bc2e4"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_jx3",
|
||||
"project_link": "nonebot-plugin-jx3",
|
||||
"name": "剑网三查询和推送",
|
||||
"desc": "是一个使用 NoneBot 框架编写的插件,提供多种剑网三功能如日常查询,预测,金价查询,鲜花,公告,沙盘,jjc,黑市,骚话,奇遇,招募以及多种消息推送功能。",
|
||||
"author": "fuyang0811",
|
||||
"homepage": "https://github.com/fuyang0811/nonebot-plugin-jx3",
|
||||
"tags": [
|
||||
{
|
||||
"label": "剑网三",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "jx3",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_xinghuo_api",
|
||||
"project_link": "nonebot-plugin-xinghuo-api",
|
||||
"name": "科大讯飞星火大模型聊天",
|
||||
"desc": "Nonebot框架下的科大讯飞星火大模型聊天插件",
|
||||
"author": "Alpaca4610",
|
||||
"homepage": "https://github.com/Alpaca4610/nonebot-plugin-xinghuo-api",
|
||||
"tags": [
|
||||
{
|
||||
"label": "AI",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "Chat",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "dicergirl",
|
||||
"project_link": "dicergirl",
|
||||
"name": "欧若可骰娘",
|
||||
"desc": "完善的跑团机器人, 支持 COC/DND/SCP 等跑团模式.",
|
||||
"author": "fu050409",
|
||||
"homepage": "https://gitee.com/unvisitor/dicer",
|
||||
"tags": [
|
||||
{
|
||||
"label": "跑团",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_uvdiviner",
|
||||
"project_link": "nonebot-plugin-uvdiviner",
|
||||
"name": "周易蓍草占卜",
|
||||
"desc": "基于古蓍草占卜的算法, 简单的使用`.divine`指令.",
|
||||
"author": "fu050409",
|
||||
"homepage": "https://gitee.com/unvisitor/nonebot-plugin-uvdiviner",
|
||||
"tags": [
|
||||
{
|
||||
"label": "占卜",
|
||||
"color": "#440e0e"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_push",
|
||||
"project_link": "nonebot-plugin-push",
|
||||
"name": "消息推送插件",
|
||||
"desc": "通过邮件、Feishu Webhook 等方式推送消息",
|
||||
"author": "mobyw",
|
||||
"homepage": "https://github.com/mobyw/nonebot-plugin-push",
|
||||
"tags": [
|
||||
{
|
||||
"label": "邮件",
|
||||
"color": "#91c0bd"
|
||||
},
|
||||
{
|
||||
"label": "飞书",
|
||||
"color": "#7aaccc"
|
||||
},
|
||||
{
|
||||
"label": "推送",
|
||||
"color": "#9e97c4"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "library",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_sudo",
|
||||
"project_link": "nonebot-plugin-sudo",
|
||||
"name": "SUDO",
|
||||
"desc": "以指定用户身份执行命令",
|
||||
"author": "This-is-XiaoDeng",
|
||||
"homepage": "https://github.com/This-is-XiaoDeng/nonebot-plugin-sudo",
|
||||
"tags": [],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_arxivRSS",
|
||||
"project_link": "nonebot-plugin-arxivRSS",
|
||||
"name": "arxiv订阅",
|
||||
"desc": "订阅arxiv指定领域每天更新的论文",
|
||||
"author": "LuckySJTU",
|
||||
"homepage": "https://github.com/LuckySJTU/nonebot-plugin-arxivRSS/",
|
||||
"tags": [
|
||||
{
|
||||
"label": "arxiv",
|
||||
"color": "#f30f0f"
|
||||
},
|
||||
{
|
||||
"label": "RSS",
|
||||
"color": "#70a7d8"
|
||||
},
|
||||
{
|
||||
"label": "论文",
|
||||
"color": "#f541dc"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_anime_trace",
|
||||
"project_link": "nonebot-plugin-anime-trace",
|
||||
"name": "识别动漫gal角色",
|
||||
"desc": "通过ai.animedb.cn的api识别动漫、galgame角色",
|
||||
"author": "tomorinao-www",
|
||||
"homepage": "https://github.com/tomorinao-www/nonebot-plugin-anime-trace",
|
||||
"tags": [
|
||||
{
|
||||
"label": "AI",
|
||||
"color": "#f541dc"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_send_message",
|
||||
"project_link": "nonebot-plugin-send-message",
|
||||
"name": "双向聊天插件",
|
||||
"desc": "一个支持双向聊天的传话插件,可用于临时聊天而不想加好友场景",
|
||||
"author": "bingqiu456",
|
||||
"homepage": "https://github.com/bingqiu456/nonebot_plugin_send_message",
|
||||
"tags": [
|
||||
{
|
||||
"label": "传话",
|
||||
"color": "#52ea75"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_requests",
|
||||
"project_link": "nonebot-plugin-requests",
|
||||
"name": "Nonebot Requests",
|
||||
"desc": "封装 ForwardDriver 实现 HttpClient 功能",
|
||||
"author": "Ailitonia",
|
||||
"homepage": "https://github.com/Ailitonia/nonebot-plugin-requests",
|
||||
"tags": [
|
||||
{
|
||||
"label": "requests",
|
||||
"color": "#80bcc2"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "library",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_shutup",
|
||||
"project_link": "nonebot-plugin-shutup",
|
||||
"name": "雪豹闭嘴",
|
||||
"desc": "基于 NoneBot2 的 插件,用于机器人当前会话闭嘴,支持全适配器",
|
||||
"author": "Agnes4m",
|
||||
"homepage": "https://github.com/Agnes4m/nonebot_pluigin_shutup",
|
||||
"tags": [
|
||||
{
|
||||
"label": "阻断",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_simple_httpcat",
|
||||
"project_link": "nonebot-plugin-simple-httpcat",
|
||||
"name": "httpcat-状态猫😺",
|
||||
"desc": "简单粗暴的httpcat插件,参考了zjkwdy大佬的weather_lite插件",
|
||||
"author": "XTxiaoting14332",
|
||||
"homepage": "https://github.com/XTxiaoting14332/nonebot-plugin-simple-httpcat",
|
||||
"tags": [
|
||||
{
|
||||
"label": "httpcat",
|
||||
"color": "#5dbfc8"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_qrcode2",
|
||||
"project_link": "nonebot-plugin-qrcode2",
|
||||
"name": "二维码",
|
||||
"desc": "通过pyzbar解析二维码",
|
||||
"author": "tomorinao-www",
|
||||
"homepage": "https://github.com/tomorinao-www/nonebot-plugin-qrcode2",
|
||||
"tags": [
|
||||
{
|
||||
"label": "qrcode",
|
||||
"color": "#f541dc"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_fakemsg",
|
||||
"project_link": "nonebot-plugin-fakemsg",
|
||||
"name": "消息伪造",
|
||||
"desc": "伪造消息",
|
||||
"author": "Cvandia",
|
||||
"homepage": "https://github.com/Cvandia/nonebot-plugin-fakemsg",
|
||||
"tags": [
|
||||
{
|
||||
"label": "合并转发",
|
||||
"color": "#1ae32c"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_alchelper",
|
||||
"project_link": "nonebot-plugin-alchelper",
|
||||
"name": "Alconna 帮助工具",
|
||||
"desc": "基于 nonebot-plugin-alconna,给出所有命令帮助以及统计",
|
||||
"author": "RF-Tar-Railt",
|
||||
"homepage": "https://github.com/RF-Tar-Railt/nonebot-plugin-alchelper",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Alconna",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_souti",
|
||||
"project_link": "nonebot-plugin-souti",
|
||||
"name": "nonebot-plugin-souti",
|
||||
"desc": "使用百度不挂科题库, 可以实现文字搜题功能",
|
||||
"author": "xiaoWangSec",
|
||||
"homepage": "https://github.com/xiaoWangSec/nonebot-plugin-souti",
|
||||
"tags": [
|
||||
{
|
||||
"label": "搜题",
|
||||
"color": "#49e3d5"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_helper_plus",
|
||||
"project_link": "nonebot-plugin-helper-plus",
|
||||
"name": "helper_plus",
|
||||
"desc": "带有指令调用控制的高级helper",
|
||||
"author": "fR0Z863xF",
|
||||
"homepage": "https://github.com/fR0Z863xF/nonebot-plugin-helper-plus",
|
||||
"tags": [
|
||||
{
|
||||
"label": "帮助",
|
||||
"color": "#eae152"
|
||||
},
|
||||
{
|
||||
"label": "命令控制",
|
||||
"color": "#52ea5c"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_wearskirt",
|
||||
"project_link": "nonebot-plugin-wearskirt",
|
||||
"name": "女装 !",
|
||||
"desc": "Nonebot 赛博女装插件",
|
||||
"author": "Lfhsheng",
|
||||
"homepage": "https://github.com/Lfhsheng/nonebot-plugin-wearskirt",
|
||||
"tags": [
|
||||
{
|
||||
"label": "女装",
|
||||
"color": "#ffc0cb"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_skland_arksign",
|
||||
"project_link": "nonebot-plugin-skland-arksign",
|
||||
"name": "森空岛明日方舟签到器",
|
||||
"desc": "私聊机器人以获得自动明日方舟森空岛签到服务",
|
||||
"author": "GuGuMur",
|
||||
"homepage": "https://github.com/GuGuMur/nonebot-plugin-skland-arksign",
|
||||
"tags": [
|
||||
{
|
||||
"label": "森空岛",
|
||||
"color": "#c8eb21"
|
||||
},
|
||||
{
|
||||
"label": "明日方舟",
|
||||
"color": "#111111"
|
||||
},
|
||||
{
|
||||
"label": "签到",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": [
|
||||
"nonebot.adapters.kaiheila",
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
"nonebot.adapters.qqguild",
|
||||
"nonebot.adapters.telegram"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_group_whitelist",
|
||||
"project_link": "nonebot-plugin-group-whitelist",
|
||||
"name": "nonebot_plugin_group_whitelist",
|
||||
"desc": "简易群聊白名单",
|
||||
"author": "Rikka-desu",
|
||||
"homepage": "https://github.com/Rikka-desu/nonebot_plugin_group_whitelist",
|
||||
"tags": [],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": ["nonebot.adapters.onebot.v11"]
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_ernie",
|
||||
"project_link": "nonebot-plugin-ernie",
|
||||
"name": "文心一言",
|
||||
"desc": "Nonebot框架下的文心一言聊天插件",
|
||||
"author": "Noctulus",
|
||||
"homepage": "https://github.com/Noctulus/nonebot-plugin-ernie",
|
||||
"tags": [
|
||||
{
|
||||
"label": "文心一言",
|
||||
"color": "#2e317c"
|
||||
}
|
||||
],
|
||||
"is_official": false,
|
||||
"type": "application",
|
||||
"supported_adapters": null
|
||||
}
|
||||
]
|
||||
|
@@ -208,7 +208,7 @@ async def _(e: Union[ActionFailed, NetworkError]): ...
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
|
||||
```python {4,16}
|
||||
```python {5,15}
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot import on_command
|
||||
@@ -230,7 +230,7 @@ async def _(event: Annotated[Event, Depends(check)]):
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {2,14}
|
||||
```python {3,13}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
@@ -256,7 +256,7 @@ async def _(event: Event = Depends(check)):
|
||||
|
||||
特别的,我们可以为 `Dependent` 对象定义一系列前置子依赖,它们会在参数执行前被顺序执行,且返回值将会被忽略,例如:
|
||||
|
||||
```python {2,14}
|
||||
```python {11}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
|
@@ -453,7 +453,7 @@ nonebot.init(superusers={"123123123"})
|
||||
- **类型**: `set[str]`
|
||||
- **默认值**: `set()`
|
||||
|
||||
机器人昵称,通常协议适配器会根据用户是否 @user 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。
|
||||
机器人昵称,通常协议适配器会根据用户是否 @bot 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
@@ -374,6 +374,14 @@ async def _(matcher: Matcher):
|
||||
|
||||
`get_last_receive` 操作接受一个可选的 default 参数。当事件不存在时,将返回 default 或 `None`。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@matcher.handle()
|
||||
async def _(matcher: Matcher):
|
||||
event = matcher.get_last_receive(default=None)
|
||||
```
|
||||
|
||||
### set_receive
|
||||
|
||||
设置 / 覆盖一个 `receive` 接收的事件。
|
||||
|
@@ -38,9 +38,9 @@ options:
|
||||
在以下的示例中,为了更好的理解多种类型的消息组成方式,我们将使用 `Console` 协议适配器来演示消息序列的使用方法。在实际使用中,你需要确保你使用的**消息序列类型**与你所要发送的**平台类型**一致。
|
||||
:::
|
||||
|
||||
通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过依赖注入 [`EventMessage`](../advanced/dependency.mdx#eventmessage), 或者使用 `event.get_message()` 获取。
|
||||
通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过依赖注入 [`EventMessage`](../advanced/dependency.mdx#eventmessage),或者使用 `event.get_message()` 获取。
|
||||
|
||||
由于消息序列是 `List[MessageSegment]` 的子类, 所以你总是可以用和操作 `List` 类似的方式来处理消息序列。例如:
|
||||
由于消息序列是 `List[MessageSegment]` 的子类,所以你总是可以用和操作 `List` 类似的方式来处理消息序列。例如:
|
||||
|
||||
```python
|
||||
>>> from nonebot.adapters.console import Message, MessageSegment
|
||||
@@ -278,14 +278,14 @@ msg == Message(
|
||||
|
||||
### 使用消息模板
|
||||
|
||||
为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列
|
||||
为了提供安全可靠的跨平台模板字符,我们提供了一个消息模板功能来构建消息序列
|
||||
|
||||
它在以下常见场景中尤其有用:
|
||||
它在以下常见场景中尤其有用:
|
||||
|
||||
- 多行富文本编排(包含图片,文字以及表情等)
|
||||
- 多行富文本编排(包含图片,文字以及表情等)
|
||||
- 客制化(由 Bot 最终用户提供消息模板时)
|
||||
|
||||
在事实上, 它的用法和 `str.format` 极为相近, 所以你在使用的时候, 总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果,这里给出几个简单的例子。
|
||||
在事实上,它的用法和 `str.format` 极为相近,所以你在使用的时候,总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果,这里给出几个简单的例子。
|
||||
|
||||
默认情况下,消息模板采用 `str` 纯文本形式的格式化:
|
||||
|
||||
|
@@ -1,134 +0,0 @@
|
||||
---
|
||||
sidebar_position: 9
|
||||
description: nonebot.consts 模块
|
||||
---
|
||||
|
||||
# nonebot.consts
|
||||
|
||||
本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
||||
|
||||
## _var_ `RECEIVE_KEY` {#RECEIVE_KEY}
|
||||
|
||||
- **类型:** Literal['_receive_{id}']
|
||||
|
||||
- **说明:** `receive` 存储 key
|
||||
|
||||
## _var_ `LAST_RECEIVE_KEY` {#LAST_RECEIVE_KEY}
|
||||
|
||||
- **类型:** Literal['_last_receive']
|
||||
|
||||
- **说明:** `last_receive` 存储 key
|
||||
|
||||
## _var_ `ARG_KEY` {#ARG_KEY}
|
||||
|
||||
- **类型:** Literal['{key}']
|
||||
|
||||
- **说明:** `arg` 存储 key
|
||||
|
||||
## _var_ `REJECT_TARGET` {#REJECT_TARGET}
|
||||
|
||||
- **类型:** Literal['_current_target']
|
||||
|
||||
- **说明:** 当前 `reject` 目标存储 key
|
||||
|
||||
## _var_ `REJECT_CACHE_TARGET` {#REJECT_CACHE_TARGET}
|
||||
|
||||
- **类型:** Literal['_next_target']
|
||||
|
||||
- **说明:** 下一个 `reject` 目标存储 key
|
||||
|
||||
## _var_ `PREFIX_KEY` {#PREFIX_KEY}
|
||||
|
||||
- **类型:** Literal['_prefix']
|
||||
|
||||
- **说明:** 命令前缀存储 key
|
||||
|
||||
## _var_ `CMD_KEY` {#CMD_KEY}
|
||||
|
||||
- **类型:** Literal['command']
|
||||
|
||||
- **说明:** 命令元组存储 key
|
||||
|
||||
## _var_ `RAW_CMD_KEY` {#RAW_CMD_KEY}
|
||||
|
||||
- **类型:** Literal['raw_command']
|
||||
|
||||
- **说明:** 命令文本存储 key
|
||||
|
||||
## _var_ `CMD_ARG_KEY` {#CMD_ARG_KEY}
|
||||
|
||||
- **类型:** Literal['command_arg']
|
||||
|
||||
- **说明:** 命令参数存储 key
|
||||
|
||||
## _var_ `CMD_START_KEY` {#CMD_START_KEY}
|
||||
|
||||
- **类型:** Literal['command_start']
|
||||
|
||||
- **说明:** 命令开头存储 key
|
||||
|
||||
## _var_ `CMD_WHITESPACE_KEY` {#CMD_WHITESPACE_KEY}
|
||||
|
||||
- **类型:** Literal['command_whitespace']
|
||||
|
||||
- **说明:** 命令与参数间空白符存储 key
|
||||
|
||||
## _var_ `SHELL_ARGS` {#SHELL_ARGS}
|
||||
|
||||
- **类型:** Literal['_args']
|
||||
|
||||
- **说明:** shell 命令 parse 后参数字典存储 key
|
||||
|
||||
## _var_ `SHELL_ARGV` {#SHELL_ARGV}
|
||||
|
||||
- **类型:** Literal['_argv']
|
||||
|
||||
- **说明:** shell 命令原始参数列表存储 key
|
||||
|
||||
## _var_ `REGEX_MATCHED` {#REGEX_MATCHED}
|
||||
|
||||
- **类型:** Literal['_matched']
|
||||
|
||||
- **说明:** 正则匹配结果存储 key
|
||||
|
||||
## _var_ `REGEX_STR` {#REGEX_STR}
|
||||
|
||||
- **类型:** Literal['_matched_str']
|
||||
|
||||
- **说明:** 正则匹配文本存储 key
|
||||
|
||||
## _var_ `REGEX_GROUP` {#REGEX_GROUP}
|
||||
|
||||
- **类型:** Literal['_matched_groups']
|
||||
|
||||
- **说明:** 正则匹配 group 元组存储 key
|
||||
|
||||
## _var_ `REGEX_DICT` {#REGEX_DICT}
|
||||
|
||||
- **类型:** 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
|
@@ -1,79 +0,0 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: nonebot.message 模块
|
||||
---
|
||||
|
||||
# nonebot.message
|
||||
|
||||
本模块定义了事件处理主要流程。
|
||||
|
||||
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
|
||||
|
||||
## _def_ `event_preprocessor(func)` {#event_preprocessor}
|
||||
|
||||
- **说明:** 事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_EventPreProcessor](typing.md#T_EventPreProcessor))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_EventPreProcessor](typing.md#T_EventPreProcessor)
|
||||
|
||||
## _def_ `event_postprocessor(func)` {#event_postprocessor}
|
||||
|
||||
- **说明:** 事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_EventPostProcessor](typing.md#T_EventPostProcessor))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_EventPostProcessor](typing.md#T_EventPostProcessor)
|
||||
|
||||
## _def_ `run_preprocessor(func)` {#run_preprocessor}
|
||||
|
||||
- **说明:** 运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_RunPreProcessor](typing.md#T_RunPreProcessor))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_RunPreProcessor](typing.md#T_RunPreProcessor)
|
||||
|
||||
## _def_ `run_postprocessor(func)` {#run_postprocessor}
|
||||
|
||||
- **说明:** 运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_RunPostProcessor](typing.md#T_RunPostProcessor))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_RunPostProcessor](typing.md#T_RunPostProcessor)
|
||||
|
||||
## _async def_ `handle_event(bot, event)` {#handle_event}
|
||||
|
||||
- **说明:** 处理一个事件。调用该函数以实现分发事件。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `bot` ([Bot](adapters/index.md#Bot)): Bot 对象
|
||||
|
||||
- `event` ([Event](adapters/index.md#Event)): Event 对象
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
- **用法**
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
asyncio.create_task(handle_event(bot, event))
|
||||
```
|
@@ -1,6 +0,0 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: 编写适配器对接新的平台
|
||||
---
|
||||
|
||||
# 编写适配器
|
@@ -1,6 +0,0 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: 在商店发布自己的插件
|
||||
---
|
||||
|
||||
# 发布插件
|
@@ -219,7 +219,7 @@ async def _(e: Union[ActionFailed, NetworkError]): ...
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
|
||||
```python {4,16}
|
||||
```python {5,15}
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot import on_command
|
||||
@@ -241,7 +241,7 @@ async def _(event: Annotated[Event, Depends(check)]):
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {2,14}
|
||||
```python {3,13}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
@@ -267,7 +267,7 @@ async def _(event: Event = Depends(check)):
|
||||
|
||||
特别的,我们可以为 `Dependent` 对象定义一系列前置子依赖,它们会在参数执行前被顺序执行,且返回值将会被忽略,例如:
|
||||
|
||||
```python {2,14}
|
||||
```python {11}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
|
@@ -482,7 +482,7 @@ nonebot.init(superusers={"123123123"})
|
||||
- **类型**: `set[str]`
|
||||
- **默认值**: `set()`
|
||||
|
||||
机器人昵称,通常协议适配器会根据用户是否 @user 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。
|
||||
机器人昵称,通常协议适配器会根据用户是否 @bot 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
@@ -374,6 +374,14 @@ async def _(matcher: Matcher):
|
||||
|
||||
`get_last_receive` 操作接受一个可选的 default 参数。当事件不存在时,将返回 default 或 `None`。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@matcher.handle()
|
||||
async def _(matcher: Matcher):
|
||||
event = matcher.get_last_receive(default=None)
|
||||
```
|
||||
|
||||
### set_receive
|
||||
|
||||
设置 / 覆盖一个 `receive` 接收的事件。
|
||||
|
@@ -38,9 +38,9 @@ options:
|
||||
在以下的示例中,为了更好的理解多种类型的消息组成方式,我们将使用 `Console` 协议适配器来演示消息序列的使用方法。在实际使用中,你需要确保你使用的**消息序列类型**与你所要发送的**平台类型**一致。
|
||||
:::
|
||||
|
||||
通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过依赖注入 [`EventMessage`](../advanced/dependency.mdx#eventmessage), 或者使用 `event.get_message()` 获取。
|
||||
通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过依赖注入 [`EventMessage`](../advanced/dependency.mdx#eventmessage),或者使用 `event.get_message()` 获取。
|
||||
|
||||
由于消息序列是 `List[MessageSegment]` 的子类, 所以你总是可以用和操作 `List` 类似的方式来处理消息序列。例如:
|
||||
由于消息序列是 `List[MessageSegment]` 的子类,所以你总是可以用和操作 `List` 类似的方式来处理消息序列。例如:
|
||||
|
||||
```python
|
||||
>>> from nonebot.adapters.console import Message, MessageSegment
|
||||
@@ -278,14 +278,14 @@ msg == Message(
|
||||
|
||||
### 使用消息模板
|
||||
|
||||
为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列
|
||||
为了提供安全可靠的跨平台模板字符,我们提供了一个消息模板功能来构建消息序列
|
||||
|
||||
它在以下常见场景中尤其有用:
|
||||
它在以下常见场景中尤其有用:
|
||||
|
||||
- 多行富文本编排(包含图片,文字以及表情等)
|
||||
- 多行富文本编排(包含图片,文字以及表情等)
|
||||
- 客制化(由 Bot 最终用户提供消息模板时)
|
||||
|
||||
在事实上, 它的用法和 `str.format` 极为相近, 所以你在使用的时候, 总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果,这里给出几个简单的例子。
|
||||
在事实上,它的用法和 `str.format` 极为相近,所以你在使用的时候,总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果,这里给出几个简单的例子。
|
||||
|
||||
默认情况下,消息模板采用 `str` 纯文本形式的格式化:
|
||||
|
||||
|
@@ -35,7 +35,7 @@ driver = nonebot.get_driver()
|
||||
driver.register_adapter(Adapter)
|
||||
```
|
||||
|
||||
我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。
|
||||
我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。如果我们需要多平台支持,可以多次调用 `register_adapter` 方法来注册多个适配器。
|
||||
|
||||
## 获取已注册的适配器
|
||||
|
@@ -71,7 +71,9 @@ async def _(foo: str = "bar"): ...
|
||||
|
||||
获取当前事件的 Bot 对象。
|
||||
|
||||
通过标注参数为 `Bot` 类型,或者一系列 `Bot` 类型,即可获取到当前事件的 Bot 对象。为兼容性考虑,如果参数名为 `bot` 且无类型注解,也会视为 `Bot` 依赖注入。
|
||||
通过标注参数为 `Bot` 类型,或者一系列 `Bot` 类型,即可获取到当前事件的 Bot 对象。为兼容性考虑,如果参数名为 `bot` 且无类型注解,也会视为 Bot 依赖注入。
|
||||
|
||||
Bot 依赖注入支持重载(即:可以标注参数为子类型)且具有[重载优先检查权](../appendices/overload.md#重载)。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.10" label="Python 3.10+" default>
|
||||
@@ -108,7 +110,9 @@ async def _(bot): ... # 兼容性处理
|
||||
|
||||
获取当前事件。
|
||||
|
||||
通过标注参数为 `Event` 类型,或者一系列 `Event` 类型,即可获取到当前事件。为兼容性考虑,如果参数名为 `event` 且无类型注解,也会视为 `Event` 依赖注入。
|
||||
通过标注参数为 `Event` 类型,或者一系列 `Event` 类型,即可获取到当前事件。为兼容性考虑,如果参数名为 `event` 且无类型注解,也会视为 Event 依赖注入。
|
||||
|
||||
Event 依赖注入支持重载(即:可以标注参数为子类型)且具有[重载优先检查权](../appendices/overload.md#重载)。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.10" label="Python 3.10+" default>
|
||||
@@ -143,6 +147,8 @@ async def _(event): ... # 兼容性处理
|
||||
|
||||
获取当前[会话状态](../appendices/session-state.md)。
|
||||
|
||||
通过标注参数为 `T_State` 类型,即可获取到当前会话状态。为兼容性考虑,如果参数名为 `state` 且无类型注解,也会视为 State 依赖注入。
|
||||
|
||||
```python
|
||||
from nonebot.typing import T_State
|
||||
|
||||
@@ -153,10 +159,15 @@ async def _(foo: T_State): ...
|
||||
|
||||
获取当前事件响应器实例。常用于使用[事件响应器操作](../appendices/session-control.mdx)。
|
||||
|
||||
通过标注参数为 `Matcher` 类型,或者一系列 `Matcher` 类型,即可获取到当前事件。为兼容性考虑,如果参数名为 `matcher` 且无类型注解,也会视为 Matcher 依赖注入。
|
||||
|
||||
Matcher 依赖注入支持重载(即:可以标注参数为子类型)且具有[重载优先检查权](../appendices/overload.md#重载)。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
async def _(matcher: Matcher): ...
|
||||
async def _(foo: Matcher): ...
|
||||
async def _(matcher): ... # 兼容性处理
|
||||
```
|
||||
|
||||
### Exception
|
||||
@@ -208,44 +219,42 @@ async def _(e: Union[ActionFailed, NetworkError]): ...
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
|
||||
```python {4,16}
|
||||
```python {5,15}
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters.console import MessageEvent
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
async def check(event: MessageEvent, matcher: Matcher) -> MessageEvent:
|
||||
async def check(event: Event) -> Event:
|
||||
if event.get_user_id() in BLACKLIST:
|
||||
await matcher.finish()
|
||||
await test.finish()
|
||||
return event
|
||||
|
||||
@test.handle()
|
||||
async def _(event: Annotated[MessageEvent, Depends(check)]):
|
||||
async def _(event: Annotated[Event, Depends(check)]):
|
||||
...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {2,14}
|
||||
```python {3,13}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters.console import MessageEvent
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
async def check(event: MessageEvent, matcher: Matcher) -> MessageEvent:
|
||||
async def check(event: Event) -> Event:
|
||||
if event.get_user_id() in BLACKLIST:
|
||||
await matcher.finish()
|
||||
await test.finish()
|
||||
return event
|
||||
|
||||
@test.handle()
|
||||
async def _(event: MessageEvent = Depends(check)):
|
||||
async def _(event: Event = Depends(check)):
|
||||
...
|
||||
```
|
||||
|
||||
@@ -256,6 +265,24 @@ async def _(event: MessageEvent = Depends(check)):
|
||||
|
||||
通过将 `Depends` 包裹的子依赖作为参数的默认值,我们就可以在执行事件处理函数之前执行子依赖,并将其返回值作为参数传入事件处理函数。子依赖和普通的事件处理函数并没有区别,同样可以使用依赖注入,并且可以返回任何类型的值。但需要注意的是,如果事件处理函数参数的类型注解与子依赖返回值的类型**不一致**,将会触发[重载](../appendices/overload.md)而跳过当前事件处理函数。
|
||||
|
||||
特别的,我们可以为 `Dependent` 对象定义一系列前置子依赖,它们会在参数执行前被顺序执行,且返回值将会被忽略,例如:
|
||||
|
||||
```python {11}
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
async def check(event: Event):
|
||||
if event.get_user_id() in BLACKLIST:
|
||||
await test.finish()
|
||||
|
||||
@test.handle(parameterless=[Depends(check)])
|
||||
async def _():
|
||||
...
|
||||
```
|
||||
|
||||
### 依赖缓存
|
||||
|
||||
NoneBot 在执行子依赖时,会将其返回值缓存起来。当我们在使用子依赖时,`Depends` 具有一个参数 `use_cache`,默认为 `True`。此时在事件处理流程中,多次使用同一个子依赖时,将会使用缓存中的结果而不会重复执行。这在很多情景中非常有用,例如:
|
||||
@@ -326,6 +353,80 @@ async def _(x: int = Depends(random_result, use_cache=False)):
|
||||
缓存的生命周期与当前接收到的事件相同。接收到事件后,子依赖在首次执行时缓存,在该事件处理完成后,缓存就会被清除。
|
||||
:::
|
||||
|
||||
### 类型转换与校验
|
||||
|
||||
在依赖注入系统中,我们可以对子依赖的返回值进行自动类型转换与校验。这个功能由 Pydantic 支持,因此我们通过参数类型注解自动使用 Pydantic 支持的类型转换。例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
|
||||
```python {6,9}
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot.params import Depends
|
||||
from nonebot.adapters import Event
|
||||
|
||||
def get_user_id(event: Event) -> str:
|
||||
return event.get_user_id()
|
||||
|
||||
async def _(user_id: Annotated[int, Depends(get_user_id, validate=True)]):
|
||||
print(user_id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {4,7}
|
||||
from nonebot.params import Depends
|
||||
from nonebot.adapters import Event
|
||||
|
||||
def get_user_id(event: Event) -> str:
|
||||
return event.get_user_id()
|
||||
|
||||
async def _(user_id: int = Depends(get_user_id, validate=True)):
|
||||
print(user_id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
在进行类型自动转换的同时,Pydantic 还支持对数据进行更多的限制,如:大于、小于、长度等。使用方法如下:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
|
||||
```python {7,10}
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
from nonebot.params import Depends
|
||||
from nonebot.adapters import Event
|
||||
|
||||
def get_user_id(event: Event) -> str:
|
||||
return event.get_user_id()
|
||||
|
||||
async def _(user_id: Annotated[int, Depends(get_user_id, validate=Field(gt=100))]):
|
||||
print(user_id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {5,8}
|
||||
from pydantic import Field
|
||||
from nonebot.params import Depends
|
||||
from nonebot.adapters import Event
|
||||
|
||||
def get_user_id(event: Event) -> str:
|
||||
return event.get_user_id()
|
||||
|
||||
async def _(user_id: int = Depends(get_user_id, validate=Field(gt=100))):
|
||||
print(user_id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### 类作为依赖
|
||||
|
||||
在前面的事例中,我们使用了函数作为子依赖。实际上,我们还可以使用类作为依赖。当我们在实例化一个类的时候,其实我们就在调用它,类本身也是一个可调用对象。例如:
|
@@ -22,21 +22,22 @@ options:
|
||||
|
||||
## 驱动器类型
|
||||
|
||||
驱动器的类型有两种:
|
||||
驱动器类型大体上可以分为两种:
|
||||
|
||||
- `ForwardDriver`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。
|
||||
- `ReverseDriver`:即服务端型驱动器,多用于使用 WebHook,接收 WebSocket 客户端连接等情形。
|
||||
- `Forward`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。
|
||||
- `Reverse`:即服务端型驱动器,多用于使用 WebHook,接收 WebSocket 客户端连接等情形。
|
||||
|
||||
客户端型驱动器具有以下两种功能:
|
||||
客户端型驱动器可以分为以下两种:
|
||||
|
||||
1. 异步发送 HTTP 请求,自定义 `HTTP Method`、`URL`、`Header`、`Body`、`Cookie`、`Proxy`、`Timeout` 等。
|
||||
2. 异步建立 WebSocket 连接上下文,自定义 `WebSocket URL`、`Header`、`Cookie`、`Proxy`、`Timeout` 等。
|
||||
|
||||
服务端型驱动器通常为 ASGI 应用框架,具有以下功能:
|
||||
服务端型驱动器目前有:
|
||||
|
||||
1. 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
|
||||
2. 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
|
||||
3. 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。
|
||||
1. ASGI 应用框架,具有以下功能:
|
||||
- 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
|
||||
- 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
|
||||
- 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。
|
||||
|
||||
## 配置驱动器
|
||||
|
||||
@@ -79,7 +80,7 @@ DRIVER=~none
|
||||
|
||||
### FastAPI(默认)
|
||||
|
||||
**类型:**服务端驱动器
|
||||
**类型:**ASGI 服务端驱动器
|
||||
|
||||
> FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
|
||||
|
||||
@@ -185,7 +186,7 @@ nonebot.run(app="bot:app")
|
||||
|
||||
### Quart
|
||||
|
||||
**类型:**`ReverseDriver`
|
||||
**类型:**ASGI 服务端驱动器
|
||||
|
||||
> Quart is an asyncio reimplementation of the popular Flask microframework API.
|
||||
|
||||
@@ -249,7 +250,7 @@ nonebot.run(app="bot:app")
|
||||
|
||||
### HTTPX
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
**类型:**HTTP 客户端驱动器
|
||||
|
||||
:::warning 注意
|
||||
本驱动器仅支持 HTTP 请求,不支持 WebSocket 连接请求。
|
||||
@@ -263,7 +264,7 @@ DRIVER=~httpx
|
||||
|
||||
### websockets
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
**类型:**WebSocket 客户端驱动器
|
||||
|
||||
:::warning 注意
|
||||
本驱动器仅支持 WebSocket 连接请求,不支持 HTTP 请求。
|
||||
@@ -277,7 +278,7 @@ DRIVER=~websockets
|
||||
|
||||
### AIOHTTP
|
||||
|
||||
**类型:**`ForwardDriver`
|
||||
**类型:**HTTP/WebSocket 客户端驱动器
|
||||
|
||||
> [AIOHTTP](https://docs.aiohttp.org/): Asynchronous HTTP Client/Server for asyncio and Python.
|
||||
|
@@ -10,7 +10,11 @@ options:
|
||||
|
||||
# 事件响应器进阶
|
||||
|
||||
在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,以及内置的响应规则。
|
||||
在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,内置的响应规则,与第三方响应规则拓展。
|
||||
|
||||
:::tip 提示
|
||||
事件响应器允许继承,你可以通过直接继承 `Matcher` 类来创建一个新的事件响应器。
|
||||
:::
|
||||
|
||||
## 事件响应器组成
|
||||
|
||||
@@ -287,6 +291,19 @@ sub_cmd = group.command("sub")
|
||||
help_cmd = group.command("help")
|
||||
```
|
||||
|
||||
命令别名 aliases 默认不会添加 `CommandGroup` 设定的前缀,如果需要为 aliases 添加前缀,可以添加 `prefix_aliases=True` 参数:
|
||||
|
||||
```python
|
||||
from nonebot import CommandGroup
|
||||
|
||||
group = CommandGroup("cmd", prefix_aliases=True)
|
||||
|
||||
cmd = group.command(tuple())
|
||||
help_cmd = group.command("help", aliases={"帮助"})
|
||||
```
|
||||
|
||||
这样就能成功匹配 `/cmd`、`/cmd.help`、`/cmd.帮助` 命令。如果未设置,将默认匹配 `/cmd`、`/cmd.help`、`/帮助` 命令。
|
||||
|
||||
### `MatcherGroup`
|
||||
|
||||
`MatcherGroup` 可以用于管理一系列具有相同属性的响应器。
|
||||
@@ -302,3 +319,20 @@ group = MatcherGroup(rule=to_me())
|
||||
matcher1 = group.on_message()
|
||||
matcher2 = group.on_message()
|
||||
```
|
||||
|
||||
## 第三方响应规则
|
||||
|
||||
### Alconna
|
||||
|
||||
[`nonebot-plugin-alconna`](https://github.com/nonebot/plugin-alconna) 是一类提供了拓展响应规则的插件。
|
||||
该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
|
||||
是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||
|
||||
该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。
|
||||
|
||||
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
|
||||
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
|
||||
|
||||
该插件同时通过提供 `UniMessage` (通用消息模型) 实现了**跨平台接收和发送消息**的功能。
|
||||
|
||||
详情请阅读最佳实践中的 [命令解析拓展](../best-practice/alconna/README.mdx) 章节。
|
@@ -31,7 +31,7 @@ NoneBot 是一个插件化的框架,可以通过加载插件来扩展功能。
|
||||
|
||||
我们需要在插件顶层模块 `example/__init__.py` 中添加插件元数据,如下所示:
|
||||
|
||||
```python {1,5-11} title=example/__init__.py
|
||||
```python {1,5-12} title=example/__init__.py
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .config import Config
|
||||
@@ -40,12 +40,19 @@ __plugin_meta__ = PluginMetadata(
|
||||
name="示例插件",
|
||||
description="这是一个示例插件",
|
||||
usage="没什么用",
|
||||
type="application",
|
||||
config=Config,
|
||||
extra={},
|
||||
)
|
||||
```
|
||||
|
||||
我们可以看到,插件元数据 `PluginMetadata` 有三个基本属性:插件名称、插件描述、插件使用方法。除此之外,还有两个可选的属性。`config` 属性用于指定插件的[配置类](../appendices/config.mdx#插件配置),`extra` 属性,它是一个字典,可以用于存储任意信息。其他插件可以通过约定 `extra` 字典的键名来达成收集某些特殊信息的目的。
|
||||
我们可以看到,插件元数据 `PluginMetadata` 有三个基本属性:插件名称、插件描述、插件使用方法。除此之外,还有几个可选的属性(具体填写见[发布插件](../developer/plugin-publishing.mdx#填写插件元数据)章节):
|
||||
|
||||
- `type`:插件类别,发布插件必填。当前有效类别有:`library`(为其他插件编写提供功能),`application`(向机器人用户提供功能);
|
||||
- `homepage`:插件项目主页,发布插件必填;
|
||||
- `config`:插件的[配置类](../appendices/config.mdx#插件配置),如无配置类可不填;
|
||||
- `supported_adapters`:支持的适配器模块名集合,若插件可以保证兼容所有适配器(即仅使用基本适配器功能)可不填写;
|
||||
- `extra`:一个字典,可以用于存储任意信息。其他插件可以通过约定 `extra` 字典的键名来达成收集某些特殊信息的目的。
|
||||
|
||||
请注意,这里的**插件名称**是供使用者或机器人用户查看的,与插件索引名称无关。**插件索引名称(插件模块名称)**仅用于 NoneBot 插件系统**内部索引**。
|
||||
|
@@ -12,7 +12,7 @@ options:
|
||||
|
||||
在[驱动器](./driver.md)一节中,我们了解了驱动器的两种类型。既然驱动器可以作为服务端运行,那么我们就可以向驱动器添加路由规则,从而实现自定义的 API 接口等功能。在添加路由规则时,我们需要注意驱动器的类型,详情可以参考[选择驱动器](./driver.md#配置驱动器)。
|
||||
|
||||
NoneBot 中,我们可以通过两种途径向驱动器添加路由规则:
|
||||
NoneBot 中,我们可以通过两种途径向 ASGI 驱动器添加路由规则:
|
||||
|
||||
1. 通过 NoneBot 的兼容层建立路由规则。
|
||||
2. 直接向 ASGI 应用添加路由规则。
|
||||
@@ -23,9 +23,9 @@ NoneBot 中,我们可以通过两种途径向驱动器添加路由规则:
|
||||
|
||||
```python {3}
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import ReverseDriver
|
||||
from nonebot.drivers import ASGIMixin
|
||||
|
||||
can_use = isinstance(get_driver(), ReverseDriver)
|
||||
can_use = isinstance(get_driver(), ASGIMixin)
|
||||
```
|
||||
|
||||
## 通过兼容层添加路由
|
||||
@@ -45,12 +45,12 @@ NoneBot 兼容层定义了两个数据类 `HTTPServerSetup` 和 `WebSocketServer
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import URL, Request, Response, HTTPServerSetup
|
||||
from nonebot.drivers import URL, Request, Response, ASGIMixin, HTTPServerSetup
|
||||
|
||||
async def hello(request: Request) -> Response:
|
||||
return Response(200, content="Hello, world!")
|
||||
|
||||
if isinstance((driver := get_driver()), ReverseDriver):
|
||||
if isinstance((driver := get_driver()), ASGIMixin):
|
||||
driver.setup_http_server(
|
||||
HTTPServerSetup(
|
||||
path=URL("/hello"),
|
||||
@@ -75,7 +75,7 @@ if isinstance((driver := get_driver()), ReverseDriver):
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
from nonebot.drivers import URL, WebSocket, WebSocketServerSetup
|
||||
from nonebot.drivers import URL, ASGIMixin, WebSocket, WebSocketServerSetup
|
||||
|
||||
async def ws_handler(ws: WebSocket):
|
||||
await ws.accept()
|
||||
@@ -91,7 +91,7 @@ async def ws_handler(ws: WebSocket):
|
||||
await websocket.close()
|
||||
# do some cleanup
|
||||
|
||||
if isinstance((driver := get_driver()), ReverseDriver):
|
||||
if isinstance((driver := get_driver()), ASGIMixin):
|
||||
driver.setup_websocket_server(
|
||||
WebSocketServerSetup(
|
||||
path=URL("/ws"),
|
@@ -7,7 +7,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
本模块定义了协议适配基类,各协议请继承以下基类。
|
||||
|
||||
使用 [Driver.register_adapter](../drivers/index.md#Driver-register_adapter) 注册适配器。
|
||||
使用 [Driver.register_adapter](../drivers/index.md#Driver-register-adapter) 注册适配器。
|
||||
|
||||
## _abstract class_ `Bot(adapter, self_id)` {#Bot}
|
||||
|
||||
@@ -29,7 +29,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **说明:** 协议适配器实例
|
||||
|
||||
### _instance-var_ `self_id` {#Bot-self_id}
|
||||
### _instance-var_ `self_id` {#Bot-self-id}
|
||||
|
||||
- **类型:** str
|
||||
|
||||
@@ -47,7 +47,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **说明:** 全局 NoneBot 配置
|
||||
|
||||
### _async method_ `call_api(api, **data)` {#Bot-call_api}
|
||||
### _async method_ `call_api(api, **data)` {#Bot-call-api}
|
||||
|
||||
- **说明:** 调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
|
||||
|
||||
@@ -84,7 +84,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- Any
|
||||
|
||||
### _classmethod_ `on_calling_api(func)` {#Bot-on_calling_api}
|
||||
### _classmethod_ `on_calling_api(func)` {#Bot-on-calling-api}
|
||||
|
||||
- **说明**
|
||||
|
||||
@@ -98,13 +98,13 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_CallingAPIHook](../typing.md#T_CallingAPIHook))
|
||||
- `func` ([T_CallingAPIHook](../typing.md#T-CallingAPIHook))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_CallingAPIHook](../typing.md#T_CallingAPIHook)
|
||||
- [T_CallingAPIHook](../typing.md#T-CallingAPIHook)
|
||||
|
||||
### _classmethod_ `on_called_api(func)` {#Bot-on_called_api}
|
||||
### _classmethod_ `on_called_api(func)` {#Bot-on-called-api}
|
||||
|
||||
- **说明**
|
||||
|
||||
@@ -120,11 +120,11 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **参数**
|
||||
|
||||
- `func` ([T_CalledAPIHook](../typing.md#T_CalledAPIHook))
|
||||
- `func` ([T_CalledAPIHook](../typing.md#T-CalledAPIHook))
|
||||
|
||||
- **返回**
|
||||
|
||||
- [T_CalledAPIHook](../typing.md#T_CalledAPIHook)
|
||||
- [T_CalledAPIHook](../typing.md#T-CalledAPIHook)
|
||||
|
||||
## _abstract class_ `Event(<auto>)` {#Event}
|
||||
|
||||
@@ -144,7 +144,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- E
|
||||
|
||||
### _abstract method_ `get_type()` {#Event-get_type}
|
||||
### _abstract method_ `get_type()` {#Event-get-type}
|
||||
|
||||
- **说明:** 获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。
|
||||
|
||||
@@ -156,7 +156,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `get_event_name()` {#Event-get_event_name}
|
||||
### _abstract method_ `get_event_name()` {#Event-get-event-name}
|
||||
|
||||
- **说明:** 获取事件名称的方法。
|
||||
|
||||
@@ -168,7 +168,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `get_event_description()` {#Event-get_event_description}
|
||||
### _abstract method_ `get_event_description()` {#Event-get-event-description}
|
||||
|
||||
- **说明:** 获取事件描述的方法,通常为事件具体内容。
|
||||
|
||||
@@ -180,13 +180,14 @@ description: nonebot.adapters 模块
|
||||
|
||||
- str
|
||||
|
||||
### _method_ `get_log_string()` {#Event-get_log_string}
|
||||
### _method_ `get_log_string()` {#Event-get-log-string}
|
||||
|
||||
- **说明**
|
||||
|
||||
获取事件日志信息的方法。
|
||||
|
||||
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,可以抛出 `NoLogException` 异常。
|
||||
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,
|
||||
可以抛出 `NoLogException` 异常。
|
||||
|
||||
- **参数**
|
||||
|
||||
@@ -198,9 +199,9 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **异常**
|
||||
|
||||
- NoLogException
|
||||
- NoLogException: 希望 NoneBot 隐藏该事件日志
|
||||
|
||||
### _abstract method_ `get_user_id()` {#Event-get_user_id}
|
||||
### _abstract method_ `get_user_id()` {#Event-get-user-id}
|
||||
|
||||
- **说明:** 获取事件主体 id 的方法,通常是用户 id 。
|
||||
|
||||
@@ -212,7 +213,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `get_session_id()` {#Event-get_session_id}
|
||||
### _abstract method_ `get_session_id()` {#Event-get-session-id}
|
||||
|
||||
- **说明:** 获取会话 id 的方法,用于判断当前事件属于哪一个会话, 通常是用户 id、群组 id 组合。
|
||||
|
||||
@@ -224,7 +225,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `get_message()` {#Event-get_message}
|
||||
### _abstract method_ `get_message()` {#Event-get-message}
|
||||
|
||||
- **说明:** 获取事件消息内容的方法。
|
||||
|
||||
@@ -236,7 +237,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- [Message](#Message)
|
||||
|
||||
### _method_ `get_plaintext()` {#Event-get_plaintext}
|
||||
### _method_ `get_plaintext()` {#Event-get-plaintext}
|
||||
|
||||
- **说明**
|
||||
|
||||
@@ -252,7 +253,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- str
|
||||
|
||||
### _abstract method_ `is_tome()` {#Event-is_tome}
|
||||
### _abstract method_ `is_tome()` {#Event-is-tome}
|
||||
|
||||
- **说明:** 获取事件是否与机器人有关的方法。
|
||||
|
||||
@@ -276,7 +277,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- `driver` ([Driver](../drivers/index.md#Driver)): [Driver](../drivers/index.md#Driver) 实例
|
||||
|
||||
- `**kwargs` (Any): 其他由 [Driver.register_adapter](../drivers/index.md#Driver-register_adapter) 传入的额外参数
|
||||
- `**kwargs` (Any): 其他由 [Driver.register_adapter](../drivers/index.md#Driver-register-adapter) 传入的额外参数
|
||||
|
||||
### _instance-var_ `driver` {#Adapter-driver}
|
||||
|
||||
@@ -290,7 +291,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **说明:** 本协议适配器已建立连接的 [Bot](#Bot) 实例
|
||||
|
||||
### _abstract classmethod_ `get_name()` {#Adapter-get_name}
|
||||
### _abstract classmethod_ `get_name()` {#Adapter-get-name}
|
||||
|
||||
- **说明:** 当前协议适配器的名称
|
||||
|
||||
@@ -308,7 +309,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **说明:** 全局 NoneBot 配置
|
||||
|
||||
### _method_ `bot_connect(bot)` {#Adapter-bot_connect}
|
||||
### _method_ `bot_connect(bot)` {#Adapter-bot-connect}
|
||||
|
||||
- **说明**
|
||||
|
||||
@@ -324,7 +325,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `bot_disconnect(bot)` {#Adapter-bot_disconnect}
|
||||
### _method_ `bot_disconnect(bot)` {#Adapter-bot-disconnect}
|
||||
|
||||
- **说明**
|
||||
|
||||
@@ -340,7 +341,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- None
|
||||
|
||||
### _method_ `setup_http_server(setup)` {#Adapter-setup_http_server}
|
||||
### _method_ `setup_http_server(setup)` {#Adapter-setup-http-server}
|
||||
|
||||
- **说明:** 设置一个 HTTP 服务器路由配置
|
||||
|
||||
@@ -352,7 +353,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `setup_websocket_server(setup)` {#Adapter-setup_websocket_server}
|
||||
### _method_ `setup_websocket_server(setup)` {#Adapter-setup-websocket-server}
|
||||
|
||||
- **说明:** 设置一个 WebSocket 服务器路由配置
|
||||
|
||||
@@ -390,7 +391,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
## _abstract class_ `Message(<auto>)` {#Message}
|
||||
|
||||
- **说明:** 消息数组
|
||||
- **说明:** 消息序列
|
||||
|
||||
- **参数**
|
||||
|
||||
@@ -402,9 +403,9 @@ description: nonebot.adapters 模块
|
||||
|
||||
创建消息模板。
|
||||
|
||||
用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板
|
||||
|
||||
并且提供了拓展的格式化控制符, 可以用适用于该消息类型的 `MessageSegment` 的工厂方法创建消息
|
||||
用法和 `str.format` 大致相同,支持以 `Message` 对象作为消息模板并输出消息对象。
|
||||
并且提供了拓展的格式化控制符,
|
||||
可以通过该消息类型的 `MessageSegment` 工厂方法创建消息。
|
||||
|
||||
- **参数**
|
||||
|
||||
@@ -412,9 +413,9 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **返回**
|
||||
|
||||
- [MessageTemplate](#MessageTemplate)[TM]: 消息格式化器
|
||||
- [MessageTemplate](#MessageTemplate)[Self]: 消息格式化器
|
||||
|
||||
### _abstract classmethod_ `get_segment_class()` {#Message-get_segment_class}
|
||||
### _abstract classmethod_ `get_segment_class()` {#Message-get-segment-class}
|
||||
|
||||
- **说明:** 获取消息段类型
|
||||
|
||||
@@ -426,39 +427,153 @@ description: nonebot.adapters 模块
|
||||
|
||||
- type[TMS]
|
||||
|
||||
### _method_ `index(value, *args)` {#Message-index}
|
||||
### _abstract staticmethod_ `_construct(msg)` {#Message--construct}
|
||||
|
||||
- **说明:** 构造消息数组
|
||||
|
||||
- **参数**
|
||||
|
||||
- `msg` (str)
|
||||
|
||||
- **返回**
|
||||
|
||||
- Iterable[TMS]
|
||||
|
||||
### _method_ `__getitem__(args)` {#Message---getitem--}
|
||||
|
||||
- **重载**
|
||||
|
||||
**1.** `(args) -> Self`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `args` (str): 消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 所有类型为 `args` 的消息段
|
||||
|
||||
**2.** `(args) -> TMS`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `args` (tuple[str, int]): 消息段类型和索引
|
||||
|
||||
- **返回**
|
||||
|
||||
- TMS: 类型为 `args[0]` 的消息段第 `args[1]` 个
|
||||
|
||||
**3.** `(args) -> Self`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `args` (tuple[str, slice]): 消息段类型和切片
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 类型为 `args[0]` 的消息段切片 `args[1]`
|
||||
|
||||
**4.** `(args) -> TMS`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `args` (int): 索引
|
||||
|
||||
- **返回**
|
||||
|
||||
- TMS: 第 `args` 个消息段
|
||||
|
||||
**5.** `(args) -> Self`
|
||||
|
||||
- **参数**
|
||||
|
||||
- `args` (slice): 切片
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 消息切片 `args`
|
||||
|
||||
### _method_ `__contains__(value)` {#Message---contains--}
|
||||
|
||||
- **说明:** 检查消息段是否存在
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str): 消息段或消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- bool: 消息内是否存在给定消息段或给定类型的消息段
|
||||
|
||||
### _method_ `has(value)` {#Message-has}
|
||||
|
||||
- **说明:** 与 [`__contains__`](#Message---contains--) 相同
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str)
|
||||
|
||||
- `*args`
|
||||
- **返回**
|
||||
|
||||
- bool
|
||||
|
||||
### _method_ `index(value, *args)` {#Message-index}
|
||||
|
||||
- **说明:** 索引消息段
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str): 消息段或者消息段类型
|
||||
|
||||
- `*args` (SupportsIndex)
|
||||
|
||||
- `arg`: start 与 end
|
||||
|
||||
- **返回**
|
||||
|
||||
- int
|
||||
- int: 索引 index
|
||||
|
||||
- **异常**
|
||||
|
||||
- ValueError: 消息段不存在
|
||||
|
||||
### _method_ `get(type_, count=None)` {#Message-get}
|
||||
|
||||
- **说明:** 获取指定类型的消息段
|
||||
|
||||
- **参数**
|
||||
|
||||
- `type_` (str)
|
||||
- `type_` (str): 消息段类型
|
||||
|
||||
- `count` (int | None)
|
||||
- `count` (int | None): 获取个数
|
||||
|
||||
- **返回**
|
||||
|
||||
- TM
|
||||
- Self: 构建的新消息
|
||||
|
||||
### _method_ `count(value)` {#Message-count}
|
||||
|
||||
- **说明:** 计算指定消息段的个数
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str)
|
||||
- `value` (TMS | str): 消息段或消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- int
|
||||
- int: 个数
|
||||
|
||||
### _method_ `only(value)` {#Message-only}
|
||||
|
||||
- **说明:** 检查消息中是否仅包含指定消息段
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (TMS | str): 指定消息段或消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- bool: 是否仅包含指定消息段
|
||||
|
||||
### _method_ `append(obj)` {#Message-append}
|
||||
|
||||
@@ -470,7 +585,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **返回**
|
||||
|
||||
- TM
|
||||
- Self
|
||||
|
||||
### _method_ `extend(obj)` {#Message-extend}
|
||||
|
||||
@@ -478,23 +593,61 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **参数**
|
||||
|
||||
- `obj` (TM | Iterable[TMS]): 要添加的消息数组
|
||||
- `obj` (Self | Iterable[TMS]): 要添加的消息数组
|
||||
|
||||
- **返回**
|
||||
|
||||
- TM
|
||||
- Self
|
||||
|
||||
### _method_ `join(iterable)` {#Message-join}
|
||||
|
||||
- **说明:** 将多个消息连接并将自身作为分割
|
||||
|
||||
- **参数**
|
||||
|
||||
- `iterable` (Iterable[TMS | Self]): 要连接的消息
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 连接后的消息
|
||||
|
||||
### _method_ `copy()` {#Message-copy}
|
||||
|
||||
- **说明:** 深拷贝消息
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- TM
|
||||
- Self
|
||||
|
||||
### _method_ `extract_plain_text()` {#Message-extract_plain_text}
|
||||
### _method_ `include(*types)` {#Message-include}
|
||||
|
||||
- **说明:** 过滤消息
|
||||
|
||||
- **参数**
|
||||
|
||||
- `*types` (str): 包含的消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 新构造的消息
|
||||
|
||||
### _method_ `exclude(*types)` {#Message-exclude}
|
||||
|
||||
- **说明:** 过滤消息
|
||||
|
||||
- **参数**
|
||||
|
||||
- `*types` (str): 不包含的消息段类型
|
||||
|
||||
- **返回**
|
||||
|
||||
- Self: 新构造的消息
|
||||
|
||||
### _method_ `extract_plain_text()` {#Message-extract-plain-text}
|
||||
|
||||
- **说明:** 提取消息内纯文本消息
|
||||
|
||||
@@ -526,7 +679,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **说明:** 消息段数据
|
||||
|
||||
### _abstract classmethod_ `get_message_class()` {#MessageSegment-get_message_class}
|
||||
### _abstract classmethod_ `get_message_class()` {#MessageSegment-get-message-class}
|
||||
|
||||
- **说明:** 获取消息数组类型
|
||||
|
||||
@@ -538,6 +691,28 @@ description: nonebot.adapters 模块
|
||||
|
||||
- type[TM]
|
||||
|
||||
### _abstract method_ `__str__()` {#MessageSegment---str--}
|
||||
|
||||
- **说明:** 该消息段所代表的 str,在命令匹配部分使用
|
||||
|
||||
- **参数**
|
||||
|
||||
empty
|
||||
|
||||
- **返回**
|
||||
|
||||
- str
|
||||
|
||||
### _method_ `__add__(other)` {#MessageSegment---add--}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `other` (str | TMS | Iterable[TMS])
|
||||
|
||||
- **返回**
|
||||
|
||||
- TM
|
||||
|
||||
### _method_ `get(key, default=None)` {#MessageSegment-get}
|
||||
|
||||
- **参数**
|
||||
@@ -580,6 +755,16 @@ description: nonebot.adapters 模块
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `join(iterable)` {#MessageSegment-join}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `iterable` (Iterable[TMS | TM])
|
||||
|
||||
- **返回**
|
||||
|
||||
- TM
|
||||
|
||||
### _method_ `copy()` {#MessageSegment-copy}
|
||||
|
||||
- **参数**
|
||||
@@ -588,9 +773,9 @@ description: nonebot.adapters 模块
|
||||
|
||||
- **返回**
|
||||
|
||||
- T
|
||||
- Self
|
||||
|
||||
### _abstract method_ `is_text()` {#MessageSegment-is_text}
|
||||
### _abstract method_ `is_text()` {#MessageSegment-is-text}
|
||||
|
||||
- **说明:** 当前消息段是否为纯文本
|
||||
|
||||
@@ -612,7 +797,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- `factory` (type[str] | type[TM]): 消息类型工厂,默认为 `str`
|
||||
|
||||
### _method_ `add_format_spec(spec, name=None)` {#MessageTemplate-add_format_spec}
|
||||
### _method_ `add_format_spec(spec, name=None)` {#MessageTemplate-add-format-spec}
|
||||
|
||||
- **参数**
|
||||
|
||||
@@ -638,7 +823,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- untyped
|
||||
|
||||
### _method_ `format_map(mapping)` {#MessageTemplate-format_map}
|
||||
### _method_ `format_map(mapping)` {#MessageTemplate-format-map}
|
||||
|
||||
- **说明:** 根据传入字典和模板生成消息对象, 在传入字段名不是有效标识符时有用
|
||||
|
||||
@@ -664,7 +849,7 @@ description: nonebot.adapters 模块
|
||||
|
||||
- TF
|
||||
|
||||
### _method_ `format_field(value, format_spec)` {#MessageTemplate-format_field}
|
||||
### _method_ `format_field(value, format_spec)` {#MessageTemplate-format-field}
|
||||
|
||||
- **参数**
|
||||
|
@@ -7,9 +7,11 @@ description: nonebot.config 模块
|
||||
|
||||
本模块定义了 NoneBot 本身运行所需的配置项。
|
||||
|
||||
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
|
||||
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
|
||||
[`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
|
||||
|
||||
配置项需符合特殊格式或 json 序列化格式。详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
||||
配置项需符合特殊格式或 json 序列化格式
|
||||
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
||||
|
||||
## _class_ `Env(<auto>)` {#Env}
|
||||
|
||||
@@ -17,7 +19,7 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
|
||||
运行环境配置。大小写不敏感。
|
||||
|
||||
将会从 `环境变量` > `.env 环境配置文件` 的优先级读取环境信息。
|
||||
将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息。
|
||||
|
||||
- **参数**
|
||||
|
||||
@@ -60,6 +62,8 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
|
||||
`~` 为 `nonebot.drivers.` 的缩写。
|
||||
|
||||
配置方法参考: [配置驱动器](https://nonebot.dev/docs/advanced/driver#%E9%85%8D%E7%BD%AE%E9%A9%B1%E5%8A%A8%E5%99%A8)
|
||||
|
||||
### _class-var_ `host` {#Config-host}
|
||||
|
||||
- **类型:** IPvAnyAddress
|
||||
@@ -72,15 +76,15 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
|
||||
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的端口。
|
||||
|
||||
### _class-var_ `log_level` {#Config-log_level}
|
||||
### _class-var_ `log_level` {#Config-log-level}
|
||||
|
||||
- **类型:** int | str
|
||||
|
||||
- **说明**
|
||||
|
||||
NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称
|
||||
NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称。
|
||||
|
||||
参考 [`loguru 日志等级`](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
||||
参考 [记录日志](https://nonebot.dev/docs/appendices/log),[loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
||||
|
||||
:::tip 提示
|
||||
日志等级名称应为大写,如 `INFO`。
|
||||
@@ -93,7 +97,7 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
LOG_LEVEL=INFO
|
||||
```
|
||||
|
||||
### _class-var_ `api_timeout` {#Config-api_timeout}
|
||||
### _class-var_ `api_timeout` {#Config-api-timeout}
|
||||
|
||||
- **类型:** float | None
|
||||
|
||||
@@ -117,11 +121,15 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
|
||||
- **说明:** 机器人昵称。
|
||||
|
||||
### _class-var_ `command_start` {#Config-command_start}
|
||||
### _class-var_ `command_start` {#Config-command-start}
|
||||
|
||||
- **类型:** set[str]
|
||||
|
||||
- **说明:** 命令的起始标记,用于判断一条消息是不是命令。
|
||||
- **说明**
|
||||
|
||||
命令的起始标记,用于判断一条消息是不是命令。
|
||||
|
||||
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
||||
|
||||
- **用法**
|
||||
|
||||
@@ -129,11 +137,15 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
COMMAND_START=["/", ""]
|
||||
```
|
||||
|
||||
### _class-var_ `command_sep` {#Config-command_sep}
|
||||
### _class-var_ `command_sep` {#Config-command-sep}
|
||||
|
||||
- **类型:** set[str]
|
||||
|
||||
- **说明:** 命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
||||
- **说明**
|
||||
|
||||
命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
||||
|
||||
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
||||
|
||||
- **用法**
|
||||
|
||||
@@ -141,7 +153,7 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`pytho
|
||||
COMMAND_SEP=["."]
|
||||
```
|
||||
|
||||
### _class-var_ `session_expire_timeout` {#Config-session_expire_timeout}
|
||||
### _class-var_ `session_expire_timeout` {#Config-session-expire-timeout}
|
||||
|
||||
- **类型:** timedelta
|
||||
|
116
website/versioned_docs/version-2.1.0/api/consts.md
Normal file
116
website/versioned_docs/version-2.1.0/api/consts.md
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
sidebar_position: 9
|
||||
description: nonebot.consts 模块
|
||||
---
|
||||
|
||||
# nonebot.consts
|
||||
|
||||
本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
||||
|
||||
## _var_ `RECEIVE_KEY` {#RECEIVE-KEY}
|
||||
|
||||
- **类型:** Literal['\_receive\_{id}']
|
||||
|
||||
- **说明:** `receive` 存储 key
|
||||
|
||||
## _var_ `LAST_RECEIVE_KEY` {#LAST-RECEIVE-KEY}
|
||||
|
||||
- **类型:** Literal['\_last\_receive']
|
||||
|
||||
- **说明:** `last_receive` 存储 key
|
||||
|
||||
## _var_ `ARG_KEY` {#ARG-KEY}
|
||||
|
||||
- **类型:** Literal['{key}']
|
||||
|
||||
- **说明:** `arg` 存储 key
|
||||
|
||||
## _var_ `REJECT_TARGET` {#REJECT-TARGET}
|
||||
|
||||
- **类型:** Literal['\_current\_target']
|
||||
|
||||
- **说明:** 当前 `reject` 目标存储 key
|
||||
|
||||
## _var_ `REJECT_CACHE_TARGET` {#REJECT-CACHE-TARGET}
|
||||
|
||||
- **类型:** Literal['\_next\_target']
|
||||
|
||||
- **说明:** 下一个 `reject` 目标存储 key
|
||||
|
||||
## _var_ `PREFIX_KEY` {#PREFIX-KEY}
|
||||
|
||||
- **类型:** Literal['\_prefix']
|
||||
|
||||
- **说明:** 命令前缀存储 key
|
||||
|
||||
## _var_ `CMD_KEY` {#CMD-KEY}
|
||||
|
||||
- **类型:** Literal['command']
|
||||
|
||||
- **说明:** 命令元组存储 key
|
||||
|
||||
## _var_ `RAW_CMD_KEY` {#RAW-CMD-KEY}
|
||||
|
||||
- **类型:** Literal['raw\_command']
|
||||
|
||||
- **说明:** 命令文本存储 key
|
||||
|
||||
## _var_ `CMD_ARG_KEY` {#CMD-ARG-KEY}
|
||||
|
||||
- **类型:** Literal['command\_arg']
|
||||
|
||||
- **说明:** 命令参数存储 key
|
||||
|
||||
## _var_ `CMD_START_KEY` {#CMD-START-KEY}
|
||||
|
||||
- **类型:** Literal['command\_start']
|
||||
|
||||
- **说明:** 命令开头存储 key
|
||||
|
||||
## _var_ `CMD_WHITESPACE_KEY` {#CMD-WHITESPACE-KEY}
|
||||
|
||||
- **类型:** Literal['command\_whitespace']
|
||||
|
||||
- **说明:** 命令与参数间空白符存储 key
|
||||
|
||||
## _var_ `SHELL_ARGS` {#SHELL-ARGS}
|
||||
|
||||
- **类型:** Literal['\_args']
|
||||
|
||||
- **说明:** shell 命令 parse 后参数字典存储 key
|
||||
|
||||
## _var_ `SHELL_ARGV` {#SHELL-ARGV}
|
||||
|
||||
- **类型:** Literal['\_argv']
|
||||
|
||||
- **说明:** shell 命令原始参数列表存储 key
|
||||
|
||||
## _var_ `REGEX_MATCHED` {#REGEX-MATCHED}
|
||||
|
||||
- **类型:** Literal['\_matched']
|
||||
|
||||
- **说明:** 正则匹配结果存储 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,7 +7,7 @@ description: nonebot.dependencies 模块
|
||||
|
||||
本模块模块实现了依赖注入的定义与处理。
|
||||
|
||||
## _abstract class_ `Param(<auto>)` {#Param}
|
||||
## _abstract class_ `Param(*args, validate=False, **kwargs)` {#Param}
|
||||
|
||||
- **说明**
|
||||
|
||||
@@ -17,7 +17,11 @@ description: nonebot.dependencies 模块
|
||||
|
||||
- **参数**
|
||||
|
||||
auto
|
||||
- `*args`
|
||||
|
||||
- `validate` (bool)
|
||||
|
||||
- `**kwargs` (Any)
|
||||
|
||||
## _class_ `Dependent(<auto>)` {#Dependent}
|
||||
|
||||
@@ -35,7 +39,7 @@ description: nonebot.dependencies 模块
|
||||
|
||||
- `allow_types`: 允许的参数类型
|
||||
|
||||
### _staticmethod_ `parse_params(call, allow_types)` {#Dependent-parse_params}
|
||||
### _staticmethod_ `parse_params(call, allow_types)` {#Dependent-parse-params}
|
||||
|
||||
- **参数**
|
||||
|
||||
@@ -45,9 +49,9 @@ description: nonebot.dependencies 模块
|
||||
|
||||
- **返回**
|
||||
|
||||
- tuple[ModelField]
|
||||
- tuple[ModelField, ...]
|
||||
|
||||
### _staticmethod_ `parse_parameterless(parameterless, allow_types)` {#Dependent-parse_parameterless}
|
||||
### _staticmethod_ `parse_parameterless(parameterless, allow_types)` {#Dependent-parse-parameterless}
|
||||
|
||||
- **参数**
|
||||
|
@@ -5,7 +5,7 @@ description: nonebot.dependencies.utils 模块
|
||||
|
||||
# nonebot.dependencies.utils
|
||||
|
||||
## _def_ `get_typed_signature(call)` {#get_typed_signature}
|
||||
## _def_ `get_typed_signature(call)` {#get-typed-signature}
|
||||
|
||||
- **说明:** 获取可调用对象签名
|
||||
|
||||
@@ -17,7 +17,7 @@ description: nonebot.dependencies.utils 模块
|
||||
|
||||
- inspect.Signature
|
||||
|
||||
## _def_ `get_typed_annotation(param, globalns)` {#get_typed_annotation}
|
||||
## _def_ `get_typed_annotation(param, globalns)` {#get-typed-annotation}
|
||||
|
||||
- **说明:** 获取参数的类型注解
|
||||
|
||||
@@ -31,14 +31,16 @@ description: nonebot.dependencies.utils 模块
|
||||
|
||||
- Any
|
||||
|
||||
## _def_ `check_field_type(field, value)` {#check_field_type}
|
||||
## _def_ `check_field_type(field, value)` {#check-field-type}
|
||||
|
||||
- **说明:** 检查字段类型是否匹配
|
||||
|
||||
- **参数**
|
||||
|
||||
- `field` (ModelField)
|
||||
|
||||
- `value` (V)
|
||||
- `value` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- V
|
||||
- Any
|
@@ -6,7 +6,7 @@
|
||||
|
||||
empty
|
||||
|
||||
### _method_ `on_startup(func)` {#Lifespan-on_startup}
|
||||
### _method_ `on_startup(func)` {#Lifespan-on-startup}
|
||||
|
||||
- **参数**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
- LIFESPAN_FUNC
|
||||
|
||||
### _method_ `on_shutdown(func)` {#Lifespan-on_shutdown}
|
||||
### _method_ `on_shutdown(func)` {#Lifespan-on-shutdown}
|
||||
|
||||
- **参数**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user