Skip to content

feat: 添加插件扩展页面功能#4861

Open
qa296 wants to merge 4 commits intoAstrBotDevs:masterfrom
qa296:master
Open

feat: 添加插件扩展页面功能#4861
qa296 wants to merge 4 commits intoAstrBotDevs:masterfrom
qa296:master

Conversation

@qa296
Copy link

@qa296 qa296 commented Feb 4, 2026

Modifications / 改动点

核心文件修改:

  1. 后端修改 (astrbot/dashboard/routes/plugin.py)

    • 添加 /plugin/extension_page API 端点,用于获取插件扩展页面的 HTML 内容
    • get_plugins() 方法中添加 extension_page 字段,标识插件是否具有扩展页面
    • 实现文件路径检查逻辑:扩展页面固定位于 <plugin_root>/web/index.html
    • 向 HTML 注入 window.ASTRBOT_CONFIG 配置对象(包含 API 地址、插件名称、JWT 令牌)
  2. 前端组件修改 (dashboard/src/components/shared/ExtensionCard.vue)

    • 添加 view-extension-page 事件发射器
    • 添加 viewExtensionPage() 函数
    • 在按钮区域添加"插件扩展页面"按钮,使用 mdi-web 图标
    • 按钮仅在非市场模式且插件有扩展页面时显示
  3. 前端视图修改 (dashboard/src/views/ExtensionPage.vue)

    • 添加 extensionPageDialog 响应式状态(包含 show、html、title 字段)
    • 实现 openExtensionPage()fetchExtensionPage() 函数
    • 添加扩展页面对话框,使用全屏 v-dialog
    • 对话框内使用 iframe 加载扩展页面 HTML,配置 sandbox="allow-scripts allow-forms allow-same-origin" 安全属性
  4. 国际化修改

    • dashboard/src/i18n/locales/zh-CN/features/extension.json:添加 "extensionPage": "插件扩展页面"
    • dashboard/src/i18n/locales/en-US/features/extension.json:添加 "extensionPage": "Extension Page"

