Skip to content

Commit 62201dc

Browse files
committed
feat(webui): 社区插件商店与 bundled auto 加载
- 社区索引远程拉取、git 安装 API 与 registry - load_bundled_extra_plugins 默认 auto,local/plugins 自动纳入 - 文档与内置索引兜底配置
1 parent 568f1e3 commit 62201dc

22 files changed

Lines changed: 952 additions & 47 deletions
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"version": 1,
3+
"updated_at": "2026-06-17",
4+
"description": "本地覆盖示例。日常策展在 https://github.com/PallasBot/community-plugin-index ;复制到 data/pallas_config/community_plugin_index.json 可离线覆盖。",
5+
"plugins": [
6+
{
7+
"id": "my_plugin",
8+
"name": "示例社区插件",
9+
"description": "NoneBot 插件目录,含 __init__.py 与 __plugin_meta__。",
10+
"repository": "https://github.com/ORG/REPO.git",
11+
"ref": "main",
12+
"author": "作者",
13+
"tags": ["示例"],
14+
"min_pallas_version": "4.0.0"
15+
}
16+
]
17+
}

config/community_plugin_index.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"version": 1,
3+
"updated_at": "2026-06-17",
4+
"description": "内置兜底索引(通常为空)。日常策展见 https://github.com/PallasBot/community-plugin-index",
5+
"plugins": []
6+
}

config/pallas.example.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ db = "PallasBot"
1818
# ── 按需取消注释 ────────────────────────────────────────────────────
1919
# log_level = "INFO"
2020
# access_token = "" # 公网暴露 HTTP 时建议设置
21-
# extra_plugin_dirs = ["local/plugins"]
22-
# load_bundled_extra_plugins = false
21+
# extra_plugin_dirs = ["local/plugins"] # 未配置时,local/plugins 有有效插件包会自动纳入
22+
# load_bundled_extra_plugins = "auto" # true | false | auto(默认 auto:pip 未装时用仓库内副本)
2323

2424
# [bootstrap.postgres]
2525
# host = "127.0.0.1"

docs/architecture/site-customization-and-updates.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
## 站点自有插件
1818

1919
1. 在仓库根创建 `local/plugins/<插件名>/__init__.py`(标准 NoneBot 插件结构)。
20-
2.**`config/pallas.toml`**(非 `pyproject.toml`)启用:
20+
2.**`config/pallas.toml`**(非 `pyproject.toml`)启用(推荐;`local/plugins` 下已有插件包时未配置也会自动加载)
2121

