feat: add documentation and changelog viewing features #7252
feat: add documentation and changelog viewing features #7252VanillaNahida wants to merge 7 commits intoAstrBotDevs:masterfrom
Conversation
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- In
_fetch_remote_readme, consider validating thatrepo_urlactually points to a GitHub repository (e.g., checking the host) before constructingraw.githubusercontent.comURLs, or explicitly handling/early-returning for non-GitHub URLs to avoid confusing failures. - The
_fetch_remote_readmeHTTP calls currently have no timeout and create a newaiohttp.ClientSessionper invocation; adding a reasonable timeout and reusing a session/connector would help avoid hanging requests and reduce overhead under load. - When trying to resolve the README in
_fetch_remote_readme, you currently hardcodemainandmasterbranches; you might want to fall back to the repository's default branch via the GitHub API or make the branch configurable to better handle repos using other default branch names.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_fetch_remote_readme`, consider validating that `repo_url` actually points to a GitHub repository (e.g., checking the host) before constructing `raw.githubusercontent.com` URLs, or explicitly handling/early-returning for non-GitHub URLs to avoid confusing failures.
- The `_fetch_remote_readme` HTTP calls currently have no timeout and create a new `aiohttp.ClientSession` per invocation; adding a reasonable timeout and reusing a session/connector would help avoid hanging requests and reduce overhead under load.
- When trying to resolve the README in `_fetch_remote_readme`, you currently hardcode `main` and `master` branches; you might want to fall back to the repository's default branch via the GitHub API or make the branch configurable to better handle repos using other default branch names.
## Individual Comments
### Comment 1
<location path="dashboard/src/components/extension/MarketPluginCard.vue" line_range="23-26" />
<code_context>
});
-const emit = defineEmits(["install"]);
+const emit = defineEmits(["install", "viewReadme"]);
const normalizePlatformList = (platforms) => {
</code_context>
<issue_to_address>
**issue (bug_risk):** The emitted event name `viewReadme` does not match the parent listener `@view-readme`, which will prevent the handler from firing.
In Vue 3, custom event names are case-sensitive and are not auto-converted between camelCase and kebab-case. Here you emit `"viewReadme"`, but the parent listens with `@view-readme`, so the handler will never run.
Use a single naming style for the event in both places, e.g.:
- Kebab-case: `defineEmits(["view-readme"])` and `emit("view-readme", plugin)`
- CamelCase: keep `emit("viewReadme", plugin)` and change the parent to `@viewReadme="viewReadme"`.
</issue_to_address>
### Comment 2
<location path="astrbot/dashboard/routes/plugin.py" line_range="755-757" />
<code_context>
+ logger.debug(f"正在获取插件 {plugin_name} 的README文件内容, repo: {repo_url}")
+ # 如果提供了 repo_url,优先从远程获取
+ if repo_url:
+ try:
+ readme_content = await self._fetch_remote_readme(repo_url)
+ if readme_content:
+ return (
+ Response()
+ .ok({"content": readme_content}, "成功获取README内容")
+ .__dict__
+ )
+ else:
+ return Response().error("无法从远程仓库获取README文件").__dict__
+ except Exception as e:
+ logger.error(f"从远程获取README失败: {traceback.format_exc()}")
+ return Response().error(f"获取README失败: {e!s}").__dict__
+
+ # 否则从本地获取
</code_context>
<issue_to_address>
**🚨 suggestion (security):** The API response exposes raw exception messages to clients, which can leak internal details.
In the remote README error path, `Response().error(f"获取README失败: {e!s}")` returns the raw exception message to clients. Instead, keep detailed info (including `e` and stack traces) in logs only, and return a generic, static error message and/or error code in the API response.
```suggestion
except Exception as e:
logger.error(f"从远程获取README失败: {traceback.format_exc()}")
# 不将具体异常信息暴露给客户端,仅返回通用错误提示
return Response().error("获取README失败,请稍后重试").__dict__
```
</issue_to_address>
### Comment 3
<location path="astrbot/dashboard/routes/plugin.py" line_range="818-824" />
<code_context>
+ # 支持格式: https://github.com/owner/repo 或 https://github.com/owner/repo.git
+ repo_url = repo_url.rstrip("/").replace(".git", "")
+
+ # 提取 owner 和 repo
+ parts = repo_url.split("/")
+ if len(parts) < 2:
+ return None
+
+ owner = parts[-2]
+ repo = parts[-1]
+
+ # 尝试多种README文件名
</code_context>
<issue_to_address>
**suggestion (bug_risk):** GitHub URL parsing is very permissive and may derive incorrect owner/repo for non-standard URLs.
The logic currently takes `parts[-2]`/`parts[-1]` for `owner`/`repo`, so it will also treat URLs like `https://example.com/foo/bar` or `https://github.com/owner` as valid, leading to incorrect `owner`/`repo` and 404s against `raw.githubusercontent.com`.
It would be safer to:
- Check that the hostname is `github.com` (or another explicitly supported host), and
- Require at least two path segments after the domain before extracting `owner` and `repo`.
If validation fails, consider returning `None` (or a specific error) so callers can clearly distinguish an invalid repo URL from a missing README.
Suggested implementation:
```python
async def _fetch_remote_readme(self, repo_url: str) -> str | None:
"""从远程GitHub仓库获取README内容"""
# 解析GitHub仓库URL
# 支持格式: https://github.com/owner/repo 或 https://github.com/owner/repo.git
# 先去掉末尾的斜杠和 .git 后缀,避免影响解析
repo_url = repo_url.rstrip("/")
if repo_url.endswith(".git"):
repo_url = repo_url[:-4]
# 使用 urlparse 严格解析 URL,校验域名和路径
parsed = urlparse(repo_url)
# 仅支持 GitHub 仓库链接
if parsed.netloc.lower() != "github.com":
return None
# 提取路径中的 owner 和 repo,要求至少有两个段
path_parts = [part for part in parsed.path.strip("/").split("/") if part]
if len(path_parts) < 2:
return None
owner, repo = path_parts[0], path_parts[1]
```
To make this compile, ensure that `urlparse` is imported at the top of `astrbot/dashboard/routes/plugin.py`, for example:
<<<<<<< SEARCH
import ssl
=======
import ssl
from urllib.parse import urlparse
>>>>>>> REPLACE
Adjust the exact SEARCH block for the imports to match your existing import section if it differs.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const emit = defineEmits(["install", "viewReadme"]); | ||
|
|
||
| const normalizePlatformList = (platforms) => { | ||
| if (!Array.isArray(platforms)) return []; |
There was a problem hiding this comment.
issue (bug_risk): The emitted event name viewReadme does not match the parent listener @view-readme, which will prevent the handler from firing.
In Vue 3, custom event names are case-sensitive and are not auto-converted between camelCase and kebab-case. Here you emit "viewReadme", but the parent listens with @view-readme, so the handler will never run.
Use a single naming style for the event in both places, e.g.:
- Kebab-case:
defineEmits(["view-readme"])andemit("view-readme", plugin) - CamelCase: keep
emit("viewReadme", plugin)and change the parent to@viewReadme="viewReadme".
| except Exception as e: | ||
| logger.error(f"从远程获取README失败: {traceback.format_exc()}") | ||
| return Response().error(f"获取README失败: {e!s}").__dict__ |
There was a problem hiding this comment.
🚨 suggestion (security): The API response exposes raw exception messages to clients, which can leak internal details.
In the remote README error path, Response().error(f"获取README失败: {e!s}") returns the raw exception message to clients. Instead, keep detailed info (including e and stack traces) in logs only, and return a generic, static error message and/or error code in the API response.
| except Exception as e: | |
| logger.error(f"从远程获取README失败: {traceback.format_exc()}") | |
| return Response().error(f"获取README失败: {e!s}").__dict__ | |
| except Exception as e: | |
| logger.error(f"从远程获取README失败: {traceback.format_exc()}") | |
| # 不将具体异常信息暴露给客户端,仅返回通用错误提示 | |
| return Response().error("获取README失败,请稍后重试").__dict__ |
There was a problem hiding this comment.
Code Review
This pull request implements functionality to fetch and display plugin README files from remote GitHub repositories. Key changes include a new backend method for retrieving remote content and frontend updates to provide 'View Documentation' and 'View Changelog' actions. Feedback focuses on improving the robustness of the repository URL parsing and ensuring that empty README files are handled correctly by checking for non-null content rather than truthiness.
astrbot/dashboard/routes/plugin.py
Outdated
| """从远程GitHub仓库获取README内容""" | ||
| # 解析GitHub仓库URL | ||
| # 支持格式: https://github.com/owner/repo 或 https://github.com/owner/repo.git | ||
| repo_url = repo_url.rstrip("/").replace(".git", "") |
There was a problem hiding this comment.
| if repo_url: | ||
| try: | ||
| readme_content = await self._fetch_remote_readme(repo_url) | ||
| if readme_content: |
What's change:
Modifications / 改动点
plugin.py, 添加了获取远程README文档的函数,可以实现在未安装插件的时候直接在WebUI内查看插件文档Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
🤓 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.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Add support for viewing plugin documentation from the marketplace and changelogs for installed plugins directly in the dashboard.
New Features:
Enhancements: