mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 11:16:43 +00:00
Compare commits
1 Commits
v2.4.0
...
publish/is
Author | SHA1 | Date | |
---|---|---|---|
|
d8c36e8eff |
@@ -9,12 +9,13 @@
|
|||||||
"vscode": {
|
"vscode": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"python.analysis.diagnosticMode": "workspace",
|
"python.analysis.diagnosticMode": "workspace",
|
||||||
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
"ruff.organizeImports": false,
|
"ruff.organizeImports": false,
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter",
|
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.ruff": "explicit",
|
"source.fixAll.ruff": true,
|
||||||
"source.organizeImports": "explicit"
|
"source.organizeImports": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
|||||||
open_collective: nonebot
|
open_collective: nonebot
|
||||||
custom: ["https://afdian.com/@nonebot"]
|
custom: ["https://afdian.net/@nonebot"]
|
||||||
|
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@@ -35,12 +35,3 @@ updates:
|
|||||||
actions:
|
actions:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
|
|
||||||
- package-ecosystem: devcontainers
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
groups:
|
|
||||||
devcontainers:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
|
2
.github/workflows/release-drafter.yml
vendored
2
.github/workflows/release-drafter.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Update Changelog
|
- name: Update Changelog
|
||||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||||
with:
|
with:
|
||||||
changelog_file: website/src/changelog/changelog.md
|
changelog_file: website/src/pages/changelog.md
|
||||||
latest_changes_position: '# 更新日志\n\n'
|
latest_changes_position: '# 更新日志\n\n'
|
||||||
latest_changes_title: "## 最近更新"
|
latest_changes_title: "## 最近更新"
|
||||||
replace_regex: '(?<=## 最近更新\n)[\s\S]*?(?=\n## )'
|
replace_regex: '(?<=## 最近更新\n)[\s\S]*?(?=\n## )'
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
- name: Archive Changelog
|
- name: Archive Changelog
|
||||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||||
with:
|
with:
|
||||||
changelog_file: website/src/changelog/changelog.md
|
changelog_file: website/src/pages/changelog.md
|
||||||
archive_regex: '(?<=## )最近更新(?=\n)'
|
archive_regex: '(?<=## )最近更新(?=\n)'
|
||||||
archive_title: ${{ env.TAG_NAME }}
|
archive_title: ${{ env.TAG_NAME }}
|
||||||
commit_and_push: false
|
commit_and_push: false
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,7 +7,6 @@ docs_build/_build
|
|||||||
!tests/.env
|
!tests/.env
|
||||||
.docusaurus
|
.docusaurus
|
||||||
website/docs/api/**/*.md
|
website/docs/api/**/*.md
|
||||||
website/src/pages/changelog/**/*
|
|
||||||
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/python,node,visualstudiocode,jetbrains,macos,windows,linux
|
# Created by https://www.toptal.com/developers/gitignore/api/python,node,visualstudiocode,jetbrains,macos,windows,linux
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python,node,visualstudiocode,jetbrains,macos,windows,linux
|
# Edit at https://www.toptal.com/developers/gitignore?templates=python,node,visualstudiocode,jetbrains,macos,windows,linux
|
||||||
|
@@ -7,23 +7,30 @@ ci:
|
|||||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.7.1
|
rev: v0.5.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
stages: [pre-commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 5.13.2
|
rev: 5.13.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
stages: [pre-commit]
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 24.10.0
|
rev: 24.8.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
stages: [pre-commit]
|
stages: [commit]
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: v4.0.0-alpha.8
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
|
||||||
|
stages: [commit]
|
||||||
|
|
||||||
- repo: https://github.com/nonebot/nonemoji
|
- repo: https://github.com/nonebot/nonemoji
|
||||||
rev: v0.1.4
|
rev: v0.1.4
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
See [changelog.md](./website/src/changelog/changelog.md) or <https://nonebot.dev/changelog>
|
See [changelog.md](./website/src/pages/changelog.md) or <https://nonebot.dev/changelog>
|
||||||
|
@@ -126,6 +126,7 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
| Mirai([仓库](https://github.com/nonebot/adapter-mirai),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ✅ | QQ 协议 |
|
| Mirai([仓库](https://github.com/nonebot/adapter-mirai),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ✅ | QQ 协议 |
|
||||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||||
|
| Mirai([仓库](https://github.com/ieew/nonebot_adapter_mirai2),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ↗️ | QQ 协议,由社区贡献 |
|
||||||
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||||
|
@@ -69,6 +69,16 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"is_official": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot.adapters.mirai2",
|
||||||
|
"project_link": "nonebot_adapter_mirai2",
|
||||||
|
"name": "mirai2",
|
||||||
|
"desc": "为 nonebot2 添加 mirai_api_http2.x的兼容适配器",
|
||||||
|
"author": "ieew",
|
||||||
|
"homepage": "https://github.com/ieew/nonebot_adapter_mirai2",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot.adapters.onebot.v12",
|
"module_name": "nonebot.adapters.onebot.v12",
|
||||||
"project_link": "nonebot-adapter-onebot",
|
"project_link": "nonebot-adapter-onebot",
|
||||||
|
@@ -632,30 +632,5 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "小安提Bot",
|
|
||||||
"desc": "服务于音游 舞萌DX 的多功能Bot",
|
|
||||||
"author": "Ant1816",
|
|
||||||
"homepage": "https://github.com/Ant1816/Ant1Bot",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "maimaiDX",
|
|
||||||
"color": "#52ea9a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "音游",
|
|
||||||
"color": "#f74b18"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CanrotBot",
|
|
||||||
"desc": "有很多实用功能的bot,也有很多没什么用的娱乐功能;接入了大模型,并且有一部分功能可以被大模型调用。主打一个全都有(",
|
|
||||||
"author": "wangyw15",
|
|
||||||
"homepage": "https://github.com/wangyw15/CanrotBot",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -278,6 +278,13 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_simplemusic",
|
||||||
|
"project_link": "nonebot-plugin-simplemusic",
|
||||||
|
"author": "MeetWq",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nb2chan",
|
"module_name": "nb2chan",
|
||||||
"project_link": "nb2chan",
|
"project_link": "nb2chan",
|
||||||
@@ -702,6 +709,13 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_hikarisearch",
|
||||||
|
"project_link": "nonebot-plugin-hikarisearch",
|
||||||
|
"author": "MeetWq",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot_plugin_mediawiki",
|
"module_name": "nonebot_plugin_mediawiki",
|
||||||
"project_link": "nonebot-plugin-mediawiki",
|
"project_link": "nonebot-plugin-mediawiki",
|
||||||
@@ -1743,6 +1757,18 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_sqlalchemy",
|
||||||
|
"project_link": "nonebot-plugin-sqlalchemy",
|
||||||
|
"author": "ssttkkl",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "sql",
|
||||||
|
"color": "#ad1717"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot_plugin_blacklist",
|
"module_name": "nonebot_plugin_blacklist",
|
||||||
"project_link": "nonebot-plugin-blacklist",
|
"project_link": "nonebot-plugin-blacklist",
|
||||||
@@ -4135,6 +4161,13 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_error_alert",
|
||||||
|
"project_link": "nonebot-plugin-error-alert",
|
||||||
|
"author": "ssttkkl",
|
||||||
|
"tags": [],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot_plugin_ocgbot_v2",
|
"module_name": "nonebot_plugin_ocgbot_v2",
|
||||||
"project_link": "nonebot-plugin-ocgbot-v2",
|
"project_link": "nonebot-plugin-ocgbot-v2",
|
||||||
@@ -6300,6 +6333,26 @@
|
|||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "nonebot_plugin_ntqq_restart",
|
||||||
|
"project_link": "nonebot-plugin-ntqq-restart",
|
||||||
|
"author": "kanbereina",
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"label": "llonebot",
|
||||||
|
"color": "#f9e642"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "NTQQ",
|
||||||
|
"color": "#ede9e9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Windows",
|
||||||
|
"color": "#52aaea"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_official": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"module_name": "nonebot_plugin_eve_tool",
|
"module_name": "nonebot_plugin_eve_tool",
|
||||||
"project_link": "nonebot-plugin-eve-tool",
|
"project_link": "nonebot-plugin-eve-tool",
|
||||||
@@ -6659,915 +6712,5 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_official": false
|
"is_official": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "mai2_pcount",
|
|
||||||
"project_link": "mai2_pcount",
|
|
||||||
"author": "shengwang52005",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "舞萌",
|
|
||||||
"color": "#eabf0b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "机厅",
|
|
||||||
"color": "#a60a25"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_dify",
|
|
||||||
"project_link": "nonebot-plugin-dify",
|
|
||||||
"author": "gsskk",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "LLM",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Dify",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_looklike",
|
|
||||||
"project_link": "nonebot-plugin-looklike",
|
|
||||||
"author": "tkgs0",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_wait_a_minute",
|
|
||||||
"project_link": "nonebot-plugin-wait-a-minute",
|
|
||||||
"author": "shoucandanghehe",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "hook",
|
|
||||||
"color": "#ff5349"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_repesix",
|
|
||||||
"project_link": "nonebot-plugin-repesix",
|
|
||||||
"author": "tkgs0",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_liteyukibot",
|
|
||||||
"project_link": "nonebot-plugin-liteyukibot",
|
|
||||||
"author": "snowykami",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "liteyuki",
|
|
||||||
"color": "#d0e9ff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "轻雪",
|
|
||||||
"color": "#a2d8f4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_mute",
|
|
||||||
"project_link": "nonebot_plugin_mute",
|
|
||||||
"author": "shengwang52005",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "禁言",
|
|
||||||
"color": "#ff0303"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "娱乐",
|
|
||||||
"color": "#03fff2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nekro_agent",
|
|
||||||
"project_link": "nekro-agent",
|
|
||||||
"author": "KroMiose",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "Agent",
|
|
||||||
"color": "#ece349"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "ChatAI",
|
|
||||||
"color": "#3cae69"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_lagrange",
|
|
||||||
"project_link": "nonebot_plugin_lagrange",
|
|
||||||
"author": "Lonely-Sails",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "Lagrange",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_mccheck",
|
|
||||||
"project_link": "nonebot-plugin-mccheck",
|
|
||||||
"author": "molanp",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "Minecraft",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "i18n",
|
|
||||||
"color": "#39c5bb"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_lxns_maimai",
|
|
||||||
"project_link": "nonebot-plugin-lxns-maimai",
|
|
||||||
"author": "KomoriDev",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "maimai",
|
|
||||||
"color": "#228be6"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_sendpic",
|
|
||||||
"project_link": "nonebot-plugin-sendpic",
|
|
||||||
"author": "Funny1Potato",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_llob_master",
|
|
||||||
"project_link": "nonebot-plugin-llob-master",
|
|
||||||
"author": "kanbereina",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "LLOneBot",
|
|
||||||
"color": "#e3e9e9"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Windows",
|
|
||||||
"color": "#1da6eb"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_yoyogame",
|
|
||||||
"project_link": "nonebot-plugin-yoyogame",
|
|
||||||
"author": "ChenXu233",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "game",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "多人游戏",
|
|
||||||
"color": "#73fe1f"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_tea_silencer",
|
|
||||||
"project_link": "nonebot-plugin-tea-silencer",
|
|
||||||
"author": "youlanan",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "拦截",
|
|
||||||
"color": "#fe7931"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "屏蔽",
|
|
||||||
"color": "#31c8fe"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_avalon",
|
|
||||||
"project_link": "nonebot-plugin-avalon",
|
|
||||||
"author": "SamuNatsu",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "game",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_werewolf",
|
|
||||||
"project_link": "nonebot-plugin-werewolf",
|
|
||||||
"author": "wyf7685",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "game",
|
|
||||||
"color": "#16acf3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "狼人杀",
|
|
||||||
"color": "#f3161a"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_tarina_lang_turbo",
|
|
||||||
"project_link": "nonebot-plugin-tarina-lang-turbo",
|
|
||||||
"author": "shoucandanghehe",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "i18n",
|
|
||||||
"color": "#ea5f52"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot-plugin-ACGalaxy",
|
|
||||||
"project_link": "nonebot-plugin-ACGalaxy",
|
|
||||||
"author": "zouXH-god",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "bilibili",
|
|
||||||
"color": "#52eaea"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "漫展",
|
|
||||||
"color": "#52ea65"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_QRrender",
|
|
||||||
"project_link": "nonebot-plugin-QRrender",
|
|
||||||
"author": "Funny1Potato",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "二维码",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_weather_rank",
|
|
||||||
"project_link": "nonebot-plugin-weather-rank",
|
|
||||||
"author": "hanasa2023",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "weather",
|
|
||||||
"color": "#43f1ff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "天气",
|
|
||||||
"color": "#43f1ff"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_witff",
|
|
||||||
"project_link": "nonebot-plugin-witff",
|
|
||||||
"author": "TheChenXI",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "furry",
|
|
||||||
"color": "#ea5f52"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_logstream",
|
|
||||||
"project_link": "nonebot-plugin-logstream",
|
|
||||||
"author": "KiKi-XC",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "log",
|
|
||||||
"color": "#41fae7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "SSE",
|
|
||||||
"color": "#1eefff"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_uninfo",
|
|
||||||
"project_link": "nonebot-plugin-uninfo",
|
|
||||||
"author": "RF-Tar-Railt",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "跨平台",
|
|
||||||
"color": "#5752ea"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "用户数据",
|
|
||||||
"color": "#ea52d2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "群组频道数据",
|
|
||||||
"color": "#ea7d52"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_inspect",
|
|
||||||
"project_link": "nonebot-plugin-inspect",
|
|
||||||
"author": "RF-Tar-Railt",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "多平台适配",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_batch_withdrawal",
|
|
||||||
"project_link": "nonebot-plugin-batch-withdrawal",
|
|
||||||
"author": "zhongwen-4",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_yareminder",
|
|
||||||
"project_link": "nonebot-plugin-yareminder",
|
|
||||||
"author": "yao-yun",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_calc_game",
|
|
||||||
"project_link": "nonebot-plugin-calc-game",
|
|
||||||
"author": "Yurchiu",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "game",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_fun_content",
|
|
||||||
"project_link": "nonebot-plugin-fun-content",
|
|
||||||
"author": "chsiyu",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_pjsk_helper",
|
|
||||||
"project_link": "nonebot-plugin-pjsk-helper",
|
|
||||||
"author": "Atr1ck",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_color_see_see",
|
|
||||||
"project_link": "nonebot-plugin-color-see-see",
|
|
||||||
"author": "FrostN0v0",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "game",
|
|
||||||
"color": "#97e7e1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_githubmodels",
|
|
||||||
"project_link": "nonebot-plugin-githubmodels",
|
|
||||||
"author": "lyqgzbl",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_beatsaberscore",
|
|
||||||
"project_link": "nonebot-plugin-beatsaberscore",
|
|
||||||
"author": "qwq12738qwq",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "Beat Saber",
|
|
||||||
"color": "#456df1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_SimpleToWrite",
|
|
||||||
"project_link": "nonebot-plugin-SimpleToWrite",
|
|
||||||
"author": "STESmly",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "编写简化",
|
|
||||||
"color": "#104772"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "小白向推荐",
|
|
||||||
"color": "#149581"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_nonechat",
|
|
||||||
"project_link": "nonebot-plugin-nonechat",
|
|
||||||
"author": "hanasa2023",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "LLM",
|
|
||||||
"color": "#52eacf"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_marshoai",
|
|
||||||
"project_link": "nonebot-plugin-marshoai",
|
|
||||||
"author": "Asankilp",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "猫娘",
|
|
||||||
"color": "#e6a432"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "AI",
|
|
||||||
"color": "#32c3e6"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_osu_match_monitor",
|
|
||||||
"project_link": "nonebot-plugin-osu-match-monitor",
|
|
||||||
"author": "Sevenyine",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "OSU",
|
|
||||||
"color": "#da6699"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_lolinfo",
|
|
||||||
"project_link": "nonebot-plugin-lolinfo",
|
|
||||||
"author": "Shadow403",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "LOL",
|
|
||||||
"color": "#02ceff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "英雄联盟",
|
|
||||||
"color": "#ff02fb"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_bf5_grouptools",
|
|
||||||
"project_link": "nonebot_plugin_bf5_grouptools",
|
|
||||||
"author": "Lonely-Sails",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "战地五",
|
|
||||||
"color": "#529aea"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_mc_watcher",
|
|
||||||
"project_link": "nonebot_plugin_mc_watcher",
|
|
||||||
"author": "Lonely-Sails",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "Minecraft",
|
|
||||||
"color": "#d79f10"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_zxpm",
|
|
||||||
"project_link": "nonebot-plugin-zxpm",
|
|
||||||
"author": "HibiKier",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "小真寻",
|
|
||||||
"color": "#fbe4e4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "多平台适配",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "插件管理",
|
|
||||||
"color": "#456df1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_lingyi_chat",
|
|
||||||
"project_link": "nonebot-plugin-lingyi-chat",
|
|
||||||
"author": "yeying-xingchen",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "ChatGPT",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "AI",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_bfvsearch",
|
|
||||||
"project_link": "nonebot_plugin_bfvsearch",
|
|
||||||
"author": "swallow513",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "战地五",
|
|
||||||
"color": "#529aea"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_safeR18",
|
|
||||||
"project_link": "nonebot-plugin-safer18",
|
|
||||||
"author": "ChenXu233",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "图片",
|
|
||||||
"color": "#76ecfd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "R18",
|
|
||||||
"color": "#fd0004"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_updater",
|
|
||||||
"project_link": "nonebot-plugin-updater",
|
|
||||||
"author": "hanasa2023",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "插件更新",
|
|
||||||
"color": "#dd2200"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_npu",
|
|
||||||
"project_link": "nonebot-plugin-npu",
|
|
||||||
"author": "qllokirin",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "西工大",
|
|
||||||
"color": "#66ccff"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_running_state",
|
|
||||||
"project_link": "nonebot-plugin-running-state",
|
|
||||||
"author": "zhongwen-4",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "server",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_zxreport",
|
|
||||||
"project_link": "nonebot-plugin-zxreport",
|
|
||||||
"author": "HibiKier",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "小真寻",
|
|
||||||
"color": "#fbe4e4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "多平台适配",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "日报",
|
|
||||||
"color": "#dd628b"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_buy",
|
|
||||||
"project_link": "nonebot-plugin-buy",
|
|
||||||
"author": "Onimaimai",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_nailongremove",
|
|
||||||
"project_link": "nonebot-plugin-nailongremove",
|
|
||||||
"author": "Refound-445",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "图像分类模型",
|
|
||||||
"color": "#5269ea"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_pmhelp",
|
|
||||||
"project_link": "nonebot-plugin-pmhelp",
|
|
||||||
"author": "CM-Edelweiss",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "帮助",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_jtj",
|
|
||||||
"project_link": "nonebot-plugin-jtj",
|
|
||||||
"author": "Onimaimai",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_BR",
|
|
||||||
"project_link": "nonebot_plugin_BR",
|
|
||||||
"author": "Agnes4m",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "游戏",
|
|
||||||
"color": "#fa0404"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_boom",
|
|
||||||
"project_link": "nonebot-plugin-boom",
|
|
||||||
"author": "yeying-xingchen",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "unsafe",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_picsetu",
|
|
||||||
"project_link": "nonebot-plugin-picsetu",
|
|
||||||
"author": "zhongwen-4",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_gotify",
|
|
||||||
"project_link": "nonebot-plugin-gotify",
|
|
||||||
"author": "snowykami",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "gotify",
|
|
||||||
"color": "#6eddff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "通知推送",
|
|
||||||
"color": "#6eddff"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_voicemusic",
|
|
||||||
"project_link": "nonebot-plugin-voicemusic",
|
|
||||||
"author": "Onimaimai",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_fishspeech_tts",
|
|
||||||
"project_link": "nonebot-plugin-fishspeech-tts",
|
|
||||||
"author": "Cvandia",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "TTS",
|
|
||||||
"color": "#23e907"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "语音合成",
|
|
||||||
"color": "#e9297f"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_summary",
|
|
||||||
"project_link": "nonebot-plugin-summary",
|
|
||||||
"author": "ChenXu233",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "省流",
|
|
||||||
"color": "#57f99a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "AI",
|
|
||||||
"color": "#c0d048"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_ddrace",
|
|
||||||
"project_link": "nonebot-plugin-ddrace",
|
|
||||||
"author": "gongfuture",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "game",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "DDNet",
|
|
||||||
"color": "#f39c12"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_mai_arcade",
|
|
||||||
"project_link": "nonebot-plugin-mai-arcade",
|
|
||||||
"author": "YuuzukiRin",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "maimai",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "arcade",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_prevent_withdrawal",
|
|
||||||
"project_link": "nonebot-prevent-withdrawal",
|
|
||||||
"author": "zhongwen-4",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_bilimusic",
|
|
||||||
"project_link": "nonebot_plugin_bilimusic",
|
|
||||||
"author": "Lonely-Sails",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "bilibili",
|
|
||||||
"color": "#52d5ea"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "music",
|
|
||||||
"color": "#ea52ca"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_mmm",
|
|
||||||
"project_link": "nonebot-plugin-mmm",
|
|
||||||
"author": "eya46",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "人机合一",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_pong",
|
|
||||||
"project_link": "nonebot-plugin-pong",
|
|
||||||
"author": "eya46",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "ping",
|
|
||||||
"color": "#ff0000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "pong",
|
|
||||||
"color": "#0000ff"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_partner_join",
|
|
||||||
"project_link": "nonebot-plugin-partner-join",
|
|
||||||
"author": "YuuzukiRin",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "maimai",
|
|
||||||
"color": "#a1b5e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "picture",
|
|
||||||
"color": "#a1b5e8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_zxpix",
|
|
||||||
"project_link": "nonebot-plugin-zxpix",
|
|
||||||
"author": "HibiKier",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "小真寻",
|
|
||||||
"color": "#fbe4e4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "多平台适配",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "xp",
|
|
||||||
"color": "#e96ab5"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_impart",
|
|
||||||
"project_link": "nonebot-plugin-impart",
|
|
||||||
"author": "YuuzukiRin",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "impart",
|
|
||||||
"color": "#fa9650"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_wife",
|
|
||||||
"project_link": "nonebot-plugin-wife",
|
|
||||||
"author": "tkgs0",
|
|
||||||
"tags": [],
|
|
||||||
"is_official": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"module_name": "nonebot_plugin_comfyui",
|
|
||||||
"project_link": "nonebot-plugin-comfyui",
|
|
||||||
"author": "DiaoDaiaChan",
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"label": "comfyui",
|
|
||||||
"color": "#ea5252"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "AI绘图",
|
|
||||||
"color": "#ea5252"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"is_official": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
2634
envs/pydantic-v1/poetry.lock
generated
2634
envs/pydantic-v1/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
2737
envs/pydantic-v2/poetry.lock
generated
2737
envs/pydantic-v2/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
1384
envs/test/poetry.lock
generated
1384
envs/test/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -8,11 +8,11 @@ packages = [{ include = "nonebot-test.py" }]
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
trio = "^0.27.0"
|
nonebug = "^0.3.7"
|
||||||
nonebug = "^0.4.1"
|
|
||||||
wsproto = "^1.2.0"
|
wsproto = "^1.2.0"
|
||||||
pytest-cov = "^5.0.0"
|
pytest-cov = "^5.0.0"
|
||||||
pytest-xdist = "^3.0.2"
|
pytest-xdist = "^3.0.2"
|
||||||
|
pytest-asyncio = "^0.23.2"
|
||||||
werkzeug = ">=2.3.6,<4.0.0"
|
werkzeug = ">=2.3.6,<4.0.0"
|
||||||
coverage-conditional-plugin = "^0.9.0"
|
coverage-conditional-plugin = "^0.9.0"
|
||||||
|
|
||||||
|
@@ -39,8 +39,6 @@
|
|||||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot 模块
|
description: nonebot 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -3,8 +3,6 @@
|
|||||||
使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器。
|
使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.adapters 模块
|
description: nonebot.adapters 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -3,8 +3,6 @@
|
|||||||
为兼容 Pydantic V1 与 V2 版本,定义了一系列兼容函数与类供使用。
|
为兼容 Pydantic V1 与 V2 版本,定义了一系列兼容函数与类供使用。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 16
|
sidebar_position: 16
|
||||||
description: nonebot.compat 模块
|
description: nonebot.compat 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -7,8 +7,6 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
|
|||||||
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.config 模块
|
description: nonebot.config 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 9
|
sidebar_position: 9
|
||||||
description: nonebot.consts 模块
|
description: nonebot.consts 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,32 +1,22 @@
|
|||||||
"""本模块模块实现了依赖注入的定义与处理。
|
"""本模块模块实现了依赖注入的定义与处理。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.dependencies 模块
|
description: nonebot.dependencies 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
from functools import partial
|
|
||||||
from dataclasses import field, dataclass
|
from dataclasses import field, dataclass
|
||||||
from collections.abc import Iterable, Awaitable
|
from collections.abc import Iterable, Awaitable
|
||||||
from typing import Any, Generic, TypeVar, Callable, Optional, cast
|
from typing import Any, Generic, TypeVar, Callable, Optional, cast
|
||||||
|
|
||||||
import anyio
|
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.typing import _DependentCallable
|
from nonebot.typing import _DependentCallable
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
|
from nonebot.utils import run_sync, is_coroutine_callable
|
||||||
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
|
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
|
||||||
from nonebot.utils import (
|
|
||||||
run_sync,
|
|
||||||
run_coro_with_shield,
|
|
||||||
is_coroutine_callable,
|
|
||||||
flatten_exception_group,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .utils import check_field_type, get_typed_signature
|
from .utils import check_field_type, get_typed_signature
|
||||||
|
|
||||||
@@ -92,16 +82,7 @@ class Dependent(Generic[R]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def __call__(self, **kwargs: Any) -> R:
|
async def __call__(self, **kwargs: Any) -> R:
|
||||||
exception: Optional[BaseExceptionGroup[SkippedException]] = None
|
try:
|
||||||
|
|
||||||
def _handle_skipped(exc_group: BaseExceptionGroup[SkippedException]):
|
|
||||||
nonlocal exception
|
|
||||||
exception = exc_group
|
|
||||||
# raise one of the exceptions instead
|
|
||||||
excs = list(flatten_exception_group(exc_group))
|
|
||||||
logger.trace(f"{self} skipped due to {excs}")
|
|
||||||
|
|
||||||
with catch({SkippedException: _handle_skipped}):
|
|
||||||
# do pre-check
|
# do pre-check
|
||||||
await self.check(**kwargs)
|
await self.check(**kwargs)
|
||||||
|
|
||||||
@@ -113,8 +94,9 @@ class Dependent(Generic[R]):
|
|||||||
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
|
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
|
||||||
else:
|
else:
|
||||||
return await run_sync(cast(Callable[..., R], self.call))(**values)
|
return await run_sync(cast(Callable[..., R], self.call))(**values)
|
||||||
|
except SkippedException as e:
|
||||||
raise exception
|
logger.trace(f"{self} skipped due to {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_params(
|
def parse_params(
|
||||||
@@ -182,17 +164,10 @@ class Dependent(Generic[R]):
|
|||||||
return cls(call, params, parameterless_params)
|
return cls(call, params, parameterless_params)
|
||||||
|
|
||||||
async def check(self, **params: Any) -> None:
|
async def check(self, **params: Any) -> None:
|
||||||
if self.parameterless:
|
await asyncio.gather(*(param._check(**params) for param in self.parameterless))
|
||||||
async with anyio.create_task_group() as tg:
|
await asyncio.gather(
|
||||||
for param in self.parameterless:
|
*(cast(Param, param.field_info)._check(**params) for param in self.params)
|
||||||
tg.start_soon(partial(param._check, **params))
|
)
|
||||||
|
|
||||||
if self.params:
|
|
||||||
async with anyio.create_task_group() as tg:
|
|
||||||
for param in self.params:
|
|
||||||
tg.start_soon(
|
|
||||||
partial(cast(Param, param.field_info)._check, **params)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _solve_field(self, field: ModelField, params: dict[str, Any]) -> Any:
|
async def _solve_field(self, field: ModelField, params: dict[str, Any]) -> Any:
|
||||||
param = cast(Param, field.field_info)
|
param = cast(Param, field.field_info)
|
||||||
@@ -208,22 +183,10 @@ class Dependent(Generic[R]):
|
|||||||
await param._solve(**params)
|
await param._solve(**params)
|
||||||
|
|
||||||
# solve param values
|
# solve param values
|
||||||
result: dict[str, Any] = {}
|
values = await asyncio.gather(
|
||||||
if not self.params:
|
*(self._solve_field(field, params) for field in self.params)
|
||||||
return result
|
)
|
||||||
|
return {field.name: value for field, value in zip(self.params, values)}
|
||||||
async def _solve_field(field: ModelField, params: dict[str, Any]) -> None:
|
|
||||||
value = await self._solve_field(field, params)
|
|
||||||
result[field.name] = value
|
|
||||||
|
|
||||||
async with anyio.create_task_group() as tg:
|
|
||||||
for field in self.params:
|
|
||||||
# shield the task to prevent cancellation
|
|
||||||
# when one of the tasks raises an exception
|
|
||||||
# this will improve the dependency cache reusability
|
|
||||||
tg.start_soon(run_coro_with_shield, _solve_field(field, params))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
__autodoc__ = {"CustomConfig": False}
|
__autodoc__ = {"CustomConfig": False}
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.dependencies.utils 模块
|
description: nonebot.dependencies.utils 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -3,8 +3,6 @@
|
|||||||
各驱动请继承以下基类。
|
各驱动请继承以下基类。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.drivers 模块
|
description: nonebot.drivers 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[aiohttp]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.drivers.aiohttp 模块
|
description: nonebot.drivers.aiohttp 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[fastapi]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.drivers.fastapi 模块
|
description: nonebot.drivers.fastapi 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[httpx]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.drivers.httpx 模块
|
description: nonebot.drivers.httpx 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -5,25 +5,19 @@
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 6
|
sidebar_position: 6
|
||||||
description: nonebot.drivers.none 模块
|
description: nonebot.drivers.none 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import signal
|
import signal
|
||||||
from typing import Optional
|
import asyncio
|
||||||
|
import threading
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
import anyio
|
|
||||||
from anyio.abc import TaskGroup
|
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.consts import WINDOWS
|
from nonebot.consts import WINDOWS
|
||||||
from nonebot.config import Env, Config
|
from nonebot.config import Env, Config
|
||||||
from nonebot.drivers import Driver as BaseDriver
|
from nonebot.drivers import Driver as BaseDriver
|
||||||
from nonebot.utils import flatten_exception_group
|
|
||||||
|
|
||||||
HANDLED_SIGNALS = (
|
HANDLED_SIGNALS = (
|
||||||
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
||||||
@@ -39,8 +33,8 @@ class Driver(BaseDriver):
|
|||||||
def __init__(self, env: Env, config: Config):
|
def __init__(self, env: Env, config: Config):
|
||||||
super().__init__(env, config)
|
super().__init__(env, config)
|
||||||
|
|
||||||
self.should_exit: anyio.Event = anyio.Event()
|
self.should_exit: asyncio.Event = asyncio.Event()
|
||||||
self.force_exit: anyio.Event = anyio.Event()
|
self.force_exit: bool = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
@@ -58,97 +52,84 @@ class Driver(BaseDriver):
|
|||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
"""启动 none driver"""
|
"""启动 none driver"""
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
anyio.run(self._serve)
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(self._serve())
|
||||||
|
|
||||||
async def _serve(self):
|
async def _serve(self):
|
||||||
async with anyio.create_task_group() as driver_tg:
|
self._install_signal_handlers()
|
||||||
driver_tg.start_soon(self._handle_signals)
|
await self._startup()
|
||||||
driver_tg.start_soon(self._listen_force_exit, driver_tg)
|
if self.should_exit.is_set():
|
||||||
driver_tg.start_soon(self._handle_lifespan, driver_tg)
|
return
|
||||||
|
await self._main_loop()
|
||||||
async def _handle_signals(self):
|
await self._shutdown()
|
||||||
try:
|
|
||||||
with anyio.open_signal_receiver(*HANDLED_SIGNALS) as signal_receiver:
|
|
||||||
async for sig in signal_receiver:
|
|
||||||
self.exit(force=self.should_exit.is_set())
|
|
||||||
except NotImplementedError:
|
|
||||||
# Windows
|
|
||||||
for sig in HANDLED_SIGNALS:
|
|
||||||
signal.signal(sig, self._handle_legacy_signal)
|
|
||||||
|
|
||||||
# backport for Windows signal handling
|
|
||||||
def _handle_legacy_signal(self, sig, frame):
|
|
||||||
self.exit(force=self.should_exit.is_set())
|
|
||||||
|
|
||||||
async def _handle_lifespan(self, tg: TaskGroup):
|
|
||||||
try:
|
|
||||||
await self._startup()
|
|
||||||
|
|
||||||
if self.should_exit.is_set():
|
|
||||||
return
|
|
||||||
|
|
||||||
await self._listen_exit()
|
|
||||||
|
|
||||||
await self._shutdown()
|
|
||||||
finally:
|
|
||||||
tg.cancel_scope.cancel()
|
|
||||||
|
|
||||||
async def _startup(self):
|
async def _startup(self):
|
||||||
def handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
try:
|
||||||
self.should_exit.set()
|
await self._lifespan.startup()
|
||||||
|
except Exception as e:
|
||||||
for exc in flatten_exception_group(exc_group):
|
logger.opt(colors=True, exception=e).error(
|
||||||
logger.opt(colors=True, exception=exc).error(
|
|
||||||
"<r><bg #f8bbd0>Error occurred while running startup hook."
|
|
||||||
"</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
logger.error(
|
|
||||||
"<r><bg #f8bbd0>Application startup failed. "
|
"<r><bg #f8bbd0>Application startup failed. "
|
||||||
"Exiting.</bg #f8bbd0></r>"
|
"Exiting.</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
self.should_exit.set()
|
||||||
|
return
|
||||||
|
|
||||||
with catch({Exception: handle_exception}):
|
logger.info("Application startup completed.")
|
||||||
await self._lifespan.startup()
|
|
||||||
|
|
||||||
if not self.should_exit.is_set():
|
async def _main_loop(self):
|
||||||
logger.info("Application startup completed.")
|
|
||||||
|
|
||||||
async def _listen_exit(self, tg: Optional[TaskGroup] = None):
|
|
||||||
await self.should_exit.wait()
|
await self.should_exit.wait()
|
||||||
|
|
||||||
if tg is not None:
|
|
||||||
tg.cancel_scope.cancel()
|
|
||||||
|
|
||||||
async def _shutdown(self):
|
async def _shutdown(self):
|
||||||
logger.info("Shutting down")
|
logger.info("Shutting down")
|
||||||
logger.info("Waiting for application shutdown. (CTRL+C to force quit)")
|
|
||||||
|
|
||||||
error_occurred: bool = False
|
logger.info("Waiting for application shutdown.")
|
||||||
|
|
||||||
def handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
try:
|
||||||
nonlocal error_occurred
|
await self._lifespan.shutdown()
|
||||||
|
except Exception as e:
|
||||||
error_occurred = True
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running shutdown function. "
|
||||||
for exc in flatten_exception_group(exc_group):
|
"Ignored!</bg #f8bbd0></r>"
|
||||||
logger.opt(colors=True, exception=exc).error(
|
|
||||||
"<r><bg #f8bbd0>Error occurred while running shutdown hook."
|
|
||||||
"</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
logger.error(
|
|
||||||
"<r><bg #f8bbd0>Application shutdown failed. "
|
|
||||||
"Exiting.</bg #f8bbd0></r>"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with catch({Exception: handle_exception}):
|
for task in asyncio.all_tasks():
|
||||||
await self._lifespan.shutdown()
|
if task is not asyncio.current_task() and not task.done():
|
||||||
|
task.cancel()
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
if not error_occurred:
|
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
||||||
logger.info("Application shutdown complete.")
|
if tasks and not self.force_exit:
|
||||||
|
logger.info("Waiting for tasks to finish. (CTRL+C to force quit)")
|
||||||
|
while tasks and not self.force_exit:
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
||||||
|
|
||||||
async def _listen_force_exit(self, tg: TaskGroup):
|
for task in tasks:
|
||||||
await self.force_exit.wait()
|
task.cancel()
|
||||||
tg.cancel_scope.cancel()
|
|
||||||
|
await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
|
||||||
|
logger.info("Application shutdown complete.")
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.stop()
|
||||||
|
|
||||||
|
def _install_signal_handlers(self) -> None:
|
||||||
|
if threading.current_thread() is not threading.main_thread():
|
||||||
|
# Signals can only be listened to from the main thread.
|
||||||
|
return
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
try:
|
||||||
|
for sig in HANDLED_SIGNALS:
|
||||||
|
loop.add_signal_handler(sig, self._handle_exit, sig, None)
|
||||||
|
except NotImplementedError:
|
||||||
|
# Windows
|
||||||
|
for sig in HANDLED_SIGNALS:
|
||||||
|
signal.signal(sig, self._handle_exit)
|
||||||
|
|
||||||
|
def _handle_exit(self, sig, frame):
|
||||||
|
self.exit(force=self.should_exit.is_set())
|
||||||
|
|
||||||
def exit(self, force: bool = False):
|
def exit(self, force: bool = False):
|
||||||
"""退出 none driver
|
"""退出 none driver
|
||||||
@@ -159,4 +140,4 @@ class Driver(BaseDriver):
|
|||||||
if not self.should_exit.is_set():
|
if not self.should_exit.is_set():
|
||||||
self.should_exit.set()
|
self.should_exit.set()
|
||||||
if force:
|
if force:
|
||||||
self.force_exit.set()
|
self.force_exit = True
|
||||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[quart]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.drivers.quart 模块
|
description: nonebot.drivers.quart 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[websockets]
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 4
|
sidebar_position: 4
|
||||||
description: nonebot.drivers.websockets 模块
|
description: nonebot.drivers.websockets 模块
|
||||||
"""
|
"""
|
||||||
@@ -71,8 +69,6 @@ class Mixin(WebSocketClientMixin):
|
|||||||
@override
|
@override
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
|
||||||
if setup.proxy is not None:
|
|
||||||
logger.warning("proxy is not supported by websockets driver")
|
|
||||||
connection = Connect(
|
connection = Connect(
|
||||||
str(setup.url),
|
str(setup.url),
|
||||||
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
|
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
|
||||||
|
@@ -25,8 +25,6 @@ NoneBotException
|
|||||||
```
|
```
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 10
|
sidebar_position: 10
|
||||||
description: nonebot.exception 模块
|
description: nonebot.exception 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,14 +1,11 @@
|
|||||||
import abc
|
import abc
|
||||||
|
import asyncio
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional, Protocol
|
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional, Protocol
|
||||||
|
|
||||||
import anyio
|
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.config import Config
|
from nonebot.config import Config
|
||||||
from nonebot.exception import MockApiException
|
from nonebot.exception import MockApiException
|
||||||
from nonebot.utils import flatten_exception_group
|
|
||||||
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
|
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -79,49 +76,21 @@ class Bot(abc.ABC):
|
|||||||
skip_calling_api: bool = False
|
skip_calling_api: bool = False
|
||||||
exception: Optional[Exception] = None
|
exception: Optional[Exception] = None
|
||||||
|
|
||||||
if self._calling_api_hook:
|
if coros := [hook(self, api, data) for hook in self._calling_api_hook]:
|
||||||
logger.debug("Running CallingAPI hooks...")
|
try:
|
||||||
|
logger.debug("Running CallingAPI hooks...")
|
||||||
def _handle_mock_api_exception(
|
await asyncio.gather(*coros)
|
||||||
exc_group: BaseExceptionGroup[MockApiException],
|
except MockApiException as e:
|
||||||
) -> None:
|
|
||||||
nonlocal skip_calling_api, result
|
|
||||||
|
|
||||||
excs = [
|
|
||||||
exc
|
|
||||||
for exc in flatten_exception_group(exc_group)
|
|
||||||
if isinstance(exc, MockApiException)
|
|
||||||
]
|
|
||||||
if not excs:
|
|
||||||
return
|
|
||||||
elif len(excs) > 1:
|
|
||||||
logger.warning(
|
|
||||||
"Multiple hooks want to mock API result. Use the first one."
|
|
||||||
)
|
|
||||||
|
|
||||||
skip_calling_api = True
|
skip_calling_api = True
|
||||||
result = excs[0].result
|
result = e.result
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Calling API {api} is cancelled. Return {result!r} instead."
|
f"Calling API {api} is cancelled. Return {result} instead."
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running CallingAPI hook. "
|
||||||
|
"Running cancelled!</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
|
||||||
for exc in flatten_exception_group(exc_group):
|
|
||||||
logger.opt(colors=True, exception=exc).error(
|
|
||||||
"<r><bg #f8bbd0>Error when running CallingAPI hook. "
|
|
||||||
"Running cancelled!</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
|
|
||||||
with catch(
|
|
||||||
{
|
|
||||||
MockApiException: _handle_mock_api_exception,
|
|
||||||
Exception: _handle_exception,
|
|
||||||
}
|
|
||||||
):
|
|
||||||
async with anyio.create_task_group() as tg:
|
|
||||||
for hook in self._calling_api_hook:
|
|
||||||
tg.start_soon(hook, self, api, data)
|
|
||||||
|
|
||||||
if not skip_calling_api:
|
if not skip_calling_api:
|
||||||
try:
|
try:
|
||||||
@@ -129,48 +98,25 @@ class Bot(abc.ABC):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
exception = e
|
exception = e
|
||||||
|
|
||||||
if self._called_api_hook:
|
if coros := [
|
||||||
logger.debug("Running CalledAPI hooks...")
|
hook(self, exception, api, data, result) for hook in self._called_api_hook
|
||||||
|
]:
|
||||||
def _handle_mock_api_exception(
|
try:
|
||||||
exc_group: BaseExceptionGroup[MockApiException],
|
logger.debug("Running CalledAPI hooks...")
|
||||||
) -> None:
|
await asyncio.gather(*coros)
|
||||||
nonlocal result, exception
|
except MockApiException as e:
|
||||||
|
# mock api result
|
||||||
excs = [
|
result = e.result
|
||||||
exc
|
# ignore exception
|
||||||
for exc in flatten_exception_group(exc_group)
|
|
||||||
if isinstance(exc, MockApiException)
|
|
||||||
]
|
|
||||||
if not excs:
|
|
||||||
return
|
|
||||||
elif len(excs) > 1:
|
|
||||||
logger.warning(
|
|
||||||
"Multiple hooks want to mock API result. Use the first one."
|
|
||||||
)
|
|
||||||
|
|
||||||
result = excs[0].result
|
|
||||||
exception = None
|
exception = None
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Calling API {api} result is mocked. Return {result} instead."
|
f"Calling API {api} result is mocked. Return {result} instead."
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
def _handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
logger.opt(colors=True, exception=e).error(
|
||||||
for exc in flatten_exception_group(exc_group):
|
"<r><bg #f8bbd0>Error when running CalledAPI hook. "
|
||||||
logger.opt(colors=True, exception=exc).error(
|
"Running cancelled!</bg #f8bbd0></r>"
|
||||||
"<r><bg #f8bbd0>Error when running CalledAPI hook. "
|
)
|
||||||
"Running cancelled!</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
|
|
||||||
with catch(
|
|
||||||
{
|
|
||||||
MockApiException: _handle_mock_api_exception,
|
|
||||||
Exception: _handle_exception,
|
|
||||||
}
|
|
||||||
):
|
|
||||||
async with anyio.create_task_group() as tg:
|
|
||||||
for hook in self._called_api_hook:
|
|
||||||
tg.start_soon(hook, self, exception, api, data, result)
|
|
||||||
|
|
||||||
if exception:
|
if exception:
|
||||||
raise exception
|
raise exception
|
||||||
|
@@ -1,11 +1,6 @@
|
|||||||
from types import TracebackType
|
from collections.abc import Awaitable
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import TypeAlias
|
||||||
from collections.abc import Iterable, Awaitable
|
from typing import Any, Union, Callable, cast
|
||||||
from typing import Any, Union, Callable, Optional, cast
|
|
||||||
|
|
||||||
import anyio
|
|
||||||
from anyio.abc import TaskGroup
|
|
||||||
from exceptiongroup import suppress
|
|
||||||
|
|
||||||
from nonebot.utils import run_sync, is_coroutine_callable
|
from nonebot.utils import run_sync, is_coroutine_callable
|
||||||
|
|
||||||
@@ -16,24 +11,10 @@ LIFESPAN_FUNC: TypeAlias = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
|
|||||||
|
|
||||||
class Lifespan:
|
class Lifespan:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._task_group: Optional[TaskGroup] = None
|
|
||||||
|
|
||||||
self._startup_funcs: list[LIFESPAN_FUNC] = []
|
self._startup_funcs: list[LIFESPAN_FUNC] = []
|
||||||
self._ready_funcs: list[LIFESPAN_FUNC] = []
|
self._ready_funcs: list[LIFESPAN_FUNC] = []
|
||||||
self._shutdown_funcs: list[LIFESPAN_FUNC] = []
|
self._shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||||
|
|
||||||
@property
|
|
||||||
def task_group(self) -> TaskGroup:
|
|
||||||
if self._task_group is None:
|
|
||||||
raise RuntimeError("Lifespan not started")
|
|
||||||
return self._task_group
|
|
||||||
|
|
||||||
@task_group.setter
|
|
||||||
def task_group(self, task_group: TaskGroup) -> None:
|
|
||||||
if self._task_group is not None:
|
|
||||||
raise RuntimeError("Lifespan already started")
|
|
||||||
self._task_group = task_group
|
|
||||||
|
|
||||||
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
self._startup_funcs.append(func)
|
self._startup_funcs.append(func)
|
||||||
return func
|
return func
|
||||||
@@ -48,7 +29,7 @@ class Lifespan:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _run_lifespan_func(
|
async def _run_lifespan_func(
|
||||||
funcs: Iterable[LIFESPAN_FUNC],
|
funcs: list[LIFESPAN_FUNC],
|
||||||
) -> None:
|
) -> None:
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
if is_coroutine_callable(func):
|
if is_coroutine_callable(func):
|
||||||
@@ -57,44 +38,18 @@ class Lifespan:
|
|||||||
await run_sync(cast(SYNC_LIFESPAN_FUNC, func))()
|
await run_sync(cast(SYNC_LIFESPAN_FUNC, func))()
|
||||||
|
|
||||||
async def startup(self) -> None:
|
async def startup(self) -> None:
|
||||||
# create background task group
|
|
||||||
self.task_group = anyio.create_task_group()
|
|
||||||
await self.task_group.__aenter__()
|
|
||||||
|
|
||||||
# run startup funcs
|
|
||||||
if self._startup_funcs:
|
if self._startup_funcs:
|
||||||
await self._run_lifespan_func(self._startup_funcs)
|
await self._run_lifespan_func(self._startup_funcs)
|
||||||
|
|
||||||
# run ready funcs
|
|
||||||
if self._ready_funcs:
|
if self._ready_funcs:
|
||||||
await self._run_lifespan_func(self._ready_funcs)
|
await self._run_lifespan_func(self._ready_funcs)
|
||||||
|
|
||||||
async def shutdown(
|
async def shutdown(self) -> None:
|
||||||
self,
|
|
||||||
*,
|
|
||||||
exc_type: Optional[type[BaseException]] = None,
|
|
||||||
exc_val: Optional[BaseException] = None,
|
|
||||||
exc_tb: Optional[TracebackType] = None,
|
|
||||||
) -> None:
|
|
||||||
if self._shutdown_funcs:
|
if self._shutdown_funcs:
|
||||||
# reverse shutdown funcs to ensure stack order
|
await self._run_lifespan_func(self._shutdown_funcs)
|
||||||
await self._run_lifespan_func(reversed(self._shutdown_funcs))
|
|
||||||
|
|
||||||
# shutdown background task group
|
|
||||||
self.task_group.cancel_scope.cancel()
|
|
||||||
|
|
||||||
with suppress(Exception):
|
|
||||||
await self.task_group.__aexit__(exc_type, exc_val, exc_tb)
|
|
||||||
|
|
||||||
self._task_group = None
|
|
||||||
|
|
||||||
async def __aenter__(self) -> None:
|
async def __aenter__(self) -> None:
|
||||||
await self.startup()
|
await self.startup()
|
||||||
|
|
||||||
async def __aexit__(
|
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||||
self,
|
await self.shutdown()
|
||||||
exc_type: Optional[type[BaseException]],
|
|
||||||
exc_val: Optional[BaseException],
|
|
||||||
exc_tb: Optional[TracebackType],
|
|
||||||
) -> None:
|
|
||||||
await self.shutdown(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb)
|
|
||||||
|
@@ -1,20 +1,17 @@
|
|||||||
import abc
|
import abc
|
||||||
|
import asyncio
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing_extensions import Self, TypeAlias
|
from typing_extensions import Self, TypeAlias
|
||||||
from contextlib import AsyncExitStack, asynccontextmanager
|
from contextlib import AsyncExitStack, asynccontextmanager
|
||||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional
|
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional
|
||||||
|
|
||||||
from anyio.abc import TaskGroup
|
|
||||||
from anyio import CancelScope, create_task_group
|
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.config import Env, Config
|
from nonebot.config import Env, Config
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
|
from nonebot.utils import escape_tag, run_coro_with_catch
|
||||||
from nonebot.internal.params import BotParam, DependParam, DefaultParam
|
from nonebot.internal.params import BotParam, DependParam, DefaultParam
|
||||||
from nonebot.utils import escape_tag, run_coro_with_catch, flatten_exception_group
|
|
||||||
from nonebot.typing import (
|
from nonebot.typing import (
|
||||||
T_DependencyCache,
|
T_DependencyCache,
|
||||||
T_BotConnectionHook,
|
T_BotConnectionHook,
|
||||||
@@ -64,6 +61,7 @@ class Driver(abc.ABC):
|
|||||||
self.config: Config = config
|
self.config: Config = config
|
||||||
"""全局配置对象"""
|
"""全局配置对象"""
|
||||||
self._bots: dict[str, "Bot"] = {}
|
self._bots: dict[str, "Bot"] = {}
|
||||||
|
self._bot_tasks: set[asyncio.Task] = set()
|
||||||
self._lifespan = Lifespan()
|
self._lifespan = Lifespan()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@@ -77,10 +75,6 @@ class Driver(abc.ABC):
|
|||||||
"""获取当前所有已连接的 Bot"""
|
"""获取当前所有已连接的 Bot"""
|
||||||
return self._bots
|
return self._bots
|
||||||
|
|
||||||
@property
|
|
||||||
def task_group(self) -> TaskGroup:
|
|
||||||
return self._lifespan.task_group
|
|
||||||
|
|
||||||
def register_adapter(self, adapter: type["Adapter"], **kwargs) -> None:
|
def register_adapter(self, adapter: type["Adapter"], **kwargs) -> None:
|
||||||
"""注册一个协议适配器
|
"""注册一个协议适配器
|
||||||
|
|
||||||
@@ -118,6 +112,8 @@ class Driver(abc.ABC):
|
|||||||
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
|
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.on_shutdown(self._cleanup)
|
||||||
|
|
||||||
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||||
"""注册一个启动时执行的函数"""
|
"""注册一个启动时执行的函数"""
|
||||||
return self._lifespan.on_startup(func)
|
return self._lifespan.on_startup(func)
|
||||||
@@ -158,63 +154,66 @@ class Driver(abc.ABC):
|
|||||||
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
||||||
self._bots[bot.self_id] = bot
|
self._bots[bot.self_id] = bot
|
||||||
|
|
||||||
if not self._bot_connection_hook:
|
|
||||||
return
|
|
||||||
|
|
||||||
def handle_exception(exc_group: BaseExceptionGroup) -> None:
|
|
||||||
for exc in flatten_exception_group(exc_group):
|
|
||||||
logger.opt(colors=True, exception=exc).error(
|
|
||||||
"<r><bg #f8bbd0>"
|
|
||||||
"Error when running WebSocketConnection hook:"
|
|
||||||
"</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _run_hook(bot: "Bot") -> None:
|
async def _run_hook(bot: "Bot") -> None:
|
||||||
dependency_cache: T_DependencyCache = {}
|
dependency_cache: T_DependencyCache = {}
|
||||||
with CancelScope(shield=True), catch({Exception: handle_exception}):
|
async with AsyncExitStack() as stack:
|
||||||
async with AsyncExitStack() as stack, create_task_group() as tg:
|
if coros := [
|
||||||
for hook in self._bot_connection_hook:
|
run_coro_with_catch(
|
||||||
tg.start_soon(
|
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
|
||||||
run_coro_with_catch,
|
(SkippedException,),
|
||||||
hook(
|
)
|
||||||
bot=bot, stack=stack, dependency_cache=dependency_cache
|
for hook in self._bot_connection_hook
|
||||||
),
|
]:
|
||||||
(SkippedException,),
|
try:
|
||||||
|
await asyncio.gather(*coros)
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>"
|
||||||
|
"Error when running WebSocketConnection hook. "
|
||||||
|
"Running cancelled!"
|
||||||
|
"</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.task_group.start_soon(_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:
|
def _bot_disconnect(self, bot: "Bot") -> None:
|
||||||
"""在连接断开后,调用该函数来注销 bot 对象"""
|
"""在连接断开后,调用该函数来注销 bot 对象"""
|
||||||
if bot.self_id in self._bots:
|
if bot.self_id in self._bots:
|
||||||
del self._bots[bot.self_id]
|
del self._bots[bot.self_id]
|
||||||
|
|
||||||
if not self._bot_disconnection_hook:
|
|
||||||
return
|
|
||||||
|
|
||||||
def handle_exception(exc_group: BaseExceptionGroup) -> None:
|
|
||||||
for exc in flatten_exception_group(exc_group):
|
|
||||||
logger.opt(colors=True, exception=exc).error(
|
|
||||||
"<r><bg #f8bbd0>"
|
|
||||||
"Error when running WebSocketDisConnection hook:"
|
|
||||||
"</bg #f8bbd0></r>"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _run_hook(bot: "Bot") -> None:
|
async def _run_hook(bot: "Bot") -> None:
|
||||||
dependency_cache: T_DependencyCache = {}
|
dependency_cache: T_DependencyCache = {}
|
||||||
# shield cancellation to ensure bot disconnect hooks are always run
|
async with AsyncExitStack() as stack:
|
||||||
with CancelScope(shield=True), catch({Exception: handle_exception}):
|
if coros := [
|
||||||
async with create_task_group() as tg, AsyncExitStack() as stack:
|
run_coro_with_catch(
|
||||||
for hook in self._bot_disconnection_hook:
|
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
|
||||||
tg.start_soon(
|
(SkippedException,),
|
||||||
run_coro_with_catch,
|
)
|
||||||
hook(
|
for hook in self._bot_disconnection_hook
|
||||||
bot=bot, stack=stack, dependency_cache=dependency_cache
|
]:
|
||||||
),
|
try:
|
||||||
(SkippedException,),
|
await asyncio.gather(*coros)
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>"
|
||||||
|
"Error when running WebSocketDisConnection hook. "
|
||||||
|
"Running cancelled!"
|
||||||
|
"</bg #f8bbd0></r>"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.task_group.start_soon(_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 Mixin(abc.ABC):
|
class Mixin(abc.ABC):
|
||||||
|
@@ -22,13 +22,11 @@ from typing import ( # noqa: UP035
|
|||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.internal.rule import Rule
|
from nonebot.internal.rule import Rule
|
||||||
|
from nonebot.utils import classproperty
|
||||||
from nonebot.dependencies import Param, Dependent
|
from nonebot.dependencies import Param, Dependent
|
||||||
from nonebot.internal.permission import User, Permission
|
from nonebot.internal.permission import User, Permission
|
||||||
from nonebot.utils import classproperty, flatten_exception_group
|
|
||||||
from nonebot.internal.adapter import (
|
from nonebot.internal.adapter import (
|
||||||
Bot,
|
Bot,
|
||||||
Event,
|
Event,
|
||||||
@@ -814,34 +812,28 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
f"bot={bot}, event={event!r}, state={state!r}"
|
f"bot={bot}, event={event!r}, state={state!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _handle_stop_propagation(exc_group: BaseExceptionGroup[StopPropagation]):
|
|
||||||
self.block = True
|
|
||||||
|
|
||||||
with self.ensure_context(bot, event):
|
with self.ensure_context(bot, event):
|
||||||
try:
|
try:
|
||||||
with catch({StopPropagation: _handle_stop_propagation}):
|
# Refresh preprocess state
|
||||||
# Refresh preprocess state
|
self.state.update(state)
|
||||||
self.state.update(state)
|
|
||||||
|
|
||||||
while self.remain_handlers:
|
while self.remain_handlers:
|
||||||
handler = self.remain_handlers.pop(0)
|
handler = self.remain_handlers.pop(0)
|
||||||
current_handler.set(handler)
|
current_handler.set(handler)
|
||||||
logger.debug(f"Running handler {handler}")
|
logger.debug(f"Running handler {handler}")
|
||||||
|
try:
|
||||||
def _handle_skipped(
|
await handler(
|
||||||
exc_group: BaseExceptionGroup[SkippedException],
|
matcher=self,
|
||||||
):
|
bot=bot,
|
||||||
logger.debug(f"Handler {handler} skipped")
|
event=event,
|
||||||
|
state=self.state,
|
||||||
with catch({SkippedException: _handle_skipped}):
|
stack=stack,
|
||||||
await handler(
|
dependency_cache=dependency_cache,
|
||||||
matcher=self,
|
)
|
||||||
bot=bot,
|
except SkippedException:
|
||||||
event=event,
|
logger.debug(f"Handler {handler} skipped")
|
||||||
state=self.state,
|
except StopPropagation:
|
||||||
stack=stack,
|
self.block = True
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
)
|
|
||||||
finally:
|
finally:
|
||||||
logger.info(f"{self} running complete")
|
logger.info(f"{self} running complete")
|
||||||
|
|
||||||
@@ -854,54 +846,10 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
stack: Optional[AsyncExitStack] = None,
|
stack: Optional[AsyncExitStack] = None,
|
||||||
dependency_cache: Optional[T_DependencyCache] = None,
|
dependency_cache: Optional[T_DependencyCache] = None,
|
||||||
):
|
):
|
||||||
exc: Optional[Union[FinishedException, RejectedException, PausedException]] = (
|
try:
|
||||||
None
|
|
||||||
)
|
|
||||||
|
|
||||||
def _handle_special_exception(
|
|
||||||
exc_group: BaseExceptionGroup[
|
|
||||||
Union[FinishedException, RejectedException, PausedException]
|
|
||||||
]
|
|
||||||
):
|
|
||||||
nonlocal exc
|
|
||||||
excs = list(flatten_exception_group(exc_group))
|
|
||||||
if len(excs) > 1:
|
|
||||||
logger.warning(
|
|
||||||
"Multiple session control exceptions occurred. "
|
|
||||||
"NoneBot will choose the proper one."
|
|
||||||
)
|
|
||||||
finished_exc = next(
|
|
||||||
(e for e in excs if isinstance(e, FinishedException)),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
rejected_exc = next(
|
|
||||||
(e for e in excs if isinstance(e, RejectedException)),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
paused_exc = next(
|
|
||||||
(e for e in excs if isinstance(e, PausedException)),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
exc = finished_exc or rejected_exc or paused_exc
|
|
||||||
elif isinstance(
|
|
||||||
excs[0], (FinishedException, RejectedException, PausedException)
|
|
||||||
):
|
|
||||||
exc = excs[0]
|
|
||||||
|
|
||||||
with catch(
|
|
||||||
{
|
|
||||||
(
|
|
||||||
FinishedException,
|
|
||||||
RejectedException,
|
|
||||||
PausedException,
|
|
||||||
): _handle_special_exception
|
|
||||||
}
|
|
||||||
):
|
|
||||||
await self.simple_run(bot, event, state, stack, dependency_cache)
|
await self.simple_run(bot, event, state, stack, dependency_cache)
|
||||||
|
|
||||||
if isinstance(exc, FinishedException):
|
except RejectedException:
|
||||||
pass
|
|
||||||
elif isinstance(exc, RejectedException):
|
|
||||||
await self.resolve_reject()
|
await self.resolve_reject()
|
||||||
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
||||||
permission = await self.update_permission(
|
permission = await self.update_permission(
|
||||||
@@ -922,7 +870,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
default_type_updater=self.__class__._default_type_updater,
|
default_type_updater=self.__class__._default_type_updater,
|
||||||
default_permission_updater=self.__class__._default_permission_updater,
|
default_permission_updater=self.__class__._default_permission_updater,
|
||||||
)
|
)
|
||||||
elif isinstance(exc, PausedException):
|
except PausedException:
|
||||||
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
||||||
permission = await self.update_permission(
|
permission = await self.update_permission(
|
||||||
bot, event, stack, dependency_cache
|
bot, event, stack, dependency_cache
|
||||||
@@ -942,3 +890,5 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
default_type_updater=self.__class__._default_type_updater,
|
default_type_updater=self.__class__._default_type_updater,
|
||||||
default_permission_updater=self.__class__._default_permission_updater,
|
default_permission_updater=self.__class__._default_permission_updater,
|
||||||
)
|
)
|
||||||
|
except FinishedException:
|
||||||
|
pass
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
from enum import Enum
|
|
||||||
from typing_extensions import Self, get_args, override, get_origin
|
from typing_extensions import Self, get_args, override, get_origin
|
||||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||||
from typing import (
|
from typing import (
|
||||||
@@ -13,11 +13,8 @@ from typing import (
|
|||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
import anyio
|
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
||||||
|
|
||||||
from nonebot.exception import SkippedException
|
|
||||||
from nonebot.dependencies import Param, Dependent
|
from nonebot.dependencies import Param, Dependent
|
||||||
from nonebot.dependencies.utils import check_field_type
|
from nonebot.dependencies.utils import check_field_type
|
||||||
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
|
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
|
||||||
@@ -96,78 +93,6 @@ def Depends(
|
|||||||
return DependsInner(dependency, use_cache=use_cache, validate=validate)
|
return DependsInner(dependency, use_cache=use_cache, validate=validate)
|
||||||
|
|
||||||
|
|
||||||
class CacheState(str, Enum):
|
|
||||||
"""子依赖缓存状态"""
|
|
||||||
|
|
||||||
PENDING = "PENDING"
|
|
||||||
FINISHED = "FINISHED"
|
|
||||||
|
|
||||||
|
|
||||||
class DependencyCache:
|
|
||||||
"""子依赖结果缓存。
|
|
||||||
|
|
||||||
用于缓存子依赖的结果,以避免重复计算。
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._state = CacheState.PENDING
|
|
||||||
self._result: Any = None
|
|
||||||
self._exception: Optional[BaseException] = None
|
|
||||||
self._waiter = anyio.Event()
|
|
||||||
|
|
||||||
def done(self) -> bool:
|
|
||||||
return self._state == CacheState.FINISHED
|
|
||||||
|
|
||||||
def result(self) -> Any:
|
|
||||||
"""获取子依赖结果"""
|
|
||||||
|
|
||||||
if self._state != CacheState.FINISHED:
|
|
||||||
raise RuntimeError("Result is not ready")
|
|
||||||
|
|
||||||
if self._exception is not None:
|
|
||||||
raise self._exception
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
def exception(self) -> Optional[BaseException]:
|
|
||||||
"""获取子依赖异常"""
|
|
||||||
|
|
||||||
if self._state != CacheState.FINISHED:
|
|
||||||
raise RuntimeError("Result is not ready")
|
|
||||||
|
|
||||||
return self._exception
|
|
||||||
|
|
||||||
def set_result(self, result: Any) -> None:
|
|
||||||
"""设置子依赖结果"""
|
|
||||||
|
|
||||||
if self._state != CacheState.PENDING:
|
|
||||||
raise RuntimeError(f"Cache state invalid: {self._state}")
|
|
||||||
|
|
||||||
self._result = result
|
|
||||||
self._state = CacheState.FINISHED
|
|
||||||
self._waiter.set()
|
|
||||||
|
|
||||||
def set_exception(self, exception: BaseException) -> None:
|
|
||||||
"""设置子依赖异常"""
|
|
||||||
|
|
||||||
if self._state != CacheState.PENDING:
|
|
||||||
raise RuntimeError(f"Cache state invalid: {self._state}")
|
|
||||||
|
|
||||||
self._exception = exception
|
|
||||||
self._state = CacheState.FINISHED
|
|
||||||
self._waiter.set()
|
|
||||||
|
|
||||||
async def wait(self):
|
|
||||||
"""等待子依赖结果"""
|
|
||||||
await self._waiter.wait()
|
|
||||||
if self._state != CacheState.FINISHED:
|
|
||||||
raise RuntimeError("Invalid cache state")
|
|
||||||
|
|
||||||
if self._exception is not None:
|
|
||||||
raise self._exception
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
class DependParam(Param):
|
class DependParam(Param):
|
||||||
"""子依赖注入参数。
|
"""子依赖注入参数。
|
||||||
|
|
||||||
@@ -269,27 +194,17 @@ class DependParam(Param):
|
|||||||
call = cast(Callable[..., Any], sub_dependent.call)
|
call = cast(Callable[..., Any], sub_dependent.call)
|
||||||
|
|
||||||
# solve sub dependency with current cache
|
# solve sub dependency with current cache
|
||||||
exc: Optional[BaseExceptionGroup[SkippedException]] = None
|
sub_values = await sub_dependent.solve(
|
||||||
|
stack=stack,
|
||||||
def _handle_skipped(exc_group: BaseExceptionGroup[SkippedException]):
|
dependency_cache=dependency_cache,
|
||||||
nonlocal exc
|
**kwargs,
|
||||||
exc = exc_group
|
)
|
||||||
|
|
||||||
with catch({SkippedException: _handle_skipped}):
|
|
||||||
sub_values = await sub_dependent.solve(
|
|
||||||
stack=stack,
|
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
if exc is not None:
|
|
||||||
raise exc
|
|
||||||
|
|
||||||
# run dependency function
|
# run dependency function
|
||||||
|
task: asyncio.Task[Any]
|
||||||
if use_cache and call in dependency_cache:
|
if use_cache and call in dependency_cache:
|
||||||
return await dependency_cache[call].wait()
|
return await dependency_cache[call]
|
||||||
|
elif is_gen_callable(call) or is_async_gen_callable(call):
|
||||||
if is_gen_callable(call) or is_async_gen_callable(call):
|
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
stack, AsyncExitStack
|
stack, AsyncExitStack
|
||||||
), "Generator dependency should be called in context"
|
), "Generator dependency should be called in context"
|
||||||
@@ -297,28 +212,17 @@ class DependParam(Param):
|
|||||||
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
||||||
else:
|
else:
|
||||||
cm = asynccontextmanager(call)(**sub_values)
|
cm = asynccontextmanager(call)(**sub_values)
|
||||||
|
task = asyncio.create_task(stack.enter_async_context(cm))
|
||||||
target = stack.enter_async_context(cm)
|
dependency_cache[call] = task
|
||||||
|
return await task
|
||||||
elif is_coroutine_callable(call):
|
elif is_coroutine_callable(call):
|
||||||
target = call(**sub_values)
|
task = asyncio.create_task(call(**sub_values))
|
||||||
|
dependency_cache[call] = task
|
||||||
|
return await task
|
||||||
else:
|
else:
|
||||||
target = run_sync(call)(**sub_values)
|
task = asyncio.create_task(run_sync(call)(**sub_values))
|
||||||
|
dependency_cache[call] = task
|
||||||
dependency_cache[call] = cache = DependencyCache()
|
return await task
|
||||||
try:
|
|
||||||
result = await target
|
|
||||||
except Exception as e:
|
|
||||||
cache.set_exception(e)
|
|
||||||
raise
|
|
||||||
except BaseException as e:
|
|
||||||
cache.set_exception(e)
|
|
||||||
# remove cache when base exception occurs
|
|
||||||
# e.g. CancelledError
|
|
||||||
dependency_cache.pop(call, None)
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
cache.set_result(result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
async def _check(self, **kwargs: Any) -> None:
|
async def _check(self, **kwargs: Any) -> None:
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
|
import asyncio
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import Union, ClassVar, NoReturn, Optional
|
from typing import Union, ClassVar, NoReturn, Optional
|
||||||
|
|
||||||
import anyio
|
|
||||||
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.utils import run_coro_with_catch
|
from nonebot.utils import run_coro_with_catch
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
@@ -71,26 +70,22 @@ class Permission:
|
|||||||
"""
|
"""
|
||||||
if not self.checkers:
|
if not self.checkers:
|
||||||
return True
|
return True
|
||||||
|
results = await asyncio.gather(
|
||||||
result = False
|
*(
|
||||||
|
run_coro_with_catch(
|
||||||
async def _run_checker(checker: Dependent[bool]) -> None:
|
checker(
|
||||||
nonlocal result
|
bot=bot,
|
||||||
# calculate the result first to avoid data racing
|
event=event,
|
||||||
is_passed = await run_coro_with_catch(
|
stack=stack,
|
||||||
checker(
|
dependency_cache=dependency_cache,
|
||||||
bot=bot, event=event, stack=stack, dependency_cache=dependency_cache
|
),
|
||||||
),
|
(SkippedException,),
|
||||||
(SkippedException,),
|
False,
|
||||||
False,
|
)
|
||||||
)
|
for checker in self.checkers
|
||||||
result |= is_passed
|
),
|
||||||
|
)
|
||||||
async with anyio.create_task_group() as tg:
|
return any(results)
|
||||||
for checker in self.checkers:
|
|
||||||
tg.start_soon(_run_checker, checker)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __and__(self, other: object) -> NoReturn:
|
def __and__(self, other: object) -> NoReturn:
|
||||||
raise RuntimeError("And operation between Permissions is not allowed.")
|
raise RuntimeError("And operation between Permissions is not allowed.")
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
|
import asyncio
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import Union, ClassVar, NoReturn, Optional
|
from typing import Union, ClassVar, NoReturn, Optional
|
||||||
|
|
||||||
import anyio
|
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
|
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.exception import SkippedException
|
from nonebot.exception import SkippedException
|
||||||
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
||||||
@@ -73,33 +71,22 @@ class Rule:
|
|||||||
"""
|
"""
|
||||||
if not self.checkers:
|
if not self.checkers:
|
||||||
return True
|
return True
|
||||||
|
try:
|
||||||
result = True
|
results = await asyncio.gather(
|
||||||
|
*(
|
||||||
def _handle_skipped_exception(
|
checker(
|
||||||
exc_group: BaseExceptionGroup[SkippedException],
|
bot=bot,
|
||||||
) -> None:
|
event=event,
|
||||||
nonlocal result
|
state=state,
|
||||||
result = False
|
stack=stack,
|
||||||
|
dependency_cache=dependency_cache,
|
||||||
async def _run_checker(checker: Dependent[bool]) -> None:
|
)
|
||||||
nonlocal result
|
for checker in self.checkers
|
||||||
# calculate the result first to avoid data racing
|
)
|
||||||
is_passed = await checker(
|
|
||||||
bot=bot,
|
|
||||||
event=event,
|
|
||||||
state=state,
|
|
||||||
stack=stack,
|
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
)
|
)
|
||||||
result &= is_passed
|
except SkippedException:
|
||||||
|
return False
|
||||||
with catch({SkippedException: _handle_skipped_exception}):
|
return all(results)
|
||||||
async with anyio.create_task_group() as tg:
|
|
||||||
for checker in self.checkers:
|
|
||||||
tg.start_soon(_run_checker, checker)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
|
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
|
||||||
if other is None:
|
if other is None:
|
||||||
|
@@ -8,8 +8,6 @@ NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
|
|||||||
[loguru]: https://github.com/Delgan/loguru
|
[loguru]: https://github.com/Delgan/loguru
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 7
|
sidebar_position: 7
|
||||||
description: nonebot.log 模块
|
description: nonebot.log 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.matcher 模块
|
description: nonebot.matcher 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -3,36 +3,27 @@
|
|||||||
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
|
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.message 模块
|
description: nonebot.message 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from contextlib import AsyncExitStack
|
from contextlib import AsyncExitStack
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Optional
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
import anyio
|
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.rule import TrieRule
|
from nonebot.rule import TrieRule
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
from nonebot.matcher import Matcher, matchers
|
from nonebot.matcher import Matcher, matchers
|
||||||
|
from nonebot.utils import escape_tag, run_coro_with_catch
|
||||||
from nonebot.exception import (
|
from nonebot.exception import (
|
||||||
NoLogException,
|
NoLogException,
|
||||||
StopPropagation,
|
StopPropagation,
|
||||||
IgnoredException,
|
IgnoredException,
|
||||||
SkippedException,
|
SkippedException,
|
||||||
)
|
)
|
||||||
from nonebot.utils import (
|
|
||||||
escape_tag,
|
|
||||||
run_coro_with_catch,
|
|
||||||
run_coro_with_shield,
|
|
||||||
flatten_exception_group,
|
|
||||||
)
|
|
||||||
from nonebot.typing import (
|
from nonebot.typing import (
|
||||||
T_State,
|
T_State,
|
||||||
T_DependencyCache,
|
T_DependencyCache,
|
||||||
@@ -132,21 +123,6 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
|||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
def _handle_ignored_exception(msg: str) -> Callable[[BaseExceptionGroup], None]:
|
|
||||||
def _handle(exc_group: BaseExceptionGroup[IgnoredException]) -> None:
|
|
||||||
logger.opt(colors=True).info(msg)
|
|
||||||
|
|
||||||
return _handle
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_exception(msg: str) -> Callable[[BaseExceptionGroup], None]:
|
|
||||||
def _handle(exc_group: BaseExceptionGroup[Exception]) -> None:
|
|
||||||
for exc in flatten_exception_group(exc_group):
|
|
||||||
logger.opt(colors=True, exception=exc).error(msg)
|
|
||||||
|
|
||||||
return _handle
|
|
||||||
|
|
||||||
|
|
||||||
async def _apply_event_preprocessors(
|
async def _apply_event_preprocessors(
|
||||||
bot: "Bot",
|
bot: "Bot",
|
||||||
event: "Event",
|
event: "Event",
|
||||||
@@ -174,21 +150,10 @@ async def _apply_event_preprocessors(
|
|||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Running PreProcessors...")
|
logger.debug("Running PreProcessors...")
|
||||||
|
|
||||||
with catch(
|
try:
|
||||||
{
|
await asyncio.gather(
|
||||||
IgnoredException: _handle_ignored_exception(
|
*(
|
||||||
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
run_coro_with_catch(
|
||||||
),
|
|
||||||
Exception: _handle_exception(
|
|
||||||
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
|
||||||
"Event ignored!</bg #f8bbd0></r>"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
):
|
|
||||||
async with anyio.create_task_group() as tg:
|
|
||||||
for proc in _event_preprocessors:
|
|
||||||
tg.start_soon(
|
|
||||||
run_coro_with_catch,
|
|
||||||
proc(
|
proc(
|
||||||
bot=bot,
|
bot=bot,
|
||||||
event=event,
|
event=event,
|
||||||
@@ -198,10 +163,22 @@ async def _apply_event_preprocessors(
|
|||||||
),
|
),
|
||||||
(SkippedException,),
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
|
for proc in _event_preprocessors
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except IgnoredException:
|
||||||
|
logger.opt(colors=True).info(
|
||||||
|
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||||
|
"Event ignored!</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
async def _apply_event_postprocessors(
|
async def _apply_event_postprocessors(
|
||||||
@@ -228,17 +205,10 @@ async def _apply_event_postprocessors(
|
|||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Running PostProcessors...")
|
logger.debug("Running PostProcessors...")
|
||||||
|
|
||||||
with catch(
|
try:
|
||||||
{
|
await asyncio.gather(
|
||||||
Exception: _handle_exception(
|
*(
|
||||||
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
run_coro_with_catch(
|
||||||
)
|
|
||||||
}
|
|
||||||
):
|
|
||||||
async with anyio.create_task_group() as tg:
|
|
||||||
for proc in _event_postprocessors:
|
|
||||||
tg.start_soon(
|
|
||||||
run_coro_with_catch,
|
|
||||||
proc(
|
proc(
|
||||||
bot=bot,
|
bot=bot,
|
||||||
event=event,
|
event=event,
|
||||||
@@ -248,6 +218,13 @@ async def _apply_event_postprocessors(
|
|||||||
),
|
),
|
||||||
(SkippedException,),
|
(SkippedException,),
|
||||||
)
|
)
|
||||||
|
for proc in _event_postprocessors
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _apply_run_preprocessors(
|
async def _apply_run_preprocessors(
|
||||||
@@ -275,38 +252,35 @@ async def _apply_run_preprocessors(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# ensure matcher function can be correctly called
|
# ensure matcher function can be correctly called
|
||||||
with (
|
with matcher.ensure_context(bot, event):
|
||||||
matcher.ensure_context(bot, event),
|
try:
|
||||||
catch(
|
await asyncio.gather(
|
||||||
{
|
*(
|
||||||
IgnoredException: _handle_ignored_exception(
|
run_coro_with_catch(
|
||||||
f"{matcher} running is <b>cancelled</b>"
|
proc(
|
||||||
),
|
matcher=matcher,
|
||||||
Exception: _handle_exception(
|
bot=bot,
|
||||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
event=event,
|
||||||
"Running cancelled!</bg #f8bbd0></r>"
|
state=state,
|
||||||
),
|
stack=stack,
|
||||||
}
|
dependency_cache=dependency_cache,
|
||||||
),
|
),
|
||||||
):
|
(SkippedException,),
|
||||||
async with anyio.create_task_group() as tg:
|
)
|
||||||
for proc in _run_preprocessors:
|
for proc in _run_preprocessors
|
||||||
tg.start_soon(
|
|
||||||
run_coro_with_catch,
|
|
||||||
proc(
|
|
||||||
matcher=matcher,
|
|
||||||
bot=bot,
|
|
||||||
event=event,
|
|
||||||
state=state,
|
|
||||||
stack=stack,
|
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
),
|
|
||||||
(SkippedException,),
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
except IgnoredException:
|
||||||
|
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
||||||
|
"Running cancelled!</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
async def _apply_run_postprocessors(
|
async def _apply_run_postprocessors(
|
||||||
@@ -330,32 +304,29 @@ async def _apply_run_postprocessors(
|
|||||||
if not _run_postprocessors:
|
if not _run_postprocessors:
|
||||||
return
|
return
|
||||||
|
|
||||||
with (
|
with matcher.ensure_context(bot, event):
|
||||||
matcher.ensure_context(bot, event),
|
try:
|
||||||
catch(
|
await asyncio.gather(
|
||||||
{
|
*(
|
||||||
Exception: _handle_exception(
|
run_coro_with_catch(
|
||||||
"<r><bg #f8bbd0>Error when running RunPostProcessors"
|
proc(
|
||||||
"</bg #f8bbd0></r>"
|
matcher=matcher,
|
||||||
)
|
exception=exception,
|
||||||
}
|
bot=bot,
|
||||||
),
|
event=event,
|
||||||
):
|
state=matcher.state,
|
||||||
async with anyio.create_task_group() as tg:
|
stack=stack,
|
||||||
for proc in _run_postprocessors:
|
dependency_cache=dependency_cache,
|
||||||
tg.start_soon(
|
),
|
||||||
run_coro_with_catch,
|
(SkippedException,),
|
||||||
proc(
|
)
|
||||||
matcher=matcher,
|
for proc in _run_postprocessors
|
||||||
exception=exception,
|
|
||||||
bot=bot,
|
|
||||||
event=event,
|
|
||||||
state=matcher.state,
|
|
||||||
stack=stack,
|
|
||||||
dependency_cache=dependency_cache,
|
|
||||||
),
|
|
||||||
(SkippedException,),
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.opt(colors=True, exception=e).error(
|
||||||
|
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _check_matcher(
|
async def _check_matcher(
|
||||||
@@ -452,9 +423,8 @@ async def _run_matcher(
|
|||||||
|
|
||||||
exception = None
|
exception = None
|
||||||
|
|
||||||
logger.debug(f"Running {matcher}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.debug(f"Running {matcher}")
|
||||||
await matcher.run(bot, event, state, stack, dependency_cache)
|
await matcher.run(bot, event, state, stack, dependency_cache)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(
|
||||||
@@ -522,7 +492,8 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
|
|
||||||
用法:
|
用法:
|
||||||
```python
|
```python
|
||||||
driver.task_group.start_soon(handle_event, bot, event)
|
import asyncio
|
||||||
|
asyncio.create_task(handle_event(bot, event))
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
show_log = True
|
show_log = True
|
||||||
@@ -557,13 +528,6 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
break_flag = False
|
break_flag = False
|
||||||
|
|
||||||
def _handle_stop_propagation(exc_group: BaseExceptionGroup) -> None:
|
|
||||||
nonlocal break_flag
|
|
||||||
|
|
||||||
break_flag = True
|
|
||||||
logger.debug("Stop event propagation")
|
|
||||||
|
|
||||||
# iterate through all priority until stop propagation
|
# iterate through all priority until stop propagation
|
||||||
for priority in sorted(matchers.keys()):
|
for priority in sorted(matchers.keys()):
|
||||||
if break_flag:
|
if break_flag:
|
||||||
@@ -572,30 +536,23 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
|||||||
if show_log:
|
if show_log:
|
||||||
logger.debug(f"Checking for matchers in priority {priority}...")
|
logger.debug(f"Checking for matchers in priority {priority}...")
|
||||||
|
|
||||||
if not (priority_matchers := matchers[priority]):
|
pending_tasks = [
|
||||||
continue
|
check_and_run_matcher(
|
||||||
|
matcher, bot, event, state.copy(), stack, dependency_cache
|
||||||
with catch(
|
)
|
||||||
{
|
for matcher in matchers[priority]
|
||||||
StopPropagation: _handle_stop_propagation,
|
]
|
||||||
Exception: _handle_exception(
|
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
|
||||||
|
for result in results:
|
||||||
|
if not isinstance(result, Exception):
|
||||||
|
continue
|
||||||
|
if isinstance(result, StopPropagation):
|
||||||
|
break_flag = True
|
||||||
|
logger.debug("Stop event propagation")
|
||||||
|
else:
|
||||||
|
logger.opt(colors=True, exception=result).error(
|
||||||
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
||||||
),
|
)
|
||||||
}
|
|
||||||
):
|
|
||||||
async with anyio.create_task_group() as tg:
|
|
||||||
for matcher in priority_matchers:
|
|
||||||
tg.start_soon(
|
|
||||||
run_coro_with_shield,
|
|
||||||
check_and_run_matcher(
|
|
||||||
matcher,
|
|
||||||
bot,
|
|
||||||
event,
|
|
||||||
state.copy(),
|
|
||||||
stack,
|
|
||||||
dependency_cache,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if show_log:
|
if show_log:
|
||||||
logger.debug("Checking for matchers completed")
|
logger.debug("Checking for matchers completed")
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
"""本模块定义了依赖注入的各类参数。
|
"""本模块定义了依赖注入的各类参数。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 4
|
sidebar_position: 4
|
||||||
description: nonebot.params 模块
|
description: nonebot.params 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -5,8 +5,6 @@
|
|||||||
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 6
|
sidebar_position: 6
|
||||||
description: nonebot.permission 模块
|
description: nonebot.permission 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -32,8 +32,6 @@
|
|||||||
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.model.PluginMetadata>`
|
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.model.PluginMetadata>`
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: nonebot.plugin 模块
|
description: nonebot.plugin 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
"""本模块定义插件加载接口。
|
"""本模块定义插件加载接口。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: nonebot.plugin.load 模块
|
description: nonebot.plugin.load 模块
|
||||||
"""
|
"""
|
||||||
@@ -22,7 +20,7 @@ from . import _managers, get_plugin, _module_name_to_plugin_id
|
|||||||
try: # pragma: py-gte-311
|
try: # pragma: py-gte-311
|
||||||
import tomllib # pyright: ignore[reportMissingImports]
|
import tomllib # pyright: ignore[reportMissingImports]
|
||||||
except ModuleNotFoundError: # pragma: py-lt-311
|
except ModuleNotFoundError: # pragma: py-lt-311
|
||||||
import tomli as tomllib # pyright: ignore[reportMissingImports]
|
import tomli as tomllib
|
||||||
|
|
||||||
|
|
||||||
def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
||||||
|
@@ -3,8 +3,6 @@
|
|||||||
参考: [import hooks](https://docs.python.org/3/reference/import.html#import-hooks), [PEP302](https://www.python.org/dev/peps/pep-0302/)
|
参考: [import hooks](https://docs.python.org/3/reference/import.html#import-hooks), [PEP302](https://www.python.org/dev/peps/pep-0302/)
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.plugin.manager 模块
|
description: nonebot.plugin.manager 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
"""本模块定义插件相关信息。
|
"""本模块定义插件相关信息。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: nonebot.plugin.model 模块
|
description: nonebot.plugin.model 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
"""本模块定义事件响应器便携定义函数。
|
"""本模块定义事件响应器便携定义函数。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: nonebot.plugin.on 模块
|
description: nonebot.plugin.on 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -5,8 +5,6 @@
|
|||||||
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 5
|
sidebar_position: 5
|
||||||
description: nonebot.rule 模块
|
description: nonebot.rule 模块
|
||||||
"""
|
"""
|
||||||
|
@@ -6,8 +6,6 @@
|
|||||||
[`typing`](https://docs.python.org/3/library/typing.html)。
|
[`typing`](https://docs.python.org/3/library/typing.html)。
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 11
|
sidebar_position: 11
|
||||||
description: nonebot.typing 模块
|
description: nonebot.typing 模块
|
||||||
"""
|
"""
|
||||||
@@ -21,9 +19,10 @@ from typing import TYPE_CHECKING, TypeVar
|
|||||||
from typing_extensions import ParamSpec, TypeAlias, get_args, override, get_origin
|
from typing_extensions import ParamSpec, TypeAlias, get_args, override, get_origin
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from asyncio import Task
|
||||||
|
|
||||||
from nonebot.adapters import Bot
|
from nonebot.adapters import Bot
|
||||||
from nonebot.permission import Permission
|
from nonebot.permission import Permission
|
||||||
from nonebot.internal.params import DependencyCache
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
@@ -257,5 +256,5 @@ T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
|
|||||||
- MatcherParam: Matcher 对象
|
- MatcherParam: Matcher 对象
|
||||||
- DefaultParam: 带有默认值的参数
|
- DefaultParam: 带有默认值的参数
|
||||||
"""
|
"""
|
||||||
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "DependencyCache"]
|
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "Task[t.Any]"]
|
||||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||||
|
@@ -1,30 +1,27 @@
|
|||||||
"""本模块包含了 NoneBot 的一些工具函数
|
"""本模块包含了 NoneBot 的一些工具函数
|
||||||
|
|
||||||
FrontMatter:
|
FrontMatter:
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
sidebar_position: 8
|
sidebar_position: 8
|
||||||
description: nonebot.utils 模块
|
description: nonebot.utils 模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
import importlib
|
import importlib
|
||||||
import contextlib
|
import contextlib
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from contextvars import copy_context
|
||||||
from functools import wraps, partial
|
from functools import wraps, partial
|
||||||
from contextlib import AbstractContextManager, asynccontextmanager
|
from contextlib import AbstractContextManager, asynccontextmanager
|
||||||
from typing_extensions import ParamSpec, get_args, override, get_origin
|
from typing_extensions import ParamSpec, get_args, override, get_origin
|
||||||
|
from collections.abc import Mapping, Sequence, Coroutine, AsyncGenerator
|
||||||
from typing import Any, Union, Generic, TypeVar, Callable, Optional, overload
|
from typing import Any, Union, Generic, TypeVar, Callable, Optional, overload
|
||||||
from collections.abc import Mapping, Sequence, Coroutine, Generator, AsyncGenerator
|
|
||||||
|
|
||||||
import anyio
|
|
||||||
import anyio.to_thread
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from exceptiongroup import BaseExceptionGroup, catch
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from nonebot.typing import (
|
from nonebot.typing import (
|
||||||
@@ -40,7 +37,6 @@ R = TypeVar("R")
|
|||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
K = TypeVar("K")
|
K = TypeVar("K")
|
||||||
V = TypeVar("V")
|
V = TypeVar("V")
|
||||||
E = TypeVar("E", bound=BaseException)
|
|
||||||
|
|
||||||
|
|
||||||
def escape_tag(s: str) -> str:
|
def escape_tag(s: str) -> str:
|
||||||
@@ -180,9 +176,11 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
|
|||||||
|
|
||||||
@wraps(call)
|
@wraps(call)
|
||||||
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||||
return await anyio.to_thread.run_sync(
|
loop = asyncio.get_running_loop()
|
||||||
partial(call, *args, **kwargs), abandon_on_cancel=True
|
pfunc = partial(call, *args, **kwargs)
|
||||||
)
|
context = copy_context()
|
||||||
|
result = await loop.run_in_executor(None, partial(context.run, pfunc))
|
||||||
|
return result
|
||||||
|
|
||||||
return _wrapper
|
return _wrapper
|
||||||
|
|
||||||
@@ -234,34 +232,10 @@ async def run_coro_with_catch(
|
|||||||
协程的返回值或发生异常时的指定值
|
协程的返回值或发生异常时的指定值
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with catch({exc: lambda exc_group: None}):
|
try:
|
||||||
return await coro
|
return await coro
|
||||||
|
except exc:
|
||||||
return return_on_err
|
return return_on_err
|
||||||
|
|
||||||
|
|
||||||
async def run_coro_with_shield(coro: Coroutine[Any, Any, T]) -> T:
|
|
||||||
"""运行协程并在取消时屏蔽取消异常。
|
|
||||||
|
|
||||||
参数:
|
|
||||||
coro: 要运行的协程
|
|
||||||
|
|
||||||
返回:
|
|
||||||
协程的返回值
|
|
||||||
"""
|
|
||||||
|
|
||||||
with anyio.CancelScope(shield=True):
|
|
||||||
return await coro
|
|
||||||
|
|
||||||
|
|
||||||
def flatten_exception_group(
|
|
||||||
exc_group: BaseExceptionGroup[E],
|
|
||||||
) -> Generator[E, None, None]:
|
|
||||||
for exc in exc_group.exceptions:
|
|
||||||
if isinstance(exc, BaseExceptionGroup):
|
|
||||||
yield from flatten_exception_group(exc)
|
|
||||||
else:
|
|
||||||
yield exc
|
|
||||||
|
|
||||||
|
|
||||||
def get_name(obj: Any) -> str:
|
def get_name(obj: Any) -> str:
|
||||||
|
2894
poetry.lock
generated
2894
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "nonebot2"
|
name = "nonebot2"
|
||||||
version = "2.4.0"
|
version = "2.3.2"
|
||||||
description = "An asynchronous python bot framework."
|
description = "An asynchronous python bot framework."
|
||||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -22,14 +22,12 @@ include = ["nonebot/py.typed"]
|
|||||||
[tool.poetry.urls]
|
[tool.poetry.urls]
|
||||||
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
||||||
"Changelog" = "https://nonebot.dev/changelog"
|
"Changelog" = "https://nonebot.dev/changelog"
|
||||||
"Funding" = "https://afdian.com/@nonebot"
|
"Funding" = "https://afdian.net/@nonebot"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
yarl = "^1.7.2"
|
yarl = "^1.7.2"
|
||||||
anyio = "^4.4.0"
|
|
||||||
pygtrie = "^2.4.1"
|
pygtrie = "^2.4.1"
|
||||||
exceptiongroup = "^1.2.2"
|
|
||||||
loguru = ">=0.6.0,<1.0.0"
|
loguru = ">=0.6.0,<1.0.0"
|
||||||
python-dotenv = ">=0.21.0,<2.0.0"
|
python-dotenv = ">=0.21.0,<2.0.0"
|
||||||
typing-extensions = ">=4.4.0,<5.0.0"
|
typing-extensions = ">=4.4.0,<5.0.0"
|
||||||
@@ -46,7 +44,7 @@ uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
|||||||
], optional = true }
|
], optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "^0.7.0"
|
ruff = "^0.4.0"
|
||||||
isort = "^5.10.1"
|
isort = "^5.10.1"
|
||||||
black = "^24.0.0"
|
black = "^24.0.0"
|
||||||
nonemoji = "^0.1.2"
|
nonemoji = "^0.1.2"
|
||||||
@@ -67,6 +65,7 @@ fastapi = ["fastapi", "uvicorn"]
|
|||||||
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
|
asyncio_mode = "strict"
|
||||||
addopts = "--cov=nonebot --cov-report=term-missing"
|
addopts = "--cov=nonebot --cov-report=term-missing"
|
||||||
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
||||||
|
|
||||||
|
@@ -1,10 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from functools import wraps
|
from typing import TYPE_CHECKING
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from typing_extensions import ParamSpec
|
|
||||||
from typing import TYPE_CHECKING, TypeVar, Callable
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import NONEBOT_INIT_KWARGS
|
from nonebug import NONEBOT_INIT_KWARGS
|
||||||
@@ -22,9 +20,6 @@ os.environ["CONFIG_OVERRIDE"] = "new"
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from nonebot.plugin import Plugin
|
from nonebot.plugin import Plugin
|
||||||
|
|
||||||
P = ParamSpec("P")
|
|
||||||
R = TypeVar("R")
|
|
||||||
|
|
||||||
collect_ignore = ["plugins/", "dynamic/", "bad_plugins/"]
|
collect_ignore = ["plugins/", "dynamic/", "bad_plugins/"]
|
||||||
|
|
||||||
|
|
||||||
@@ -43,36 +38,14 @@ def load_driver(request: pytest.FixtureRequest) -> Driver:
|
|||||||
return DriverClass(Env(environment=global_driver.env), global_driver.config)
|
return DriverClass(Env(environment=global_driver.env), global_driver.config)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", params=[pytest.param("asyncio"), pytest.param("trio")])
|
|
||||||
def anyio_backend(request: pytest.FixtureRequest):
|
|
||||||
return request.param
|
|
||||||
|
|
||||||
|
|
||||||
def run_once(func: Callable[P, R]) -> Callable[P, R]:
|
|
||||||
result = ...
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
||||||
nonlocal result
|
|
||||||
if result is not Ellipsis:
|
|
||||||
return result
|
|
||||||
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
return result
|
|
||||||
|
|
||||||
return _wrapper
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
@run_once
|
def load_plugin(nonebug_init: None) -> set["Plugin"]:
|
||||||
def load_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
|
|
||||||
# preload global plugins
|
# preload global plugins
|
||||||
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
@run_once
|
def load_builtin_plugin(nonebug_init: None) -> set["Plugin"]:
|
||||||
def load_builtin_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
|
|
||||||
# preload builtin plugins
|
# preload builtin plugins
|
||||||
return nonebot.load_builtin_plugins("echo", "single_session")
|
return nonebot.load_builtin_plugins("echo", "single_session")
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import anyio
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from nonebot import on_message
|
from nonebot import on_message
|
||||||
@@ -106,26 +105,3 @@ async def validate_field(x: int = Depends(lambda: "1", validate=Field(gt=0))):
|
|||||||
|
|
||||||
async def validate_field_fail(x: int = Depends(lambda: "0", validate=Field(gt=0))):
|
async def validate_field_fail(x: int = Depends(lambda: "0", validate=Field(gt=0))):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
async def _dep():
|
|
||||||
await anyio.sleep(1)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def _dep_mismatch():
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
async def cache_exception_func1(
|
|
||||||
dep: int = Depends(_dep),
|
|
||||||
mismatch: dict = Depends(_dep_mismatch),
|
|
||||||
):
|
|
||||||
raise RuntimeError("Never reach here")
|
|
||||||
|
|
||||||
|
|
||||||
async def cache_exception_func2(
|
|
||||||
dep: int = Depends(_dep),
|
|
||||||
match: int = Depends(_dep_mismatch),
|
|
||||||
):
|
|
||||||
return dep
|
|
||||||
|
@@ -17,7 +17,7 @@ from nonebot.drivers import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_adapter_connect(app: App, driver: Driver):
|
async def test_adapter_connect(app: App, driver: Driver):
|
||||||
last_connect_bot: Optional[Bot] = None
|
last_connect_bot: Optional[Bot] = None
|
||||||
last_disconnect_bot: Optional[Bot] = None
|
last_disconnect_bot: Optional[Bot] = None
|
||||||
@@ -45,6 +45,7 @@ async def test_adapter_connect(app: App, driver: Driver):
|
|||||||
assert bot.self_id not in adapter.bots
|
assert bot.self_id not in adapter.bots
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -74,7 +75,7 @@ async def test_adapter_connect(app: App, driver: Driver):
|
|||||||
],
|
],
|
||||||
indirect=True,
|
indirect=True,
|
||||||
)
|
)
|
||||||
def test_adapter_server(driver: Driver):
|
async def test_adapter_server(driver: Driver):
|
||||||
last_http_setup: Optional[HTTPServerSetup] = None
|
last_http_setup: Optional[HTTPServerSetup] = None
|
||||||
last_ws_setup: Optional[WebSocketServerSetup] = None
|
last_ws_setup: Optional[WebSocketServerSetup] = None
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ def test_adapter_server(driver: Driver):
|
|||||||
assert last_ws_setup is setup
|
assert last_ws_setup is setup
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -158,7 +159,7 @@ async def test_adapter_http_client(driver: Driver):
|
|||||||
assert last_request is request
|
assert last_request is request
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import anyio
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
|
||||||
@@ -8,7 +7,7 @@ from nonebot.adapters import Bot
|
|||||||
from nonebot.exception import MockApiException
|
from nonebot.exception import MockApiException
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_bot_call_api(app: App):
|
async def test_bot_call_api(app: App):
|
||||||
async with app.test_api() as ctx:
|
async with app.test_api() as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
@@ -24,7 +23,7 @@ async def test_bot_call_api(app: App):
|
|||||||
await bot.call_api("test")
|
await bot.call_api("test")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_bot_calling_api_hook_simple(app: App):
|
async def test_bot_calling_api_hook_simple(app: App):
|
||||||
runned: bool = False
|
runned: bool = False
|
||||||
|
|
||||||
@@ -50,7 +49,7 @@ async def test_bot_calling_api_hook_simple(app: App):
|
|||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_bot_calling_api_hook_mock(app: App):
|
async def test_bot_calling_api_hook_mock(app: App):
|
||||||
runned: bool = False
|
runned: bool = False
|
||||||
|
|
||||||
@@ -77,47 +76,7 @@ async def test_bot_calling_api_hook_mock(app: App):
|
|||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_bot_calling_api_hook_multi_mock(app: App):
|
|
||||||
runned1: bool = False
|
|
||||||
runned2: bool = False
|
|
||||||
event = anyio.Event()
|
|
||||||
|
|
||||||
async def calling_api_hook1(bot: Bot, api: str, data: dict[str, Any]):
|
|
||||||
nonlocal runned1
|
|
||||||
runned1 = True
|
|
||||||
event.set()
|
|
||||||
|
|
||||||
raise MockApiException(1)
|
|
||||||
|
|
||||||
async def calling_api_hook2(bot: Bot, api: str, data: dict[str, Any]):
|
|
||||||
nonlocal runned2
|
|
||||||
runned2 = True
|
|
||||||
with anyio.fail_after(1):
|
|
||||||
await event.wait()
|
|
||||||
|
|
||||||
raise MockApiException(2)
|
|
||||||
|
|
||||||
hooks = set()
|
|
||||||
|
|
||||||
with pytest.MonkeyPatch.context() as m:
|
|
||||||
m.setattr(Bot, "_calling_api_hook", hooks)
|
|
||||||
|
|
||||||
Bot.on_calling_api(calling_api_hook1)
|
|
||||||
Bot.on_calling_api(calling_api_hook2)
|
|
||||||
|
|
||||||
assert hooks == {calling_api_hook1, calling_api_hook2}
|
|
||||||
|
|
||||||
async with app.test_api() as ctx:
|
|
||||||
bot = ctx.create_bot()
|
|
||||||
result = await bot.call_api("test")
|
|
||||||
|
|
||||||
assert runned1 is True
|
|
||||||
assert runned2 is True
|
|
||||||
assert result == 1
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_bot_called_api_hook_simple(app: App):
|
async def test_bot_called_api_hook_simple(app: App):
|
||||||
runned: bool = False
|
runned: bool = False
|
||||||
|
|
||||||
@@ -149,7 +108,7 @@ async def test_bot_called_api_hook_simple(app: App):
|
|||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_bot_called_api_hook_mock(app: App):
|
async def test_bot_called_api_hook_mock(app: App):
|
||||||
runned: bool = False
|
runned: bool = False
|
||||||
|
|
||||||
@@ -191,56 +150,3 @@ async def test_bot_called_api_hook_mock(app: App):
|
|||||||
|
|
||||||
assert runned is True
|
assert runned is True
|
||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_bot_called_api_hook_multi_mock(app: App):
|
|
||||||
runned1: bool = False
|
|
||||||
runned2: bool = False
|
|
||||||
event = anyio.Event()
|
|
||||||
|
|
||||||
async def called_api_hook1(
|
|
||||||
bot: Bot,
|
|
||||||
exception: Optional[Exception],
|
|
||||||
api: str,
|
|
||||||
data: dict[str, Any],
|
|
||||||
result: Any,
|
|
||||||
):
|
|
||||||
nonlocal runned1
|
|
||||||
runned1 = True
|
|
||||||
event.set()
|
|
||||||
|
|
||||||
raise MockApiException(1)
|
|
||||||
|
|
||||||
async def called_api_hook2(
|
|
||||||
bot: Bot,
|
|
||||||
exception: Optional[Exception],
|
|
||||||
api: str,
|
|
||||||
data: dict[str, Any],
|
|
||||||
result: Any,
|
|
||||||
):
|
|
||||||
nonlocal runned2
|
|
||||||
runned2 = True
|
|
||||||
with anyio.fail_after(1):
|
|
||||||
await event.wait()
|
|
||||||
|
|
||||||
raise MockApiException(2)
|
|
||||||
|
|
||||||
hooks = set()
|
|
||||||
|
|
||||||
with pytest.MonkeyPatch.context() as m:
|
|
||||||
m.setattr(Bot, "_called_api_hook", hooks)
|
|
||||||
|
|
||||||
Bot.on_called_api(called_api_hook1)
|
|
||||||
Bot.on_called_api(called_api_hook2)
|
|
||||||
|
|
||||||
assert hooks == {called_api_hook1, called_api_hook2}
|
|
||||||
|
|
||||||
async with app.test_api() as ctx:
|
|
||||||
bot = ctx.create_bot()
|
|
||||||
ctx.should_call_api("test", {}, True)
|
|
||||||
result = await bot.call_api("test")
|
|
||||||
|
|
||||||
assert runned1 is True
|
|
||||||
assert runned2 is True
|
|
||||||
assert result == 1
|
|
||||||
|
@@ -25,7 +25,7 @@ async def _dependency() -> int:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_event_preprocessors", set())
|
m.setattr(message, "_event_preprocessors", set())
|
||||||
@@ -58,7 +58,7 @@ async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned, "event_preprocessor should runned"
|
assert runned, "event_preprocessor should runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_event_preprocessors", set())
|
m.setattr(message, "_event_preprocessors", set())
|
||||||
@@ -88,7 +88,7 @@ async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPat
|
|||||||
assert not runned, "matcher should not runned"
|
assert not runned, "matcher should not runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_event_preprocessor_exception(
|
async def test_event_preprocessor_exception(
|
||||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
):
|
):
|
||||||
@@ -132,7 +132,7 @@ async def test_event_preprocessor_exception(
|
|||||||
assert "RuntimeError: test" in capsys.readouterr().out
|
assert "RuntimeError: test" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_event_postprocessors", set())
|
m.setattr(message, "_event_postprocessors", set())
|
||||||
@@ -165,7 +165,7 @@ async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned, "event_postprocessor should runned"
|
assert runned, "event_postprocessor should runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_event_postprocessor_exception(
|
async def test_event_postprocessor_exception(
|
||||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
):
|
):
|
||||||
@@ -202,7 +202,7 @@ async def test_event_postprocessor_exception(
|
|||||||
assert "RuntimeError: test" in capsys.readouterr().out
|
assert "RuntimeError: test" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_run_preprocessors", set())
|
m.setattr(message, "_run_preprocessors", set())
|
||||||
@@ -239,7 +239,7 @@ async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned, "run_preprocessor should runned"
|
assert runned, "run_preprocessor should runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_run_preprocessors", set())
|
m.setattr(message, "_run_preprocessors", set())
|
||||||
@@ -269,7 +269,7 @@ async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch
|
|||||||
assert not runned, "matcher should not runned"
|
assert not runned, "matcher should not runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_run_preprocessor_exception(
|
async def test_run_preprocessor_exception(
|
||||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
):
|
):
|
||||||
@@ -313,7 +313,7 @@ async def test_run_preprocessor_exception(
|
|||||||
assert "RuntimeError: test" in capsys.readouterr().out
|
assert "RuntimeError: test" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(message, "_run_postprocessors", set())
|
m.setattr(message, "_run_postprocessors", set())
|
||||||
@@ -351,7 +351,7 @@ async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned, "run_postprocessor should runned"
|
assert runned, "run_postprocessor should runned"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_run_postprocessor_exception(
|
async def test_run_postprocessor_exception(
|
||||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||||
):
|
):
|
||||||
|
@@ -17,12 +17,14 @@ from nonebot.compat import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_default_config():
|
@pytest.mark.asyncio
|
||||||
|
async def test_default_config():
|
||||||
assert DEFAULT_CONFIG.get("extra") == "allow"
|
assert DEFAULT_CONFIG.get("extra") == "allow"
|
||||||
assert DEFAULT_CONFIG.get("arbitrary_types_allowed") is True
|
assert DEFAULT_CONFIG.get("arbitrary_types_allowed") is True
|
||||||
|
|
||||||
|
|
||||||
def test_field_info():
|
@pytest.mark.asyncio
|
||||||
|
async def test_field_info():
|
||||||
# required should be convert to PydanticUndefined
|
# required should be convert to PydanticUndefined
|
||||||
assert FieldInfo(Required).default is PydanticUndefined
|
assert FieldInfo(Required).default is PydanticUndefined
|
||||||
|
|
||||||
@@ -30,7 +32,8 @@ def test_field_info():
|
|||||||
assert FieldInfo(test="test").extra["test"] == "test"
|
assert FieldInfo(test="test").extra["test"] == "test"
|
||||||
|
|
||||||
|
|
||||||
def test_type_adapter():
|
@pytest.mark.asyncio
|
||||||
|
async def test_type_adapter():
|
||||||
t = TypeAdapter(Annotated[int, FieldInfo(ge=1)])
|
t = TypeAdapter(Annotated[int, FieldInfo(ge=1)])
|
||||||
|
|
||||||
assert t.validate_python(2) == 2
|
assert t.validate_python(2) == 2
|
||||||
@@ -44,7 +47,8 @@ def test_type_adapter():
|
|||||||
t.validate_json("0")
|
t.validate_json("0")
|
||||||
|
|
||||||
|
|
||||||
def test_model_dump():
|
@pytest.mark.asyncio
|
||||||
|
async def test_model_dump():
|
||||||
class TestModel(BaseModel):
|
class TestModel(BaseModel):
|
||||||
test1: int
|
test1: int
|
||||||
test2: int
|
test2: int
|
||||||
@@ -53,7 +57,8 @@ def test_model_dump():
|
|||||||
assert model_dump(TestModel(test1=1, test2=2), exclude={"test1"}) == {"test2": 2}
|
assert model_dump(TestModel(test1=1, test2=2), exclude={"test1"}) == {"test2": 2}
|
||||||
|
|
||||||
|
|
||||||
def test_custom_validation():
|
@pytest.mark.asyncio
|
||||||
|
async def test_custom_validation():
|
||||||
called = []
|
called = []
|
||||||
|
|
||||||
@custom_validation
|
@custom_validation
|
||||||
@@ -80,7 +85,8 @@ def test_custom_validation():
|
|||||||
assert called == [1, 2]
|
assert called == [1, 2]
|
||||||
|
|
||||||
|
|
||||||
def test_validate_json():
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_json():
|
||||||
class TestModel(BaseModel):
|
class TestModel(BaseModel):
|
||||||
test1: int
|
test1: int
|
||||||
test2: str
|
test2: str
|
||||||
|
@@ -50,14 +50,16 @@ class ExampleWithoutDelimiter(Example):
|
|||||||
env_nested_delimiter = None
|
env_nested_delimiter = None
|
||||||
|
|
||||||
|
|
||||||
def test_config_no_env():
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_no_env():
|
||||||
config = Example(_env_file=None)
|
config = Example(_env_file=None)
|
||||||
assert config.simple == ""
|
assert config.simple == ""
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
config.common_config
|
config.common_config
|
||||||
|
|
||||||
|
|
||||||
def test_config_with_env():
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_with_env():
|
||||||
config = Example(_env_file=(".env", ".env.example"))
|
config = Example(_env_file=(".env", ".env.example"))
|
||||||
assert config.simple == "simple"
|
assert config.simple == "simple"
|
||||||
|
|
||||||
@@ -100,7 +102,8 @@ def test_config_with_env():
|
|||||||
config.other_nested_inner__b
|
config.other_nested_inner__b
|
||||||
|
|
||||||
|
|
||||||
def test_config_error_env():
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_error_env():
|
||||||
with pytest.MonkeyPatch().context() as m:
|
with pytest.MonkeyPatch().context() as m:
|
||||||
m.setenv("COMPLEX", "not json")
|
m.setenv("COMPLEX", "not json")
|
||||||
|
|
||||||
@@ -108,7 +111,8 @@ def test_config_error_env():
|
|||||||
Example(_env_file=(".env", ".env.example"))
|
Example(_env_file=(".env", ".env.example"))
|
||||||
|
|
||||||
|
|
||||||
def test_config_without_delimiter():
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_without_delimiter():
|
||||||
config = ExampleWithoutDelimiter()
|
config = ExampleWithoutDelimiter()
|
||||||
assert config.nested.a == 1
|
assert config.nested.a == 1
|
||||||
assert config.nested.b == 0
|
assert config.nested.b == 0
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
|
import asyncio
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
|
|
||||||
import anyio
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ from nonebot.drivers import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver", [pytest.param("nonebot.drivers.none:Driver", id="none")], indirect=True
|
"driver", [pytest.param("nonebot.drivers.none:Driver", id="none")], indirect=True
|
||||||
)
|
)
|
||||||
@@ -59,22 +59,22 @@ async def test_lifespan(driver: Driver):
|
|||||||
|
|
||||||
@driver.on_shutdown
|
@driver.on_shutdown
|
||||||
async def _shutdown1():
|
async def _shutdown1():
|
||||||
assert shutdown_log == [2]
|
assert shutdown_log == []
|
||||||
shutdown_log.append(1)
|
shutdown_log.append(1)
|
||||||
|
|
||||||
@driver.on_shutdown
|
@driver.on_shutdown
|
||||||
async def _shutdown2():
|
async def _shutdown2():
|
||||||
assert shutdown_log == []
|
assert shutdown_log == [1]
|
||||||
shutdown_log.append(2)
|
shutdown_log.append(2)
|
||||||
|
|
||||||
async with driver._lifespan:
|
async with driver._lifespan:
|
||||||
assert start_log == [1, 2]
|
assert start_log == [1, 2]
|
||||||
assert ready_log == [1, 2]
|
assert ready_log == [1, 2]
|
||||||
|
|
||||||
assert shutdown_log == [2, 1]
|
assert shutdown_log == [1, 2]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -99,10 +99,10 @@ async def test_http_server(app: App, driver: Driver):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.text == "test"
|
assert response.text == "test"
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -155,10 +155,10 @@ async def test_websocket_server(app: App, driver: Driver):
|
|||||||
|
|
||||||
await ws.close(code=1000)
|
await ws.close(code=1000)
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -171,10 +171,9 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
assert isinstance(driver, ASGIMixin)
|
assert isinstance(driver, ASGIMixin)
|
||||||
|
|
||||||
ws: Optional[WebSocket] = None
|
ws: Optional[WebSocket] = None
|
||||||
ws_ready = anyio.Event()
|
ws_ready = asyncio.Event()
|
||||||
ws_should_close = anyio.Event()
|
ws_should_close = asyncio.Event()
|
||||||
|
|
||||||
# create a background task before the ws connection established
|
|
||||||
async def background_task():
|
async def background_task():
|
||||||
try:
|
try:
|
||||||
await ws_ready.wait()
|
await ws_ready.wait()
|
||||||
@@ -186,6 +185,8 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
finally:
|
finally:
|
||||||
ws_should_close.set()
|
ws_should_close.set()
|
||||||
|
|
||||||
|
task = asyncio.create_task(background_task())
|
||||||
|
|
||||||
async def _handle_ws(websocket: WebSocket) -> None:
|
async def _handle_ws(websocket: WebSocket) -> None:
|
||||||
nonlocal ws
|
nonlocal ws
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
@@ -198,9 +199,7 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
ws_setup = WebSocketServerSetup(URL("/ws_test"), "ws_test", _handle_ws)
|
ws_setup = WebSocketServerSetup(URL("/ws_test"), "ws_test", _handle_ws)
|
||||||
driver.setup_websocket_server(ws_setup)
|
driver.setup_websocket_server(ws_setup)
|
||||||
|
|
||||||
async with anyio.create_task_group() as tg, app.test_server(driver.asgi) as ctx:
|
async with app.test_server(driver.asgi) as ctx:
|
||||||
tg.start_soon(background_task)
|
|
||||||
|
|
||||||
client = ctx.get_client()
|
client = ctx.get_client()
|
||||||
|
|
||||||
async with client.websocket_connect("/ws_test") as websocket:
|
async with client.websocket_connect("/ws_test") as websocket:
|
||||||
@@ -212,10 +211,11 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
if not e.args or "websocket.close" not in str(e.args[0]):
|
if not e.args or "websocket.close" not in str(e.args[0]):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await task
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -304,10 +304,10 @@ async def test_http_client(driver: Driver, server_url: URL):
|
|||||||
"test3": "test",
|
"test3": "test",
|
||||||
}, "file parsing error"
|
}, "file parsing error"
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -419,10 +419,10 @@ async def test_http_client_session(driver: Driver, server_url: URL):
|
|||||||
"test3": "test",
|
"test3": "test",
|
||||||
}, "file parsing error"
|
}, "file parsing error"
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
@@ -452,9 +452,10 @@ async def test_websocket_client(driver: Driver, server_url: URL):
|
|||||||
with pytest.raises(WebSocketClosed, match=r"code=1000"):
|
with pytest.raises(WebSocketClosed, match=r"code=1000"):
|
||||||
await ws.receive()
|
await ws.receive()
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("driver", "driver_type"),
|
("driver", "driver_type"),
|
||||||
[
|
[
|
||||||
@@ -471,11 +472,11 @@ async def test_websocket_client(driver: Driver, server_url: URL):
|
|||||||
],
|
],
|
||||||
indirect=["driver"],
|
indirect=["driver"],
|
||||||
)
|
)
|
||||||
def test_combine_driver(driver: Driver, driver_type: str):
|
async def test_combine_driver(driver: Driver, driver_type: str):
|
||||||
assert driver.type == driver_type
|
assert driver.type == driver_type
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_bot_connect_hook(app: App, driver: Driver):
|
async def test_bot_connect_hook(app: App, driver: Driver):
|
||||||
with pytest.MonkeyPatch.context() as m:
|
with pytest.MonkeyPatch.context() as m:
|
||||||
conn_hooks: set[Dependent[Any]] = set()
|
conn_hooks: set[Dependent[Any]] = set()
|
||||||
@@ -532,7 +533,7 @@ async def test_bot_connect_hook(app: App, driver: Driver):
|
|||||||
async with app.test_api() as ctx:
|
async with app.test_api() as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
|
|
||||||
await anyio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
if not conn_should_be_called:
|
if not conn_should_be_called:
|
||||||
pytest.fail("on_bot_connect hook not called")
|
pytest.fail("on_bot_connect hook not called")
|
||||||
|
@@ -4,7 +4,7 @@ from nonebug import App
|
|||||||
from utils import FakeMessage, FakeMessageSegment, make_fake_event
|
from utils import FakeMessage, FakeMessageSegment, make_fake_event
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_echo(app: App):
|
async def test_echo(app: App):
|
||||||
from nonebot.plugins.echo import echo
|
from nonebot.plugins.echo import echo
|
||||||
|
|
||||||
|
@@ -14,7 +14,8 @@ from nonebot import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_init():
|
@pytest.mark.asyncio
|
||||||
|
async def test_init():
|
||||||
env = nonebot.get_driver().env
|
env = nonebot.get_driver().env
|
||||||
assert env == "test"
|
assert env == "test"
|
||||||
|
|
||||||
@@ -34,28 +35,31 @@ def test_init():
|
|||||||
assert config.not_nested == "some string"
|
assert config.not_nested == "some string"
|
||||||
|
|
||||||
|
|
||||||
def test_get_driver(monkeypatch: pytest.MonkeyPatch):
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_driver(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(nonebot, "_driver", None)
|
m.setattr(nonebot, "_driver", None)
|
||||||
with pytest.raises(ValueError, match="initialized"):
|
with pytest.raises(ValueError, match="initialized"):
|
||||||
get_driver()
|
get_driver()
|
||||||
|
|
||||||
|
|
||||||
def test_get_asgi():
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_asgi(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
assert isinstance(driver, ReverseDriver)
|
assert isinstance(driver, ReverseDriver)
|
||||||
assert isinstance(driver, ASGIMixin)
|
assert isinstance(driver, ASGIMixin)
|
||||||
assert get_asgi() == driver.asgi
|
assert get_asgi() == driver.asgi
|
||||||
|
|
||||||
|
|
||||||
def test_get_app():
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_app(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
assert isinstance(driver, ReverseDriver)
|
assert isinstance(driver, ReverseDriver)
|
||||||
assert isinstance(driver, ASGIMixin)
|
assert isinstance(driver, ASGIMixin)
|
||||||
assert get_app() == driver.server_app
|
assert get_app() == driver.server_app
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_get_adapter(app: App, monkeypatch: pytest.MonkeyPatch):
|
async def test_get_adapter(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
async with app.test_api() as ctx:
|
async with app.test_api() as ctx:
|
||||||
adapter = ctx.create_adapter()
|
adapter = ctx.create_adapter()
|
||||||
@@ -70,7 +74,8 @@ async def test_get_adapter(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
get_adapter("not exist")
|
get_adapter("not exist")
|
||||||
|
|
||||||
|
|
||||||
def test_run(monkeypatch: pytest.MonkeyPatch):
|
@pytest.mark.asyncio
|
||||||
|
async def test_run(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
runned = False
|
runned = False
|
||||||
|
|
||||||
def mock_run(*args, **kwargs):
|
def mock_run(*args, **kwargs):
|
||||||
@@ -88,7 +93,8 @@ def test_run(monkeypatch: pytest.MonkeyPatch):
|
|||||||
assert runned
|
assert runned
|
||||||
|
|
||||||
|
|
||||||
def test_get_bot(app: App, monkeypatch: pytest.MonkeyPatch):
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_bot(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="no bots"):
|
with pytest.raises(ValueError, match="no bots"):
|
||||||
|
@@ -12,7 +12,8 @@ from nonebot.permission import User, Permission
|
|||||||
from nonebot.message import _check_matcher, check_and_run_matcher
|
from nonebot.message import _check_matcher, check_and_run_matcher
|
||||||
|
|
||||||
|
|
||||||
def test_matcher_info(app: App):
|
@pytest.mark.asyncio
|
||||||
|
async def test_matcher_info(app: App):
|
||||||
from plugins.matcher.matcher_info import matcher
|
from plugins.matcher.matcher_info import matcher
|
||||||
|
|
||||||
assert issubclass(matcher, Matcher)
|
assert issubclass(matcher, Matcher)
|
||||||
@@ -42,7 +43,7 @@ def test_matcher_info(app: App):
|
|||||||
assert matcher._source.lineno == 3
|
assert matcher._source.lineno == 3
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_check(app: App):
|
async def test_matcher_check(app: App):
|
||||||
async def falsy():
|
async def falsy():
|
||||||
return False
|
return False
|
||||||
@@ -86,7 +87,7 @@ async def test_matcher_check(app: App):
|
|||||||
assert await _check_matcher(test_rule_error, bot, event, {}) is False
|
assert await _check_matcher(test_rule_error, bot, event, {}) is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_handle(app: App):
|
async def test_matcher_handle(app: App):
|
||||||
from plugins.matcher.matcher_process import test_handle
|
from plugins.matcher.matcher_process import test_handle
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ async def test_matcher_handle(app: App):
|
|||||||
ctx.should_finished()
|
ctx.should_finished()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_got(app: App):
|
async def test_matcher_got(app: App):
|
||||||
from plugins.matcher.matcher_process import test_got
|
from plugins.matcher.matcher_process import test_got
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ async def test_matcher_got(app: App):
|
|||||||
ctx.receive_event(bot, event_next)
|
ctx.receive_event(bot, event_next)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_receive(app: App):
|
async def test_matcher_receive(app: App):
|
||||||
from plugins.matcher.matcher_process import test_receive
|
from plugins.matcher.matcher_process import test_receive
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ async def test_matcher_receive(app: App):
|
|||||||
ctx.should_paused()
|
ctx.should_paused()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_combine(app: App):
|
async def test_matcher_combine(app: App):
|
||||||
from plugins.matcher.matcher_process import test_combine
|
from plugins.matcher.matcher_process import test_combine
|
||||||
|
|
||||||
@@ -163,7 +164,7 @@ async def test_matcher_combine(app: App):
|
|||||||
ctx.receive_event(bot, event_next)
|
ctx.receive_event(bot, event_next)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_preset(app: App):
|
async def test_matcher_preset(app: App):
|
||||||
from plugins.matcher.matcher_process import test_preset
|
from plugins.matcher.matcher_process import test_preset
|
||||||
|
|
||||||
@@ -181,7 +182,7 @@ async def test_matcher_preset(app: App):
|
|||||||
ctx.receive_event(bot, event_next)
|
ctx.receive_event(bot, event_next)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_overload(app: App):
|
async def test_matcher_overload(app: App):
|
||||||
from plugins.matcher.matcher_process import test_overload
|
from plugins.matcher.matcher_process import test_overload
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ async def test_matcher_overload(app: App):
|
|||||||
ctx.should_finished()
|
ctx.should_finished()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_destroy(app: App):
|
async def test_matcher_destroy(app: App):
|
||||||
from plugins.matcher.matcher_process import test_destroy
|
from plugins.matcher.matcher_process import test_destroy
|
||||||
|
|
||||||
@@ -209,7 +210,7 @@ async def test_matcher_destroy(app: App):
|
|||||||
assert len(matchers[test_destroy.priority]) == 0
|
assert len(matchers[test_destroy.priority]) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_type_updater(app: App):
|
async def test_type_updater(app: App):
|
||||||
from plugins.matcher.matcher_type import test_type_updater, test_custom_updater
|
from plugins.matcher.matcher_type import test_type_updater, test_custom_updater
|
||||||
|
|
||||||
@@ -230,7 +231,7 @@ async def test_type_updater(app: App):
|
|||||||
assert new_type == "custom"
|
assert new_type == "custom"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_default_permission_updater(app: App):
|
async def test_default_permission_updater(app: App):
|
||||||
from plugins.matcher.matcher_permission import (
|
from plugins.matcher.matcher_permission import (
|
||||||
default_permission,
|
default_permission,
|
||||||
@@ -251,7 +252,7 @@ async def test_default_permission_updater(app: App):
|
|||||||
assert checker.perm is default_permission
|
assert checker.perm is default_permission
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_user_permission_updater(app: App):
|
async def test_user_permission_updater(app: App):
|
||||||
from plugins.matcher.matcher_permission import (
|
from plugins.matcher.matcher_permission import (
|
||||||
default_permission,
|
default_permission,
|
||||||
@@ -273,7 +274,7 @@ async def test_user_permission_updater(app: App):
|
|||||||
assert checker.perm is default_permission
|
assert checker.perm is default_permission
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_custom_permission_updater(app: App):
|
async def test_custom_permission_updater(app: App):
|
||||||
from plugins.matcher.matcher_permission import (
|
from plugins.matcher.matcher_permission import (
|
||||||
new_permission,
|
new_permission,
|
||||||
@@ -290,7 +291,7 @@ async def test_custom_permission_updater(app: App):
|
|||||||
assert new_perm is new_permission
|
assert new_perm is new_permission
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_run(app: App):
|
async def test_run(app: App):
|
||||||
with app.provider.context({}):
|
with app.provider.context({}):
|
||||||
assert not matchers
|
assert not matchers
|
||||||
@@ -321,46 +322,37 @@ async def test_run(app: App):
|
|||||||
assert len(matchers[0][0].handlers) == 0
|
assert len(matchers[0][0].handlers) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_temp(app: App):
|
async def test_temp(app: App):
|
||||||
from plugins.matcher.matcher_expire import test_temp_matcher
|
from plugins.matcher.matcher_expire import test_temp_matcher
|
||||||
|
|
||||||
event = make_fake_event(_type="test")()
|
event = make_fake_event(_type="test")()
|
||||||
with app.provider.context({test_temp_matcher.priority: [test_temp_matcher]}):
|
async with app.test_api() as ctx:
|
||||||
async with app.test_api() as ctx:
|
bot = ctx.create_bot()
|
||||||
bot = ctx.create_bot()
|
assert test_temp_matcher in matchers[test_temp_matcher.priority]
|
||||||
assert test_temp_matcher in matchers[test_temp_matcher.priority]
|
await check_and_run_matcher(test_temp_matcher, bot, event, {})
|
||||||
await check_and_run_matcher(test_temp_matcher, bot, event, {})
|
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
|
||||||
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_datetime_expire(app: App):
|
async def test_datetime_expire(app: App):
|
||||||
from plugins.matcher.matcher_expire import test_datetime_matcher
|
from plugins.matcher.matcher_expire import test_datetime_matcher
|
||||||
|
|
||||||
event = make_fake_event()()
|
event = make_fake_event()()
|
||||||
with app.provider.context(
|
async with app.test_api() as ctx:
|
||||||
{test_datetime_matcher.priority: [test_datetime_matcher]}
|
bot = ctx.create_bot()
|
||||||
):
|
assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
|
||||||
async with app.test_matcher(test_datetime_matcher) as ctx:
|
await check_and_run_matcher(test_datetime_matcher, bot, event, {})
|
||||||
bot = ctx.create_bot()
|
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
|
||||||
assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
|
|
||||||
await check_and_run_matcher(test_datetime_matcher, bot, event, {})
|
|
||||||
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_timedelta_expire(app: App):
|
async def test_timedelta_expire(app: App):
|
||||||
from plugins.matcher.matcher_expire import test_timedelta_matcher
|
from plugins.matcher.matcher_expire import test_timedelta_matcher
|
||||||
|
|
||||||
event = make_fake_event()()
|
event = make_fake_event()()
|
||||||
with app.provider.context(
|
async with app.test_api() as ctx:
|
||||||
{test_timedelta_matcher.priority: [test_timedelta_matcher]}
|
bot = ctx.create_bot()
|
||||||
):
|
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
|
||||||
async with app.test_api() as ctx:
|
await check_and_run_matcher(test_timedelta_matcher, bot, event, {})
|
||||||
bot = ctx.create_bot()
|
assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
|
||||||
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
|
|
||||||
await check_and_run_matcher(test_timedelta_matcher, bot, event, {})
|
|
||||||
assert (
|
|
||||||
test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
|
|
||||||
)
|
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
|
|
||||||
from nonebot.matcher import DEFAULT_PROVIDER_CLASS, matchers
|
from nonebot.matcher import DEFAULT_PROVIDER_CLASS, matchers
|
||||||
|
|
||||||
|
|
||||||
def test_manager(app: App):
|
@pytest.mark.asyncio
|
||||||
|
async def test_manager(app: App):
|
||||||
try:
|
try:
|
||||||
default_provider = matchers.provider
|
default_provider = matchers.provider
|
||||||
matchers.set_provider(DEFAULT_PROVIDER_CLASS)
|
matchers.set_provider(DEFAULT_PROVIDER_CLASS)
|
||||||
|
@@ -2,7 +2,6 @@ import re
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from nonebug import App
|
from nonebug import App
|
||||||
from exceptiongroup import BaseExceptionGroup
|
|
||||||
|
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.dependencies import Dependent
|
from nonebot.dependencies import Dependent
|
||||||
@@ -37,7 +36,7 @@ from nonebot.consts import (
|
|||||||
UNKNOWN_PARAM = "Unknown parameter"
|
UNKNOWN_PARAM = "Unknown parameter"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_depend(app: App):
|
async def test_depend(app: App):
|
||||||
from plugins.param.param_depend import (
|
from plugins.param.param_depend import (
|
||||||
ClassDependency,
|
ClassDependency,
|
||||||
@@ -51,8 +50,6 @@ async def test_depend(app: App):
|
|||||||
annotated_depend,
|
annotated_depend,
|
||||||
sub_type_mismatch,
|
sub_type_mismatch,
|
||||||
validate_field_fail,
|
validate_field_fail,
|
||||||
cache_exception_func1,
|
|
||||||
cache_exception_func2,
|
|
||||||
annotated_class_depend,
|
annotated_class_depend,
|
||||||
annotated_multi_depend,
|
annotated_multi_depend,
|
||||||
annotated_prior_depend,
|
annotated_prior_depend,
|
||||||
@@ -93,67 +90,36 @@ async def test_depend(app: App):
|
|||||||
|
|
||||||
assert runned == [1, 1, 1]
|
assert runned == [1, 1, 1]
|
||||||
|
|
||||||
runned.clear()
|
|
||||||
|
|
||||||
async with app.test_dependent(
|
async with app.test_dependent(
|
||||||
annotated_class_depend, allow_types=[DependParam]
|
annotated_class_depend, allow_types=[DependParam]
|
||||||
) as ctx:
|
) as ctx:
|
||||||
ctx.should_return(ClassDependency(x=1, y=2))
|
ctx.should_return(ClassDependency(x=1, y=2))
|
||||||
|
|
||||||
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info: # noqa: PT012
|
with pytest.raises(TypeMisMatch): # noqa: PT012
|
||||||
async with app.test_dependent(
|
async with app.test_dependent(
|
||||||
sub_type_mismatch, allow_types=[DependParam, BotParam]
|
sub_type_mismatch, allow_types=[DependParam, BotParam]
|
||||||
) as ctx:
|
) as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
ctx.pass_params(bot=bot)
|
ctx.pass_params(bot=bot)
|
||||||
|
|
||||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
|
||||||
assert exc_info.group_contains(TypeMisMatch)
|
|
||||||
|
|
||||||
async with app.test_dependent(validate, allow_types=[DependParam]) as ctx:
|
async with app.test_dependent(validate, allow_types=[DependParam]) as ctx:
|
||||||
ctx.should_return(1)
|
ctx.should_return(1)
|
||||||
|
|
||||||
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
with pytest.raises(TypeMisMatch):
|
||||||
async with app.test_dependent(validate_fail, allow_types=[DependParam]) as ctx:
|
async with app.test_dependent(validate_fail, allow_types=[DependParam]) as ctx:
|
||||||
...
|
...
|
||||||
|
|
||||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
|
||||||
assert exc_info.group_contains(TypeMisMatch)
|
|
||||||
|
|
||||||
async with app.test_dependent(validate_field, allow_types=[DependParam]) as ctx:
|
async with app.test_dependent(validate_field, allow_types=[DependParam]) as ctx:
|
||||||
ctx.should_return(1)
|
ctx.should_return(1)
|
||||||
|
|
||||||
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
with pytest.raises(TypeMisMatch):
|
||||||
async with app.test_dependent(
|
async with app.test_dependent(
|
||||||
validate_field_fail, allow_types=[DependParam]
|
validate_field_fail, allow_types=[DependParam]
|
||||||
) as ctx:
|
) as ctx:
|
||||||
...
|
...
|
||||||
|
|
||||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
|
||||||
assert exc_info.group_contains(TypeMisMatch)
|
|
||||||
|
|
||||||
# test cache reuse when exception raised
|
@pytest.mark.asyncio
|
||||||
dependency_cache = {}
|
|
||||||
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
|
||||||
async with app.test_dependent(
|
|
||||||
cache_exception_func1, allow_types=[DependParam]
|
|
||||||
) as ctx:
|
|
||||||
ctx.pass_params(dependency_cache=dependency_cache)
|
|
||||||
|
|
||||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
|
||||||
assert exc_info.group_contains(TypeMisMatch)
|
|
||||||
|
|
||||||
# dependency solve tasks should be shielded even if one of them raises an exception
|
|
||||||
assert len(dependency_cache) == 2
|
|
||||||
|
|
||||||
async with app.test_dependent(
|
|
||||||
cache_exception_func2, allow_types=[DependParam]
|
|
||||||
) as ctx:
|
|
||||||
ctx.pass_params(dependency_cache=dependency_cache)
|
|
||||||
ctx.should_return(1)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_bot(app: App):
|
async def test_bot(app: App):
|
||||||
from plugins.param.param_bot import (
|
from plugins.param.param_bot import (
|
||||||
FooBot,
|
FooBot,
|
||||||
@@ -191,14 +157,11 @@ async def test_bot(app: App):
|
|||||||
ctx.pass_params(bot=bot)
|
ctx.pass_params(bot=bot)
|
||||||
ctx.should_return(bot)
|
ctx.should_return(bot)
|
||||||
|
|
||||||
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info: # noqa: PT012
|
with pytest.raises(TypeMisMatch): # noqa: PT012
|
||||||
async with app.test_dependent(sub_bot, allow_types=[BotParam]) as ctx:
|
async with app.test_dependent(sub_bot, allow_types=[BotParam]) as ctx:
|
||||||
bot = ctx.create_bot()
|
bot = ctx.create_bot()
|
||||||
ctx.pass_params(bot=bot)
|
ctx.pass_params(bot=bot)
|
||||||
|
|
||||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
|
||||||
assert exc_info.group_contains(TypeMisMatch)
|
|
||||||
|
|
||||||
async with app.test_dependent(union_bot, allow_types=[BotParam]) as ctx:
|
async with app.test_dependent(union_bot, allow_types=[BotParam]) as ctx:
|
||||||
bot = ctx.create_bot(base=FooBot)
|
bot = ctx.create_bot(base=FooBot)
|
||||||
ctx.pass_params(bot=bot)
|
ctx.pass_params(bot=bot)
|
||||||
@@ -218,7 +181,7 @@ async def test_bot(app: App):
|
|||||||
app.test_dependent(not_bot, allow_types=[BotParam])
|
app.test_dependent(not_bot, allow_types=[BotParam])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_event(app: App):
|
async def test_event(app: App):
|
||||||
from plugins.param.param_event import (
|
from plugins.param.param_event import (
|
||||||
FooEvent,
|
FooEvent,
|
||||||
@@ -260,13 +223,10 @@ async def test_event(app: App):
|
|||||||
ctx.pass_params(event=fake_fooevent)
|
ctx.pass_params(event=fake_fooevent)
|
||||||
ctx.should_return(fake_fooevent)
|
ctx.should_return(fake_fooevent)
|
||||||
|
|
||||||
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
with pytest.raises(TypeMisMatch):
|
||||||
async with app.test_dependent(sub_event, allow_types=[EventParam]) as ctx:
|
async with app.test_dependent(sub_event, allow_types=[EventParam]) as ctx:
|
||||||
ctx.pass_params(event=fake_event)
|
ctx.pass_params(event=fake_event)
|
||||||
|
|
||||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
|
||||||
assert exc_info.group_contains(TypeMisMatch)
|
|
||||||
|
|
||||||
async with app.test_dependent(union_event, allow_types=[EventParam]) as ctx:
|
async with app.test_dependent(union_event, allow_types=[EventParam]) as ctx:
|
||||||
ctx.pass_params(event=fake_fooevent)
|
ctx.pass_params(event=fake_fooevent)
|
||||||
ctx.should_return(fake_fooevent)
|
ctx.should_return(fake_fooevent)
|
||||||
@@ -307,7 +267,7 @@ async def test_event(app: App):
|
|||||||
ctx.should_return(fake_event.is_tome())
|
ctx.should_return(fake_event.is_tome())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_state(app: App):
|
async def test_state(app: App):
|
||||||
from plugins.param.param_state import (
|
from plugins.param.param_state import (
|
||||||
state,
|
state,
|
||||||
@@ -458,7 +418,7 @@ async def test_state(app: App):
|
|||||||
ctx.should_return(fake_state[KEYWORD_KEY])
|
ctx.should_return(fake_state[KEYWORD_KEY])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher(app: App):
|
async def test_matcher(app: App):
|
||||||
from plugins.param.param_matcher import (
|
from plugins.param.param_matcher import (
|
||||||
FooMatcher,
|
FooMatcher,
|
||||||
@@ -497,13 +457,10 @@ async def test_matcher(app: App):
|
|||||||
ctx.pass_params(matcher=foo_matcher)
|
ctx.pass_params(matcher=foo_matcher)
|
||||||
ctx.should_return(foo_matcher)
|
ctx.should_return(foo_matcher)
|
||||||
|
|
||||||
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
with pytest.raises(TypeMisMatch):
|
||||||
async with app.test_dependent(sub_matcher, allow_types=[MatcherParam]) as ctx:
|
async with app.test_dependent(sub_matcher, allow_types=[MatcherParam]) as ctx:
|
||||||
ctx.pass_params(matcher=fake_matcher)
|
ctx.pass_params(matcher=fake_matcher)
|
||||||
|
|
||||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
|
||||||
assert exc_info.group_contains(TypeMisMatch)
|
|
||||||
|
|
||||||
async with app.test_dependent(union_matcher, allow_types=[MatcherParam]) as ctx:
|
async with app.test_dependent(union_matcher, allow_types=[MatcherParam]) as ctx:
|
||||||
ctx.pass_params(matcher=foo_matcher)
|
ctx.pass_params(matcher=foo_matcher)
|
||||||
ctx.should_return(foo_matcher)
|
ctx.should_return(foo_matcher)
|
||||||
@@ -539,7 +496,7 @@ async def test_matcher(app: App):
|
|||||||
ctx.should_return(event_next)
|
ctx.should_return(event_next)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_arg(app: App):
|
async def test_arg(app: App):
|
||||||
from plugins.param.param_arg import (
|
from plugins.param.param_arg import (
|
||||||
arg,
|
arg,
|
||||||
@@ -591,7 +548,7 @@ async def test_arg(app: App):
|
|||||||
ctx.should_return(message.extract_plain_text())
|
ctx.should_return(message.extract_plain_text())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_exception(app: App):
|
async def test_exception(app: App):
|
||||||
from plugins.param.param_exception import exc, legacy_exc
|
from plugins.param.param_exception import exc, legacy_exc
|
||||||
|
|
||||||
@@ -605,7 +562,7 @@ async def test_exception(app: App):
|
|||||||
ctx.should_return(exception)
|
ctx.should_return(exception)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_default(app: App):
|
async def test_default(app: App):
|
||||||
from plugins.param.param_default import default
|
from plugins.param.param_default import default
|
||||||
|
|
||||||
@@ -613,7 +570,8 @@ async def test_default(app: App):
|
|||||||
ctx.should_return(1)
|
ctx.should_return(1)
|
||||||
|
|
||||||
|
|
||||||
def test_priority():
|
@pytest.mark.asyncio
|
||||||
|
async def test_priority():
|
||||||
from plugins.param.priority import complex_priority
|
from plugins.param.priority import complex_priority
|
||||||
|
|
||||||
dependent = Dependent[None].parse(
|
dependent = Dependent[None].parse(
|
||||||
|
@@ -22,7 +22,7 @@ from nonebot.permission import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_permission(app: App):
|
async def test_permission(app: App):
|
||||||
async def falsy():
|
async def falsy():
|
||||||
return False
|
return False
|
||||||
@@ -54,7 +54,7 @@ async def test_permission(app: App):
|
|||||||
assert await Permission(truthy, skipped)(bot, event) is True
|
assert await Permission(truthy, skipped)(bot, event) is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(("type", "expected"), [("message", True), ("notice", False)])
|
@pytest.mark.parametrize(("type", "expected"), [("message", True), ("notice", False)])
|
||||||
async def test_message(type: str, expected: bool):
|
async def test_message(type: str, expected: bool):
|
||||||
dependent = next(iter(MESSAGE.checkers))
|
dependent = next(iter(MESSAGE.checkers))
|
||||||
@@ -66,7 +66,7 @@ async def test_message(type: str, expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("notice", True)])
|
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("notice", True)])
|
||||||
async def test_notice(type: str, expected: bool):
|
async def test_notice(type: str, expected: bool):
|
||||||
dependent = next(iter(NOTICE.checkers))
|
dependent = next(iter(NOTICE.checkers))
|
||||||
@@ -78,7 +78,7 @@ async def test_notice(type: str, expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("request", True)])
|
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("request", True)])
|
||||||
async def test_request(type: str, expected: bool):
|
async def test_request(type: str, expected: bool):
|
||||||
dependent = next(iter(REQUEST.checkers))
|
dependent = next(iter(REQUEST.checkers))
|
||||||
@@ -90,7 +90,7 @@ async def test_request(type: str, expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("type", "expected"), [("message", False), ("meta_event", True)]
|
("type", "expected"), [("message", False), ("meta_event", True)]
|
||||||
)
|
)
|
||||||
@@ -104,7 +104,7 @@ async def test_metaevent(type: str, expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("type", "user_id", "expected"),
|
("type", "user_id", "expected"),
|
||||||
[
|
[
|
||||||
@@ -128,7 +128,7 @@ async def test_superuser(app: App, type: str, user_id: str, expected: bool):
|
|||||||
assert await dependent(bot=bot, event=event) == expected
|
assert await dependent(bot=bot, event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("session_ids", "session_id", "expected"),
|
("session_ids", "session_id", "expected"),
|
||||||
[
|
[
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
|
import pytest
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.plugin import PluginManager, _managers
|
from nonebot.plugin import PluginManager, _managers
|
||||||
|
|
||||||
|
|
||||||
def test_get_plugin():
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_plugin():
|
||||||
# check simple plugin
|
# check simple plugin
|
||||||
plugin = nonebot.get_plugin("export")
|
plugin = nonebot.get_plugin("export")
|
||||||
assert plugin
|
assert plugin
|
||||||
@@ -20,7 +22,8 @@ def test_get_plugin():
|
|||||||
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
||||||
|
|
||||||
|
|
||||||
def test_get_plugin_by_module_name():
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_plugin_by_module_name():
|
||||||
# check get plugin by exact module name
|
# check get plugin by exact module name
|
||||||
plugin = nonebot.get_plugin_by_module_name("plugins.nested")
|
plugin = nonebot.get_plugin_by_module_name("plugins.nested")
|
||||||
assert plugin
|
assert plugin
|
||||||
@@ -45,7 +48,8 @@ def test_get_plugin_by_module_name():
|
|||||||
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
||||||
|
|
||||||
|
|
||||||
def test_get_available_plugin():
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_available_plugin():
|
||||||
old_managers = _managers.copy()
|
old_managers = _managers.copy()
|
||||||
_managers.clear()
|
_managers.clear()
|
||||||
try:
|
try:
|
||||||
@@ -59,7 +63,8 @@ def test_get_available_plugin():
|
|||||||
_managers.extend(old_managers)
|
_managers.extend(old_managers)
|
||||||
|
|
||||||
|
|
||||||
def test_get_plugin_config():
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_plugin_config():
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
plugin_config: int
|
plugin_config: int
|
||||||
|
|
||||||
|
@@ -1,44 +1,15 @@
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from functools import wraps
|
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from typing import TypeVar, Callable
|
|
||||||
from typing_extensions import ParamSpec
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
from nonebot.plugin import (
|
from nonebot.plugin import Plugin, PluginManager, _managers, inherit_supported_adapters
|
||||||
Plugin,
|
|
||||||
PluginManager,
|
|
||||||
_plugins,
|
|
||||||
_managers,
|
|
||||||
inherit_supported_adapters,
|
|
||||||
)
|
|
||||||
|
|
||||||
P = ParamSpec("P")
|
|
||||||
R = TypeVar("R")
|
|
||||||
|
|
||||||
|
|
||||||
def _recover(func: Callable[P, R]) -> Callable[P, R]:
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_plugin():
|
||||||
@wraps(func)
|
|
||||||
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
||||||
origin_managers = _managers.copy()
|
|
||||||
origin_plugins = _plugins.copy()
|
|
||||||
try:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
_managers.clear()
|
|
||||||
_managers.extend(origin_managers)
|
|
||||||
_plugins.clear()
|
|
||||||
_plugins.update(origin_plugins)
|
|
||||||
|
|
||||||
return _wrapper
|
|
||||||
|
|
||||||
|
|
||||||
@_recover
|
|
||||||
def test_load_plugin():
|
|
||||||
# check regular
|
# check regular
|
||||||
assert nonebot.load_plugin("dynamic.simple")
|
assert nonebot.load_plugin("dynamic.simple")
|
||||||
|
|
||||||
@@ -49,7 +20,8 @@ def test_load_plugin():
|
|||||||
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
||||||
|
|
||||||
|
|
||||||
def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[Plugin]):
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[Plugin]):
|
||||||
loaded_plugins = {
|
loaded_plugins = {
|
||||||
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
||||||
}
|
}
|
||||||
@@ -72,7 +44,8 @@ def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[Plugin]
|
|||||||
PluginManager(search_path=["plugins"]).load_all_plugins()
|
PluginManager(search_path=["plugins"]).load_all_plugins()
|
||||||
|
|
||||||
|
|
||||||
def test_load_nested_plugin():
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_nested_plugin():
|
||||||
parent_plugin = nonebot.get_plugin("nested")
|
parent_plugin = nonebot.get_plugin("nested")
|
||||||
sub_plugin = nonebot.get_plugin("nested:nested_subplugin")
|
sub_plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||||
sub_plugin2 = nonebot.get_plugin("nested:nested_subplugin2")
|
sub_plugin2 = nonebot.get_plugin("nested:nested_subplugin2")
|
||||||
@@ -84,16 +57,16 @@ def test_load_nested_plugin():
|
|||||||
assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}
|
assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}
|
||||||
|
|
||||||
|
|
||||||
@_recover
|
@pytest.mark.asyncio
|
||||||
def test_load_json():
|
async def test_load_json():
|
||||||
nonebot.load_from_json("./plugins.json")
|
nonebot.load_from_json("./plugins.json")
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
nonebot.load_from_json("./plugins.invalid.json")
|
nonebot.load_from_json("./plugins.invalid.json")
|
||||||
|
|
||||||
|
|
||||||
@_recover
|
@pytest.mark.asyncio
|
||||||
def test_load_toml():
|
async def test_load_toml():
|
||||||
nonebot.load_from_toml("./plugins.toml")
|
nonebot.load_from_toml("./plugins.toml")
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Cannot find"):
|
with pytest.raises(ValueError, match="Cannot find"):
|
||||||
@@ -103,54 +76,52 @@ def test_load_toml():
|
|||||||
nonebot.load_from_toml("./plugins.invalid.toml")
|
nonebot.load_from_toml("./plugins.invalid.toml")
|
||||||
|
|
||||||
|
|
||||||
@_recover
|
@pytest.mark.asyncio
|
||||||
def test_bad_plugin():
|
async def test_bad_plugin():
|
||||||
nonebot.load_plugins("bad_plugins")
|
nonebot.load_plugins("bad_plugins")
|
||||||
|
|
||||||
assert nonebot.get_plugin("bad_plugin") is None
|
assert nonebot.get_plugin("bad_plugin") is None
|
||||||
|
|
||||||
|
|
||||||
@_recover
|
@pytest.mark.asyncio
|
||||||
def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
async def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||||
def _patched_find(name: str):
|
def _patched_find(name: str):
|
||||||
pytest.fail("require existing plugin should not call find_manager_by_name")
|
pytest.fail("require existing plugin should not call find_manager_by_name")
|
||||||
|
|
||||||
with monkeypatch.context() as m:
|
monkeypatch.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
||||||
m.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
|
||||||
|
|
||||||
# require use module name
|
# require use module name
|
||||||
nonebot.require("plugins.export")
|
nonebot.require("plugins.export")
|
||||||
# require use plugin id
|
# require use plugin id
|
||||||
nonebot.require("export")
|
nonebot.require("export")
|
||||||
nonebot.require("nested:nested_subplugin")
|
nonebot.require("nested:nested_subplugin")
|
||||||
|
|
||||||
|
|
||||||
@_recover
|
@pytest.mark.asyncio
|
||||||
def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||||
pm = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
|
m = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
|
||||||
_managers.append(pm)
|
_managers.append(m)
|
||||||
num_managers = len(_managers)
|
num_managers = len(_managers)
|
||||||
|
|
||||||
origin_load = PluginManager.load_plugin
|
origin_load = PluginManager.load_plugin
|
||||||
|
|
||||||
def _patched_load(self: PluginManager, name: str):
|
def _patched_load(self: PluginManager, name: str):
|
||||||
assert self is pm
|
assert self is m
|
||||||
return origin_load(self, name)
|
return origin_load(self, name)
|
||||||
|
|
||||||
with monkeypatch.context() as m:
|
monkeypatch.setattr(PluginManager, "load_plugin", _patched_load)
|
||||||
m.setattr(PluginManager, "load_plugin", _patched_load)
|
|
||||||
|
|
||||||
# require standalone plugin
|
# require standalone plugin
|
||||||
nonebot.require("dynamic.require_not_loaded")
|
nonebot.require("dynamic.require_not_loaded")
|
||||||
# require searched plugin
|
# require searched plugin
|
||||||
nonebot.require("dynamic.require_not_loaded.subplugin1")
|
nonebot.require("dynamic.require_not_loaded.subplugin1")
|
||||||
nonebot.require("require_not_loaded:subplugin2")
|
nonebot.require("require_not_loaded:subplugin2")
|
||||||
|
|
||||||
assert len(_managers) == num_managers
|
assert len(_managers) == num_managers
|
||||||
|
|
||||||
|
|
||||||
@_recover
|
@pytest.mark.asyncio
|
||||||
def test_require_not_declared():
|
async def test_require_not_declared():
|
||||||
num_managers = len(_managers)
|
num_managers = len(_managers)
|
||||||
|
|
||||||
nonebot.require("dynamic.require_not_declared")
|
nonebot.require("dynamic.require_not_declared")
|
||||||
@@ -159,13 +130,14 @@ def test_require_not_declared():
|
|||||||
assert _managers[-1].plugins == {"dynamic.require_not_declared"}
|
assert _managers[-1].plugins == {"dynamic.require_not_declared"}
|
||||||
|
|
||||||
|
|
||||||
@_recover
|
@pytest.mark.asyncio
|
||||||
def test_require_not_found():
|
async def test_require_not_found():
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
nonebot.require("some_plugin_not_exist")
|
nonebot.require("some_plugin_not_exist")
|
||||||
|
|
||||||
|
|
||||||
def test_plugin_metadata():
|
@pytest.mark.asyncio
|
||||||
|
async def test_plugin_metadata():
|
||||||
from plugins.metadata import Config, FakeAdapter
|
from plugins.metadata import Config, FakeAdapter
|
||||||
|
|
||||||
plugin = nonebot.get_plugin("metadata")
|
plugin = nonebot.get_plugin("metadata")
|
||||||
@@ -185,7 +157,8 @@ def test_plugin_metadata():
|
|||||||
assert plugin.metadata.get_supported_adapters() == {FakeAdapter}
|
assert plugin.metadata.get_supported_adapters() == {FakeAdapter}
|
||||||
|
|
||||||
|
|
||||||
def test_inherit_supported_adapters_not_found():
|
@pytest.mark.asyncio
|
||||||
|
async def test_inherit_supported_adapters_not_found():
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
inherit_supported_adapters("some_plugin_not_exist")
|
inherit_supported_adapters("some_plugin_not_exist")
|
||||||
|
|
||||||
@@ -193,6 +166,7 @@ def test_inherit_supported_adapters_not_found():
|
|||||||
inherit_supported_adapters("export")
|
inherit_supported_adapters("export")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("inherit_plugins", "expected"),
|
("inherit_plugins", "expected"),
|
||||||
[
|
[
|
||||||
@@ -259,7 +233,7 @@ def test_inherit_supported_adapters_not_found():
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_inherit_supported_adapters_combine(
|
async def test_inherit_supported_adapters_combine(
|
||||||
inherit_plugins: tuple[str], expected: set[str]
|
inherit_plugins: tuple[str], expected: set[str]
|
||||||
):
|
):
|
||||||
assert inherit_supported_adapters(*inherit_plugins) == expected
|
assert inherit_supported_adapters(*inherit_plugins) == expected
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from nonebot.plugin import PluginManager, _managers
|
from nonebot.plugin import PluginManager, _managers
|
||||||
|
|
||||||
|
|
||||||
def test_load_plugin_name():
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_plugin_name():
|
||||||
m = PluginManager(plugins=["dynamic.manager"])
|
m = PluginManager(plugins=["dynamic.manager"])
|
||||||
try:
|
_managers.append(m)
|
||||||
_managers.append(m)
|
|
||||||
|
|
||||||
# load by plugin id
|
# load by plugin id
|
||||||
module1 = m.load_plugin("manager")
|
module1 = m.load_plugin("manager")
|
||||||
# load by module name
|
# load by module name
|
||||||
module2 = m.load_plugin("dynamic.manager")
|
module2 = m.load_plugin("dynamic.manager")
|
||||||
assert module1
|
assert module1
|
||||||
assert module2
|
assert module2
|
||||||
assert module1 is module2
|
assert module1 is module2
|
||||||
finally:
|
|
||||||
_managers.remove(m)
|
|
||||||
|
@@ -18,6 +18,7 @@ from nonebot.rule import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("matcher_name", "pre_rule_factory", "has_permission"),
|
("matcher_name", "pre_rule_factory", "has_permission"),
|
||||||
[
|
[
|
||||||
@@ -101,7 +102,7 @@ from nonebot.rule import (
|
|||||||
pytest.param("matcher_group_on_type", lambda e: IsTypeRule(e), True),
|
pytest.param("matcher_group_on_type", lambda e: IsTypeRule(e), True),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_on(
|
async def test_on(
|
||||||
matcher_name: str,
|
matcher_name: str,
|
||||||
pre_rule_factory: Optional[Callable[[type[Event]], T_RuleChecker]],
|
pre_rule_factory: Optional[Callable[[type[Event]], T_RuleChecker]],
|
||||||
has_permission: bool,
|
has_permission: bool,
|
||||||
@@ -149,7 +150,8 @@ def test_on(
|
|||||||
assert matcher.module_name == "plugins.plugin.matchers"
|
assert matcher.module_name == "plugins.plugin.matchers"
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_on():
|
@pytest.mark.asyncio
|
||||||
|
async def test_runtime_on():
|
||||||
import plugins.plugin.matchers as module
|
import plugins.plugin.matchers as module
|
||||||
from plugins.plugin.matchers import matcher_on_factory
|
from plugins.plugin.matchers import matcher_on_factory
|
||||||
|
|
||||||
|
@@ -49,7 +49,7 @@ from nonebot.rule import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_rule(app: App):
|
async def test_rule(app: App):
|
||||||
async def falsy():
|
async def falsy():
|
||||||
return False
|
return False
|
||||||
@@ -81,7 +81,7 @@ async def test_rule(app: App):
|
|||||||
assert await Rule(truthy, skipped)(bot, event, {}) is False
|
assert await Rule(truthy, skipped)(bot, event, {}) is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_trie(app: App):
|
async def test_trie(app: App):
|
||||||
TrieRule.add_prefix("/fake-prefix", TRIE_VALUE("/", ("fake-prefix",)))
|
TrieRule.add_prefix("/fake-prefix", TRIE_VALUE("/", ("fake-prefix",)))
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ async def test_trie(app: App):
|
|||||||
del TrieRule.prefix["/fake-prefix"]
|
del TrieRule.prefix["/fake-prefix"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("msg", "ignorecase", "type", "text", "expected"),
|
("msg", "ignorecase", "type", "text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -186,7 +186,7 @@ async def test_startswith(
|
|||||||
assert await dependent(event=event, state=state) == expected
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("msg", "ignorecase", "type", "text", "expected"),
|
("msg", "ignorecase", "type", "text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -226,7 +226,7 @@ async def test_endswith(
|
|||||||
assert await dependent(event=event, state=state) == expected
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("msg", "ignorecase", "type", "text", "expected"),
|
("msg", "ignorecase", "type", "text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -266,7 +266,7 @@ async def test_fullmatch(
|
|||||||
assert await dependent(event=event, state=state) == expected
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("kws", "type", "text", "expected"),
|
("kws", "type", "text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -298,7 +298,7 @@ async def test_keyword(
|
|||||||
assert await dependent(event=event, state=state) == expected
|
assert await dependent(event=event, state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("cmds", "force_whitespace", "cmd", "whitespace", "arg_text", "expected"),
|
("cmds", "force_whitespace", "cmd", "whitespace", "arg_text", "expected"),
|
||||||
[
|
[
|
||||||
@@ -344,7 +344,7 @@ async def test_command(
|
|||||||
assert await dependent(state=state) == expected
|
assert await dependent(state=state) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_shell_command():
|
async def test_shell_command():
|
||||||
state: T_State
|
state: T_State
|
||||||
CMD = ("test",)
|
CMD = ("test",)
|
||||||
@@ -451,7 +451,7 @@ async def test_shell_command():
|
|||||||
assert state[SHELL_ARGS].status != 0
|
assert state[SHELL_ARGS].status != 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("pattern", "type", "text", "expected", "matched"),
|
("pattern", "type", "text", "expected", "matched"),
|
||||||
[
|
[
|
||||||
@@ -494,7 +494,7 @@ async def test_regex(
|
|||||||
assert result.span() == matched.span()
|
assert result.span() == matched.span()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("expected", [True, False])
|
@pytest.mark.parametrize("expected", [True, False])
|
||||||
async def test_to_me(expected: bool):
|
async def test_to_me(expected: bool):
|
||||||
test_to_me = to_me()
|
test_to_me = to_me()
|
||||||
@@ -507,7 +507,7 @@ async def test_to_me(expected: bool):
|
|||||||
assert await dependent(event=event) == expected
|
assert await dependent(event=event) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_is_type():
|
async def test_is_type():
|
||||||
Event1 = make_fake_event()
|
Event1 = make_fake_event()
|
||||||
Event2 = make_fake_event()
|
Event2 = make_fake_event()
|
||||||
|
@@ -5,7 +5,7 @@ import pytest
|
|||||||
from utils import make_fake_event
|
from utils import make_fake_event
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.asyncio
|
||||||
async def test_matcher_mutex():
|
async def test_matcher_mutex():
|
||||||
from nonebot.plugins.single_session import matcher_mutex, _running_matcher
|
from nonebot.plugins.single_session import matcher_mutex, _running_matcher
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
|
|
||||||
### 异步优先
|
### 异步优先
|
||||||
|
|
||||||
NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) / [trio](https://trio.readthedocs.io/en/stable/) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。
|
NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。
|
||||||
|
|
||||||
### 完整的类型注解
|
### 完整的类型注解
|
||||||
|
|
||||||
|
@@ -77,10 +77,6 @@ NoneBot 为四种类型的事件响应器提供了五个基本的辅助函数:
|
|||||||
|
|
||||||
## 内置响应规则
|
## 内置响应规则
|
||||||
|
|
||||||
:::tip
|
|
||||||
响应规则的使用方法可以参考 [深入 - 响应规则](../appendices/rule.md)。
|
|
||||||
:::
|
|
||||||
|
|
||||||
### `startswith`
|
### `startswith`
|
||||||
|
|
||||||
`startswith` 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。
|
`startswith` 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。
|
||||||
|
@@ -82,16 +82,14 @@ async def do_something(bot: Bot):
|
|||||||
|
|
||||||
### 事件预处理
|
### 事件预处理
|
||||||
|
|
||||||
这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。在这个钩子函数内抛出 `nonebot.exception.IgnoredException` 会使 NoneBot 忽略该事件。
|
这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot.exception import IgnoredException
|
|
||||||
from nonebot.message import event_preprocessor
|
from nonebot.message import event_preprocessor
|
||||||
|
|
||||||
@event_preprocessor
|
@event_preprocessor
|
||||||
async def do_something(event: Event):
|
async def do_something(event: Event):
|
||||||
if not event.is_tome():
|
pass
|
||||||
raise IgnoredException("some reason")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 事件后处理
|
### 事件后处理
|
||||||
@@ -108,16 +106,14 @@ async def do_something(event: Event):
|
|||||||
|
|
||||||
### 运行预处理
|
### 运行预处理
|
||||||
|
|
||||||
这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。在这个钩子函数内抛出 `nonebot.exception.IgnoredException` 也会使 NoneBot 忽略本次运行。
|
这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from nonebot.message import run_preprocessor
|
from nonebot.message import run_preprocessor
|
||||||
from nonebot.exception import IgnoredException
|
|
||||||
|
|
||||||
@run_preprocessor
|
@run_preprocessor
|
||||||
async def do_something(event: Event, matcher: Matcher):
|
async def do_something(event: Event, matcher: Matcher):
|
||||||
if not event.is_tome():
|
pass
|
||||||
raise IgnoredException("some reason")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 运行后处理
|
### 运行后处理
|
||||||
|
@@ -166,7 +166,7 @@ COMMON_CONFIG=common config # 这个配置项在任何环境中都会被加载
|
|||||||
在生产环境中,可以通过设置环境变量 `ENVIRONMENT=prod` 来确保 NoneBot 读取正确的环境配置。
|
在生产环境中,可以通过设置环境变量 `ENVIRONMENT=prod` 来确保 NoneBot 读取正确的环境配置。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
#### .env.\{ENVIRONMENT\} 文件
|
#### .env.{ENVIRONMENT} 文件
|
||||||
|
|
||||||
`.env.{ENVIRONMENT}` 文件类似于预设,可以让我们在多套不同的配置方案中灵活切换,默认 NoneBot 会读取 `.env.prod` 配置。如果你使用了 `nb-cli` 创建 `simple` 项目,那么将含有两套预设配置:`.env.dev` 和 `.env.prod`。
|
`.env.{ENVIRONMENT}` 文件类似于预设,可以让我们在多套不同的配置方案中灵活切换,默认 NoneBot 会读取 `.env.prod` 配置。如果你使用了 `nb-cli` 创建 `simple` 项目,那么将含有两套预设配置:`.env.dev` 和 `.env.prod`。
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ nonebot.init(_env_file=".env.dev")
|
|||||||
|
|
||||||
这将忽略在 `.env` 文件或环境变量中指定的 `ENVIRONMENT` 配置项。
|
这将忽略在 `.env` 文件或环境变量中指定的 `ENVIRONMENT` 配置项。
|
||||||
|
|
||||||
## 读取全局配置项
|
## 读取配置项
|
||||||
|
|
||||||
NoneBot 的全局配置对象可以通过 `driver` 获取,如:
|
NoneBot 的全局配置对象可以通过 `driver` 获取,如:
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ superusers = config.superusers
|
|||||||
|
|
||||||
## 插件配置
|
## 插件配置
|
||||||
|
|
||||||
在一个涉及大量配置项的项目中,通过直接读取全局配置项的方式显然并不高效。同时,由于额外的全局配置项没有预先定义,开发时编辑器将无法提示字段与类型,并且运行时没有对配置项直接进行合法性检查。那么就需要一种方式来规范定义插件配置项。
|
在一个涉及大量配置项的项目中,通过直接读取配置项的方式显然并不高效。同时,由于额外的全局配置项没有预先定义,开发时编辑器将无法提示字段与类型,并且运行时没有对配置项直接进行合法性检查。那么就需要一种方式来规范定义插件配置项。
|
||||||
|
|
||||||
在 NoneBot 中,我们使用强大高效的 `pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在 `weather` 插件目录中新建 `config.py` 来定义一个模型:
|
在 NoneBot 中,我们使用强大高效的 `pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在 `weather` 插件目录中新建 `config.py` 来定义一个模型:
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ class Config(BaseModel):
|
|||||||
|
|
||||||
在 `config.py` 中,我们定义了一个 `Config` 类,它继承自 `pydantic.BaseModel`,并定义了一些配置项。在 `Config` 类中,我们还定义了一个 `check_priority` 方法,它用于检查 `weather_command_priority` 配置项的合法性。更多关于 `pydantic` 的编写方式,可以参考 [pydantic 官方文档](https://docs.pydantic.dev/)。
|
在 `config.py` 中,我们定义了一个 `Config` 类,它继承自 `pydantic.BaseModel`,并定义了一些配置项。在 `Config` 类中,我们还定义了一个 `check_priority` 方法,它用于检查 `weather_command_priority` 配置项的合法性。更多关于 `pydantic` 的编写方式,可以参考 [pydantic 官方文档](https://docs.pydantic.dev/)。
|
||||||
|
|
||||||
在定义好配置模型后,我们可以在插件加载时通过配置模型获取插件配置:
|
在定义好配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型并使用:
|
||||||
|
|
||||||
```python {5,11} title=weather/__init__.py
|
```python {5,11} title=weather/__init__.py
|
||||||
from nonebot import get_plugin_config
|
from nonebot import get_plugin_config
|
||||||
|
@@ -29,9 +29,8 @@ import Messenger from "@site/src/components/Messenger";
|
|||||||
|
|
||||||
例如,我们可以在 `weather` 插件中添加一个超级用户可用的指令:
|
例如,我们可以在 `weather` 插件中添加一个超级用户可用的指令:
|
||||||
|
|
||||||
```python {3,9} title=weather/__init__.py
|
```python {2,8} title=weather/__init__.py
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from nonebot.params import Command
|
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
|
|
||||||
manage = on_command(
|
manage = on_command(
|
||||||
|
@@ -20,11 +20,7 @@ options:
|
|||||||
|
|
||||||
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
||||||
|
|
||||||
```python {7,8} title=weather/__init__.py
|
```python {3,4} title=weather/__init__.py
|
||||||
from nonebot import get_plugin_config
|
|
||||||
|
|
||||||
from .config import Config
|
|
||||||
|
|
||||||
plugin_config = get_plugin_config(Config)
|
plugin_config = get_plugin_config(Config)
|
||||||
|
|
||||||
async def is_enable() -> bool:
|
async def is_enable() -> bool:
|
||||||
@@ -58,11 +54,8 @@ weather = on_command("天气", rule=rule)
|
|||||||
|
|
||||||
在定义响应规则时,我们可以将规则进行细分,来更好地复用规则。而在使用时,我们需要合并多个规则。除了使用 `Rule` 对象来组合多个 `RuleChecker` 外,我们还可以对 `Rule` 对象进行合并。在原 `weather` 插件中,我们可以将 `rule=to_me()` 与 `rule=is_enable` 使用 `&` 运算符合并:
|
在定义响应规则时,我们可以将规则进行细分,来更好地复用规则。而在使用时,我们需要合并多个规则。除了使用 `Rule` 对象来组合多个 `RuleChecker` 外,我们还可以对 `Rule` 对象进行合并。在原 `weather` 插件中,我们可以将 `rule=to_me()` 与 `rule=is_enable` 使用 `&` 运算符合并:
|
||||||
|
|
||||||
```python {13} title=weather/__init__.py
|
```python {10} title=weather/__init__.py
|
||||||
from nonebot.rule import to_me
|
from nonebot.rule import to_me
|
||||||
from nonebot import get_plugin_config
|
|
||||||
|
|
||||||
from .config import Config
|
|
||||||
|
|
||||||
plugin_config = get_plugin_config(Config)
|
plugin_config = get_plugin_config(Config)
|
||||||
|
|
||||||
@@ -73,7 +66,7 @@ weather = on_command(
|
|||||||
"天气",
|
"天气",
|
||||||
rule=to_me() & is_enable,
|
rule=to_me() & is_enable,
|
||||||
aliases={"weather", "查天气"},
|
aliases={"weather", "查天气"},
|
||||||
priority=plugin_config.weather_command_priority,
|
priority=plugin_config.weather_command_priority
|
||||||
block=True,
|
block=True,
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
@@ -71,14 +71,14 @@ alc = Alconna(".rd{roll:int}")
|
|||||||
assert alc.parse(".rd123").header["roll"] == 123
|
assert alc.parse(".rd123").header["roll"] == 123
|
||||||
```
|
```
|
||||||
|
|
||||||
Bracket Header 类似 python 里的 f-string 写法,通过 `"{}"` 声明匹配类型
|
Bracket Header 类似 python 里的 f-string 写法,通过 "{}" 声明匹配类型
|
||||||
|
|
||||||
`"{}"` 中的内容为 "name:type or pat":
|
"{}" 中的内容为 "name:type or pat":
|
||||||
|
|
||||||
- `"{}"`, `"{:}"` ⇔ `"(.+)"`, 占位符
|
- "{}", "{:}" ⇔ "(.+)", 占位符
|
||||||
- `"{foo}"` ⇔ `"(?P<foo>.+)"`
|
- "{foo}" ⇔ "(?P<foo>.+)"
|
||||||
- `"{:\d+}"` ⇔ `"(\d+)"`
|
- "{:\d+}" ⇔ "(\d+)"
|
||||||
- `"{foo:int}"` ⇔ `"(?P<foo>\d+)"`,其中 `"int"` 部分若能转为 `BasePattern` 则读取里面的表达式
|
- "{foo:int}" ⇔ "(?P<foo>\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
|
||||||
|
|
||||||
## 参数声明(Args)
|
## 参数声明(Args)
|
||||||
|
|
||||||
@@ -321,7 +321,7 @@ opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
|||||||
- `keep_crlf`: 命令解析时是否保留换行字符
|
- `keep_crlf`: 命令解析时是否保留换行字符
|
||||||
- `compact`: 命令是否允许第一个参数紧随头部
|
- `compact`: 命令是否允许第一个参数紧随头部
|
||||||
- `strict`: 命令是否严格匹配,若为 False 则未知参数将作为名为 $extra 的参数
|
- `strict`: 命令是否严格匹配,若为 False 则未知参数将作为名为 $extra 的参数
|
||||||
- `context_style`: 命令上下文插值的风格,None 为关闭,bracket 为 `{...}`,parentheses 为 `$(...)`
|
- `context_style`: 命令上下文插值的风格,None 为关闭,bracket 为 {...},parentheses 为 $(...)
|
||||||
- `extra`: 命令的自定义额外信息
|
- `extra`: 命令的自定义额外信息
|
||||||
|
|
||||||
元数据一定使用 `meta=...` 形式传入:
|
元数据一定使用 `meta=...` 形式传入:
|
||||||
|
@@ -96,7 +96,7 @@ class Other(Segment):
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tips
|
||||||
|
|
||||||
或许你注意到了 `Segment` 上有一个 `children` 属性。
|
或许你注意到了 `Segment` 上有一个 `children` 属性。
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ msg.extend([Text("text")])
|
|||||||
|
|
||||||
这里额外说明 `UniMessage.template` 的拓展控制符
|
这里额外说明 `UniMessage.template` 的拓展控制符
|
||||||
|
|
||||||
相比 `Message`,UniMessage 对于 `{:XXX}` 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
|
相比 `Message`,UniMessage 对于 {:XXX} 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
|
||||||
|
|
||||||
以 At(...) 为例:
|
以 At(...) 为例:
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ UniMessage(At("user", "123"))
|
|||||||
UniMessage(At("user", "123"))
|
UniMessage(At("user", "123"))
|
||||||
```
|
```
|
||||||
|
|
||||||
而在 `AlconnaMatcher` 中,`{:XXX}` 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
|
而在 `AlconnaMatcher` 中,{:XXX} 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
|
||||||
|
|
||||||
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
|
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
|
||||||
from arclet.alconna import Alconna, Args
|
from arclet.alconna import Alconna, Args
|
||||||
|
@@ -74,17 +74,7 @@ pip install pytest-asyncio
|
|||||||
|
|
||||||
## 配置测试
|
## 配置测试
|
||||||
|
|
||||||
在开始测试之前,我们需要对测试进行一些配置,以正确启动我们的机器人。
|
在开始测试之前,我们需要对测试进行一些配置,以正确启动我们的机器人。在 `tests` 目录下新建 `conftest.py` 文件,添加以下内容:
|
||||||
|
|
||||||
首先我们需要配置 pytest-asyncio,在 `pyproject.toml` 的 pytest 配置部分添加:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[tool.pytest.ini_options]
|
|
||||||
asyncio_mode = "auto"
|
|
||||||
asyncio_default_fixture_loop_scope = "session"
|
|
||||||
```
|
|
||||||
|
|
||||||
然后,我们在 `tests` 目录下新建 `conftest.py` 文件,添加以下内容:
|
|
||||||
|
|
||||||
```python title=tests/conftest.py
|
```python title=tests/conftest.py
|
||||||
import pytest
|
import pytest
|
||||||
@@ -93,7 +83,7 @@ import nonebot
|
|||||||
from nonebot.adapters.console import Adapter as ConsoleAdapter
|
from nonebot.adapters.console import Adapter as ConsoleAdapter
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
async def after_nonebot_init(after_nonebot_init: None):
|
def load_bot():
|
||||||
# 加载适配器
|
# 加载适配器
|
||||||
driver = nonebot.get_driver()
|
driver = nonebot.get_driver()
|
||||||
driver.register_adapter(ConsoleAdapter)
|
driver.register_adapter(ConsoleAdapter)
|
||||||
@@ -104,10 +94,9 @@ async def after_nonebot_init(after_nonebot_init: None):
|
|||||||
|
|
||||||
这样,我们就可以在测试中使用机器人的插件了。通常,我们不需要自行初始化 NoneBot,NoneBug 已经为我们运行了 `nonebot.init()`。如果需要自定义 NoneBot 初始化的参数,我们可以在 `conftest.py` 中添加 `pytest_configure` 钩子函数。例如,我们可以修改 NoneBot 配置环境为 `test` 并从环境变量中输入配置:
|
这样,我们就可以在测试中使用机器人的插件了。通常,我们不需要自行初始化 NoneBot,NoneBug 已经为我们运行了 `nonebot.init()`。如果需要自定义 NoneBot 初始化的参数,我们可以在 `conftest.py` 中添加 `pytest_configure` 钩子函数。例如,我们可以修改 NoneBot 配置环境为 `test` 并从环境变量中输入配置:
|
||||||
|
|
||||||
```python {4,6,8-10} title=tests/conftest.py
|
```python {3,5,7-9} title=tests/conftest.py
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
|
||||||
from nonebug import NONEBOT_INIT_KWARGS
|
from nonebug import NONEBOT_INIT_KWARGS
|
||||||
|
|
||||||
os.environ["ENVIRONMENT"] = "test"
|
os.environ["ENVIRONMENT"] = "test"
|
||||||
@@ -116,16 +105,6 @@ def pytest_configure(config: pytest.Config):
|
|||||||
config.stash[NONEBOT_INIT_KWARGS] = {"secret": os.getenv("INPUT_SECRET")}
|
config.stash[NONEBOT_INIT_KWARGS] = {"secret": os.getenv("INPUT_SECRET")}
|
||||||
```
|
```
|
||||||
|
|
||||||
NoneBug 默认也会为我们管理 lifespan 的 startup 与 shutdown。如果不希望 NoneBug 管理 lifespan,你可以在 `pytest_configure` 里添加以下配置:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import pytest
|
|
||||||
from nonebug import NONEBOT_START_LIFESPAN
|
|
||||||
|
|
||||||
def pytest_configure(config: pytest.Config):
|
|
||||||
config.stash[NONEBOT_START_LIFESPAN] = False
|
|
||||||
```
|
|
||||||
|
|
||||||
## 编写插件测试
|
## 编写插件测试
|
||||||
|
|
||||||
在配置完成插件加载后,我们就可以在测试中使用插件了。NoneBug 通过 pytest fixture `app` 提供各种测试方法,我们可以在测试中使用它来测试插件。现在,我们创建一个测试脚本来测试[深入指南](../../appendices/session-control.mdx)中编写的天气插件。首先,我们先要导入我们需要的模块:
|
在配置完成插件加载后,我们就可以在测试中使用插件了。NoneBug 通过 pytest fixture `app` 提供各种测试方法,我们可以在测试中使用它来测试插件。现在,我们创建一个测试脚本来测试[深入指南](../../appendices/session-control.mdx)中编写的天气插件。首先,我们先要导入我们需要的模块:
|
||||||
|
@@ -1,15 +1,13 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 0
|
sidebar_position: 0
|
||||||
description: 开源软件供应链点亮计划 - 暑期 2021
|
description: 开源软件供应链点亮计划 - 暑期 2021
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 暑期 2021
|
# 暑期 2021
|
||||||
|
|
||||||
**开源软件供应链点亮计划 - 暑期 2021** 是**中国科学院软件研究所**与 **openEuler 社区**共同举办的一项面向高校学生的暑期活动,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer.iscas.ac.cn/) 和 [帮助文档](https://summer.iscas.ac.cn/help/)。
|
**开源软件供应链点亮计划 - 暑期 2021** 是**中国科学院软件研究所**与 **openEuler 社区**共同举办的一项面向高校学生的暑期活动,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer.iscas.ac.cn/) 和 [帮助文档](https://summer.iscas.ac.cn/help/)。
|
||||||
|
|
||||||
NoneBot 社区有幸作为开源社区参与了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学在上面给出的活动官网报名,或通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
|
NoneBot 社区有幸作为开源社区参与了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学在上面给出的活动官网报名,或通过 <contact@nonebot.dev> 联系我们。
|
||||||
|
|
||||||
## NoneBot v1
|
## NoneBot v1
|
||||||
|
|
||||||
|
@@ -1,15 +1,13 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
description: 开源之夏 - 暑期 2022
|
description: 开源之夏 - 暑期 2022
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 暑期 2022
|
# 暑期 2022
|
||||||
|
|
||||||
**开源之夏 - 暑期 2022** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
**开源之夏 - 暑期 2022** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
||||||
|
|
||||||
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/#/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a/) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学加入 QQ 群 [737131827](https://jq.qq.com/?_wv=1027&k=PEgyGeEu) 或通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
|
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/#/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a/) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学加入 QQ 群 [737131827](https://jq.qq.com/?_wv=1027&k=PEgyGeEu) 或通过 <contact@nonebot.dev> 联系我们。
|
||||||
|
|
||||||
## NoneBot2 命令行 CLI 交互体验升级
|
## NoneBot2 命令行 CLI 交互体验升级
|
||||||
|
|
||||||
|
@@ -1,15 +1,13 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
description: 开源之夏 - 暑期 2023
|
description: 开源之夏 - 暑期 2023
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 暑期 2023
|
# 暑期 2023
|
||||||
|
|
||||||
**开源之夏 - 暑期 2023** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
**开源之夏 - 暑期 2023** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
||||||
|
|
||||||
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
|
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 <contact@nonebot.dev> 联系我们。
|
||||||
|
|
||||||
## NoneBot 项目管理图形化面板
|
## NoneBot 项目管理图形化面板
|
||||||
|
|
||||||
|
@@ -1,15 +1,13 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: 开源之夏 - 暑期 2024
|
description: 开源之夏 - 暑期 2024
|
||||||
mdx:
|
|
||||||
format: md
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 暑期 2024
|
# 暑期 2024
|
||||||
|
|
||||||
**开源之夏 - 暑期 2024** 是**中国科学院软件研究所**发起的**开源软件供应链点亮计划**系列暑期活动,旨在鼓励高校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。活动联合各大开源社区,针对重要开源软件的开发与维护提供项目开发任务,并向全球高校学生开放报名。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
**开源之夏 - 暑期 2024** 是**中国科学院软件研究所**发起的**开源软件供应链点亮计划**系列暑期活动,旨在鼓励高校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。活动联合各大开源社区,针对重要开源软件的开发与维护提供项目开发任务,并向全球高校学生开放报名。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
|
||||||
|
|
||||||
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
|
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 <contact@nonebot.dev> 联系我们。
|
||||||
|
|
||||||
## NonePress 官网组件库更新与优化
|
## NonePress 官网组件库更新与优化
|
||||||
|
|
||||||
|
@@ -107,4 +107,4 @@ if __name__ == "__main__":
|
|||||||
python bot.py
|
python bot.py
|
||||||
```
|
```
|
||||||
|
|
||||||
如果你后续使用了 `nb-cli` ,你仍可以使用 `nb run` 命令来运行机器人,`nb-cli` 会自动检测入口文件 `bot.py` 是否存在并运行。同时,你也可以使用 `nb run --reload` 来自动检测代码的更改并自动重新运行入口文件。
|
如果你后续使用了 `nb-cli` ,你仍可以使用 `nb run` 命令来运行机器人,`nb-cli` 会自动检测入口文件 `bot.py` 是否存在并运行。
|
||||||
|
@@ -51,8 +51,8 @@ from nonebot.rule import to_me
|
|||||||
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
|
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
|
||||||
```
|
```
|
||||||
|
|
||||||
这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令的响应规则,需要私聊或 `@bot` 时才会响应,优先级为 10(越小越优先),阻断事件向后续优先级传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。
|
这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令,需要私聊或 `@bot` 时才会响应,优先级为 10 ,阻断事件传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。
|
||||||
|
|
||||||
:::tip 提示
|
:::tip 提示
|
||||||
需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶 - 基本辅助函数](../advanced/matcher.md#基本辅助函数)或 [API 文档](../api/plugin/on.md#on)。
|
需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶](../advanced/matcher.md)或编辑器的提示。
|
||||||
:::
|
:::
|
||||||
|
317
website/docusaurus.config.js
Normal file
317
website/docusaurus.config.js
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Note: type annotations allow type checking and IDEs autocompletion
|
||||||
|
|
||||||
|
// color mode config
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["colorMode"]} */
|
||||||
|
const colorMode = {
|
||||||
|
defaultMode: "light",
|
||||||
|
respectPrefersColorScheme: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// navbar config
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["navbar"]} */
|
||||||
|
const navbar = {
|
||||||
|
title: "NoneBot",
|
||||||
|
logo: {
|
||||||
|
alt: "NoneBot",
|
||||||
|
src: "logo.png",
|
||||||
|
href: "/",
|
||||||
|
target: "_self",
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
},
|
||||||
|
hideOnScroll: false,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "指南",
|
||||||
|
type: "docsMenu",
|
||||||
|
category: "tutorial",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "深入",
|
||||||
|
type: "docsMenu",
|
||||||
|
category: "appendices",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "进阶",
|
||||||
|
type: "docsMenu",
|
||||||
|
category: "advanced",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "API",
|
||||||
|
type: "doc",
|
||||||
|
docId: "api/index",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "更多",
|
||||||
|
type: "dropdown",
|
||||||
|
to: "/store/plugins",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "最佳实践",
|
||||||
|
type: "doc",
|
||||||
|
docId: "best-practice/scheduler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "开发者",
|
||||||
|
type: "doc",
|
||||||
|
docId: "developer/plugin-publishing",
|
||||||
|
},
|
||||||
|
{ label: "社区", type: "doc", docId: "community/contact" },
|
||||||
|
{ label: "开源之夏", type: "doc", docId: "ospp/2024" },
|
||||||
|
{ label: "商店", to: "/store/plugins" },
|
||||||
|
{ label: "更新日志", to: "/changelog" },
|
||||||
|
{ label: "论坛", href: "https://discussions.nonebot.dev" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// footer config
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["footer"]} */
|
||||||
|
const footer = {
|
||||||
|
style: "light",
|
||||||
|
logo: {
|
||||||
|
alt: "NoneBot",
|
||||||
|
src: "logo.png",
|
||||||
|
href: "/",
|
||||||
|
target: "_self",
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
},
|
||||||
|
copyright: `Copyright © ${new Date().getFullYear()} NoneBot. All rights reserved.`,
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
title: "Learn",
|
||||||
|
items: [
|
||||||
|
{ label: "Introduction", to: "/docs/" },
|
||||||
|
{ label: "QuickStart", to: "/docs/quick-start" },
|
||||||
|
{ label: "Changelog", to: "/changelog" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "NoneBot Team",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Homepage",
|
||||||
|
href: "https://nonebot.dev",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "NoneBot V1",
|
||||||
|
href: "https://v1.nonebot.dev",
|
||||||
|
},
|
||||||
|
{ label: "NoneBot CLI", href: "https://cli.nonebot.dev" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Related",
|
||||||
|
items: [
|
||||||
|
{ label: "OneBot", href: "https://onebot.dev/" },
|
||||||
|
{ label: "go-cqhttp", href: "https://docs.go-cqhttp.org/" },
|
||||||
|
{ label: "Mirai", href: "https://mirai.mamoe.net/" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// prism config
|
||||||
|
/** @type {import('prism-react-renderer').PrismTheme} */
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line import/order
|
||||||
|
const lightCodeTheme = require("prism-react-renderer/themes/github");
|
||||||
|
/** @type {import('prism-react-renderer').PrismTheme} */
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line import/order
|
||||||
|
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
|
||||||
|
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["prism"]} */
|
||||||
|
const prism = {
|
||||||
|
theme: lightCodeTheme,
|
||||||
|
darkTheme: darkCodeTheme,
|
||||||
|
additionalLanguages: ["docker", "ini"],
|
||||||
|
};
|
||||||
|
|
||||||
|
// algolia config
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["algolia"]} */
|
||||||
|
const algolia = {
|
||||||
|
appId: "X0X5UACHZQ",
|
||||||
|
apiKey: "ac03e1ac2bd0812e2ea38c0cc1ea38c5",
|
||||||
|
indexName: "nonebot",
|
||||||
|
contextualSearch: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// nonepress config
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["nonepress"]} */
|
||||||
|
const nonepress = {
|
||||||
|
tailwindConfig: require("./tailwind.config"),
|
||||||
|
navbar: {
|
||||||
|
docsVersionDropdown: {
|
||||||
|
dropdownItemsAfter: [
|
||||||
|
{
|
||||||
|
label: "1.x",
|
||||||
|
href: "https://v1.nonebot.dev/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
socialLinks: [
|
||||||
|
{
|
||||||
|
icon: ["fab", "github"],
|
||||||
|
href: "https://github.com/nonebot/nonebot2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
socialLinks: [
|
||||||
|
{
|
||||||
|
icon: ["fab", "github"],
|
||||||
|
href: "https://github.com/nonebot/nonebot2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ["fab", "qq"],
|
||||||
|
href: "https://jq.qq.com/?_wv=1027&k=5OFifDh",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ["fab", "telegram"],
|
||||||
|
href: "https://t.me/botuniverse",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ["fab", "discord"],
|
||||||
|
href: "https://discord.gg/VKtE6Gdc4h",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// theme config
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig} */
|
||||||
|
const themeConfig = {
|
||||||
|
colorMode,
|
||||||
|
navbar,
|
||||||
|
footer,
|
||||||
|
prism,
|
||||||
|
algolia,
|
||||||
|
nonepress,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('@docusaurus/types').Config} */
|
||||||
|
const siteConfig = {
|
||||||
|
title: "NoneBot",
|
||||||
|
tagline: "跨平台 Python 异步机器人框架",
|
||||||
|
favicon: "icons/favicon.ico",
|
||||||
|
|
||||||
|
// Set the production url of your site here
|
||||||
|
url: "https://nonebot.dev",
|
||||||
|
// Set the /<baseUrl>/ pathname under which your site is served
|
||||||
|
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||||
|
baseUrl: process.env.BASE_URL || "/",
|
||||||
|
|
||||||
|
// GitHub pages deployment config.
|
||||||
|
// If you aren't using GitHub pages, you don't need these.
|
||||||
|
organizationName: "nonebot", // Usually your GitHub org/user name.
|
||||||
|
projectName: "nonebot2", // Usually your repo name.
|
||||||
|
|
||||||
|
onBrokenLinks: "throw",
|
||||||
|
onBrokenMarkdownLinks: "warn",
|
||||||
|
|
||||||
|
// Even if you don't use internalization, you can use this field to set useful
|
||||||
|
// metadata like html lang. For example, if your site is Chinese, you may want
|
||||||
|
// to replace "en" with "zh-Hans".
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: "zh-Hans",
|
||||||
|
locales: ["zh-Hans"],
|
||||||
|
},
|
||||||
|
|
||||||
|
headTags: [
|
||||||
|
// 百度搜索资源平台
|
||||||
|
// https://ziyuan.baidu.com/
|
||||||
|
{
|
||||||
|
tagName: "meta",
|
||||||
|
attributes: {
|
||||||
|
name: "baidu-site-verification",
|
||||||
|
content: "codeva-0GTZpDnDrW",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scripts: [
|
||||||
|
// 百度统计
|
||||||
|
// https://tongji.baidu.com/
|
||||||
|
{
|
||||||
|
type: "text/javascript",
|
||||||
|
charset: "UTF-8",
|
||||||
|
src: "https://hm.baidu.com/hm.js?875efa50097818701ee681edd63eaac6",
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
// 万维广告
|
||||||
|
// https://wwads.cn/
|
||||||
|
{
|
||||||
|
type: "text/javascript",
|
||||||
|
charset: "UTF-8",
|
||||||
|
src: "https://cdn.wwads.cn/js/makemoney.js",
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
// uwu logo
|
||||||
|
{
|
||||||
|
type: "text/javascript",
|
||||||
|
charset: "UTF-8",
|
||||||
|
src: "/uwu.js",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
"@nullbot/docusaurus-preset-nonepress",
|
||||||
|
/** @type {import('@nullbot/docusaurus-preset-nonepress').Options} */
|
||||||
|
({
|
||||||
|
docs: {
|
||||||
|
sidebarPath: require.resolve("./sidebars.js"),
|
||||||
|
// Please change this to your repo.
|
||||||
|
editUrl: "https://github.com/nonebot/nonebot2/edit/master/website/",
|
||||||
|
showLastUpdateAuthor: true,
|
||||||
|
showLastUpdateTime: true,
|
||||||
|
// exclude: [
|
||||||
|
// "**/_*.{js,jsx,ts,tsx,md,mdx}",
|
||||||
|
// "**/_*/**",
|
||||||
|
// "**/*.test.{js,jsx,ts,tsx}",
|
||||||
|
// "**/__tests__/**",
|
||||||
|
// ],
|
||||||
|
// async sidebarItemsGenerator({
|
||||||
|
// isCategoryIndex: defaultCategoryIndexMatcher,
|
||||||
|
// defaultSidebarItemsGenerator,
|
||||||
|
// ...args
|
||||||
|
// }) {
|
||||||
|
// return defaultSidebarItemsGenerator({
|
||||||
|
// ...args,
|
||||||
|
// isCategoryIndex(doc) {
|
||||||
|
// // disable category index convention for generated API docs
|
||||||
|
// if (
|
||||||
|
// doc.directories.length > 0 &&
|
||||||
|
// doc.directories.at(-1) === "api"
|
||||||
|
// ) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return defaultCategoryIndexMatcher(doc);
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
// theme: {
|
||||||
|
// customCss: require.resolve("./src/css/custom.css"),
|
||||||
|
// },
|
||||||
|
sitemap: {
|
||||||
|
changefreq: "daily",
|
||||||
|
priority: 0.5,
|
||||||
|
},
|
||||||
|
gtag: {
|
||||||
|
trackingID: "G-MRS1GMZG0F",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
plugins: [require("./src/plugins/webpack-plugin.cjs")],
|
||||||
|
|
||||||
|
themeConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = siteConfig;
|
@@ -1,353 +0,0 @@
|
|||||||
import type { Config } from "@docusaurus/types";
|
|
||||||
import type { Options as ChangelogOptions } from "@nullbot/docusaurus-plugin-changelog";
|
|
||||||
import type * as Preset from "@nullbot/docusaurus-preset-nonepress";
|
|
||||||
import { themes } from "prism-react-renderer";
|
|
||||||
|
|
||||||
// By default, we use Docusaurus Faster
|
|
||||||
// DOCUSAURUS_SLOWER=true is useful for benchmarking faster against slower
|
|
||||||
// hyperfine --prepare 'yarn clear:website' --runs 3 'DOCUSAURUS_SLOWER=true yarn build:website:fast' 'yarn build:website:fast'
|
|
||||||
const isSlower = process.env.DOCUSAURUS_SLOWER === "true";
|
|
||||||
if (isSlower) {
|
|
||||||
console.log("🐢 Using slower Docusaurus build");
|
|
||||||
}
|
|
||||||
|
|
||||||
// color mode config
|
|
||||||
const colorMode: Preset.ThemeConfig["colorMode"] = {
|
|
||||||
defaultMode: "light",
|
|
||||||
respectPrefersColorScheme: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// navbar config
|
|
||||||
const navbar: Preset.ThemeConfig["navbar"] = {
|
|
||||||
title: "NoneBot",
|
|
||||||
logo: {
|
|
||||||
alt: "NoneBot",
|
|
||||||
src: "logo.png",
|
|
||||||
href: "/",
|
|
||||||
target: "_self",
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
},
|
|
||||||
hideOnScroll: false,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: "指南",
|
|
||||||
type: "docsMenu",
|
|
||||||
category: "tutorial",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "深入",
|
|
||||||
type: "docsMenu",
|
|
||||||
category: "appendices",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "进阶",
|
|
||||||
type: "docsMenu",
|
|
||||||
category: "advanced",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "API",
|
|
||||||
type: "doc",
|
|
||||||
docId: "api/index",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "更多",
|
|
||||||
type: "dropdown",
|
|
||||||
to: "/store/plugins",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: "最佳实践",
|
|
||||||
type: "doc",
|
|
||||||
docId: "best-practice/scheduler",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "开发者",
|
|
||||||
type: "doc",
|
|
||||||
docId: "developer/plugin-publishing",
|
|
||||||
},
|
|
||||||
{ label: "社区", type: "doc", docId: "community/contact" },
|
|
||||||
{ label: "开源之夏", type: "doc", docId: "ospp/2024" },
|
|
||||||
{ label: "商店", to: "/store/plugins" },
|
|
||||||
{ label: "更新日志", to: "/changelog" },
|
|
||||||
{ label: "论坛", href: "https://discussions.nonebot.dev" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// footer config
|
|
||||||
const footer: Preset.ThemeConfig["footer"] = {
|
|
||||||
style: "light",
|
|
||||||
logo: {
|
|
||||||
alt: "NoneBot",
|
|
||||||
src: "logo.png",
|
|
||||||
href: "/",
|
|
||||||
target: "_self",
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
},
|
|
||||||
copyright: `Copyright © ${new Date().getFullYear()} NoneBot. All rights reserved.`,
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
title: "Learn",
|
|
||||||
items: [
|
|
||||||
{ label: "Introduction", to: "/docs/" },
|
|
||||||
{ label: "QuickStart", to: "/docs/quick-start" },
|
|
||||||
{ label: "Changelog", to: "/changelog" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "NoneBot Team",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: "Homepage",
|
|
||||||
href: "https://nonebot.dev",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "NoneBot V1",
|
|
||||||
href: "https://v1.nonebot.dev",
|
|
||||||
},
|
|
||||||
{ label: "NoneBot CLI", href: "https://cli.nonebot.dev" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Related",
|
|
||||||
items: [
|
|
||||||
{ label: "OneBot", href: "https://onebot.dev/" },
|
|
||||||
{ label: "go-cqhttp", href: "https://docs.go-cqhttp.org/" },
|
|
||||||
{ label: "Mirai", href: "https://mirai.mamoe.net/" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// prism config
|
|
||||||
const lightCodeTheme = themes.github;
|
|
||||||
const darkCodeTheme = themes.dracula;
|
|
||||||
|
|
||||||
const prism: Preset.ThemeConfig["prism"] = {
|
|
||||||
theme: lightCodeTheme,
|
|
||||||
darkTheme: darkCodeTheme,
|
|
||||||
additionalLanguages: ["docker", "ini"],
|
|
||||||
};
|
|
||||||
|
|
||||||
// algolia config
|
|
||||||
const algolia: Preset.ThemeConfig["algolia"] = {
|
|
||||||
appId: "X0X5UACHZQ",
|
|
||||||
apiKey: "ac03e1ac2bd0812e2ea38c0cc1ea38c5",
|
|
||||||
indexName: "nonebot",
|
|
||||||
contextualSearch: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// nonepress config
|
|
||||||
const nonepress: Preset.ThemeConfig["nonepress"] = {
|
|
||||||
tailwindConfig: require("./tailwind.config"),
|
|
||||||
navbar: {
|
|
||||||
docsVersionDropdown: {
|
|
||||||
dropdownItemsAfter: [
|
|
||||||
{
|
|
||||||
label: "1.x",
|
|
||||||
href: "https://v1.nonebot.dev/",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
socialLinks: [
|
|
||||||
{
|
|
||||||
icon: ["fab", "github"],
|
|
||||||
href: "https://github.com/nonebot/nonebot2",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
socialLinks: [
|
|
||||||
{
|
|
||||||
icon: ["fab", "github"],
|
|
||||||
href: "https://github.com/nonebot/nonebot2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: ["fab", "qq"],
|
|
||||||
href: "https://jq.qq.com/?_wv=1027&k=5OFifDh",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: ["fab", "telegram"],
|
|
||||||
href: "https://t.me/botuniverse",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: ["fab", "discord"],
|
|
||||||
href: "https://discord.gg/VKtE6Gdc4h",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// theme config
|
|
||||||
const themeConfig: Preset.ThemeConfig = {
|
|
||||||
colorMode,
|
|
||||||
navbar,
|
|
||||||
footer,
|
|
||||||
prism,
|
|
||||||
algolia,
|
|
||||||
nonepress,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function createConfigAsync() {
|
|
||||||
return {
|
|
||||||
title: "NoneBot",
|
|
||||||
tagline: "跨平台 Python 异步机器人框架",
|
|
||||||
favicon: "icons/favicon.ico",
|
|
||||||
|
|
||||||
// Set the production url of your site here
|
|
||||||
url: "https://nonebot.dev",
|
|
||||||
// Set the /<baseUrl>/ pathname under which your site is served
|
|
||||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
|
||||||
baseUrl: process.env.BASE_URL || "/",
|
|
||||||
|
|
||||||
// GitHub pages deployment config.
|
|
||||||
// If you aren't using GitHub pages, you don't need these.
|
|
||||||
organizationName: "nonebot", // Usually your GitHub org/user name.
|
|
||||||
projectName: "nonebot2", // Usually your repo name.
|
|
||||||
|
|
||||||
onBrokenLinks: "throw",
|
|
||||||
onBrokenMarkdownLinks: "warn",
|
|
||||||
|
|
||||||
// Even if you don't use internalization, you can use this field to set useful
|
|
||||||
// metadata like html lang. For example, if your site is Chinese, you may want
|
|
||||||
// to replace "en" with "zh-Hans".
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: "zh-Hans",
|
|
||||||
locales: ["zh-Hans"],
|
|
||||||
},
|
|
||||||
|
|
||||||
headTags: [
|
|
||||||
// 百度搜索资源平台
|
|
||||||
// https://ziyuan.baidu.com/
|
|
||||||
{
|
|
||||||
tagName: "meta",
|
|
||||||
attributes: {
|
|
||||||
name: "baidu-site-verification",
|
|
||||||
content: "codeva-0GTZpDnDrW",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
scripts: [
|
|
||||||
// 百度统计
|
|
||||||
// https://tongji.baidu.com/
|
|
||||||
{
|
|
||||||
type: "text/javascript",
|
|
||||||
charset: "UTF-8",
|
|
||||||
src: "https://hm.baidu.com/hm.js?875efa50097818701ee681edd63eaac6",
|
|
||||||
async: true,
|
|
||||||
},
|
|
||||||
// 万维广告
|
|
||||||
// https://wwads.cn/
|
|
||||||
{
|
|
||||||
type: "text/javascript",
|
|
||||||
charset: "UTF-8",
|
|
||||||
src: "https://cdn.wwads.cn/js/makemoney.js",
|
|
||||||
async: true,
|
|
||||||
},
|
|
||||||
// uwu logo
|
|
||||||
{
|
|
||||||
type: "text/javascript",
|
|
||||||
charset: "UTF-8",
|
|
||||||
src: "/uwu.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
"@nullbot/docusaurus-preset-nonepress",
|
|
||||||
/** @type {import('@nullbot/docusaurus-preset-nonepress').Options} */
|
|
||||||
{
|
|
||||||
docs: {
|
|
||||||
sidebarPath: require.resolve("./sidebars.js"),
|
|
||||||
// Please change this to your repo.
|
|
||||||
editUrl: "https://github.com/nonebot/nonebot2/edit/master/website/",
|
|
||||||
showLastUpdateAuthor: true,
|
|
||||||
showLastUpdateTime: true,
|
|
||||||
// exclude: [
|
|
||||||
// "**/_*.{js,jsx,ts,tsx,md,mdx}",
|
|
||||||
// "**/_*/**",
|
|
||||||
// "**/*.test.{js,jsx,ts,tsx}",
|
|
||||||
// "**/__tests__/**",
|
|
||||||
// ],
|
|
||||||
// async sidebarItemsGenerator({
|
|
||||||
// isCategoryIndex: defaultCategoryIndexMatcher,
|
|
||||||
// defaultSidebarItemsGenerator,
|
|
||||||
// ...args
|
|
||||||
// }) {
|
|
||||||
// return defaultSidebarItemsGenerator({
|
|
||||||
// ...args,
|
|
||||||
// isCategoryIndex(doc) {
|
|
||||||
// // disable category index convention for generated API docs
|
|
||||||
// if (
|
|
||||||
// doc.directories.length > 0 &&
|
|
||||||
// doc.directories.at(-1) === "api"
|
|
||||||
// ) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// return defaultCategoryIndexMatcher(doc);
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
// theme: {
|
|
||||||
// customCss: require.resolve("./src/css/custom.css"),
|
|
||||||
// },
|
|
||||||
sitemap: {
|
|
||||||
changefreq: "daily",
|
|
||||||
priority: 0.5,
|
|
||||||
},
|
|
||||||
gtag: {
|
|
||||||
trackingID: "G-MRS1GMZG0F",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
webpack: {
|
|
||||||
jsLoader: (isServer) => ({
|
|
||||||
loader: require.resolve("swc-loader"),
|
|
||||||
options: {
|
|
||||||
jsc: {
|
|
||||||
parser: {
|
|
||||||
syntax: "typescript",
|
|
||||||
tsx: true,
|
|
||||||
},
|
|
||||||
transform: {
|
|
||||||
react: {
|
|
||||||
runtime: "automatic",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
target: "es2017",
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
type: isServer ? "commonjs" : "es6",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
plugins: [
|
|
||||||
require("./src/plugins/webpack-plugin.ts"),
|
|
||||||
[
|
|
||||||
"@nullbot/docusaurus-plugin-changelog",
|
|
||||||
{
|
|
||||||
changelogPath: "src/changelog/changelog.md",
|
|
||||||
changelogHeader: `description: Changelog
|
|
||||||
toc_max_heading_level: 2
|
|
||||||
sidebar_custom_props:
|
|
||||||
sidebar_id: changelog`,
|
|
||||||
} satisfies ChangelogOptions,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
markdown: {
|
|
||||||
mdx1Compat: {
|
|
||||||
headingIds: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
themeConfig,
|
|
||||||
} satisfies Config;
|
|
||||||
}
|
|
@@ -22,27 +22,24 @@
|
|||||||
"typecheck": "tsc"
|
"typecheck": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "^3.5.2",
|
"@docusaurus/core": "^2.4.1",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^1.6.22",
|
||||||
"@nullbot/docusaurus-plugin-changelog": "^3.0.0",
|
"@nullbot/docusaurus-preset-nonepress": "^2.1.2",
|
||||||
"@nullbot/docusaurus-preset-nonepress": "^3.0.0",
|
"clsx": "^1.2.1",
|
||||||
"@swc/core": "^1.7.26",
|
"copy-text-to-clipboard": "^3.0.1",
|
||||||
"clsx": "^2.0.0",
|
"prism-react-renderer": "^1.3.5",
|
||||||
"copy-text-to-clipboard": "^3.2.0",
|
|
||||||
"prism-react-renderer": "^2.3.0",
|
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"react": "^18.0.0",
|
"react": "^17.0.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^17.0.1",
|
||||||
"react-use-pagination": "^2.0.1",
|
"react-use-pagination": "^2.0.1"
|
||||||
"swc-loader": "^0.2.6"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "^3.5.2",
|
"@docusaurus/module-type-aliases": "^2.4.1",
|
||||||
"@nullbot/docusaurus-tsconfig": "^3.0.0",
|
"@tsconfig/docusaurus": "^1.0.5",
|
||||||
"@types/react-color": "^3.0.10",
|
"@types/react-color": "^3.0.10",
|
||||||
"asciinema-player": "^3.5.0",
|
"asciinema-player": "^3.5.0",
|
||||||
"typescript": "~5.5.2"
|
"typescript": "^4.7.4"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@@ -57,6 +54,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=16.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,15 +8,11 @@
|
|||||||
|
|
||||||
Create as many sidebars as you want.
|
Create as many sidebars as you want.
|
||||||
*/
|
*/
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
|
// @ts-check
|
||||||
import { getChangelogItemsSync } from "@nullbot/docusaurus-plugin-changelog";
|
|
||||||
|
|
||||||
const changelogPath = path.join(__dirname, "src/changelog/changelog.md");
|
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||||
const changelogItems = getChangelogItemsSync(changelogPath, 10);
|
const sidebars = {
|
||||||
|
|
||||||
const sidebars: SidebarsConfig = {
|
|
||||||
tutorial: [
|
tutorial: [
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
@@ -137,22 +133,6 @@ const sidebars: SidebarsConfig = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
changelog: [
|
|
||||||
{
|
|
||||||
type: "category",
|
|
||||||
label: "更新日志",
|
|
||||||
collapsible: false,
|
|
||||||
items: changelogItems.map<{ type: "link"; label: string; href: string }>(
|
|
||||||
(chunk, index) => ({
|
|
||||||
type: "link",
|
|
||||||
label: chunk[0]!.title,
|
|
||||||
href: `/changelog/${
|
|
||||||
index > 0 ? encodeURIComponent(chunk[0]!.title) : ""
|
|
||||||
}`,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sidebars;
|
module.exports = sidebars;
|
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useDocsVersionCandidates } from "@docusaurus/plugin-content-docs/client";
|
|
||||||
import { PageMetadata } from "@docusaurus/theme-common";
|
import { PageMetadata } from "@docusaurus/theme-common";
|
||||||
|
import { useDocsVersionCandidates } from "@docusaurus/theme-common/internal";
|
||||||
import { useVersionedSidebar } from "@nullbot/docusaurus-plugin-getsidebar/client";
|
import { useVersionedSidebar } from "@nullbot/docusaurus-plugin-getsidebar/client";
|
||||||
import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts";
|
import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts";
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ function StorePage({ title, children }: Props): JSX.Element {
|
|||||||
)!;
|
)!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page hideTableOfContents reduceContentWidth={false} sidebarId={SIDEBAR_ID}>
|
<Page hideTableOfContents reduceContentWidth={false}>
|
||||||
<SidebarContentFiller items={sidebarItems} />
|
<SidebarContentFiller items={sidebarItems} />
|
||||||
<article className="prose max-w-full">
|
<article className="prose max-w-full">
|
||||||
<h1 className="store-title">{title}</h1>
|
<h1 className="store-title">{title}</h1>
|
||||||
|
@@ -5,108 +5,7 @@ toc_max_heading_level: 2
|
|||||||
|
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
## v2.4.0
|
## 最近更新
|
||||||
|
|
||||||
### 🚀 新功能
|
|
||||||
|
|
||||||
- Feature: 跳过部分非必要的 task group 创建 [@yanyongyu](https://github.com/yanyongyu) ([#3095](https://github.com/nonebot/nonebot2/pull/3095))
|
|
||||||
- Feature: 迁移至结构化并发框架 AnyIO [@yanyongyu](https://github.com/yanyongyu) ([#3053](https://github.com/nonebot/nonebot2/pull/3053))
|
|
||||||
- Feature: 添加 websockets 驱动器 proxy 连接警告 [@shoucandanghehe](https://github.com/shoucandanghehe) ([#2916](https://github.com/nonebot/nonebot2/pull/2916))
|
|
||||||
|
|
||||||
### 🐛 Bug 修复
|
|
||||||
|
|
||||||
- Fix: 修复结构化并发子依赖取消缓存问题 [@yanyongyu](https://github.com/yanyongyu) ([#3084](https://github.com/nonebot/nonebot2/pull/3084))
|
|
||||||
|
|
||||||
### 📝 文档
|
|
||||||
|
|
||||||
- Docs: 新增 nonebug 新版启动需要的配置 [@yanyongyu](https://github.com/yanyongyu) ([#3087](https://github.com/nonebot/nonebot2/pull/3087))
|
|
||||||
- Docs: 修复侧边栏滚动 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#3062](https://github.com/nonebot/nonebot2/pull/3062))
|
|
||||||
- Docs: 升级到 Docusaurus V3 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#2956](https://github.com/nonebot/nonebot2/pull/2956))
|
|
||||||
- Docs: 修改文档示例代码与部分表述 [@yixinNB](https://github.com/yixinNB) ([#2797](https://github.com/nonebot/nonebot2/pull/2797))
|
|
||||||
- Docs: 添加钩子函数 IgnoredException 用法 [@refparo](https://github.com/refparo) ([#2912](https://github.com/nonebot/nonebot2/pull/2912))
|
|
||||||
|
|
||||||
### 💫 杂项
|
|
||||||
|
|
||||||
- Plugin: 移除不再维护的插件 [@ssttkkl](https://github.com/ssttkkl) ([#3040](https://github.com/nonebot/nonebot2/pull/3040))
|
|
||||||
- Plugin: 删除不再维护的 simplemusic hikarisearch 插件 [@MeetWq](https://github.com/MeetWq) ([#2933](https://github.com/nonebot/nonebot2/pull/2933))
|
|
||||||
- Plugin: 删除插件 `nonebot-plugin-ntqq-restart` [@kanbereina](https://github.com/kanbereina) ([#2926](https://github.com/nonebot/nonebot2/pull/2926))
|
|
||||||
- Adapter: 移除社区版 mirai 适配器 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2909](https://github.com/nonebot/nonebot2/pull/2909))
|
|
||||||
|
|
||||||
### 🍻 插件发布
|
|
||||||
|
|
||||||
- Plugin: Comfyui绘图插件 [@noneflow](https://github.com/noneflow) ([#3081](https://github.com/nonebot/nonebot2/pull/3081))
|
|
||||||
- Plugin: 每日wife [@noneflow](https://github.com/noneflow) ([#3094](https://github.com/nonebot/nonebot2/pull/3094))
|
|
||||||
- Plugin: nonebot_plugin_impart [@noneflow](https://github.com/noneflow) ([#3079](https://github.com/nonebot/nonebot2/pull/3079))
|
|
||||||
- Plugin: Pix图库 [@noneflow](https://github.com/noneflow) ([#3083](https://github.com/nonebot/nonebot2/pull/3083))
|
|
||||||
- Plugin: nonebot_plugin_partner_join [@noneflow](https://github.com/noneflow) ([#3051](https://github.com/nonebot/nonebot2/pull/3051))
|
|
||||||
- Plugin: pong [@noneflow](https://github.com/noneflow) ([#3066](https://github.com/nonebot/nonebot2/pull/3066))
|
|
||||||
- Plugin: Bot的消息也是消息 [@noneflow](https://github.com/noneflow) ([#3064](https://github.com/nonebot/nonebot2/pull/3064))
|
|
||||||
- Plugin: BiliMusic Downloader [@noneflow](https://github.com/noneflow) ([#3046](https://github.com/nonebot/nonebot2/pull/3046))
|
|
||||||
- Plugin: 防撤回 [@noneflow](https://github.com/noneflow) ([#3055](https://github.com/nonebot/nonebot2/pull/3055))
|
|
||||||
- Plugin: nonebot_plugin_mai_arcade [@noneflow](https://github.com/noneflow) ([#3047](https://github.com/nonebot/nonebot2/pull/3047))
|
|
||||||
- Plugin: DDNet 成绩查询 [@noneflow](https://github.com/noneflow) ([#3031](https://github.com/nonebot/nonebot2/pull/3031))
|
|
||||||
- Plugin: 省流 [@noneflow](https://github.com/noneflow) ([#3052](https://github.com/nonebot/nonebot2/pull/3052))
|
|
||||||
- Plugin: FishSpeechTTS [@noneflow](https://github.com/noneflow) ([#3050](https://github.com/nonebot/nonebot2/pull/3050))
|
|
||||||
- Plugin: 语音点歌 [@noneflow](https://github.com/noneflow) ([#3037](https://github.com/nonebot/nonebot2/pull/3037))
|
|
||||||
- Plugin: Gotify [@noneflow](https://github.com/noneflow) ([#3043](https://github.com/nonebot/nonebot2/pull/3043))
|
|
||||||
- Plugin: 涩图插件 [@noneflow](https://github.com/noneflow) ([#3039](https://github.com/nonebot/nonebot2/pull/3039))
|
|
||||||
- Plugin: boom [@noneflow](https://github.com/noneflow) ([#3017](https://github.com/nonebot/nonebot2/pull/3017))
|
|
||||||
- Plugin: 恶魔轮盘赌 [@noneflow](https://github.com/noneflow) ([#3033](https://github.com/nonebot/nonebot2/pull/3033))
|
|
||||||
- Plugin: 机厅 [@noneflow](https://github.com/noneflow) ([#3029](https://github.com/nonebot/nonebot2/pull/3029))
|
|
||||||
- Plugin: PM帮助 [@noneflow](https://github.com/noneflow) ([#3023](https://github.com/nonebot/nonebot2/pull/3023))
|
|
||||||
- Plugin: NailongRemove [@noneflow](https://github.com/noneflow) ([#2972](https://github.com/nonebot/nonebot2/pull/2972))
|
|
||||||
- Plugin: 团购 [@noneflow](https://github.com/noneflow) ([#3027](https://github.com/nonebot/nonebot2/pull/3027))
|
|
||||||
- Plugin: 真寻日报 [@noneflow](https://github.com/noneflow) ([#3021](https://github.com/nonebot/nonebot2/pull/3021))
|
|
||||||
- Plugin: 运行状态 [@noneflow](https://github.com/noneflow) ([#3019](https://github.com/nonebot/nonebot2/pull/3019))
|
|
||||||
- Plugin: 西工大翱翔门户成绩监控 [@noneflow](https://github.com/noneflow) ([#3013](https://github.com/nonebot/nonebot2/pull/3013))
|
|
||||||
- Plugin: nb插件更新器 [@noneflow](https://github.com/noneflow) ([#3015](https://github.com/nonebot/nonebot2/pull/3015))
|
|
||||||
- Plugin: 涩涩保存器 [@noneflow](https://github.com/noneflow) ([#2988](https://github.com/nonebot/nonebot2/pull/2988))
|
|
||||||
- Plugin: nonebot_plugin_BFVsearch [@noneflow](https://github.com/noneflow) ([#3008](https://github.com/nonebot/nonebot2/pull/3008))
|
|
||||||
- Plugin: lingyi_chat [@noneflow](https://github.com/noneflow) ([#3006](https://github.com/nonebot/nonebot2/pull/3006))
|
|
||||||
- Plugin: ZXPM插件管理 [@noneflow](https://github.com/noneflow) ([#3003](https://github.com/nonebot/nonebot2/pull/3003))
|
|
||||||
- Plugin: MinecraftWatcher [@noneflow](https://github.com/noneflow) ([#3010](https://github.com/nonebot/nonebot2/pull/3010))
|
|
||||||
- Plugin: BF5_grouptools [@noneflow](https://github.com/noneflow) ([#3004](https://github.com/nonebot/nonebot2/pull/3004))
|
|
||||||
- Plugin: lolinfo [@noneflow](https://github.com/noneflow) ([#2997](https://github.com/nonebot/nonebot2/pull/2997))
|
|
||||||
- Plugin: osu! Match Monitor [@noneflow](https://github.com/noneflow) ([#2985](https://github.com/nonebot/nonebot2/pull/2985))
|
|
||||||
- Plugin: Marsho AI插件 [@noneflow](https://github.com/noneflow) ([#2993](https://github.com/nonebot/nonebot2/pull/2993))
|
|
||||||
- Plugin: nonechat [@noneflow](https://github.com/noneflow) ([#2990](https://github.com/nonebot/nonebot2/pull/2990))
|
|
||||||
- Plugin: nonebot_plugin_SimpleToWrite [@noneflow](https://github.com/noneflow) ([#2995](https://github.com/nonebot/nonebot2/pull/2995))
|
|
||||||
- Plugin: Beat Saber查分器 [@noneflow](https://github.com/noneflow) ([#2974](https://github.com/nonebot/nonebot2/pull/2974))
|
|
||||||
- Plugin: githubmodels [@noneflow](https://github.com/noneflow) ([#2945](https://github.com/nonebot/nonebot2/pull/2945))
|
|
||||||
- Plugin: 给我点颜色瞧瞧 [@noneflow](https://github.com/noneflow) ([#2984](https://github.com/nonebot/nonebot2/pull/2984))
|
|
||||||
- Plugin: pjsk-helper [@noneflow](https://github.com/noneflow) ([#2980](https://github.com/nonebot/nonebot2/pull/2980))
|
|
||||||
- Plugin: 趣味内容插件 [@noneflow](https://github.com/noneflow) ([#2981](https://github.com/nonebot/nonebot2/pull/2981))
|
|
||||||
- Plugin: 计算器:游戏 [@noneflow](https://github.com/noneflow) ([#2976](https://github.com/nonebot/nonebot2/pull/2976))
|
|
||||||
- Plugin: nonebot-plugin-yareminder [@noneflow](https://github.com/noneflow) ([#2964](https://github.com/nonebot/nonebot2/pull/2964))
|
|
||||||
- Plugin: 批量撤回 [@noneflow](https://github.com/noneflow) ([#2966](https://github.com/nonebot/nonebot2/pull/2966))
|
|
||||||
- Plugin: inspect [@noneflow](https://github.com/noneflow) ([#2971](https://github.com/nonebot/nonebot2/pull/2971))
|
|
||||||
- Plugin: 通用信息 [@noneflow](https://github.com/noneflow) ([#2969](https://github.com/nonebot/nonebot2/pull/2969))
|
|
||||||
- Plugin: SSE日志输出流 [@noneflow](https://github.com/noneflow) ([#2960](https://github.com/nonebot/nonebot2/pull/2960))
|
|
||||||
- Plugin: WITFF [@noneflow](https://github.com/noneflow) ([#2955](https://github.com/nonebot/nonebot2/pull/2955))
|
|
||||||
- Plugin: weather-rank [@noneflow](https://github.com/noneflow) ([#2949](https://github.com/nonebot/nonebot2/pull/2949))
|
|
||||||
- Plugin: 二维码生成器 [@noneflow](https://github.com/noneflow) ([#2942](https://github.com/nonebot/nonebot2/pull/2942))
|
|
||||||
- Plugin: 次元星辰 [@noneflow](https://github.com/noneflow) ([#2935](https://github.com/nonebot/nonebot2/pull/2935))
|
|
||||||
- Plugin: nonebot-plugin-tarina-lang-turbo [@noneflow](https://github.com/noneflow) ([#2938](https://github.com/nonebot/nonebot2/pull/2938))
|
|
||||||
- Plugin: 狼人杀 [@noneflow](https://github.com/noneflow) ([#2932](https://github.com/nonebot/nonebot2/pull/2932))
|
|
||||||
- Plugin: 阿瓦隆 [@noneflow](https://github.com/noneflow) ([#2915](https://github.com/nonebot/nonebot2/pull/2915))
|
|
||||||
- Plugin: 消音器 [@noneflow](https://github.com/noneflow) ([#2919](https://github.com/nonebot/nonebot2/pull/2919))
|
|
||||||
- Plugin: 悠悠 [@noneflow](https://github.com/noneflow) ([#2928](https://github.com/nonebot/nonebot2/pull/2928))
|
|
||||||
- Plugin: LLOneBot-Master [@noneflow](https://github.com/noneflow) ([#2925](https://github.com/nonebot/nonebot2/pull/2925))
|
|
||||||
- Plugin: 无情的发图姬 [@noneflow](https://github.com/noneflow) ([#2923](https://github.com/nonebot/nonebot2/pull/2923))
|
|
||||||
- Plugin: maimai DX 查分 [@noneflow](https://github.com/noneflow) ([#2921](https://github.com/nonebot/nonebot2/pull/2921))
|
|
||||||
- Plugin: Minecraft查服 [@noneflow](https://github.com/noneflow) ([#2882](https://github.com/nonebot/nonebot2/pull/2882))
|
|
||||||
- Plugin: lagrange [@noneflow](https://github.com/noneflow) ([#2898](https://github.com/nonebot/nonebot2/pull/2898))
|
|
||||||
- Plugin: nekro-agent [@noneflow](https://github.com/noneflow) ([#2896](https://github.com/nonebot/nonebot2/pull/2896))
|
|
||||||
- Plugin: nonebot_plugin_mute [@noneflow](https://github.com/noneflow) ([#2893](https://github.com/nonebot/nonebot2/pull/2893))
|
|
||||||
- Plugin: LiteyukiBot(plugin) [@noneflow](https://github.com/noneflow) ([#2905](https://github.com/nonebot/nonebot2/pull/2905))
|
|
||||||
- Plugin: 复读6 [@noneflow](https://github.com/noneflow) ([#2900](https://github.com/nonebot/nonebot2/pull/2900))
|
|
||||||
|
|
||||||
### 🍻 机器人发布
|
|
||||||
|
|
||||||
- Bot: CanrotBot [@noneflow](https://github.com/noneflow) ([#3086](https://github.com/nonebot/nonebot2/pull/3086))
|
|
||||||
- Bot: 小安提Bot [@noneflow](https://github.com/noneflow) ([#3061](https://github.com/nonebot/nonebot2/pull/3061))
|
|
||||||
|
|
||||||
## v2.3.3
|
|
||||||
|
|
||||||
### 🚀 新功能
|
### 🚀 新功能
|
||||||
|
|
||||||
@@ -133,11 +32,6 @@ toc_max_heading_level: 2
|
|||||||
|
|
||||||
### 🍻 插件发布
|
### 🍻 插件发布
|
||||||
|
|
||||||
- Plugin: nonebot-plugin-wait-a-minute [@noneflow](https://github.com/noneflow) ([#2902](https://github.com/nonebot/nonebot2/pull/2902))
|
|
||||||
- Plugin: 你看我像 [@noneflow](https://github.com/noneflow) ([#2895](https://github.com/nonebot/nonebot2/pull/2895))
|
|
||||||
- Plugin: dify插件 [@noneflow](https://github.com/noneflow) ([#2889](https://github.com/nonebot/nonebot2/pull/2889))
|
|
||||||
- Plugin: mai2_pcount [@noneflow](https://github.com/noneflow) ([#2891](https://github.com/nonebot/nonebot2/pull/2891))
|
|
||||||
- Plugin: nonebot-plugin-ehentai-search [@noneflow](https://github.com/noneflow) ([#2885](https://github.com/nonebot/nonebot2/pull/2885))
|
|
||||||
- Plugin: pokepoke_miss [@noneflow](https://github.com/noneflow) ([#2883](https://github.com/nonebot/nonebot2/pull/2883))
|
- Plugin: pokepoke_miss [@noneflow](https://github.com/noneflow) ([#2883](https://github.com/nonebot/nonebot2/pull/2883))
|
||||||
- Plugin: 聊天截图伪造 [@noneflow](https://github.com/noneflow) ([#2880](https://github.com/nonebot/nonebot2/pull/2880))
|
- Plugin: 聊天截图伪造 [@noneflow](https://github.com/noneflow) ([#2880](https://github.com/nonebot/nonebot2/pull/2880))
|
||||||
- Plugin: ba-tools [@noneflow](https://github.com/noneflow) ([#2867](https://github.com/nonebot/nonebot2/pull/2867))
|
- Plugin: ba-tools [@noneflow](https://github.com/noneflow) ([#2867](https://github.com/nonebot/nonebot2/pull/2867))
|
@@ -1,8 +1,11 @@
|
|||||||
import path from "path";
|
// @ts-check
|
||||||
|
|
||||||
import type { PluginConfig } from "@docusaurus/types";
|
const path = require("path");
|
||||||
|
|
||||||
export default (function webpackPlugin() {
|
/**
|
||||||
|
* @returns {import('@docusaurus/types').Plugin}
|
||||||
|
*/
|
||||||
|
function webpackPlugin() {
|
||||||
return {
|
return {
|
||||||
name: "webpack-plugin",
|
name: "webpack-plugin",
|
||||||
configureWebpack() {
|
configureWebpack() {
|
||||||
@@ -15,4 +18,6 @@ export default (function webpackPlugin() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} satisfies PluginConfig);
|
}
|
||||||
|
|
||||||
|
module.exports = webpackPlugin;
|
@@ -1,26 +1,24 @@
|
|||||||
import typography from "@tailwindcss/typography";
|
const lightTheme = require("daisyui/src/theming/themes")["[data-theme=light]"];
|
||||||
import daisyui from "daisyui";
|
const darkTheme = require("daisyui/src/theming/themes")["[data-theme=dark]"];
|
||||||
import themes from "daisyui/src/theming/themes";
|
|
||||||
|
|
||||||
const lightTheme = themes.light;
|
/**
|
||||||
const darkTheme = themes.dark;
|
* @param {{[key: string]: string}} theme
|
||||||
|
* @param {string[]} exclude
|
||||||
function excludeThemeColor(
|
* @returns {{[key: string]: string}}
|
||||||
theme: { [key: string]: string },
|
*/
|
||||||
exclude: string[]
|
function excludeThemeColor(theme, exclude) {
|
||||||
): { [key: string]: string } {
|
/** @type {typeof theme} */
|
||||||
const newObj: { [key: string]: string } = {};
|
const newObj = {};
|
||||||
for (const key in theme) {
|
for (const key in theme) {
|
||||||
if (exclude.includes(key)) continue;
|
if (exclude.includes(key)) continue;
|
||||||
newObj[key] = theme[key]!;
|
newObj[key] = theme[key];
|
||||||
}
|
}
|
||||||
return newObj;
|
return newObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
module.exports = {
|
||||||
plugins: [typography, daisyui],
|
darkMode: ["class", '[data-theme="dark"]'],
|
||||||
daisyui: {
|
daisyui: {
|
||||||
base: false,
|
|
||||||
themes: [
|
themes: [
|
||||||
{
|
{
|
||||||
light: {
|
light: {
|
||||||
@@ -30,7 +28,6 @@ export default {
|
|||||||
"accent-content",
|
"accent-content",
|
||||||
]),
|
]),
|
||||||
primary: "#ea5252",
|
primary: "#ea5252",
|
||||||
"primary-content": "#ffffff",
|
|
||||||
secondary: "#ef9fbc",
|
secondary: "#ef9fbc",
|
||||||
accent: "#65c3c8",
|
accent: "#65c3c8",
|
||||||
},
|
},
|
||||||
@@ -43,13 +40,10 @@ export default {
|
|||||||
"accent-content",
|
"accent-content",
|
||||||
]),
|
]),
|
||||||
primary: "#ea5252",
|
primary: "#ea5252",
|
||||||
"primary-content": "#ffffff",
|
|
||||||
secondary: "#ef9fbc",
|
secondary: "#ef9fbc",
|
||||||
accent: "#65c3c8",
|
accent: "#65c3c8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
darkTheme: false,
|
|
||||||
},
|
},
|
||||||
darkMode: ["class", '[data-theme="dark"]'],
|
|
||||||
};
|
};
|
@@ -1,42 +1,36 @@
|
|||||||
{
|
{
|
||||||
// This file is not used in compilation. It is here just for a nice editor experience.
|
// This file is not used in compilation. It is here just for a nice editor experience.
|
||||||
"extends": "@nullbot/docusaurus-tsconfig",
|
"extends": "@tsconfig/docusaurus/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["DOM", "ESNext"],
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
"@docusaurus/module-type-aliases",
|
||||||
|
"@nullbot/docusaurus-theme-nonepress"
|
||||||
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
"@theme/*": ["./src/theme/*"]
|
"@theme/*": ["./src/theme/*"]
|
||||||
},
|
},
|
||||||
"resolveJsonModule": true,
|
|
||||||
"allowArbitraryExtensions": true,
|
|
||||||
|
|
||||||
// Duplicated from the root config, because TS does not support extending
|
/* Strict Type-Checking Options */
|
||||||
// multiple configs and we want to dogfood the @docusaurus/tsconfig one
|
|
||||||
"allowUnreachableCode": false,
|
|
||||||
"exactOptionalPropertyTypes": false,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noPropertyAccessFromIndexSignature": false,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"alwaysStrict": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"noImplicitThis": true,
|
|
||||||
"strictBindCallApply": true,
|
|
||||||
"strictFunctionTypes": true,
|
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
"strictPropertyInitialization": true,
|
"strictPropertyInitialization": true,
|
||||||
"useUnknownInCatchVariables": true,
|
"noImplicitThis": true,
|
||||||
"noUnusedLocals": false,
|
"alwaysStrict": true,
|
||||||
"noUnusedParameters": false,
|
|
||||||
"importsNotUsedAsValues": "remove",
|
|
||||||
|
|
||||||
// This is important. We run `yarn tsc` in website so we can catch issues
|
/* Disabled on purpose (handled by ESLint, should not block compilation) */
|
||||||
// with our declaration files (mostly names that are forgotten to be
|
"noUnusedParameters": false,
|
||||||
// imported, invalid semantics...). Because we don't have end-to-end type
|
|
||||||
// tests, removing this would make things much harder to catch.
|
/* Advanced Options */
|
||||||
"skipLibCheck": false
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true, // @types/webpack and webpack/types.d.ts are not the same thing
|
||||||
|
|
||||||
|
/* Use tslib */
|
||||||
|
"importHelpers": true,
|
||||||
|
"noEmitHelpers": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user