11import os
22import sys
33import time
4+ import zipfile
5+ from pathlib import Path
46
57import psutil
68
79from astrbot .core import logger
810from astrbot .core .config .default import VERSION
911from astrbot .core .utils .astrbot_path import get_astrbot_path
12+ from astrbot .core .utils .io import ensure_dir
1013
1114from .zip_updator import ReleaseInfo , RepoZipUpdator
1215
@@ -21,6 +24,30 @@ def __init__(self, repo_mirror: str = "", verify: str | bool | None = None) -> N
2124 super ().__init__ (repo_mirror , verify = verify )
2225 self .MAIN_PATH = get_astrbot_path ()
2326 self .ASTRBOT_RELEASE_API = "https://api.soulter.top/releases"
27+ self .CORE_PACKAGE_BASE_URL = (
28+ "https://astrbot-registry.soulter.top/download/astrbot-core"
29+ )
30+
31+ def _build_core_package_url (self , version : str | None ) -> str | None :
32+ """Build the hosted core package URL for a release tag.
33+
34+ Args:
35+ version: Release tag, such as ``v4.26.6``.
36+
37+ Returns:
38+ Public package URL, or None when hosted package download is disabled.
39+ """
40+
41+ if not version or not str (version ).startswith ("v" ):
42+ return None
43+
44+ base_url = os .environ .get (
45+ "ASTRBOT_CORE_PACKAGE_BASE_URL" ,
46+ self .CORE_PACKAGE_BASE_URL ,
47+ ).strip ()
48+ if not base_url :
49+ return None
50+ return f"{ base_url .rstrip ('/' )} /{ version } /source.zip"
2451
2552 def terminate_child_processes (self ) -> None :
2653 """终止当前进程的所有子进程
@@ -151,6 +178,41 @@ async def update(
151178 proxy = "" ,
152179 progress_callback = None ,
153180 ) -> None :
181+ zip_path = await self .download_update_package (
182+ latest = latest ,
183+ version = version ,
184+ proxy = proxy ,
185+ progress_callback = progress_callback ,
186+ )
187+ self .apply_update_package (zip_path )
188+
189+ if reboot :
190+ self ._reboot ()
191+
192+ async def download_update_package (
193+ self ,
194+ latest = True ,
195+ version = None ,
196+ proxy = "" ,
197+ path : str | Path = "temp.zip" ,
198+ progress_callback = None ,
199+ ) -> Path :
200+ """Download an AstrBot core update package without applying it.
201+
202+ Args:
203+ latest: Whether to download the latest release.
204+ version: Specific release tag or commit hash to download.
205+ proxy: Optional GitHub proxy prefix.
206+ path: Destination zip path.
207+ progress_callback: Optional callback for download progress payloads.
208+
209+ Returns:
210+ Path to the downloaded update package.
211+
212+ Raises:
213+ Exception: If update metadata cannot resolve a package URL.
214+ """
215+
154216 update_data = await self .fetch_release_info (self .ASTRBOT_RELEASE_API , latest )
155217 file_url = None
156218
@@ -159,15 +221,18 @@ async def update(
159221 "Error: You are running AstrBot via CLI, please use `pip` or `uv tool upgrade` to update AstrBot."
160222 ) # 避免版本管理混乱
161223
224+ target_version = None
162225 if latest :
163226 latest_version = update_data [0 ]["tag_name" ]
164227 if self .compare_version (VERSION , latest_version ) >= 0 :
165228 raise Exception ("当前已经是最新版本。" )
229+ target_version = latest_version
166230 file_url = update_data [0 ]["zipball_url" ]
167231 elif str (version ).startswith ("v" ):
168232 # 更新到指定版本
169233 for data in update_data :
170234 if data ["tag_name" ] == version :
235+ target_version = data ["tag_name" ]
171236 file_url = data ["zipball_url" ]
172237 if not file_url :
173238 raise Exception (f"未找到版本号为 { version } 的更新文件。" )
@@ -181,16 +246,49 @@ async def update(
181246 proxy = proxy .removesuffix ("/" )
182247 file_url = f"{ proxy } /{ file_url } "
183248
184- try :
185- await self ._download_file (
186- file_url ,
187- "temp.zip" ,
188- progress_callback = progress_callback ,
189- )
190- logger .info ("下载 AstrBot Core 更新文件完成,正在执行解压..." )
191- self .unzip_file ("temp.zip" , self .MAIN_PATH )
192- except BaseException as e :
193- raise e
249+ zip_path = Path (path )
250+ ensure_dir (zip_path .parent )
251+ hosted_package_url = self ._build_core_package_url (target_version )
252+ if hosted_package_url :
253+ try :
254+ logger .info (
255+ f"优先从托管存储下载 AstrBot Core 更新包: { hosted_package_url } "
256+ )
257+ await self ._download_file (
258+ hosted_package_url ,
259+ str (zip_path ),
260+ progress_callback = progress_callback ,
261+ )
262+ if not zipfile .is_zipfile (zip_path ):
263+ raise RuntimeError (
264+ "Downloaded hosted package is not a valid ZIP file"
265+ )
266+ return zip_path
267+ except Exception as exc :
268+ logger .warning (
269+ f"从托管存储下载 AstrBot Core 更新包失败: { exc } ,"
270+ "将回退到当前更新源。"
271+ )
194272
195- if reboot :
196- self ._reboot ()
273+ await self ._download_file (
274+ file_url ,
275+ str (zip_path ),
276+ progress_callback = progress_callback ,
277+ )
278+ return zip_path
279+
280+ def apply_update_package (self , zip_path : str | Path ) -> None :
281+ """Apply a previously downloaded AstrBot core update package.
282+
283+ Args:
284+ zip_path: Core update zip archive path.
285+
286+ Returns:
287+ None.
288+
289+ Raises:
290+ Exception: If the archive cannot be extracted or applied.
291+ """
292+
293+ logger .info ("下载 AstrBot Core 更新文件完成,正在执行解压..." )
294+ self .unzip_file (str (zip_path ), self .MAIN_PATH )
0 commit comments