-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat: add endpoint to fetch marketplace plugin README #8967
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| import json | ||
| import os | ||
| import ssl | ||
| import time | ||
| from collections.abc import Awaitable, Callable | ||
| from dataclasses import dataclass | ||
| from datetime import datetime, timezone | ||
|
|
@@ -29,6 +30,7 @@ | |
| PluginVersionUnsupportedError, | ||
| ) | ||
| from astrbot.core.utils.astrbot_path import get_astrbot_data_path, get_astrbot_temp_path | ||
| from astrbot.core.zip_updator import RepoZipUpdator | ||
|
|
||
| PLUGIN_UPDATE_CONCURRENCY = 3 | ||
| PLUGIN_OPERATION_FAILED_MESSAGE = "插件操作失败,请查看服务端日志。" | ||
|
|
@@ -55,6 +57,11 @@ class RegistrySource: | |
| md5_url: str | None | ||
|
|
||
|
|
||
| # 市场插件 README 的内存缓存:(repo, ref) -> (timestamp, content) | ||
| _MARKET_README_TTL = 600 | ||
| _market_readme_cache: dict[tuple[str, str], tuple[float, str]] = {} | ||
|
sourcery-ai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| class PluginServiceError(Exception): | ||
| def __init__( | ||
| self, | ||
|
|
@@ -1097,6 +1104,68 @@ def get_plugin_readme_from_dashboard_query( | |
| ) -> tuple[dict, str]: | ||
| return self.get_plugin_readme(plugin_name) | ||
|
|
||
| async def get_market_plugin_readme( | ||
| self, | ||
| *, | ||
| repo: str | None, | ||
| ref: str | None, | ||
| proxy: str | None, | ||
| ) -> tuple[dict, str]: | ||
| """从 GitHub 仓库获取市场插件的 README。 | ||
|
|
||
| 使用 ``commit_sha``(或分支)锁定到用户浏览的版本,避免读取到与市场 | ||
| 缓存不一致的文档。raw URL 采用 ``github.com/{a}/{r}/raw/{ref}/{file}`` | ||
| 形式,与 gh-proxy 的 ``{proxy}/{url}`` 前缀拼接方式兼容。 | ||
| """ | ||
| if not repo: | ||
| raise PluginServiceError("repo 参数不能为空") | ||
| try: | ||
| author, repo_name, branch = RepoZipUpdator().parse_github_url(repo) | ||
| except ValueError as exc: | ||
| raise PluginServiceError( | ||
| f"无效的 GitHub 仓库地址: {exc}", | ||
| public_message="无效的 GitHub 仓库地址", | ||
| ) from exc | ||
|
|
||
| resolved_ref = (ref or branch or "master").strip() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Defaulting to "master" may fail for repos that only use "main" when no branch is detected. If Suggested implementation: resolved_ref = (ref or branch or "main").strip()If you want to implement the more robust strategy of “trying both master and main” instead of just changing the default, you will need to:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. registry的commit_sha覆盖率100% |
||
| cache_key = (f"{author}/{repo_name}", resolved_ref) | ||
| now = time.time() | ||
| cached = _market_readme_cache.get(cache_key) | ||
| if cached and now - cached[0] < _MARKET_README_TTL: | ||
| return {"content": cached[1]}, "成功获取README内容(缓存)" | ||
|
|
||
| proxy_prefix = (proxy or "").rstrip("/") | ||
| ssl_context = ssl.create_default_context(cafile=certifi.where()) | ||
| last_status = 0 | ||
| async with aiohttp.ClientSession( | ||
| trust_env=True, | ||
| connector=aiohttp.TCPConnector(ssl=ssl_context), | ||
| timeout=aiohttp.ClientTimeout(total=15), | ||
| ) as session: | ||
| for fname in ("README.md", "readme.md", "Readme.md", "README.MD"): | ||
| raw_url = ( | ||
|
sourcery-ai[bot] marked this conversation as resolved.
|
||
| f"https://github.com/{author}/{repo_name}" | ||
| f"/raw/{resolved_ref}/{fname}" | ||
| ) | ||
| fetch_url = f"{proxy_prefix}/{raw_url}" if proxy_prefix else raw_url | ||
| try: | ||
| async with session.get(fetch_url) as resp: | ||
| last_status = resp.status | ||
| if resp.status != 200: | ||
| continue | ||
| text = await resp.text() | ||
| if text and text.strip(): | ||
| _market_readme_cache[cache_key] = (now, text) | ||
| return {"content": text}, "成功获取README内容" | ||
| except Exception as exc: | ||
| logger.warning(f"获取 {fetch_url} 失败: {exc}") | ||
| continue | ||
|
Comment on lines
+1145
to
+1162
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 当前代码通过循环依次(串行)尝试 4 种不同大小写组合的 async def fetch_one(fname: str) -> tuple[int, str | None]:
raw_url = f"https://github.com/{author}/{repo_name}/raw/{resolved_ref}/{fname}"
fetch_url = f"{proxy_prefix}/{raw_url}" if proxy_prefix else raw_url
try:
async with session.get(fetch_url) as resp:
if resp.status == 200:
text = await resp.text()
return resp.status, text
return resp.status, None
except Exception as exc:
logger.warning(f"获取 {fetch_url} 失败: {exc}")
return 0, None
results = await asyncio.gather(
*(fetch_one(fname) for fname in ("README.md", "readme.md", "Readme.md", "README.MD"))
)
last_status = 0
for status, text in results:
if status == 200 and text and text.strip():
_market_readme_cache[cache_key] = (now, text)
return {"content": text}, "成功获取README内容"
if status != 0:
last_status = status
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 绝大多数情况都写作README.md,并发会浪费 4 倍网络开销 |
||
|
|
||
| raise PluginServiceError( | ||
| f"未找到 README 文件(最后状态码: {last_status})", | ||
| public_message="未找到该插件的 README 文件", | ||
| ) | ||
|
|
||
| def get_plugin_changelog(self, plugin_name: str | None) -> tuple[dict, str]: | ||
| logger.debug(f"正在获取插件 {plugin_name} 的更新日志") | ||
| if not plugin_name: | ||
|
|
||
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.