feat: support local README language switching in plugin docs preview - #9797
Open
yuluo-feather wants to merge 1 commit into
Open
feat: support local README language switching in plugin docs preview#9797yuluo-feather wants to merge 1 commit into
yuluo-feather wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Hey - I've found 4 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="dashboard/src/views/extension/PluginDetailPage.vue" line_range="590" />
<code_context>
+ }
+ // README 语言切换链接(相对路径 README*.md):预览内本地切换,不跳转
+ const fileName = href.split("/").pop() || "";
+ if (/^README[^/]*\.md$/i.test(fileName)) {
+ link.addEventListener("click", (e) => {
+ e.preventDefault();
+ void switchReadmeFile(fileName);
+ });
}
</code_context>
<issue_to_address>
**issue (broader_impact):** README language links in market-sourced remote READMEs are intercepted and prevented from navigating, then `switchReadmeFile` returns immediately because `isMarketDetail` is true, so those links become non-functional instead of retaining the previous GitHub/remote behavior.
**Triggers:** When a market plugin README contains a relative link to another README*.md file.
**Suggested fix:** Only install the local-switch click handler when `!isMarketDetail.value`, or allow the handler to preserve the remote link behavior for market details.
```suggestion
if (!isMarketDetail.value && /^README[^/]*\.md$/i.test(fileName)) {
```
</issue_to_address>
### Comment 2
<location path="astrbot/dashboard/services/plugin_service.py" line_range="1940-1945" />
<code_context>
+ or not (file.lower().startswith("readme") and file.lower().endswith(".md"))
+ ):
+ raise PluginServiceError("非法的 README 文件名")
+ readme_path = plugin_dir / file
+ if not readme_path.is_file():
+ # 指定语言文件不存在时回退默认 README.md,行为与旧版一致
+ readme_path = plugin_dir / "README.md"
</code_context>
<issue_to_address>
**🚨 issue (security):** The filename checks reject textual path traversal but do not reject symlinks: `readme_path.is_file()` and `read_text()` follow a README*.md symlink outside the plugin directory, allowing the endpoint to return arbitrary readable files targeted by a plugin-provided symlink.
**Triggers:** When an installed plugin directory contains a symlink named README*.md pointing outside that directory.
**Suggested fix:** Resolve the candidate path and require it to remain under the resolved plugin directory, or reject symlinks before reading.
```suggestion
plugin_dir = plugin_dir.resolve()
readme_path = (plugin_dir / file).resolve()
if plugin_dir not in readme_path.parents:
raise PluginServiceError("非法的 README 文件名")
if not readme_path.is_file():
# 指定语言文件不存在时回退默认 README.md,行为与旧版一致
readme_path = (plugin_dir / "README.md").resolve()
if plugin_dir not in readme_path.parents:
raise PluginServiceError("非法的 README 文件名")
if not readme_path.is_file():
```
</issue_to_address>
### Comment 3
<location path="dashboard/src/views/extension/PluginDetailPage.vue" line_range="728-738" />
<code_context>
+ readmeError.value = "";
+ readmeEmpty.value = false;
+ try {
+ const res = await pluginApi.readme(plugin.name, fileName);
+ if (res.data.status !== "ok") {
+ readmeError.value = res.data.message || tm("messages.operationFailed");
+ return;
+ }
+ const content = res.data.data?.content || "";
+ if (!content) {
+ readmeError.value = `${fileName} 内容为空`;
+ return;
+ }
+ readmeFile.value = fileName;
+ renderedReadme.value = renderMarkdown(content);
+ } catch (err) {
</code_context>
<issue_to_address>
**issue (bug_risk):** Concurrent README clicks can complete out of order, allowing an older response to overwrite `renderedReadme` and `readmeFile` after a newer selection has already completed, so the displayed language does not necessarily match the last link clicked.
**Triggers:** When the user clicks two different README language links before the first request finishes.
**Suggested fix:** Track a request sequence or selected filename and discard responses that are no longer current.
</issue_to_address>
### Comment 4
<location path="astrbot/dashboard/api/plugins.py" line_range="820-821" />
<code_context>
service: PluginService = Depends(get_service),
):
return await _run_service(
- lambda: service.get_plugin_readme(plugin_id),
+ lambda: service.get_plugin_readme(plugin_id, file or "README.md"),
log_label="/api/plugin/readme",
)
</code_context>
<issue_to_address>
**issue (bug_risk):** The existing dashboard API test monkeypatches `get_plugin_readme` with a one-argument callable, but this route now always calls it with two positional arguments even when no `file` query parameter is supplied, causing that test request to raise `TypeError` and return the generic service error response.
**Triggers:** When the existing `test_fastapi_v1_dashboard.py` endpoint test runs with its current one-argument monkeypatch.
**Suggested fix:** Update the test double to accept the new optional argument, or have the route call the legacy one-argument form when `file` is absent.
</issue_to_address>Sourcery assessment
Approval pending. 4 findings to address first.
Blocking findings: dashboard/src/views/extension/PluginDetailPage.vue:590, astrbot/dashboard/services/plugin_service.py:1945, dashboard/src/views/extension/PluginDetailPage.vue:738, astrbot/dashboard/api/plugins.py:821
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
yuluo-feather
force-pushed
the
feat/plugin-readme-lang
branch
from
August 24, 2026 10:51
317a2fc to
e3caca0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation / 动机
插件详情页「文档预览」只渲染插件目录的
README.md。双语插件的 README 顶部通常有语言切换链接(如README.zh-CN.md),在 WebUI 预览中点击会直接新开标签页跳转到 GitHub,用户被迫离开管理界面才能阅读其他语言版本;在内网、无法访问 GitHub 的环境下则完全不可用。该功能已通过 Issue #9794 与作者讨论。Modifications / 改动点
后端:
GET /api/v1/plugins/readme与GET /api/v1/plugins/{plugin_id}/readme增加可选file参数;PluginService.get_plugin_readme支持按文件名读取(白名单README*.md+ 禁止路径穿越,指定语言文件不存在时回退默认README.md,不改变旧行为)前端:
PluginDetailPage.vue的renderMarkdown拦截以README*.md结尾的相对链接,点击时请求本地对应文件并原地重渲染,不再跳转 GitHub;市场来源(远端 URL)行为不变同步更新 OpenAPI 生成类型(
types.gen.ts)与v1.tsAPI 封装This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
ast.parse语法校验通过(plugins.py 1514 行 / plugin_service.py 2077 行)npm run build通过 —vue-tsc --noEmit && vite build,✓ built in 44.21sgit apply干净应用,前后逐文件比对一致;最终 5 files changed, 61 insertions(+), 6 deletions(-)Checklist / 检查清单
Closes [Feature] 插件文档预览支持语言切换(README 语言链接点击不跳 GitHub) #9794
Summary by Sourcery
Support in-place local language switching for plugin README previews while preserving remote marketplace link behavior.
New Features:
Bug Fixes:
Enhancements:
Summary by Sourcery
Support local language switching for plugin README previews without requiring users to leave the WebUI.
New Features:
Bug Fixes:
Enhancements: