Skip to content

Commit 64b556b

Browse files
committed
refactor: derive archive urls and enrich fetch errors
1 parent 8d23e19 commit 64b556b

3 files changed

Lines changed: 110 additions & 9 deletions

File tree

astrbot/core/updator.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import re
23
import sys
34
import time
45
from json import JSONDecodeError
@@ -220,10 +221,19 @@ async def _resolve_latest_target(self) -> tuple[str, str]:
220221
raise Exception("当前已经是最新版本。")
221222
return latest_version, latest_release["zipball_url"]
222223

223-
def _resolve_nightly_target(self) -> tuple[str, str]:
224-
return self.NIGHTLY_TAG, (
225-
f"https://github.com/AstrBotDevs/AstrBot/archive/refs/tags/{self.NIGHTLY_TAG}.zip"
224+
def _resolve_github_archive_base(self) -> str:
225+
match = re.search(
226+
r"/repos/([^/]+)/([^/]+)/releases/?$",
227+
self.GITHUB_RELEASE_API,
226228
)
229+
if match is None:
230+
raise Exception("GITHUB_RELEASE_API 格式不正确,无法解析仓库信息。")
231+
owner, repo = match.groups()
232+
return f"https://github.com/{owner}/{repo}/archive"
233+
234+
def _resolve_nightly_target(self) -> tuple[str, str]:
235+
archive_base = self._resolve_github_archive_base()
236+
return self.NIGHTLY_TAG, (f"{archive_base}/refs/tags/{self.NIGHTLY_TAG}.zip")
227237

228238
async def _resolve_tag_target(self, version_str: str) -> tuple[str, str]:
229239
releases = await self.get_releases()
@@ -235,9 +245,10 @@ async def _resolve_tag_target(self, version_str: str) -> tuple[str, str]:
235245
def _resolve_commit_target(self, version_str: str) -> tuple[str, str]:
236246
if len(version_str) != 40:
237247
raise Exception("commit hash 长度不正确,应为 40")
248+
archive_base = self._resolve_github_archive_base()
238249
return (
239250
version_str,
240-
f"https://github.com/AstrBotDevs/AstrBot/archive/{version_str}.zip",
251+
f"{archive_base}/{version_str}.zip",
241252
)
242253

243254
async def _resolve_update_target(

astrbot/core/zip_updator.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,32 @@ def __str__(self) -> str:
4343
class FetchReleaseError(Exception):
4444
"""Expected errors while fetching release metadata from remote services."""
4545

46+
def __init__(
47+
self,
48+
message: str,
49+
*,
50+
url: str | None = None,
51+
status_code: int | None = None,
52+
detail: str | None = None,
53+
) -> None:
54+
super().__init__(message)
55+
self.message = message
56+
self.url = url
57+
self.status_code = status_code
58+
self.detail = detail
59+
60+
def __str__(self) -> str:
61+
context_parts = []
62+
if self.url:
63+
context_parts.append(f"url={self.url}")
64+
if self.status_code is not None:
65+
context_parts.append(f"status_code={self.status_code}")
66+
if self.detail:
67+
context_parts.append(f"detail={self.detail}")
68+
if not context_parts:
69+
return self.message
70+
return f"{self.message} ({', '.join(context_parts)})"
71+
4672

4773
class RepoZipUpdator:
4874
def __init__(self, repo_mirror: str = "") -> None:
@@ -59,7 +85,11 @@ def _normalize_release_payload(result: object, url: str) -> list:
5985
logger.error(
6086
f"版本信息格式异常,期望列表或字典,实际为: {type(result).__name__}, url: {url}",
6187
)
62-
raise FetchReleaseError("版本信息格式异常")
88+
raise FetchReleaseError(
89+
"版本信息格式异常",
90+
url=url,
91+
detail=f"top_level_type={type(result).__name__}",
92+
)
6393

6494
if not releases:
6595
return []
@@ -107,7 +137,11 @@ def _normalize_release_payload(result: object, url: str) -> list:
107137
)
108138

109139
if not normalized:
110-
raise FetchReleaseError("版本信息全部无效")
140+
raise FetchReleaseError(
141+
"版本信息全部无效",
142+
url=url,
143+
detail=f"invalid_entries={invalid_entry_count}, total_entries={len(releases)}",
144+
)
111145

112146
return normalized
113147

@@ -135,13 +169,25 @@ async def fetch_release_info(self, url: str) -> list:
135169
logger.error(
136170
f"请求 {url} 失败,状态码: {response.status}, 内容: {text}",
137171
)
138-
raise FetchReleaseError(f"请求失败,状态码: {response.status}")
172+
raise FetchReleaseError(
173+
"请求失败",
174+
url=url,
175+
status_code=response.status,
176+
detail=text[:500],
177+
)
139178
result = await response.json()
140179
except FetchReleaseError:
141180
raise
142181
except (TimeoutError, aiohttp.ClientError, JSONDecodeError) as e:
143-
logger.error(f"解析版本信息时发生异常: {e}")
144-
raise FetchReleaseError("解析版本信息失败") from e
182+
logger.error(
183+
"解析版本信息时发生异常。"
184+
f"url={url}, error_type={type(e).__name__}, detail={e}",
185+
)
186+
raise FetchReleaseError(
187+
"解析版本信息失败",
188+
url=url,
189+
detail=f"{type(e).__name__}: {e}",
190+
) from e
145191

146192
return self._normalize_release_payload(result, url)
147193

tests/unit/test_updator.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,21 @@ def test_normalize_release_payload_raises_when_all_entries_invalid():
7979
)
8080