实现的功能:

  • 插件可提供自定义 HTML 页面(固定位置:web/index.html

  • 自动检测并标识具有扩展页面的插件

  • 扩展页面通过 iframe 沙箱环境安全加载

  • 自动注入配置对象 window.ASTRBOT_CONFIG 供前端使用

  • 使用 JWT 认证机制确保 API 通信安全

  • 完整的向后兼容(无扩展页面的插件不受影响)

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

image image

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

为插件自定义扩展页面添加后端和前端支持,使其可以从控制台中安全查看。

新功能:

  • 暴露一个 API 端点,用于提供插件扩展页面的 HTML,并基于固定的文件位置标记提供扩展页面的插件。
  • 在扩展视图中添加 UI 控件和全屏对话框,以便在沙盒 iframe 中打开插件扩展页面。
  • 在插件卡片上在可用时展示“扩展页面”操作,并提供对应的英文和中文国际化标签。
Original summary in English

Summary by Sourcery

Add backend and frontend support for plugin-defined extension pages that can be securely viewed from the dashboard.

New Features:

  • Expose an API endpoint to serve plugin extension page HTML and mark plugins that provide an extension page based on a fixed file location.
  • Add UI controls and a fullscreen dialog to open plugin extension pages in a sandboxed iframe from the extensions view.
  • Surface an "Extension Page" action on plugin cards when available, with corresponding internationalized labels in English and Chinese.

@auto-assign auto-assign bot requested review from Soulter and advent259141 February 4, 2026 05:33
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 4, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 2 个问题,并给出了一些整体性反馈:

  • get_plugin_extension_page 中,plugin_obj.name 被直接插入到注入的 <script> 块中;建议对其进行转义或通过 json.dumps(或等效方法)序列化,以避免在插件名称包含引号或特殊字符时破坏脚本或造成注入风险。
  • 当前的 HTML 注入依赖 html_content.replace("</head>", injected_script + "</head>"),如果源文档缺少闭合的 </head>,或者使用了不同的大小写/空白格式,就会失效;建议使用更健壮的注入策略,或者在找不到 </head> 时退而求其次,将脚本追加到 </body> 之前。
  • 在前端,请求 axios.get(/api/plugin/extension_page?name=${pluginName}) 应该对 pluginName 做 URL 编码(例如使用 encodeURIComponent),以正确处理带有空格或特殊字符的插件名称。
给 AI Agent 的提示词
请根据下面的代码审查意见进行修改:

## 总体意见
-`get_plugin_extension_page` 中,`plugin_obj.name` 被直接插入到注入的 `<script>` 块中;建议对其进行转义或通过 `json.dumps`(或等效方法)序列化,以避免在插件名称包含引号或特殊字符时破坏脚本或造成注入风险。
- 当前的 HTML 注入依赖 `html_content.replace("</head>", injected_script + "</head>")`,如果源文档缺少闭合的 `</head>`,或者使用了不同的大小写/空白格式,就会失效;建议使用更健壮的注入策略,或者在找不到 `</head>` 时退而求其次,将脚本追加到 `</body>` 之前。
- 在前端,请求 `axios.get(`/api/plugin/extension_page?name=${pluginName}`)` 应该对 `pluginName` 做 URL 编码(例如使用 `encodeURIComponent`),以正确处理带有空格或特殊字符的插件名称。

## 逐条评论

### 评论 1
<location> `dashboard/src/views/ExtensionPage.vue:701` </location>
<code_context>
+// 获取扩展页面内容
+async function fetchExtensionPage(pluginName) {
+  try {
+    const res = await axios.get(`/api/plugin/extension_page?name=${pluginName}`);
+    if (res.data.status === "ok") {
+      extensionPageDialog.html = res.data.data.html;
</code_context>

<issue_to_address>
**issue (bug_risk):** 在构建查询字符串时对 `pluginName` 进行 URL 编码。

未编码的值如果包含空格、`?``&` 或非 ASCII 字符,可能会导致该请求被破坏或路由错误。请在 URL 中使用 `encodeURIComponent(pluginName)`(例如:``axios.get(`/api/plugin/extension_page?name=${encodeURIComponent(pluginName)}`)``),以确保适用于所有插件名称。
</issue_to_address>

### 评论 2
<location> `dashboard/src/views/ExtensionPage.vue:2712` </location>
<code_context>
+        <iframe
+          :srcdoc="extensionPageDialog.html"
+          style="width: 100%; height: 100%; border: none;"
+          sandbox="allow-scripts allow-forms allow-same-origin"
+        ></iframe>
</code_context>

<issue_to_address>
**🚨 issue (security):** 重新审视 iframe 的 sandbox 设置;`allow-same-origin` 会削弱对不受信任插件内容的隔离。

在同时启用 `allow-scripts``allow-same-origin` 的情况下,插件的 JS 实际上以和控制面板相同的源运行,可以读写 `localStorage`、cookies 以及 `ASTRBOT_CONFIG` 以外的其他同源数据。如果插件的 HTML 并非完全可信,这会显著扩大其可访问的范围。建议移除 `allow-same-origin`,让其在独立的源中运行,同时仍可通过你注入的脚本读取 `window.ASTRBOT_CONFIG`,或者以其他方式严格限制插件代码的可访问范围。
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • In get_plugin_extension_page, plugin_obj.name is interpolated directly into the injected <script> block; consider escaping or serializing via json.dumps (or equivalent) to avoid breaking the script or creating an injection vector if plugin names contain quotes or special characters.
  • The HTML injection relies on html_content.replace("</head>", injected_script + "</head>"), which will fail if the source lacks a closing </head> or uses different casing/whitespace; consider a more robust injection strategy or falling back to appending the script before </body> when </head> is not found.
  • On the frontend, the request axios.get(/api/plugin/extension_page?name=${pluginName}) should URL-encode pluginName (e.g., encodeURIComponent) to handle plugin names with spaces or special characters correctly.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `get_plugin_extension_page`, `plugin_obj.name` is interpolated directly into the injected `<script>` block; consider escaping or serializing via `json.dumps` (or equivalent) to avoid breaking the script or creating an injection vector if plugin names contain quotes or special characters.
- The HTML injection relies on `html_content.replace("</head>", injected_script + "</head>")`, which will fail if the source lacks a closing `</head>` or uses different casing/whitespace; consider a more robust injection strategy or falling back to appending the script before `</body>` when `</head>` is not found.
- On the frontend, the request `axios.get(`/api/plugin/extension_page?name=${pluginName}`)` should URL-encode `pluginName` (e.g., `encodeURIComponent`) to handle plugin names with spaces or special characters correctly.

## Individual Comments

### Comment 1
<location> `dashboard/src/views/ExtensionPage.vue:701` </location>
<code_context>
+// 获取扩展页面内容
+async function fetchExtensionPage(pluginName) {
+  try {
+    const res = await axios.get(`/api/plugin/extension_page?name=${pluginName}`);
+    if (res.data.status === "ok") {
+      extensionPageDialog.html = res.data.data.html;
</code_context>

<issue_to_address>
**issue (bug_risk):** URL-encode `pluginName` when building the query string.

Unencoded values containing spaces, `?`, `&`, or non-ASCII characters can break or misroute this request. Use `encodeURIComponent(pluginName)` in the URL (e.g., ``axios.get(`/api/plugin/extension_page?name=${encodeURIComponent(pluginName)}`)``) to ensure it works for all plugin names.
</issue_to_address>

### Comment 2
<location> `dashboard/src/views/ExtensionPage.vue:2712` </location>
<code_context>
+        <iframe
+          :srcdoc="extensionPageDialog.html"
+          style="width: 100%; height: 100%; border: none;"
+          sandbox="allow-scripts allow-forms allow-same-origin"
+        ></iframe>
</code_context>

<issue_to_address>
**🚨 issue (security):** Revisit the iframe sandbox settings; `allow-same-origin` weakens isolation for untrusted plugin content.

With both `allow-scripts` and `allow-same-origin`, plugin JS effectively runs as the dashboard origin and can read/write `localStorage`, cookies, and other origin-scoped data beyond `ASTRBOT_CONFIG`. If plugin HTML isn’t fully trusted, this substantially expands its access. Consider removing `allow-same-origin` so it runs in a unique origin while still reading `window.ASTRBOT_CONFIG` via your injected script, or otherwise tightly limit what plugin code can reach.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dosubot dosubot bot added the feature:plugin The bug / feature is about AstrBot plugin system. label Feb 4, 2026
@Soulter
Copy link
Member

Soulter commented Feb 7, 2026

要如何使用?方便给一个文档吗

@qa296
Copy link
Author

qa296 commented Feb 8, 2026

要如何使用?方便给一个文档吗

extension-page.md

Copy link
Member

@Soulter Soulter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应该没问题,删除一下 pnpm-lock.yaml

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 10, 2026
@qa296
Copy link
Author

qa296 commented Feb 12, 2026

应该没问题,删除一下 pnpm-lock.yaml

可以了

@qa296 qa296 requested a review from Soulter February 13, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature:plugin The bug / feature is about AstrBot plugin system. lgtm This PR has been approved by a maintainer size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants