mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 19:26:44 +00:00
Compare commits
1 Commits
v2.4.0
...
publish/is
Author | SHA1 | Date | |
---|---|---|---|
|
d8c36e8eff |
@@ -9,12 +9,13 @@
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"python.analysis.diagnosticMode": "workspace",
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"ruff.organizeImports": false,
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.ruff": "explicit",
|
||||
"source.organizeImports": "explicit"
|
||||
"source.fixAll.ruff": true,
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"[javascript]": {
|
||||
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
||||
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:
|
||||
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
|
||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||
with:
|
||||
changelog_file: website/src/changelog/changelog.md
|
||||
changelog_file: website/src/pages/changelog.md
|
||||
latest_changes_position: '# 更新日志\n\n'
|
||||
latest_changes_title: "## 最近更新"
|
||||
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
|
||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||
with:
|
||||
changelog_file: website/src/changelog/changelog.md
|
||||
changelog_file: website/src/pages/changelog.md
|
||||
archive_regex: '(?<=## )最近更新(?=\n)'
|
||||
archive_title: ${{ env.TAG_NAME }}
|
||||
commit_and_push: false
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,7 +7,6 @@ docs_build/_build
|
||||
!tests/.env
|
||||
.docusaurus
|
||||
website/docs/api/**/*.md
|
||||
website/src/pages/changelog/**/*
|
||||
|
||||
# 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
|
||||
|
@@ -7,23 +7,30 @@ ci:
|
||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.7.1
|
||||
rev: v0.5.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
stages: [pre-commit]
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
stages: [pre-commit]
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.10.0
|
||||
rev: 24.8.0
|
||||
hooks:
|
||||
- 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
|
||||
rev: v0.1.4
|
||||
|
@@ -1,3 +1,3 @@
|
||||
# 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 协议 |
|
||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||
| Mirai([仓库](https://github.com/ieew/nonebot_adapter_mirai2),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||
|
@@ -69,6 +69,16 @@
|
||||
"tags": [],
|
||||
"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",
|
||||
"project_link": "nonebot-adapter-onebot",
|
||||
|
@@ -632,30 +632,5 @@
|
||||
}
|
||||
],
|
||||
"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": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_simplemusic",
|
||||
"project_link": "nonebot-plugin-simplemusic",
|
||||
"author": "MeetWq",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nb2chan",
|
||||
"project_link": "nb2chan",
|
||||
@@ -702,6 +709,13 @@
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_hikarisearch",
|
||||
"project_link": "nonebot-plugin-hikarisearch",
|
||||
"author": "MeetWq",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_mediawiki",
|
||||
"project_link": "nonebot-plugin-mediawiki",
|
||||
@@ -1743,6 +1757,18 @@
|
||||
"tags": [],
|
||||
"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",
|
||||
"project_link": "nonebot-plugin-blacklist",
|
||||
@@ -4135,6 +4161,13 @@
|
||||
"tags": [],
|
||||
"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",
|
||||
"project_link": "nonebot-plugin-ocgbot-v2",
|
||||
@@ -6300,6 +6333,26 @@
|
||||
],
|
||||
"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",
|
||||
"project_link": "nonebot-plugin-eve-tool",
|
||||
@@ -6659,915 +6712,5 @@
|
||||
}
|
||||
],
|
||||
"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]
|
||||
python = "^3.9"
|
||||
trio = "^0.27.0"
|
||||
nonebug = "^0.4.1"
|
||||
nonebug = "^0.3.7"
|
||||
wsproto = "^1.2.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
pytest-xdist = "^3.0.2"
|
||||
pytest-asyncio = "^0.23.2"
|
||||
werkzeug = ">=2.3.6,<4.0.0"
|
||||
coverage-conditional-plugin = "^0.9.0"
|
||||
|
||||
|
@@ -39,8 +39,6 @@
|
||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 0
|
||||
description: nonebot 模块
|
||||
"""
|
||||
|
@@ -3,8 +3,6 @@
|
||||
使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 0
|
||||
description: nonebot.adapters 模块
|
||||
"""
|
||||
|
@@ -3,8 +3,6 @@
|
||||
为兼容 Pydantic V1 与 V2 版本,定义了一系列兼容函数与类供使用。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 16
|
||||
description: nonebot.compat 模块
|
||||
"""
|
||||
|
@@ -7,8 +7,6 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
|
||||
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 1
|
||||
description: nonebot.config 模块
|
||||
"""
|
||||
|
@@ -1,8 +1,6 @@
|
||||
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 9
|
||||
description: nonebot.consts 模块
|
||||
"""
|
||||
|
@@ -1,32 +1,22 @@
|
||||
"""本模块模块实现了依赖注入的定义与处理。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 0
|
||||
description: nonebot.dependencies 模块
|
||||
"""
|
||||
|
||||
import abc
|
||||
import asyncio
|
||||
import inspect
|
||||
from functools import partial
|
||||
from dataclasses import field, dataclass
|
||||
from collections.abc import Iterable, Awaitable
|
||||
from typing import Any, Generic, TypeVar, Callable, Optional, cast
|
||||
|
||||
import anyio
|
||||
from exceptiongroup import BaseExceptionGroup, catch
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import _DependentCallable
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.utils import run_sync, is_coroutine_callable
|
||||
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
|
||||
|
||||
@@ -92,16 +82,7 @@ class Dependent(Generic[R]):
|
||||
)
|
||||
|
||||
async def __call__(self, **kwargs: Any) -> R:
|
||||
exception: Optional[BaseExceptionGroup[SkippedException]] = None
|
||||
|
||||
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}):
|
||||
try:
|
||||
# do pre-check
|
||||
await self.check(**kwargs)
|
||||
|
||||
@@ -113,8 +94,9 @@ class Dependent(Generic[R]):
|
||||
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
|
||||
else:
|
||||
return await run_sync(cast(Callable[..., R], self.call))(**values)
|
||||
|
||||
raise exception
|
||||
except SkippedException as e:
|
||||
logger.trace(f"{self} skipped due to {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def parse_params(
|
||||
@@ -182,16 +164,9 @@ class Dependent(Generic[R]):
|
||||
return cls(call, params, parameterless_params)
|
||||
|
||||
async def check(self, **params: Any) -> None:
|
||||
if self.parameterless:
|
||||
async with anyio.create_task_group() as tg:
|
||||
for param in self.parameterless:
|
||||
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)
|
||||
await asyncio.gather(*(param._check(**params) for param in self.parameterless))
|
||||
await asyncio.gather(
|
||||
*(cast(Param, param.field_info)._check(**params) for param in self.params)
|
||||
)
|
||||
|
||||
async def _solve_field(self, field: ModelField, params: dict[str, Any]) -> Any:
|
||||
@@ -208,22 +183,10 @@ class Dependent(Generic[R]):
|
||||
await param._solve(**params)
|
||||
|
||||
# solve param values
|
||||
result: dict[str, Any] = {}
|
||||
if not self.params:
|
||||
return result
|
||||
|
||||
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
|
||||
values = await asyncio.gather(
|
||||
*(self._solve_field(field, params) for field in self.params)
|
||||
)
|
||||
return {field.name: value for field, value in zip(self.params, values)}
|
||||
|
||||
|
||||
__autodoc__ = {"CustomConfig": False}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
"""
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 1
|
||||
description: nonebot.dependencies.utils 模块
|
||||
"""
|
||||
|
@@ -3,8 +3,6 @@
|
||||
各驱动请继承以下基类。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 0
|
||||
description: nonebot.drivers 模块
|
||||
"""
|
||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[aiohttp]
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 2
|
||||
description: nonebot.drivers.aiohttp 模块
|
||||
"""
|
||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[fastapi]
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 1
|
||||
description: nonebot.drivers.fastapi 模块
|
||||
"""
|
||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[httpx]
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 3
|
||||
description: nonebot.drivers.httpx 模块
|
||||
"""
|
||||
|
@@ -5,25 +5,19 @@
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 6
|
||||
description: nonebot.drivers.none 模块
|
||||
"""
|
||||
|
||||
import signal
|
||||
from typing import Optional
|
||||
import asyncio
|
||||
import threading
|
||||
from typing_extensions import override
|
||||
|
||||
import anyio
|
||||
from anyio.abc import TaskGroup
|
||||
from exceptiongroup import BaseExceptionGroup, catch
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.consts import WINDOWS
|
||||
from nonebot.config import Env, Config
|
||||
from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.utils import flatten_exception_group
|
||||
|
||||
HANDLED_SIGNALS = (
|
||||
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
||||
@@ -39,8 +33,8 @@ class Driver(BaseDriver):
|
||||
def __init__(self, env: Env, config: Config):
|
||||
super().__init__(env, config)
|
||||
|
||||
self.should_exit: anyio.Event = anyio.Event()
|
||||
self.force_exit: anyio.Event = anyio.Event()
|
||||
self.should_exit: asyncio.Event = asyncio.Event()
|
||||
self.force_exit: bool = False
|
||||
|
||||
@property
|
||||
@override
|
||||
@@ -58,97 +52,84 @@ class Driver(BaseDriver):
|
||||
def run(self, *args, **kwargs):
|
||||
"""启动 none driver"""
|
||||
super().run(*args, **kwargs)
|
||||
anyio.run(self._serve)
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(self._serve())
|
||||
|
||||
async def _serve(self):
|
||||
async with anyio.create_task_group() as driver_tg:
|
||||
driver_tg.start_soon(self._handle_signals)
|
||||
driver_tg.start_soon(self._listen_force_exit, driver_tg)
|
||||
driver_tg.start_soon(self._handle_lifespan, driver_tg)
|
||||
|
||||
async def _handle_signals(self):
|
||||
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:
|
||||
self._install_signal_handlers()
|
||||
await self._startup()
|
||||
|
||||
if self.should_exit.is_set():
|
||||
return
|
||||
|
||||
await self._listen_exit()
|
||||
|
||||
await self._main_loop()
|
||||
await self._shutdown()
|
||||
finally:
|
||||
tg.cancel_scope.cancel()
|
||||
|
||||
async def _startup(self):
|
||||
def handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||
self.should_exit.set()
|
||||
|
||||
for exc in flatten_exception_group(exc_group):
|
||||
logger.opt(colors=True, exception=exc).error(
|
||||
"<r><bg #f8bbd0>Error occurred while running startup hook."
|
||||
"</bg #f8bbd0></r>"
|
||||
)
|
||||
logger.error(
|
||||
try:
|
||||
await self._lifespan.startup()
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Application startup failed. "
|
||||
"Exiting.</bg #f8bbd0></r>"
|
||||
)
|
||||
self.should_exit.set()
|
||||
return
|
||||
|
||||
with catch({Exception: handle_exception}):
|
||||
await self._lifespan.startup()
|
||||
|
||||
if not self.should_exit.is_set():
|
||||
logger.info("Application startup completed.")
|
||||
|
||||
async def _listen_exit(self, tg: Optional[TaskGroup] = None):
|
||||
async def _main_loop(self):
|
||||
await self.should_exit.wait()
|
||||
|
||||
if tg is not None:
|
||||
tg.cancel_scope.cancel()
|
||||
|
||||
async def _shutdown(self):
|
||||
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:
|
||||
nonlocal error_occurred
|
||||
|
||||
error_occurred = True
|
||||
|
||||
for exc in flatten_exception_group(exc_group):
|
||||
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}):
|
||||
try:
|
||||
await self._lifespan.shutdown()
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running shutdown function. "
|
||||
"Ignored!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
for task in asyncio.all_tasks():
|
||||
if task is not asyncio.current_task() and not task.done():
|
||||
task.cancel()
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
||||
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()]
|
||||
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
if not error_occurred:
|
||||
logger.info("Application shutdown complete.")
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.stop()
|
||||
|
||||
async def _listen_force_exit(self, tg: TaskGroup):
|
||||
await self.force_exit.wait()
|
||||
tg.cancel_scope.cancel()
|
||||
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):
|
||||
"""退出 none driver
|
||||
@@ -159,4 +140,4 @@ class Driver(BaseDriver):
|
||||
if not self.should_exit.is_set():
|
||||
self.should_exit.set()
|
||||
if force:
|
||||
self.force_exit.set()
|
||||
self.force_exit = True
|
||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[quart]
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 5
|
||||
description: nonebot.drivers.quart 模块
|
||||
"""
|
||||
|
@@ -11,8 +11,6 @@ pip install nonebot2[websockets]
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 4
|
||||
description: nonebot.drivers.websockets 模块
|
||||
"""
|
||||
@@ -71,8 +69,6 @@ class Mixin(WebSocketClientMixin):
|
||||
@override
|
||||
@asynccontextmanager
|
||||
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(
|
||||
str(setup.url),
|
||||
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
|
||||
|
@@ -25,8 +25,6 @@ NoneBotException
|
||||
```
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 10
|
||||
description: nonebot.exception 模块
|
||||
"""
|
||||
|
@@ -1,14 +1,11 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional, Protocol
|
||||
|
||||
import anyio
|
||||
from exceptiongroup import BaseExceptionGroup, catch
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.config import Config
|
||||
from nonebot.exception import MockApiException
|
||||
from nonebot.utils import flatten_exception_group
|
||||
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -79,99 +76,48 @@ class Bot(abc.ABC):
|
||||
skip_calling_api: bool = False
|
||||
exception: Optional[Exception] = None
|
||||
|
||||
if self._calling_api_hook:
|
||||
if coros := [hook(self, api, data) for hook in self._calling_api_hook]:
|
||||
try:
|
||||
logger.debug("Running CallingAPI hooks...")
|
||||
|
||||
def _handle_mock_api_exception(
|
||||
exc_group: BaseExceptionGroup[MockApiException],
|
||||
) -> 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."
|
||||
)
|
||||
|
||||
await asyncio.gather(*coros)
|
||||
except MockApiException as e:
|
||||
skip_calling_api = True
|
||||
result = excs[0].result
|
||||
|
||||
result = e.result
|
||||
logger.debug(
|
||||
f"Calling API {api} is cancelled. Return {result!r} instead."
|
||||
f"Calling API {api} is cancelled. Return {result} instead."
|
||||
)
|
||||
|
||||
def _handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||
for exc in flatten_exception_group(exc_group):
|
||||
logger.opt(colors=True, exception=exc).error(
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).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:
|
||||
try:
|
||||
result = await self.adapter._call_api(self, api, **data)
|
||||
except Exception as e:
|
||||
exception = e
|
||||
|
||||
if self._called_api_hook:
|
||||
if coros := [
|
||||
hook(self, exception, api, data, result) for hook in self._called_api_hook
|
||||
]:
|
||||
try:
|
||||
logger.debug("Running CalledAPI hooks...")
|
||||
|
||||
def _handle_mock_api_exception(
|
||||
exc_group: BaseExceptionGroup[MockApiException],
|
||||
) -> None:
|
||||
nonlocal result, exception
|
||||
|
||||
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."
|
||||
)
|
||||
|
||||
result = excs[0].result
|
||||
await asyncio.gather(*coros)
|
||||
except MockApiException as e:
|
||||
# mock api result
|
||||
result = e.result
|
||||
# ignore exception
|
||||
exception = None
|
||||
logger.debug(
|
||||
f"Calling API {api} result is mocked. Return {result} instead."
|
||||
)
|
||||
|
||||
def _handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
|
||||
for exc in flatten_exception_group(exc_group):
|
||||
logger.opt(colors=True, exception=exc).error(
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<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:
|
||||
raise exception
|
||||
return result
|
||||
|
@@ -1,11 +1,6 @@
|
||||
from types import TracebackType
|
||||
from collections.abc import Awaitable
|
||||
from typing_extensions import TypeAlias
|
||||
from collections.abc import Iterable, Awaitable
|
||||
from typing import Any, Union, Callable, Optional, cast
|
||||
|
||||
import anyio
|
||||
from anyio.abc import TaskGroup
|
||||
from exceptiongroup import suppress
|
||||
from typing import Any, Union, Callable, cast
|
||||
|
||||
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:
|
||||
def __init__(self) -> None:
|
||||
self._task_group: Optional[TaskGroup] = None
|
||||
|
||||
self._startup_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._ready_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:
|
||||
self._startup_funcs.append(func)
|
||||
return func
|
||||
@@ -48,7 +29,7 @@ class Lifespan:
|
||||
|
||||
@staticmethod
|
||||
async def _run_lifespan_func(
|
||||
funcs: Iterable[LIFESPAN_FUNC],
|
||||
funcs: list[LIFESPAN_FUNC],
|
||||
) -> None:
|
||||
for func in funcs:
|
||||
if is_coroutine_callable(func):
|
||||
@@ -57,44 +38,18 @@ class Lifespan:
|
||||
await run_sync(cast(SYNC_LIFESPAN_FUNC, func))()
|
||||
|
||||
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:
|
||||
await self._run_lifespan_func(self._startup_funcs)
|
||||
|
||||
# run ready funcs
|
||||
if self._ready_funcs:
|
||||
await self._run_lifespan_func(self._ready_funcs)
|
||||
|
||||
async def shutdown(
|
||||
self,
|
||||
*,
|
||||
exc_type: Optional[type[BaseException]] = None,
|
||||
exc_val: Optional[BaseException] = None,
|
||||
exc_tb: Optional[TracebackType] = None,
|
||||
) -> None:
|
||||
async def shutdown(self) -> None:
|
||||
if self._shutdown_funcs:
|
||||
# reverse shutdown funcs to ensure stack order
|
||||
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
|
||||
await self._run_lifespan_func(self._shutdown_funcs)
|
||||
|
||||
async def __aenter__(self) -> None:
|
||||
await self.startup()
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
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)
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
await self.shutdown()
|
||||
|
@@ -1,20 +1,17 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from types import TracebackType
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing_extensions import Self, TypeAlias
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
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.config import Env, Config
|
||||
from nonebot.dependencies import Dependent
|
||||
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.utils import escape_tag, run_coro_with_catch, flatten_exception_group
|
||||
from nonebot.typing import (
|
||||
T_DependencyCache,
|
||||
T_BotConnectionHook,
|
||||
@@ -64,6 +61,7 @@ class Driver(abc.ABC):
|
||||
self.config: Config = config
|
||||
"""全局配置对象"""
|
||||
self._bots: dict[str, "Bot"] = {}
|
||||
self._bot_tasks: set[asyncio.Task] = set()
|
||||
self._lifespan = Lifespan()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -77,10 +75,6 @@ class Driver(abc.ABC):
|
||||
"""获取当前所有已连接的 Bot"""
|
||||
return self._bots
|
||||
|
||||
@property
|
||||
def task_group(self) -> TaskGroup:
|
||||
return self._lifespan.task_group
|
||||
|
||||
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>"
|
||||
)
|
||||
|
||||
self.on_shutdown(self._cleanup)
|
||||
|
||||
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_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}")
|
||||
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(
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
async with AsyncExitStack() as stack:
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
|
||||
(SkippedException,),
|
||||
)
|
||||
for hook in self._bot_connection_hook
|
||||
]:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>"
|
||||
"Error when running WebSocketConnection hook:"
|
||||
"Error when running WebSocketConnection hook. "
|
||||
"Running cancelled!"
|
||||
"</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
with CancelScope(shield=True), catch({Exception: handle_exception}):
|
||||
async with AsyncExitStack() as stack, create_task_group() as tg:
|
||||
for hook in self._bot_connection_hook:
|
||||
tg.start_soon(
|
||||
run_coro_with_catch,
|
||||
hook(
|
||||
bot=bot, stack=stack, dependency_cache=dependency_cache
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
|
||||
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:
|
||||
"""在连接断开后,调用该函数来注销 bot 对象"""
|
||||
if bot.self_id in self._bots:
|
||||
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(
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
async with AsyncExitStack() as stack:
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
|
||||
(SkippedException,),
|
||||
)
|
||||
for hook in self._bot_disconnection_hook
|
||||
]:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>"
|
||||
"Error when running WebSocketDisConnection hook:"
|
||||
"Error when running WebSocketDisConnection hook. "
|
||||
"Running cancelled!"
|
||||
"</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
# shield cancellation to ensure bot disconnect hooks are always run
|
||||
with CancelScope(shield=True), catch({Exception: handle_exception}):
|
||||
async with create_task_group() as tg, AsyncExitStack() as stack:
|
||||
for hook in self._bot_disconnection_hook:
|
||||
tg.start_soon(
|
||||
run_coro_with_catch,
|
||||
hook(
|
||||
bot=bot, stack=stack, dependency_cache=dependency_cache
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
task = asyncio.create_task(_run_hook(bot))
|
||||
task.add_done_callback(self._bot_tasks.discard)
|
||||
self._bot_tasks.add(task)
|
||||
|
||||
self.task_group.start_soon(_run_hook, bot)
|
||||
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):
|
||||
|
@@ -22,13 +22,11 @@ from typing import ( # noqa: UP035
|
||||
overload,
|
||||
)
|
||||
|
||||
from exceptiongroup import BaseExceptionGroup, catch
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.internal.rule import Rule
|
||||
from nonebot.utils import classproperty
|
||||
from nonebot.dependencies import Param, Dependent
|
||||
from nonebot.internal.permission import User, Permission
|
||||
from nonebot.utils import classproperty, flatten_exception_group
|
||||
from nonebot.internal.adapter import (
|
||||
Bot,
|
||||
Event,
|
||||
@@ -814,12 +812,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
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):
|
||||
try:
|
||||
with catch({StopPropagation: _handle_stop_propagation}):
|
||||
# Refresh preprocess state
|
||||
self.state.update(state)
|
||||
|
||||
@@ -827,13 +821,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
handler = self.remain_handlers.pop(0)
|
||||
current_handler.set(handler)
|
||||
logger.debug(f"Running handler {handler}")
|
||||
|
||||
def _handle_skipped(
|
||||
exc_group: BaseExceptionGroup[SkippedException],
|
||||
):
|
||||
logger.debug(f"Handler {handler} skipped")
|
||||
|
||||
with catch({SkippedException: _handle_skipped}):
|
||||
try:
|
||||
await handler(
|
||||
matcher=self,
|
||||
bot=bot,
|
||||
@@ -842,6 +830,10 @@ class Matcher(metaclass=MatcherMeta):
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
except SkippedException:
|
||||
logger.debug(f"Handler {handler} skipped")
|
||||
except StopPropagation:
|
||||
self.block = True
|
||||
finally:
|
||||
logger.info(f"{self} running complete")
|
||||
|
||||
@@ -854,54 +846,10 @@ class Matcher(metaclass=MatcherMeta):
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
):
|
||||
exc: Optional[Union[FinishedException, RejectedException, PausedException]] = (
|
||||
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
|
||||
}
|
||||
):
|
||||
try:
|
||||
await self.simple_run(bot, event, state, stack, dependency_cache)
|
||||
|
||||
if isinstance(exc, FinishedException):
|
||||
pass
|
||||
elif isinstance(exc, RejectedException):
|
||||
except RejectedException:
|
||||
await self.resolve_reject()
|
||||
type_ = await self.update_type(bot, event, stack, dependency_cache)
|
||||
permission = await self.update_permission(
|
||||
@@ -922,7 +870,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
default_type_updater=self.__class__._default_type_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)
|
||||
permission = await self.update_permission(
|
||||
bot, event, stack, dependency_cache
|
||||
@@ -942,3 +890,5 @@ class Matcher(metaclass=MatcherMeta):
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
default_permission_updater=self.__class__._default_permission_updater,
|
||||
)
|
||||
except FinishedException:
|
||||
pass
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
from enum import Enum
|
||||
from typing_extensions import Self, get_args, override, get_origin
|
||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||
from typing import (
|
||||
@@ -13,11 +13,8 @@ from typing import (
|
||||
cast,
|
||||
)
|
||||
|
||||
import anyio
|
||||
from exceptiongroup import BaseExceptionGroup, catch
|
||||
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
||||
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.dependencies import Param, Dependent
|
||||
from nonebot.dependencies.utils import check_field_type
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
"""子依赖注入参数。
|
||||
|
||||
@@ -269,27 +194,17 @@ class DependParam(Param):
|
||||
call = cast(Callable[..., Any], sub_dependent.call)
|
||||
|
||||
# solve sub dependency with current cache
|
||||
exc: Optional[BaseExceptionGroup[SkippedException]] = None
|
||||
|
||||
def _handle_skipped(exc_group: BaseExceptionGroup[SkippedException]):
|
||||
nonlocal exc
|
||||
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
|
||||
task: asyncio.Task[Any]
|
||||
if use_cache and call in dependency_cache:
|
||||
return await dependency_cache[call].wait()
|
||||
|
||||
if is_gen_callable(call) or is_async_gen_callable(call):
|
||||
return await dependency_cache[call]
|
||||
elif is_gen_callable(call) or is_async_gen_callable(call):
|
||||
assert isinstance(
|
||||
stack, AsyncExitStack
|
||||
), "Generator dependency should be called in context"
|
||||
@@ -297,28 +212,17 @@ class DependParam(Param):
|
||||
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
||||
else:
|
||||
cm = asynccontextmanager(call)(**sub_values)
|
||||
|
||||
target = stack.enter_async_context(cm)
|
||||
task = asyncio.create_task(stack.enter_async_context(cm))
|
||||
dependency_cache[call] = task
|
||||
return await task
|
||||
elif is_coroutine_callable(call):
|
||||
target = call(**sub_values)
|
||||
task = asyncio.create_task(call(**sub_values))
|
||||
dependency_cache[call] = task
|
||||
return await task
|
||||
else:
|
||||
target = run_sync(call)(**sub_values)
|
||||
|
||||
dependency_cache[call] = cache = DependencyCache()
|
||||
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
|
||||
task = asyncio.create_task(run_sync(call)(**sub_values))
|
||||
dependency_cache[call] = task
|
||||
return await task
|
||||
|
||||
@override
|
||||
async def _check(self, **kwargs: Any) -> None:
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import asyncio
|
||||
from typing_extensions import Self
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Union, ClassVar, NoReturn, Optional
|
||||
|
||||
import anyio
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.utils import run_coro_with_catch
|
||||
from nonebot.exception import SkippedException
|
||||
@@ -71,26 +70,22 @@ class Permission:
|
||||
"""
|
||||
if not self.checkers:
|
||||
return True
|
||||
|
||||
result = False
|
||||
|
||||
async def _run_checker(checker: Dependent[bool]) -> None:
|
||||
nonlocal result
|
||||
# calculate the result first to avoid data racing
|
||||
is_passed = await run_coro_with_catch(
|
||||
results = await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
checker(
|
||||
bot=bot, event=event, stack=stack, dependency_cache=dependency_cache
|
||||
bot=bot,
|
||||
event=event,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
False,
|
||||
)
|
||||
result |= is_passed
|
||||
|
||||
async with anyio.create_task_group() as tg:
|
||||
for checker in self.checkers:
|
||||
tg.start_soon(_run_checker, checker)
|
||||
|
||||
return result
|
||||
for checker in self.checkers
|
||||
),
|
||||
)
|
||||
return any(results)
|
||||
|
||||
def __and__(self, other: object) -> NoReturn:
|
||||
raise RuntimeError("And operation between Permissions is not allowed.")
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import asyncio
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Union, ClassVar, NoReturn, Optional
|
||||
|
||||
import anyio
|
||||
from exceptiongroup import BaseExceptionGroup, catch
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
||||
@@ -73,33 +71,22 @@ class Rule:
|
||||
"""
|
||||
if not self.checkers:
|
||||
return True
|
||||
|
||||
result = True
|
||||
|
||||
def _handle_skipped_exception(
|
||||
exc_group: BaseExceptionGroup[SkippedException],
|
||||
) -> None:
|
||||
nonlocal result
|
||||
result = False
|
||||
|
||||
async def _run_checker(checker: Dependent[bool]) -> None:
|
||||
nonlocal result
|
||||
# calculate the result first to avoid data racing
|
||||
is_passed = await checker(
|
||||
try:
|
||||
results = await asyncio.gather(
|
||||
*(
|
||||
checker(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
result &= is_passed
|
||||
|
||||
with catch({SkippedException: _handle_skipped_exception}):
|
||||
async with anyio.create_task_group() as tg:
|
||||
for checker in self.checkers:
|
||||
tg.start_soon(_run_checker, checker)
|
||||
|
||||
return result
|
||||
for checker in self.checkers
|
||||
)
|
||||
)
|
||||
except SkippedException:
|
||||
return False
|
||||
return all(results)
|
||||
|
||||
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
|
||||
if other is None:
|
||||
|
@@ -8,8 +8,6 @@ NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
|
||||
[loguru]: https://github.com/Delgan/loguru
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 7
|
||||
description: nonebot.log 模块
|
||||
"""
|
||||
|
@@ -1,8 +1,6 @@
|
||||
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 3
|
||||
description: nonebot.matcher 模块
|
||||
"""
|
||||
|
@@ -3,36 +3,27 @@
|
||||
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 2
|
||||
description: nonebot.message 模块
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional
|
||||
|
||||
import anyio
|
||||
from exceptiongroup import BaseExceptionGroup, catch
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.rule import TrieRule
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.matcher import Matcher, matchers
|
||||
from nonebot.utils import escape_tag, run_coro_with_catch
|
||||
from nonebot.exception import (
|
||||
NoLogException,
|
||||
StopPropagation,
|
||||
IgnoredException,
|
||||
SkippedException,
|
||||
)
|
||||
from nonebot.utils import (
|
||||
escape_tag,
|
||||
run_coro_with_catch,
|
||||
run_coro_with_shield,
|
||||
flatten_exception_group,
|
||||
)
|
||||
from nonebot.typing import (
|
||||
T_State,
|
||||
T_DependencyCache,
|
||||
@@ -132,21 +123,6 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
||||
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(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
@@ -174,21 +150,10 @@ async def _apply_event_preprocessors(
|
||||
if show_log:
|
||||
logger.debug("Running PreProcessors...")
|
||||
|
||||
with catch(
|
||||
{
|
||||
IgnoredException: _handle_ignored_exception(
|
||||
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
||||
),
|
||||
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,
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
@@ -198,11 +163,23 @@ async def _apply_event_preprocessors(
|
||||
),
|
||||
(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 False
|
||||
|
||||
|
||||
async def _apply_event_postprocessors(
|
||||
bot: "Bot",
|
||||
@@ -228,17 +205,10 @@ async def _apply_event_postprocessors(
|
||||
if show_log:
|
||||
logger.debug("Running PostProcessors...")
|
||||
|
||||
with catch(
|
||||
{
|
||||
Exception: _handle_exception(
|
||||
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
}
|
||||
):
|
||||
async with anyio.create_task_group() as tg:
|
||||
for proc in _event_postprocessors:
|
||||
tg.start_soon(
|
||||
run_coro_with_catch,
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
@@ -248,6 +218,13 @@ async def _apply_event_postprocessors(
|
||||
),
|
||||
(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(
|
||||
@@ -275,24 +252,11 @@ async def _apply_run_preprocessors(
|
||||
return True
|
||||
|
||||
# ensure matcher function can be correctly called
|
||||
with (
|
||||
matcher.ensure_context(bot, event),
|
||||
catch(
|
||||
{
|
||||
IgnoredException: _handle_ignored_exception(
|
||||
f"{matcher} running is <b>cancelled</b>"
|
||||
),
|
||||
Exception: _handle_exception(
|
||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
),
|
||||
}
|
||||
),
|
||||
):
|
||||
async with anyio.create_task_group() as tg:
|
||||
for proc in _run_preprocessors:
|
||||
tg.start_soon(
|
||||
run_coro_with_catch,
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
bot=bot,
|
||||
@@ -303,11 +267,21 @@ async def _apply_run_preprocessors(
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _run_preprocessors
|
||||
)
|
||||
)
|
||||
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 False
|
||||
|
||||
|
||||
async def _apply_run_postprocessors(
|
||||
bot: "Bot",
|
||||
@@ -330,21 +304,11 @@ async def _apply_run_postprocessors(
|
||||
if not _run_postprocessors:
|
||||
return
|
||||
|
||||
with (
|
||||
matcher.ensure_context(bot, event),
|
||||
catch(
|
||||
{
|
||||
Exception: _handle_exception(
|
||||
"<r><bg #f8bbd0>Error when running RunPostProcessors"
|
||||
"</bg #f8bbd0></r>"
|
||||
)
|
||||
}
|
||||
),
|
||||
):
|
||||
async with anyio.create_task_group() as tg:
|
||||
for proc in _run_postprocessors:
|
||||
tg.start_soon(
|
||||
run_coro_with_catch,
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
exception=exception,
|
||||
@@ -356,6 +320,13 @@ async def _apply_run_postprocessors(
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _run_postprocessors
|
||||
)
|
||||
)
|
||||
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(
|
||||
@@ -452,9 +423,8 @@ async def _run_matcher(
|
||||
|
||||
exception = None
|
||||
|
||||
logger.debug(f"Running {matcher}")
|
||||
|
||||
try:
|
||||
logger.debug(f"Running {matcher}")
|
||||
await matcher.run(bot, event, state, stack, dependency_cache)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
@@ -522,7 +492,8 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
|
||||
用法:
|
||||
```python
|
||||
driver.task_group.start_soon(handle_event, bot, event)
|
||||
import asyncio
|
||||
asyncio.create_task(handle_event(bot, event))
|
||||
```
|
||||
"""
|
||||
show_log = True
|
||||
@@ -557,13 +528,6 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
)
|
||||
|
||||
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
|
||||
for priority in sorted(matchers.keys()):
|
||||
if break_flag:
|
||||
@@ -572,29 +536,22 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
if show_log:
|
||||
logger.debug(f"Checking for matchers in priority {priority}...")
|
||||
|
||||
if not (priority_matchers := matchers[priority]):
|
||||
continue
|
||||
|
||||
with catch(
|
||||
{
|
||||
StopPropagation: _handle_stop_propagation,
|
||||
Exception: _handle_exception(
|
||||
"<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,
|
||||
pending_tasks = [
|
||||
check_and_run_matcher(
|
||||
matcher,
|
||||
bot,
|
||||
event,
|
||||
state.copy(),
|
||||
stack,
|
||||
dependency_cache,
|
||||
),
|
||||
matcher, bot, event, state.copy(), stack, dependency_cache
|
||||
)
|
||||
for matcher in matchers[priority]
|
||||
]
|
||||
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>"
|
||||
)
|
||||
|
||||
if show_log:
|
||||
|
@@ -1,8 +1,6 @@
|
||||
"""本模块定义了依赖注入的各类参数。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 4
|
||||
description: nonebot.params 模块
|
||||
"""
|
||||
|
@@ -5,8 +5,6 @@
|
||||
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 6
|
||||
description: nonebot.permission 模块
|
||||
"""
|
||||
|
@@ -32,8 +32,6 @@
|
||||
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.model.PluginMetadata>`
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 0
|
||||
description: nonebot.plugin 模块
|
||||
"""
|
||||
|
@@ -1,8 +1,6 @@
|
||||
"""本模块定义插件加载接口。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 1
|
||||
description: nonebot.plugin.load 模块
|
||||
"""
|
||||
@@ -22,7 +20,7 @@ from . import _managers, get_plugin, _module_name_to_plugin_id
|
||||
try: # pragma: py-gte-311
|
||||
import tomllib # pyright: ignore[reportMissingImports]
|
||||
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]:
|
||||
|
@@ -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/)
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 5
|
||||
description: nonebot.plugin.manager 模块
|
||||
"""
|
||||
|
@@ -1,8 +1,6 @@
|
||||
"""本模块定义插件相关信息。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 3
|
||||
description: nonebot.plugin.model 模块
|
||||
"""
|
||||
|
@@ -1,8 +1,6 @@
|
||||
"""本模块定义事件响应器便携定义函数。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 2
|
||||
description: nonebot.plugin.on 模块
|
||||
"""
|
||||
|
@@ -5,8 +5,6 @@
|
||||
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 5
|
||||
description: nonebot.rule 模块
|
||||
"""
|
||||
|
@@ -6,8 +6,6 @@
|
||||
[`typing`](https://docs.python.org/3/library/typing.html)。
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 11
|
||||
description: nonebot.typing 模块
|
||||
"""
|
||||
@@ -21,9 +19,10 @@ from typing import TYPE_CHECKING, TypeVar
|
||||
from typing_extensions import ParamSpec, TypeAlias, get_args, override, get_origin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from asyncio import Task
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.internal.params import DependencyCache
|
||||
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
@@ -257,5 +256,5 @@ T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "DependencyCache"]
|
||||
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "Task[t.Any]"]
|
||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||
|
@@ -1,30 +1,27 @@
|
||||
"""本模块包含了 NoneBot 的一些工具函数
|
||||
|
||||
FrontMatter:
|
||||
mdx:
|
||||
format: md
|
||||
sidebar_position: 8
|
||||
description: nonebot.utils 模块
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import asyncio
|
||||
import inspect
|
||||
import importlib
|
||||
import contextlib
|
||||
import dataclasses
|
||||
from pathlib import Path
|
||||
from collections import deque
|
||||
from contextvars import copy_context
|
||||
from functools import wraps, partial
|
||||
from contextlib import AbstractContextManager, asynccontextmanager
|
||||
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 collections.abc import Mapping, Sequence, Coroutine, Generator, AsyncGenerator
|
||||
|
||||
import anyio
|
||||
import anyio.to_thread
|
||||
from pydantic import BaseModel
|
||||
from exceptiongroup import BaseExceptionGroup, catch
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import (
|
||||
@@ -40,7 +37,6 @@ R = TypeVar("R")
|
||||
T = TypeVar("T")
|
||||
K = TypeVar("K")
|
||||
V = TypeVar("V")
|
||||
E = TypeVar("E", bound=BaseException)
|
||||
|
||||
|
||||
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)
|
||||
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
return await anyio.to_thread.run_sync(
|
||||
partial(call, *args, **kwargs), abandon_on_cancel=True
|
||||
)
|
||||
loop = asyncio.get_running_loop()
|
||||
pfunc = partial(call, *args, **kwargs)
|
||||
context = copy_context()
|
||||
result = await loop.run_in_executor(None, partial(context.run, pfunc))
|
||||
return result
|
||||
|
||||
return _wrapper
|
||||
|
||||
@@ -234,36 +232,12 @@ async def run_coro_with_catch(
|
||||
协程的返回值或发生异常时的指定值
|
||||
"""
|
||||
|
||||
with catch({exc: lambda exc_group: None}):
|
||||
try:
|
||||
return await coro
|
||||
|
||||
except exc:
|
||||
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:
|
||||
"""获取对象的名称"""
|
||||
if inspect.isfunction(obj) or inspect.isclass(obj):
|
||||
|
2894
poetry.lock
generated
2894
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot2"
|
||||
version = "2.4.0"
|
||||
version = "2.3.2"
|
||||
description = "An asynchronous python bot framework."
|
||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
@@ -22,14 +22,12 @@ include = ["nonebot/py.typed"]
|
||||
[tool.poetry.urls]
|
||||
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
|
||||
"Changelog" = "https://nonebot.dev/changelog"
|
||||
"Funding" = "https://afdian.com/@nonebot"
|
||||
"Funding" = "https://afdian.net/@nonebot"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
yarl = "^1.7.2"
|
||||
anyio = "^4.4.0"
|
||||
pygtrie = "^2.4.1"
|
||||
exceptiongroup = "^1.2.2"
|
||||
loguru = ">=0.6.0,<1.0.0"
|
||||
python-dotenv = ">=0.21.0,<2.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 }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.7.0"
|
||||
ruff = "^0.4.0"
|
||||
isort = "^5.10.1"
|
||||
black = "^24.0.0"
|
||||
nonemoji = "^0.1.2"
|
||||
@@ -67,6 +65,7 @@ fastapi = ["fastapi", "uvicorn"]
|
||||
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "strict"
|
||||
addopts = "--cov=nonebot --cov-report=term-missing"
|
||||
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
||||
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import os
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING
|
||||
from collections.abc import Generator
|
||||
from typing_extensions import ParamSpec
|
||||
from typing import TYPE_CHECKING, TypeVar, Callable
|
||||
|
||||
import pytest
|
||||
from nonebug import NONEBOT_INIT_KWARGS
|
||||
@@ -22,9 +20,6 @@ os.environ["CONFIG_OVERRIDE"] = "new"
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.plugin import Plugin
|
||||
|
||||
P = ParamSpec("P")
|
||||
R = TypeVar("R")
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@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)
|
||||
@run_once
|
||||
def load_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
|
||||
def load_plugin(nonebug_init: None) -> set["Plugin"]:
|
||||
# preload global plugins
|
||||
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
@run_once
|
||||
def load_builtin_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
|
||||
def load_builtin_plugin(nonebug_init: None) -> set["Plugin"]:
|
||||
# preload builtin plugins
|
||||
return nonebot.load_builtin_plugins("echo", "single_session")
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
from typing import Annotated
|
||||
from dataclasses import dataclass
|
||||
|
||||
import anyio
|
||||
from pydantic import Field
|
||||
|
||||
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))):
|
||||
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):
|
||||
last_connect_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
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
@@ -74,7 +75,7 @@ async def test_adapter_connect(app: App, driver: Driver):
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_adapter_server(driver: Driver):
|
||||
async def test_adapter_server(driver: Driver):
|
||||
last_http_setup: Optional[HTTPServerSetup] = None
|
||||
last_ws_setup: Optional[WebSocketServerSetup] = None
|
||||
|
||||
@@ -111,7 +112,7 @@ def test_adapter_server(driver: Driver):
|
||||
assert last_ws_setup is setup
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
@@ -158,7 +159,7 @@ async def test_adapter_http_client(driver: Driver):
|
||||
assert last_request is request
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
|
@@ -1,6 +1,5 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
import anyio
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
@@ -8,7 +7,7 @@ from nonebot.adapters import Bot
|
||||
from nonebot.exception import MockApiException
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_call_api(app: App):
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
@@ -24,7 +23,7 @@ async def test_bot_call_api(app: App):
|
||||
await bot.call_api("test")
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_calling_api_hook_simple(app: App):
|
||||
runned: bool = False
|
||||
|
||||
@@ -50,7 +49,7 @@ async def test_bot_calling_api_hook_simple(app: App):
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_calling_api_hook_mock(app: App):
|
||||
runned: bool = False
|
||||
|
||||
@@ -77,47 +76,7 @@ async def test_bot_calling_api_hook_mock(app: App):
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
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
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_called_api_hook_simple(app: App):
|
||||
runned: bool = False
|
||||
|
||||
@@ -149,7 +108,7 @@ async def test_bot_called_api_hook_simple(app: App):
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_called_api_hook_mock(app: App):
|
||||
runned: bool = False
|
||||
|
||||
@@ -191,56 +150,3 @@ async def test_bot_called_api_hook_mock(app: App):
|
||||
|
||||
assert runned is True
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor_exception(
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_postprocessor_exception(
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor_exception(
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_postprocessor_exception(
|
||||
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("arbitrary_types_allowed") is True
|
||||
|
||||
|
||||
def test_field_info():
|
||||
@pytest.mark.asyncio
|
||||
async def test_field_info():
|
||||
# required should be convert to PydanticUndefined
|
||||
assert FieldInfo(Required).default is PydanticUndefined
|
||||
|
||||
@@ -30,7 +32,8 @@ def test_field_info():
|
||||
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)])
|
||||
|
||||
assert t.validate_python(2) == 2
|
||||
@@ -44,7 +47,8 @@ def test_type_adapter():
|
||||
t.validate_json("0")
|
||||
|
||||
|
||||
def test_model_dump():
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_dump():
|
||||
class TestModel(BaseModel):
|
||||
test1: int
|
||||
test2: int
|
||||
@@ -53,7 +57,8 @@ def test_model_dump():
|
||||
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 = []
|
||||
|
||||
@custom_validation
|
||||
@@ -80,7 +85,8 @@ def test_custom_validation():
|
||||
assert called == [1, 2]
|
||||
|
||||
|
||||
def test_validate_json():
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_json():
|
||||
class TestModel(BaseModel):
|
||||
test1: int
|
||||
test2: str
|
||||
|
@@ -50,14 +50,16 @@ class ExampleWithoutDelimiter(Example):
|
||||
env_nested_delimiter = None
|
||||
|
||||
|
||||
def test_config_no_env():
|
||||
@pytest.mark.asyncio
|
||||
async def test_config_no_env():
|
||||
config = Example(_env_file=None)
|
||||
assert config.simple == ""
|
||||
with pytest.raises(AttributeError):
|
||||
config.common_config
|
||||
|
||||
|
||||
def test_config_with_env():
|
||||
@pytest.mark.asyncio
|
||||
async def test_config_with_env():
|
||||
config = Example(_env_file=(".env", ".env.example"))
|
||||
assert config.simple == "simple"
|
||||
|
||||
@@ -100,7 +102,8 @@ def test_config_with_env():
|
||||
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:
|
||||
m.setenv("COMPLEX", "not json")
|
||||
|
||||
@@ -108,7 +111,8 @@ def test_config_error_env():
|
||||
Example(_env_file=(".env", ".env.example"))
|
||||
|
||||
|
||||
def test_config_without_delimiter():
|
||||
@pytest.mark.asyncio
|
||||
async def test_config_without_delimiter():
|
||||
config = ExampleWithoutDelimiter()
|
||||
assert config.nested.a == 1
|
||||
assert config.nested.b == 0
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import json
|
||||
import asyncio
|
||||
from typing import Any, Optional
|
||||
from http.cookies import SimpleCookie
|
||||
|
||||
import anyio
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
@@ -25,7 +25,7 @@ from nonebot.drivers import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver", [pytest.param("nonebot.drivers.none:Driver", id="none")], indirect=True
|
||||
)
|
||||
@@ -59,22 +59,22 @@ async def test_lifespan(driver: Driver):
|
||||
|
||||
@driver.on_shutdown
|
||||
async def _shutdown1():
|
||||
assert shutdown_log == [2]
|
||||
assert shutdown_log == []
|
||||
shutdown_log.append(1)
|
||||
|
||||
@driver.on_shutdown
|
||||
async def _shutdown2():
|
||||
assert shutdown_log == []
|
||||
assert shutdown_log == [1]
|
||||
shutdown_log.append(2)
|
||||
|
||||
async with driver._lifespan:
|
||||
assert start_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(
|
||||
"driver",
|
||||
[
|
||||
@@ -99,10 +99,10 @@ async def test_http_server(app: App, driver: Driver):
|
||||
assert response.status_code == 200
|
||||
assert response.text == "test"
|
||||
|
||||
await anyio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
@@ -155,10 +155,10 @@ async def test_websocket_server(app: App, driver: Driver):
|
||||
|
||||
await ws.close(code=1000)
|
||||
|
||||
await anyio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
@@ -171,10 +171,9 @@ async def test_cross_context(app: App, driver: Driver):
|
||||
assert isinstance(driver, ASGIMixin)
|
||||
|
||||
ws: Optional[WebSocket] = None
|
||||
ws_ready = anyio.Event()
|
||||
ws_should_close = anyio.Event()
|
||||
ws_ready = asyncio.Event()
|
||||
ws_should_close = asyncio.Event()
|
||||
|
||||
# create a background task before the ws connection established
|
||||
async def background_task():
|
||||
try:
|
||||
await ws_ready.wait()
|
||||
@@ -186,6 +185,8 @@ async def test_cross_context(app: App, driver: Driver):
|
||||
finally:
|
||||
ws_should_close.set()
|
||||
|
||||
task = asyncio.create_task(background_task())
|
||||
|
||||
async def _handle_ws(websocket: WebSocket) -> None:
|
||||
nonlocal ws
|
||||
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)
|
||||
driver.setup_websocket_server(ws_setup)
|
||||
|
||||
async with anyio.create_task_group() as tg, app.test_server(driver.asgi) as ctx:
|
||||
tg.start_soon(background_task)
|
||||
|
||||
async with app.test_server(driver.asgi) as ctx:
|
||||
client = ctx.get_client()
|
||||
|
||||
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]):
|
||||
raise
|
||||
|
||||
await anyio.sleep(1)
|
||||
await task
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
@@ -304,10 +304,10 @@ async def test_http_client(driver: Driver, server_url: URL):
|
||||
"test3": "test",
|
||||
}, "file parsing error"
|
||||
|
||||
await anyio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
@@ -419,10 +419,10 @@ async def test_http_client_session(driver: Driver, server_url: URL):
|
||||
"test3": "test",
|
||||
}, "file parsing error"
|
||||
|
||||
await anyio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
@@ -452,9 +452,10 @@ async def test_websocket_client(driver: Driver, server_url: URL):
|
||||
with pytest.raises(WebSocketClosed, match=r"code=1000"):
|
||||
await ws.receive()
|
||||
|
||||
await anyio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("driver", "driver_type"),
|
||||
[
|
||||
@@ -471,11 +472,11 @@ async def test_websocket_client(driver: Driver, server_url: URL):
|
||||
],
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_connect_hook(app: App, driver: Driver):
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
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:
|
||||
bot = ctx.create_bot()
|
||||
|
||||
await anyio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
if not conn_should_be_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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_echo(app: App):
|
||||
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
|
||||
assert env == "test"
|
||||
|
||||
@@ -34,28 +35,31 @@ def test_init():
|
||||
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:
|
||||
m.setattr(nonebot, "_driver", None)
|
||||
with pytest.raises(ValueError, match="initialized"):
|
||||
get_driver()
|
||||
|
||||
|
||||
def test_get_asgi():
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_asgi(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
driver = get_driver()
|
||||
assert isinstance(driver, ReverseDriver)
|
||||
assert isinstance(driver, ASGIMixin)
|
||||
assert get_asgi() == driver.asgi
|
||||
|
||||
|
||||
def test_get_app():
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_app(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
driver = get_driver()
|
||||
assert isinstance(driver, ReverseDriver)
|
||||
assert isinstance(driver, ASGIMixin)
|
||||
assert get_app() == driver.server_app
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_adapter(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
async with app.test_api() as ctx:
|
||||
adapter = ctx.create_adapter()
|
||||
@@ -70,7 +74,8 @@ async def test_get_adapter(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
get_adapter("not exist")
|
||||
|
||||
|
||||
def test_run(monkeypatch: pytest.MonkeyPatch):
|
||||
@pytest.mark.asyncio
|
||||
async def test_run(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
runned = False
|
||||
|
||||
def mock_run(*args, **kwargs):
|
||||
@@ -88,7 +93,8 @@ def test_run(monkeypatch: pytest.MonkeyPatch):
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
|
||||
def test_matcher_info(app: App):
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_info(app: App):
|
||||
from plugins.matcher.matcher_info import matcher
|
||||
|
||||
assert issubclass(matcher, Matcher)
|
||||
@@ -42,7 +43,7 @@ def test_matcher_info(app: App):
|
||||
assert matcher._source.lineno == 3
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_check(app: App):
|
||||
async def falsy():
|
||||
return False
|
||||
@@ -86,7 +87,7 @@ async def test_matcher_check(app: App):
|
||||
assert await _check_matcher(test_rule_error, bot, event, {}) is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_handle(app: App):
|
||||
from plugins.matcher.matcher_process import test_handle
|
||||
|
||||
@@ -101,7 +102,7 @@ async def test_matcher_handle(app: App):
|
||||
ctx.should_finished()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_got(app: App):
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_receive(app: App):
|
||||
from plugins.matcher.matcher_process import test_receive
|
||||
|
||||
@@ -140,7 +141,7 @@ async def test_matcher_receive(app: App):
|
||||
ctx.should_paused()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_combine(app: App):
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_preset(app: App):
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_overload(app: App):
|
||||
from plugins.matcher.matcher_process import test_overload
|
||||
|
||||
@@ -195,7 +196,7 @@ async def test_matcher_overload(app: App):
|
||||
ctx.should_finished()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_destroy(app: App):
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_type_updater(app: App):
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_permission_updater(app: App):
|
||||
from plugins.matcher.matcher_permission import (
|
||||
default_permission,
|
||||
@@ -251,7 +252,7 @@ async def test_default_permission_updater(app: App):
|
||||
assert checker.perm is default_permission
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_permission_updater(app: App):
|
||||
from plugins.matcher.matcher_permission import (
|
||||
default_permission,
|
||||
@@ -273,7 +274,7 @@ async def test_user_permission_updater(app: App):
|
||||
assert checker.perm is default_permission
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_custom_permission_updater(app: App):
|
||||
from plugins.matcher.matcher_permission import (
|
||||
new_permission,
|
||||
@@ -290,7 +291,7 @@ async def test_custom_permission_updater(app: App):
|
||||
assert new_perm is new_permission
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_run(app: App):
|
||||
with app.provider.context({}):
|
||||
assert not matchers
|
||||
@@ -321,12 +322,11 @@ async def test_run(app: App):
|
||||
assert len(matchers[0][0].handlers) == 0
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_temp(app: App):
|
||||
from plugins.matcher.matcher_expire import test_temp_matcher
|
||||
|
||||
event = make_fake_event(_type="test")()
|
||||
with app.provider.context({test_temp_matcher.priority: [test_temp_matcher]}):
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_temp_matcher in matchers[test_temp_matcher.priority]
|
||||
@@ -334,33 +334,25 @@ async def test_temp(app: App):
|
||||
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_datetime_expire(app: App):
|
||||
from plugins.matcher.matcher_expire import test_datetime_matcher
|
||||
|
||||
event = make_fake_event()()
|
||||
with app.provider.context(
|
||||
{test_datetime_matcher.priority: [test_datetime_matcher]}
|
||||
):
|
||||
async with app.test_matcher(test_datetime_matcher) as ctx:
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
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):
|
||||
from plugins.matcher.matcher_expire import test_timedelta_matcher
|
||||
|
||||
event = make_fake_event()()
|
||||
with app.provider.context(
|
||||
{test_timedelta_matcher.priority: [test_timedelta_matcher]}
|
||||
):
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
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]
|
||||
)
|
||||
assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
from nonebot.matcher import DEFAULT_PROVIDER_CLASS, matchers
|
||||
|
||||
|
||||
def test_manager(app: App):
|
||||
@pytest.mark.asyncio
|
||||
async def test_manager(app: App):
|
||||
try:
|
||||
default_provider = matchers.provider
|
||||
matchers.set_provider(DEFAULT_PROVIDER_CLASS)
|
||||
|
@@ -2,7 +2,6 @@ import re
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
from exceptiongroup import BaseExceptionGroup
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.dependencies import Dependent
|
||||
@@ -37,7 +36,7 @@ from nonebot.consts import (
|
||||
UNKNOWN_PARAM = "Unknown parameter"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_depend(app: App):
|
||||
from plugins.param.param_depend import (
|
||||
ClassDependency,
|
||||
@@ -51,8 +50,6 @@ async def test_depend(app: App):
|
||||
annotated_depend,
|
||||
sub_type_mismatch,
|
||||
validate_field_fail,
|
||||
cache_exception_func1,
|
||||
cache_exception_func2,
|
||||
annotated_class_depend,
|
||||
annotated_multi_depend,
|
||||
annotated_prior_depend,
|
||||
@@ -93,67 +90,36 @@ async def test_depend(app: App):
|
||||
|
||||
assert runned == [1, 1, 1]
|
||||
|
||||
runned.clear()
|
||||
|
||||
async with app.test_dependent(
|
||||
annotated_class_depend, allow_types=[DependParam]
|
||||
) as ctx:
|
||||
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(
|
||||
sub_type_mismatch, allow_types=[DependParam, BotParam]
|
||||
) as ctx:
|
||||
bot = ctx.create_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:
|
||||
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:
|
||||
...
|
||||
|
||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||
assert exc_info.group_contains(TypeMisMatch)
|
||||
|
||||
async with app.test_dependent(validate_field, allow_types=[DependParam]) as ctx:
|
||||
ctx.should_return(1)
|
||||
|
||||
with pytest.raises((TypeMisMatch, BaseExceptionGroup)) as exc_info:
|
||||
with pytest.raises(TypeMisMatch):
|
||||
async with app.test_dependent(
|
||||
validate_field_fail, allow_types=[DependParam]
|
||||
) as ctx:
|
||||
...
|
||||
|
||||
if isinstance(exc_info.value, BaseExceptionGroup):
|
||||
assert exc_info.group_contains(TypeMisMatch)
|
||||
|
||||
# test cache reuse when exception raised
|
||||
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
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot(app: App):
|
||||
from plugins.param.param_bot import (
|
||||
FooBot,
|
||||
@@ -191,14 +157,11 @@ async def test_bot(app: App):
|
||||
ctx.pass_params(bot=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:
|
||||
bot = ctx.create_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:
|
||||
bot = ctx.create_bot(base=FooBot)
|
||||
ctx.pass_params(bot=bot)
|
||||
@@ -218,7 +181,7 @@ async def test_bot(app: App):
|
||||
app.test_dependent(not_bot, allow_types=[BotParam])
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_event(app: App):
|
||||
from plugins.param.param_event import (
|
||||
FooEvent,
|
||||
@@ -260,13 +223,10 @@ async def test_event(app: App):
|
||||
ctx.pass_params(event=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:
|
||||
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:
|
||||
ctx.pass_params(event=fake_fooevent)
|
||||
ctx.should_return(fake_fooevent)
|
||||
@@ -307,7 +267,7 @@ async def test_event(app: App):
|
||||
ctx.should_return(fake_event.is_tome())
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_state(app: App):
|
||||
from plugins.param.param_state import (
|
||||
state,
|
||||
@@ -458,7 +418,7 @@ async def test_state(app: App):
|
||||
ctx.should_return(fake_state[KEYWORD_KEY])
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher(app: App):
|
||||
from plugins.param.param_matcher import (
|
||||
FooMatcher,
|
||||
@@ -497,13 +457,10 @@ async def test_matcher(app: App):
|
||||
ctx.pass_params(matcher=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:
|
||||
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:
|
||||
ctx.pass_params(matcher=foo_matcher)
|
||||
ctx.should_return(foo_matcher)
|
||||
@@ -539,7 +496,7 @@ async def test_matcher(app: App):
|
||||
ctx.should_return(event_next)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_arg(app: App):
|
||||
from plugins.param.param_arg import (
|
||||
arg,
|
||||
@@ -591,7 +548,7 @@ async def test_arg(app: App):
|
||||
ctx.should_return(message.extract_plain_text())
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_exception(app: App):
|
||||
from plugins.param.param_exception import exc, legacy_exc
|
||||
|
||||
@@ -605,7 +562,7 @@ async def test_exception(app: App):
|
||||
ctx.should_return(exception)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_default(app: App):
|
||||
from plugins.param.param_default import default
|
||||
|
||||
@@ -613,7 +570,8 @@ async def test_default(app: App):
|
||||
ctx.should_return(1)
|
||||
|
||||
|
||||
def test_priority():
|
||||
@pytest.mark.asyncio
|
||||
async def test_priority():
|
||||
from plugins.param.priority import complex_priority
|
||||
|
||||
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 falsy():
|
||||
return False
|
||||
@@ -54,7 +54,7 @@ async def test_permission(app: App):
|
||||
assert await Permission(truthy, skipped)(bot, event) is True
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", True), ("notice", False)])
|
||||
async def test_message(type: str, expected: bool):
|
||||
dependent = next(iter(MESSAGE.checkers))
|
||||
@@ -66,7 +66,7 @@ async def test_message(type: str, expected: bool):
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("notice", True)])
|
||||
async def test_notice(type: str, expected: bool):
|
||||
dependent = next(iter(NOTICE.checkers))
|
||||
@@ -78,7 +78,7 @@ async def test_notice(type: str, expected: bool):
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("request", True)])
|
||||
async def test_request(type: str, expected: bool):
|
||||
dependent = next(iter(REQUEST.checkers))
|
||||
@@ -90,7 +90,7 @@ async def test_request(type: str, expected: bool):
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("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
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("session_ids", "session_id", "expected"),
|
||||
[
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
import nonebot
|
||||
from nonebot.plugin import PluginManager, _managers
|
||||
|
||||
|
||||
def test_get_plugin():
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_plugin():
|
||||
# check simple plugin
|
||||
plugin = nonebot.get_plugin("export")
|
||||
assert plugin
|
||||
@@ -20,7 +22,8 @@ def test_get_plugin():
|
||||
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
|
||||
plugin = nonebot.get_plugin_by_module_name("plugins.nested")
|
||||
assert plugin
|
||||
@@ -45,7 +48,8 @@ def test_get_plugin_by_module_name():
|
||||
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()
|
||||
_managers.clear()
|
||||
try:
|
||||
@@ -59,7 +63,8 @@ def test_get_available_plugin():
|
||||
_managers.extend(old_managers)
|
||||
|
||||
|
||||
def test_get_plugin_config():
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_plugin_config():
|
||||
class Config(BaseModel):
|
||||
plugin_config: int
|
||||
|
||||
|
@@ -1,44 +1,15 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from functools import wraps
|
||||
from dataclasses import asdict
|
||||
from typing import TypeVar, Callable
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
import pytest
|
||||
|
||||
import nonebot
|
||||
from nonebot.plugin import (
|
||||
Plugin,
|
||||
PluginManager,
|
||||
_plugins,
|
||||
_managers,
|
||||
inherit_supported_adapters,
|
||||
)
|
||||
|
||||
P = ParamSpec("P")
|
||||
R = TypeVar("R")
|
||||
from nonebot.plugin import Plugin, PluginManager, _managers, inherit_supported_adapters
|
||||
|
||||
|
||||
def _recover(func: Callable[P, R]) -> Callable[P, R]:
|
||||
|
||||
@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():
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugin():
|
||||
# check regular
|
||||
assert nonebot.load_plugin("dynamic.simple")
|
||||
|
||||
@@ -49,7 +20,8 @@ def test_load_plugin():
|
||||
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 = {
|
||||
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()
|
||||
|
||||
|
||||
def test_load_nested_plugin():
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nested_plugin():
|
||||
parent_plugin = nonebot.get_plugin("nested")
|
||||
sub_plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||
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}
|
||||
|
||||
|
||||
@_recover
|
||||
def test_load_json():
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_json():
|
||||
nonebot.load_from_json("./plugins.json")
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
nonebot.load_from_json("./plugins.invalid.json")
|
||||
|
||||
|
||||
@_recover
|
||||
def test_load_toml():
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_toml():
|
||||
nonebot.load_from_toml("./plugins.toml")
|
||||
|
||||
with pytest.raises(ValueError, match="Cannot find"):
|
||||
@@ -103,20 +76,19 @@ def test_load_toml():
|
||||
nonebot.load_from_toml("./plugins.invalid.toml")
|
||||
|
||||
|
||||
@_recover
|
||||
def test_bad_plugin():
|
||||
@pytest.mark.asyncio
|
||||
async def test_bad_plugin():
|
||||
nonebot.load_plugins("bad_plugins")
|
||||
|
||||
assert nonebot.get_plugin("bad_plugin") is None
|
||||
|
||||
|
||||
@_recover
|
||||
def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
def _patched_find(name: str):
|
||||
pytest.fail("require existing plugin should not call find_manager_by_name")
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
||||
monkeypatch.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
||||
|
||||
# require use module name
|
||||
nonebot.require("plugins.export")
|
||||
@@ -125,20 +97,19 @@ def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
nonebot.require("nested:nested_subplugin")
|
||||
|
||||
|
||||
@_recover
|
||||
def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
pm = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
|
||||
_managers.append(pm)
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
m = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
|
||||
_managers.append(m)
|
||||
num_managers = len(_managers)
|
||||
|
||||
origin_load = PluginManager.load_plugin
|
||||
|
||||
def _patched_load(self: PluginManager, name: str):
|
||||
assert self is pm
|
||||
assert self is m
|
||||
return origin_load(self, name)
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(PluginManager, "load_plugin", _patched_load)
|
||||
monkeypatch.setattr(PluginManager, "load_plugin", _patched_load)
|
||||
|
||||
# require standalone plugin
|
||||
nonebot.require("dynamic.require_not_loaded")
|
||||
@@ -149,8 +120,8 @@ def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
assert len(_managers) == num_managers
|
||||
|
||||
|
||||
@_recover
|
||||
def test_require_not_declared():
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_not_declared():
|
||||
num_managers = len(_managers)
|
||||
|
||||
nonebot.require("dynamic.require_not_declared")
|
||||
@@ -159,13 +130,14 @@ def test_require_not_declared():
|
||||
assert _managers[-1].plugins == {"dynamic.require_not_declared"}
|
||||
|
||||
|
||||
@_recover
|
||||
def test_require_not_found():
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_not_found():
|
||||
with pytest.raises(RuntimeError):
|
||||
nonebot.require("some_plugin_not_exist")
|
||||
|
||||
|
||||
def test_plugin_metadata():
|
||||
@pytest.mark.asyncio
|
||||
async def test_plugin_metadata():
|
||||
from plugins.metadata import Config, FakeAdapter
|
||||
|
||||
plugin = nonebot.get_plugin("metadata")
|
||||
@@ -185,7 +157,8 @@ def test_plugin_metadata():
|
||||
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):
|
||||
inherit_supported_adapters("some_plugin_not_exist")
|
||||
|
||||
@@ -193,6 +166,7 @@ def test_inherit_supported_adapters_not_found():
|
||||
inherit_supported_adapters("export")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("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]
|
||||
):
|
||||
assert inherit_supported_adapters(*inherit_plugins) == expected
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import pytest
|
||||
|
||||
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"])
|
||||
try:
|
||||
_managers.append(m)
|
||||
|
||||
# load by plugin id
|
||||
@@ -13,5 +15,3 @@ def test_load_plugin_name():
|
||||
assert module1
|
||||
assert module2
|
||||
assert module1 is module2
|
||||
finally:
|
||||
_managers.remove(m)
|
||||
|
@@ -18,6 +18,7 @@ from nonebot.rule import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("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),
|
||||
],
|
||||
)
|
||||
def test_on(
|
||||
async def test_on(
|
||||
matcher_name: str,
|
||||
pre_rule_factory: Optional[Callable[[type[Event]], T_RuleChecker]],
|
||||
has_permission: bool,
|
||||
@@ -149,7 +150,8 @@ def test_on(
|
||||
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
|
||||
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 falsy():
|
||||
return False
|
||||
@@ -81,7 +81,7 @@ async def test_rule(app: App):
|
||||
assert await Rule(truthy, skipped)(bot, event, {}) is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_trie(app: App):
|
||||
TrieRule.add_prefix("/fake-prefix", TRIE_VALUE("/", ("fake-prefix",)))
|
||||
|
||||
@@ -146,7 +146,7 @@ async def test_trie(app: App):
|
||||
del TrieRule.prefix["/fake-prefix"]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("msg", "ignorecase", "type", "text", "expected"),
|
||||
[
|
||||
@@ -186,7 +186,7 @@ async def test_startswith(
|
||||
assert await dependent(event=event, state=state) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("msg", "ignorecase", "type", "text", "expected"),
|
||||
[
|
||||
@@ -226,7 +226,7 @@ async def test_endswith(
|
||||
assert await dependent(event=event, state=state) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("msg", "ignorecase", "type", "text", "expected"),
|
||||
[
|
||||
@@ -266,7 +266,7 @@ async def test_fullmatch(
|
||||
assert await dependent(event=event, state=state) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("kws", "type", "text", "expected"),
|
||||
[
|
||||
@@ -298,7 +298,7 @@ async def test_keyword(
|
||||
assert await dependent(event=event, state=state) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("cmds", "force_whitespace", "cmd", "whitespace", "arg_text", "expected"),
|
||||
[
|
||||
@@ -344,7 +344,7 @@ async def test_command(
|
||||
assert await dependent(state=state) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_shell_command():
|
||||
state: T_State
|
||||
CMD = ("test",)
|
||||
@@ -451,7 +451,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS].status != 0
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("pattern", "type", "text", "expected", "matched"),
|
||||
[
|
||||
@@ -494,7 +494,7 @@ async def test_regex(
|
||||
assert result.span() == matched.span()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("expected", [True, False])
|
||||
async def test_to_me(expected: bool):
|
||||
test_to_me = to_me()
|
||||
@@ -507,7 +507,7 @@ async def test_to_me(expected: bool):
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_type():
|
||||
Event1 = make_fake_event()
|
||||
Event2 = make_fake_event()
|
||||
|
@@ -5,7 +5,7 @@ import pytest
|
||||
from utils import make_fake_event
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_mutex():
|
||||
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` 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。
|
||||
|
@@ -82,16 +82,14 @@ async def do_something(bot: Bot):
|
||||
|
||||
### 事件预处理
|
||||
|
||||
这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。在这个钩子函数内抛出 `nonebot.exception.IgnoredException` 会使 NoneBot 忽略该事件。
|
||||
这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。
|
||||
|
||||
```python
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.message import event_preprocessor
|
||||
|
||||
@event_preprocessor
|
||||
async def do_something(event: Event):
|
||||
if not event.is_tome():
|
||||
raise IgnoredException("some reason")
|
||||
pass
|
||||
```
|
||||
|
||||
### 事件后处理
|
||||
@@ -108,16 +106,14 @@ async def do_something(event: Event):
|
||||
|
||||
### 运行预处理
|
||||
|
||||
这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。在这个钩子函数内抛出 `nonebot.exception.IgnoredException` 也会使 NoneBot 忽略本次运行。
|
||||
这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。
|
||||
|
||||
```python
|
||||
from nonebot.message import run_preprocessor
|
||||
from nonebot.exception import IgnoredException
|
||||
|
||||
@run_preprocessor
|
||||
async def do_something(event: Event, matcher: Matcher):
|
||||
if not event.is_tome():
|
||||
raise IgnoredException("some reason")
|
||||
pass
|
||||
```
|
||||
|
||||
### 运行后处理
|
||||
|
@@ -166,7 +166,7 @@ COMMON_CONFIG=common config # 这个配置项在任何环境中都会被加载
|
||||
在生产环境中,可以通过设置环境变量 `ENVIRONMENT=prod` 来确保 NoneBot 读取正确的环境配置。
|
||||
:::
|
||||
|
||||
#### .env.\{ENVIRONMENT\} 文件
|
||||
#### .env.{ENVIRONMENT} 文件
|
||||
|
||||
`.env.{ENVIRONMENT}` 文件类似于预设,可以让我们在多套不同的配置方案中灵活切换,默认 NoneBot 会读取 `.env.prod` 配置。如果你使用了 `nb-cli` 创建 `simple` 项目,那么将含有两套预设配置:`.env.dev` 和 `.env.prod`。
|
||||
|
||||
@@ -178,7 +178,7 @@ nonebot.init(_env_file=".env.dev")
|
||||
|
||||
这将忽略在 `.env` 文件或环境变量中指定的 `ENVIRONMENT` 配置项。
|
||||
|
||||
## 读取全局配置项
|
||||
## 读取配置项
|
||||
|
||||
NoneBot 的全局配置对象可以通过 `driver` 获取,如:
|
||||
|
||||
@@ -198,7 +198,7 @@ superusers = config.superusers
|
||||
|
||||
## 插件配置
|
||||
|
||||
在一个涉及大量配置项的项目中,通过直接读取全局配置项的方式显然并不高效。同时,由于额外的全局配置项没有预先定义,开发时编辑器将无法提示字段与类型,并且运行时没有对配置项直接进行合法性检查。那么就需要一种方式来规范定义插件配置项。
|
||||
在一个涉及大量配置项的项目中,通过直接读取配置项的方式显然并不高效。同时,由于额外的全局配置项没有预先定义,开发时编辑器将无法提示字段与类型,并且运行时没有对配置项直接进行合法性检查。那么就需要一种方式来规范定义插件配置项。
|
||||
|
||||
在 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/)。
|
||||
|
||||
在定义好配置模型后,我们可以在插件加载时通过配置模型获取插件配置:
|
||||
在定义好配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型并使用:
|
||||
|
||||
```python {5,11} title=weather/__init__.py
|
||||
from nonebot import get_plugin_config
|
||||
|
@@ -29,9 +29,8 @@ import Messenger from "@site/src/components/Messenger";
|
||||
|
||||
例如,我们可以在 `weather` 插件中添加一个超级用户可用的指令:
|
||||
|
||||
```python {3,9} title=weather/__init__.py
|
||||
```python {2,8} title=weather/__init__.py
|
||||
from typing import Tuple
|
||||
from nonebot.params import Command
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
manage = on_command(
|
||||
|
@@ -20,11 +20,7 @@ options:
|
||||
|
||||
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
||||
|
||||
```python {7,8} title=weather/__init__.py
|
||||
from nonebot import get_plugin_config
|
||||
|
||||
from .config import Config
|
||||
|
||||
```python {3,4} title=weather/__init__.py
|
||||
plugin_config = get_plugin_config(Config)
|
||||
|
||||
async def is_enable() -> bool:
|
||||
@@ -58,11 +54,8 @@ weather = on_command("天气", rule=rule)
|
||||
|
||||
在定义响应规则时,我们可以将规则进行细分,来更好地复用规则。而在使用时,我们需要合并多个规则。除了使用 `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 import get_plugin_config
|
||||
|
||||
from .config import Config
|
||||
|
||||
plugin_config = get_plugin_config(Config)
|
||||
|
||||
@@ -73,7 +66,7 @@ weather = on_command(
|
||||
"天气",
|
||||
rule=to_me() & is_enable,
|
||||
aliases={"weather", "查天气"},
|
||||
priority=plugin_config.weather_command_priority,
|
||||
priority=plugin_config.weather_command_priority
|
||||
block=True,
|
||||
)
|
||||
```
|
||||
|
@@ -71,14 +71,14 @@ alc = Alconna(".rd{roll:int}")
|
||||
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>.+)"`
|
||||
- `"{:\d+}"` ⇔ `"(\d+)"`
|
||||
- `"{foo:int}"` ⇔ `"(?P<foo>\d+)"`,其中 `"int"` 部分若能转为 `BasePattern` 则读取里面的表达式
|
||||
- "{}", "{:}" ⇔ "(.+)", 占位符
|
||||
- "{foo}" ⇔ "(?P<foo>.+)"
|
||||
- "{:\d+}" ⇔ "(\d+)"
|
||||
- "{foo:int}" ⇔ "(?P<foo>\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
|
||||
|
||||
## 参数声明(Args)
|
||||
|
||||
@@ -321,7 +321,7 @@ opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
||||
- `keep_crlf`: 命令解析时是否保留换行字符
|
||||
- `compact`: 命令是否允许第一个参数紧随头部
|
||||
- `strict`: 命令是否严格匹配,若为 False 则未知参数将作为名为 $extra 的参数
|
||||
- `context_style`: 命令上下文插值的风格,None 为关闭,bracket 为 `{...}`,parentheses 为 `$(...)`
|
||||
- `context_style`: 命令上下文插值的风格,None 为关闭,bracket 为 {...},parentheses 为 $(...)
|
||||
- `extra`: 命令的自定义额外信息
|
||||
|
||||
元数据一定使用 `meta=...` 形式传入:
|
||||
|
@@ -96,7 +96,7 @@ class Other(Segment):
|
||||
|
||||
```
|
||||
|
||||
:::tip
|
||||
:::tips
|
||||
|
||||
或许你注意到了 `Segment` 上有一个 `children` 属性。
|
||||
|
||||
@@ -291,7 +291,7 @@ msg.extend([Text("text")])
|
||||
|
||||
这里额外说明 `UniMessage.template` 的拓展控制符
|
||||
|
||||
相比 `Message`,UniMessage 对于 `{:XXX}` 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
|
||||
相比 `Message`,UniMessage 对于 {:XXX} 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
|
||||
|
||||
以 At(...) 为例:
|
||||
|
||||
@@ -305,7 +305,7 @@ UniMessage(At("user", "123"))
|
||||
UniMessage(At("user", "123"))
|
||||
```
|
||||
|
||||
而在 `AlconnaMatcher` 中,`{:XXX}` 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
|
||||
而在 `AlconnaMatcher` 中,{:XXX} 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
|
||||
|
||||
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
|
||||
from arclet.alconna import Alconna, Args
|
||||
|
@@ -74,17 +74,7 @@ pip install pytest-asyncio
|
||||
|
||||
## 配置测试
|
||||
|
||||
在开始测试之前,我们需要对测试进行一些配置,以正确启动我们的机器人。
|
||||
|
||||
首先我们需要配置 pytest-asyncio,在 `pyproject.toml` 的 pytest 配置部分添加:
|
||||
|
||||
```toml
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
asyncio_default_fixture_loop_scope = "session"
|
||||
```
|
||||
|
||||
然后,我们在 `tests` 目录下新建 `conftest.py` 文件,添加以下内容:
|
||||
在开始测试之前,我们需要对测试进行一些配置,以正确启动我们的机器人。在 `tests` 目录下新建 `conftest.py` 文件,添加以下内容:
|
||||
|
||||
```python title=tests/conftest.py
|
||||
import pytest
|
||||
@@ -93,7 +83,7 @@ import nonebot
|
||||
from nonebot.adapters.console import Adapter as ConsoleAdapter
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
async def after_nonebot_init(after_nonebot_init: None):
|
||||
def load_bot():
|
||||
# 加载适配器
|
||||
driver = nonebot.get_driver()
|
||||
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` 并从环境变量中输入配置:
|
||||
|
||||
```python {4,6,8-10} title=tests/conftest.py
|
||||
```python {3,5,7-9} title=tests/conftest.py
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from nonebug import NONEBOT_INIT_KWARGS
|
||||
|
||||
os.environ["ENVIRONMENT"] = "test"
|
||||
@@ -116,16 +105,6 @@ def pytest_configure(config: pytest.Config):
|
||||
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)中编写的天气插件。首先,我们先要导入我们需要的模块:
|
||||
|
@@ -1,15 +1,13 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: 开源软件供应链点亮计划 - 暑期 2021
|
||||
mdx:
|
||||
format: md
|
||||
---
|
||||
|
||||
# 暑期 2021
|
||||
|
||||
**开源软件供应链点亮计划 - 暑期 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
|
||||
|
||||
|
@@ -1,15 +1,13 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: 开源之夏 - 暑期 2022
|
||||
mdx:
|
||||
format: md
|
||||
---
|
||||
|
||||
# 暑期 2022
|
||||
|
||||
**开源之夏 - 暑期 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 交互体验升级
|
||||
|
||||
|
@@ -1,15 +1,13 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: 开源之夏 - 暑期 2023
|
||||
mdx:
|
||||
format: md
|
||||
---
|
||||
|
||||
# 暑期 2023
|
||||
|
||||
**开源之夏 - 暑期 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 项目管理图形化面板
|
||||
|
||||
|
@@ -1,15 +1,13 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
description: 开源之夏 - 暑期 2024
|
||||
mdx:
|
||||
format: md
|
||||
---
|
||||
|
||||
# 暑期 2024
|
||||
|
||||
**开源之夏 - 暑期 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 官网组件库更新与优化
|
||||
|
||||
|
@@ -107,4 +107,4 @@ if __name__ == "__main__":
|
||||
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`、`查天气` 三个命令的响应规则,需要私聊或 `@bot` 时才会响应,优先级为 10(越小越优先),阻断事件向后续优先级传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。
|
||||
这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令,需要私聊或 `@bot` 时才会响应,优先级为 10 ,阻断事件传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。
|
||||
|
||||
:::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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.5.2",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"@nullbot/docusaurus-plugin-changelog": "^3.0.0",
|
||||
"@nullbot/docusaurus-preset-nonepress": "^3.0.0",
|
||||
"@swc/core": "^1.7.26",
|
||||
"clsx": "^2.0.0",
|
||||
"copy-text-to-clipboard": "^3.2.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"@docusaurus/core": "^2.4.1",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@nullbot/docusaurus-preset-nonepress": "^2.1.2",
|
||||
"clsx": "^1.2.1",
|
||||
"copy-text-to-clipboard": "^3.0.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.0.0",
|
||||
"react": "^17.0.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-use-pagination": "^2.0.1",
|
||||
"swc-loader": "^0.2.6"
|
||||
"react-dom": "^17.0.1",
|
||||
"react-use-pagination": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.5.2",
|
||||
"@nullbot/docusaurus-tsconfig": "^3.0.0",
|
||||
"@docusaurus/module-type-aliases": "^2.4.1",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"@types/react-color": "^3.0.10",
|
||||
"asciinema-player": "^3.5.0",
|
||||
"typescript": "~5.5.2"
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -57,6 +54,6 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
"node": ">=16.14"
|
||||
}
|
||||
}
|
||||
|
@@ -8,15 +8,11 @@
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
import path from "path";
|
||||
|
||||
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
|
||||
import { getChangelogItemsSync } from "@nullbot/docusaurus-plugin-changelog";
|
||||
// @ts-check
|
||||
|
||||
const changelogPath = path.join(__dirname, "src/changelog/changelog.md");
|
||||
const changelogItems = getChangelogItemsSync(changelogPath, 10);
|
||||
|
||||
const sidebars: SidebarsConfig = {
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
tutorial: [
|
||||
{
|
||||
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 { useDocsVersionCandidates } from "@docusaurus/plugin-content-docs/client";
|
||||
import { PageMetadata } from "@docusaurus/theme-common";
|
||||
import { useDocsVersionCandidates } from "@docusaurus/theme-common/internal";
|
||||
import { useVersionedSidebar } from "@nullbot/docusaurus-plugin-getsidebar/client";
|
||||
import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts";
|
||||
|
||||
@@ -25,7 +25,7 @@ function StorePage({ title, children }: Props): JSX.Element {
|
||||
)!;
|
||||
|
||||
return (
|
||||
<Page hideTableOfContents reduceContentWidth={false} sidebarId={SIDEBAR_ID}>
|
||||
<Page hideTableOfContents reduceContentWidth={false}>
|
||||
<SidebarContentFiller items={sidebarItems} />
|
||||
<article className="prose max-w-full">
|
||||
<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: 聊天截图伪造 [@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))
|
@@ -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 {
|
||||
name: "webpack-plugin",
|
||||
configureWebpack() {
|
||||
@@ -15,4 +18,6 @@ export default (function webpackPlugin() {
|
||||
};
|
||||
},
|
||||
};
|
||||
} satisfies PluginConfig);
|
||||
}
|
||||
|
||||
module.exports = webpackPlugin;
|
@@ -1,26 +1,24 @@
|
||||
import typography from "@tailwindcss/typography";
|
||||
import daisyui from "daisyui";
|
||||
import themes from "daisyui/src/theming/themes";
|
||||
const lightTheme = require("daisyui/src/theming/themes")["[data-theme=light]"];
|
||||
const darkTheme = require("daisyui/src/theming/themes")["[data-theme=dark]"];
|
||||
|
||||
const lightTheme = themes.light;
|
||||
const darkTheme = themes.dark;
|
||||
|
||||
function excludeThemeColor(
|
||||
theme: { [key: string]: string },
|
||||
exclude: string[]
|
||||
): { [key: string]: string } {
|
||||
const newObj: { [key: string]: string } = {};
|
||||
/**
|
||||
* @param {{[key: string]: string}} theme
|
||||
* @param {string[]} exclude
|
||||
* @returns {{[key: string]: string}}
|
||||
*/
|
||||
function excludeThemeColor(theme, exclude) {
|
||||
/** @type {typeof theme} */
|
||||
const newObj = {};
|
||||
for (const key in theme) {
|
||||
if (exclude.includes(key)) continue;
|
||||
newObj[key] = theme[key]!;
|
||||
newObj[key] = theme[key];
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
export default {
|
||||
plugins: [typography, daisyui],
|
||||
module.exports = {
|
||||
darkMode: ["class", '[data-theme="dark"]'],
|
||||
daisyui: {
|
||||
base: false,
|
||||
themes: [
|
||||
{
|
||||
light: {
|
||||
@@ -30,7 +28,6 @@ export default {
|
||||
"accent-content",
|
||||
]),
|
||||
primary: "#ea5252",
|
||||
"primary-content": "#ffffff",
|
||||
secondary: "#ef9fbc",
|
||||
accent: "#65c3c8",
|
||||
},
|
||||
@@ -43,13 +40,10 @@ export default {
|
||||
"accent-content",
|
||||
]),
|
||||
primary: "#ea5252",
|
||||
"primary-content": "#ffffff",
|
||||
secondary: "#ef9fbc",
|
||||
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.
|
||||
"extends": "@nullbot/docusaurus-tsconfig",
|
||||
"extends": "@tsconfig/docusaurus/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"node",
|
||||
"@docusaurus/module-type-aliases",
|
||||
"@nullbot/docusaurus-theme-nonepress"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@theme/*": ["./src/theme/*"]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"allowArbitraryExtensions": true,
|
||||
|
||||
// Duplicated from the root config, because TS does not support extending
|
||||
// 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 Type-Checking Options */
|
||||
"strict": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"importsNotUsedAsValues": "remove",
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
|
||||
// This is important. We run `yarn tsc` in website so we can catch issues
|
||||
// with our declaration files (mostly names that are forgotten to be
|
||||
// imported, invalid semantics...). Because we don't have end-to-end type
|
||||
// tests, removing this would make things much harder to catch.
|
||||
"skipLibCheck": false
|
||||
/* Disabled on purpose (handled by ESLint, should not block compilation) */
|
||||
"noUnusedParameters": false,
|
||||
|
||||
/* Advanced Options */
|
||||
"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