8181

82+
def test_normalize_release_payload_error_contains_context():
83+
updator = RepoZipUpdator()
84+
malformed_payload = ["invalid-release-item"]
85+
86+
with pytest.raises(FetchReleaseError) as exc_info:
87+
updator._normalize_release_payload(
88+
malformed_payload,
89+
"https://example.invalid/releases",
90+
)
91+
92+
error = exc_info.value
93+
assert error.url == "https://example.invalid/releases"
94+
assert error.detail is not None
95+
96+
8297
@pytest.mark.asyncio
8398
async def test_update_supports_nightly_tag(monkeypatch, tmp_path):
8499
updator = AstrBotUpdator()
@@ -110,6 +125,35 @@ def mock_unzip_file(zip_path: str, target_dir: str):
110125
assert captured["target_dir"] == str(tmp_path)
111126

112127

128+
def test_resolve_nightly_target_uses_repo_from_release_api():
129+
updator = AstrBotUpdator()
130+
updator.GITHUB_RELEASE_API = (
131+
"https://api.github.com/repos/example-org/example-repo/releases"
132+
)
133+
134+
target_version, file_url = updator._resolve_nightly_target()
135+
assert target_version == "nightly"
136+
assert (
137+
file_url
138+
== "https://github.com/example-org/example-repo/archive/refs/tags/nightly.zip"
139+
)
140+
141+
142+
def test_resolve_commit_target_uses_repo_from_release_api():
143+
updator = AstrBotUpdator()
144+
updator.GITHUB_RELEASE_API = (
145+
"https://api.github.com/repos/example-org/example-repo/releases"
146+
)
147+
version_str = "1234567890123456789012345678901234567890"
148+
149+
target_version, file_url = updator._resolve_commit_target(version_str)
150+
assert target_version == version_str
151+
assert (
152+
file_url
153+
== "https://github.com/example-org/example-repo/archive/1234567890123456789012345678901234567890.zip"
154+
)
155+
156+
113157
@pytest.mark.asyncio
114158
async def test_get_releases_includes_nightly_tag(monkeypatch):
115159
updator = AstrBotUpdator()

0 commit comments

Comments
 (0)