2222
```toml
2323
[bootstrap]
@@ -28,11 +28,7 @@ extra_plugin_dirs = ["local/plugins"]
2828
- **hub / worker / unified** 均会加载 `extra_plugin_dirs`;与 `src/plugins/` 或 hub 内置模块**同名时优先 local**(如 `help``callback`)。
2929
4. **覆盖主仓同名插件**:若 `local/plugins/draw/``src/plugins/draw/` 同名,会**先加载 local**,再跳过 `src` 中同名项。适合「整包 fork 式定制」;只改少量核心文件见下文「改动主仓已有插件」。
3030

31-
4.0 起官方玩法将迁入独立扩展仓,站点仍可通过 `extra_plugin_dirs` 或包管理器安装;见 [Pallas 4.0 路线图](architecture/pallas-4.0-roadmap.md)
32-
33-
也可在 `pyproject.toml``[tool.nonebot] plugin_dirs` 追加目录,但改 `pyproject.toml` 本身会被 git 跟踪;**推荐只用 `pallas.toml`**
34-
35-
4.0 起另支持 **WebUI 官方插件商店一键安装**(与本文手工投放并存;同名时仍以 local 为准)。见 [4.0 本体瘦身](pallas-4.0-slim.md#扩展安装路径并存)
31+
4.0 起另支持 **WebUI 社区插件商店**(git 安装到 `local/plugins/`)与 **官方扩展商店**(pip)。见 [社区插件商店](../guide/community-plugin-store.md)[4.0 本体瘦身](pallas-4.0-slim.md#扩展安装路径并存)
3632

3733
## 部署形态与更新方式
3834

@@ -68,6 +64,6 @@ NoneBot **不能**两个目录各加载一半同名插件;要么整包 overrid
6864

6965
## 相关实现
7066

71-
- `read_bootstrap_extra_plugin_dirs()`:`src/foundation/config/repo_settings.py`
67+
- `resolve_extra_plugin_dirs()` / `read_bootstrap_extra_plugin_dirs()`:`src/foundation/config/repo_settings.py`
7268
- 插件加载:`src/bot_runtime/plugin_loader.py`
7369
- 更新与部署检测:`src/plugins/pb_webui/manager.py`(`apply_bot_repository_update` / `inspect_bot_deployment`)

docs/guide/4.0-start.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ uv run pallas ext list
5959
```toml
6060
# config/pallas.toml
6161
[bootstrap]
62-
load_bundled_extra_plugins = true
62+
load_bundled_extra_plugins = true # 强制始终用仓库内副本
6363
```
6464

65-
生产环境请用 pip 扩展,不要长期依赖 `load_bundled_extra_plugins`
65+
生产默认 **`auto`**(可省略配置):pip 已装则用 pip,否则用镜像/仓库内副本。长期开发可设 `true`;严格 slim 设 `false`
6666

6767
---
6868

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# 社区插件商店
2+
3+
Pallas 4.0 起,控制台 **插件商店 → 社区插件** 可从策展索引浏览第三方插件,并通过 git 安装到 `local/plugins/<id>/`
4+
5+
**官方扩展**(pip / `uv sync --extra`)并存;**同名时 `local/plugins` 优先**
6+
7+
---
8+
9+
## 索引从哪里来
10+
11+
Bot **不会**让每个站点维护者手改 `config/community_plugin_index.json` 来上架插件。日常策展在独立索引仓维护:
12+
13+
| 仓库 | 作用 |
14+
| --- | --- |
15+
| [**PallasBot/community-plugin-index**](https://github.com/PallasBot/community-plugin-index) | 官方策展 JSON(`index.json`|
16+
| 各插件作者仓库 | 实际 NoneBot 插件代码 |
17+
18+
**加载顺序**(后者为兜底):
19+
20+
1. 环境变量 **`COMMUNITY_PLUGIN_INDEX_URL`**(若设置,覆盖默认远程)
21+
2. 默认远程:`https://raw.githubusercontent.com/PallasBot/community-plugin-index/main/index.json`
22+
3. `data/pallas_config/community_plugin_index.json`(站点本地覆盖)
23+
4. `config/community_plugin_index.json`(主仓内置,通常为空)
24+
25+
远程拉取失败时会 **自动回退** 本地文件,离线/Docker 内网仍可用手写索引。
26+
27+
### 私有索引
28+
29+
自建索引仓时,在 `config/pallas.toml``[env]` 写入 raw JSON 地址:
30+
31+
```toml
32+
[env]
33+
COMMUNITY_PLUGIN_INDEX_URL = "https://example.com/my-index.json"
34+
```
35+
36+
---
37+
38+
## WebUI 安装(推荐)
39+
40+
**条件**:运行环境可执行 `git`(WebUI 会 `git clone`)。
41+
42+
1. 打开 `/pallas/`**插件商店****社区插件**
43+
2. 选择条目 → **安装**(或 **安装并重启**
44+
3. 重启 Bot 后,在 **插件目录** 确认已加载
45+
46+
安装目标路径:`local/plugins/<插件 ID>/`
47+
48+
### `extra_plugin_dirs`
49+
50+
推荐在 `config/pallas.toml` 显式配置:
51+
52+
```toml
53+
[bootstrap]
54+
extra_plugin_dirs = ["local/plugins"]
55+
```
56+
57+
自 4.0 起,若 **未配置**`local/plugins/` 下已有有效插件包(含 `__init__.py`),启动时也会 **自动纳入** 加载链;仍建议写上配置,便于文档与排障。
58+
59+
Docker 挂载示例见 [站点定制 · Docker](../architecture/site-customization-and-updates.md#docker--外挂插件卷)
60+
61+
---
62+
63+
## 手动投放(不经过商店)
64+
65+
与商店安装结果相同:把 NoneBot 插件目录放到 `local/plugins/<名>/`,配置 `extra_plugin_dirs`(或依赖自动检测),重启 Bot。
66+
67+
适合:无法访问 git、或插件不在公共索引中。
68+
69+
---
70+
71+
## 收录第三方插件
72+
73+
[**community-plugin-index**](https://github.com/PallasBot/community-plugin-index) 提交 PR,在 `index.json` 追加条目。要求见该仓 README(开源、唯一 id、标准 NoneBot 结构等)。
74+
75+
索引 **只存元数据**,不托管插件源码。
76+
77+
---
78+
79+
## 与官方扩展、`auto` bundled 的关系
80+
81+
| 类型 | 安装方式 | 加载优先级 |
82+
| --- | --- | --- |
83+
| 社区 / 站点插件 | git clone 或手工 → `local/plugins/` | **最高** |
84+
| 官方 pip 扩展 | `uv sync --extra` / 商店一键装 ||
85+
| 仓库内 `src/plugins/` 副本 | 默认 **`load_bundled_extra_plugins = "auto"`**:pip 未装时用副本 ||
86+
87+
Docker 等无法 pip 的环境:镜像若带 `src/plugins/` 官方副本,`auto` 会在无 pip 包时自动加载;社区插件仍推荐 `local/plugins/`
88+
89+
详见 [安装插件 · 官方扩展](install-plugins.md#一安装官方扩展最常见)[安装官方扩展](install-extensions.md)
90+
91+
---
92+
93+
## 相关实现
94+
95+
- 索引加载:`src/console/webui/community_plugin_index.py`
96+
- 安装/卸载:`src/console/webui/community_plugin_install.py`
97+
- API:`GET /pallas/api/plugins/community-store`

docs/guide/install-extensions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
::: info 3.x 升级到 4.0
1212
已有 **`local/plugins/`****`src/plugins/` 迁移副本** 的站点**不必立刻 pip 安装**——加载链仍以 **local 优先**
13+
默认 **`load_bundled_extra_plugins = "auto"`**:pip 包已装则用 pip,未装则用仓库/镜像内 `src/plugins/` 副本。
1314
需要 WebUI 商店一键安装或统一 venv 时,再 `uv sync --extra plugins-xxx`(包来自 **PyPI**,首版可用 wheel 为 **4.0.1+**)。
1415
:::
1516

docs/guide/install-plugins.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
| --- | --- | --- |
66
| **本体 core** | 复读、帮助、控制台 | 随 Bot 自带,不用装 |
77
| **官方扩展** | 决斗、MAA、谁是卧底 | 默认**没装**,要单独安装 |
8-
| **站点插件** | 你自己写的、第三方 nb 插件 | 放到 `local/plugins/` |
8+
| **站点插件** | 你自己写的、第三方 nb 插件 | 商店 git 装或放到 `local/plugins/` |
99

1010
装完后群里发 **牛牛帮助**,新功能应出现在帮助图里。
1111
:::
@@ -33,7 +33,8 @@
3333
- Docker 官方镜像(没有完整源码树 / 没有 `uv`
3434
- 服务器 PATH 里找不到 `uv`
3535

36-
改用下面的 **方法 B**,或构建镜像时带上对应 `PALLAS_UV_EXTRAS`(见 [Docker 部署](../DockerDeployment.md))。
36+
改用下面的 **方法 B**,或构建镜像时带上对应 `PALLAS_UV_EXTRAS`(见 [Docker 部署](../DockerDeployment.md))。
37+
无网络时:若镜像内带 `src/plugins/` 官方副本,默认 **`load_bundled_extra_plugins = "auto"`** 会在 pip 未装时自动用副本(见 [安装官方扩展](install-extensions.md))。
3738
:::
3839

3940
### 方法 B:命令行装
@@ -72,13 +73,16 @@ uv run nb run
7273
local/plugins/你的插件名/__init__.py
7374
```
7475

75-
2.`config/pallas.toml` 启用:
76+
2.`config/pallas.toml` 启用(推荐;未配置时若目录下已有插件包也会自动加载)
7677

7778
```toml
7879
[bootstrap]
7980
extra_plugin_dirs = ["local/plugins"]
81+
load_bundled_extra_plugins = "auto"
8082
```
8183

84+
`load_bundled_extra_plugins` 默认即为 `"auto"`,可省略。
85+
8286
3. **重启 Bot**
8387

8488
**如何确认成功**:WebUI **插件目录** 出现该插件;与官方扩展 **同名时 local 优先加载**
@@ -94,11 +98,23 @@ extra_plugin_dirs = ["local/plugins"]
9498

9599
---
96100

101+
## 四、社区插件商店
102+
103+
第三方插件由 [**community-plugin-index**](https://github.com/PallasBot/community-plugin-index) 策展,Bot 默认拉取该仓 `index.json`;站点可用 `COMMUNITY_PLUGIN_INDEX_URL` 或本地 JSON 覆盖。
104+
105+
1. `/pallas/`**插件商店****社区插件**
106+
2. **安装** → 插件 clone 到 `local/plugins/<ID>/`
107+
3. 重启 Bot(需 `extra_plugin_dirs` 或自动检测,见上文第二节)
108+
109+
完整说明:[社区插件商店](community-plugin-store.md)
110+
111+
---
112+
97113
## 插件列表在哪看
98114

99115
- 文档:[插件手册](../plugins/README.md)(每个插件的口令与配置)
100116
- 网页:控制台 **插件目录**(当前进程实际加载了谁)
101-
- 商店:**插件商店**能装哪些官方扩展
117+
- 商店:**插件商店**官方扩展 + 社区插件
102118

103119
---
104120

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""社区插件安装与可选重启。"""
2+
3+
from __future__ import annotations
4+
5+
from src.console.cli.bot_process import bot_lifecycle_available, schedule_bot_restart
6+
from src.console.webui.community_plugin_install import (
7+
CommunityPluginInstallError,
8+
install_community_plugin,
9+
uninstall_community_plugin,
10+
)
11+
12+
13+
def append_restart_note(message: str, *, scheduled: bool) -> str:
14+
base = (message or "").strip()
15+
if scheduled:
16+
suffix = "已安排 Bot 重启。"
17+
return f"{base} {suffix}".strip() if base else suffix
18+
if base:
19+
return base
20+
return "请重启 Bot 后生效。"
21+
22+
23+
async def install_community_plugin_with_options(
24+
plugin_id: str,
25+
*,
26+
repository_url: str,
27+
ref: str = "main",
28+
restart: bool = False,
29+
) -> dict[str, str | bool]:
30+
result = await install_community_plugin(
31+
plugin_id,
32+
repository_url=repository_url,
33+
ref=ref,
34+
)
35+
scheduled = False
36+
if restart and bot_lifecycle_available():
37+
scheduled = schedule_bot_restart()
38+
result = dict(result)
39+
result["restart_scheduled"] = scheduled
40+
if restart or result.get("needs_restart"):
41+
result["message"] = append_restart_note(str(result.get("message") or ""), scheduled=scheduled)
42+
return result
43+
44+
45+
async def uninstall_community_plugin_with_options(
46+
plugin_id: str,
47+
*,
48+
restart: bool = False,
49+
) -> dict[str, str | bool]:
50+
result = await uninstall_community_plugin(plugin_id)
51+
scheduled = False
52+
if restart and bot_lifecycle_available():
53+
scheduled = schedule_bot_restart()
54+
result = dict(result)
55+
result["restart_scheduled"] = scheduled
56+
if restart or result.get("needs_restart"):
57+
result["message"] = append_restart_note(str(result.get("message") or ""), scheduled=scheduled)
58+
return result
59+
60+
61+
__all__ = [
62+
"CommunityPluginInstallError",
63+
"install_community_plugin_with_options",
64+
"uninstall_community_plugin_with_options",
65+
]

0 commit comments

Comments
 (0)