feat: improve bat daemon cli output

This commit is contained in:
2026-07-12 23:04:13 +08:00
parent 264e78c440
commit f7255f7c04
8 changed files with 3231 additions and 344 deletions
+27 -33
View File
@@ -109,13 +109,11 @@ impl OfficialLauncherBootstrapService {
/// Fetches latest official PC game config.
pub fn fetch_game_config(&self) -> Result<YostarJpLauncherGameConfig, String> {
let envelope = self.fetch_launcher_envelope("/api/launcher/game/config")?;
let config: YostarJpLauncherGameConfig =
serde_json::from_value(envelope.data).map_err(|error| {
format!("Failed to parse official launcher game config data: {error}")
})?;
let config: YostarJpLauncherGameConfig = serde_json::from_value(envelope.data)
.map_err(|error| format!("解析官方启动器 game config 数据失败:{error}"))?;
if config.game_latest_version.is_empty() || config.game_latest_file_path.is_empty() {
return Err("Official launcher game config is missing latest version or basis".into());
return Err("官方启动器 game config 缺少最新版本或基准路径".into());
}
Ok(config)
@@ -125,7 +123,7 @@ impl OfficialLauncherBootstrapService {
pub fn fetch_cdn_config(&self) -> Result<YostarJpLauncherCdnConfig, String> {
let envelope = self.fetch_launcher_envelope("/api/launcher/advanced/game/download/cdn")?;
serde_json::from_value(envelope.data)
.map_err(|error| format!("Failed to parse official launcher CDN config data: {error}"))
.map_err(|error| format!("解析官方启动器 CDN config 数据失败:{error}"))
}
/// Fetches the official remote manifest URL for a PC client version and
@@ -136,7 +134,7 @@ impl OfficialLauncherBootstrapService {
file_path: &str,
) -> Result<YostarJpLauncherManifestUrl, String> {
if version.is_empty() || file_path.is_empty() {
return Err("Launcher manifest version and file path must not be empty".into());
return Err("启动器 manifest 版本和文件路径不能为空".into());
}
let path = format!(
@@ -146,13 +144,11 @@ impl OfficialLauncherBootstrapService {
);
let envelope = self.fetch_launcher_envelope(&path)?;
let manifest_url: YostarJpLauncherManifestUrl = serde_json::from_value(envelope.data)
.map_err(|error| {
format!("Failed to parse official launcher manifest URL data: {error}")
})?;
.map_err(|error| format!("解析官方启动器 manifest URL 数据失败:{error}"))?;
if !is_official_launcher_package_url(&manifest_url.url) {
return Err(format!(
"Official launcher API returned non-official manifest URL: {}",
"官方启动器 API 返回了非官方 manifest URL{}",
manifest_url.url
));
}
@@ -166,9 +162,7 @@ impl OfficialLauncherBootstrapService {
manifest_url: &str,
) -> Result<YostarJpLauncherRemoteManifest, String> {
if !is_official_launcher_package_url(manifest_url) {
return Err(format!(
"Refusing to fetch non-official launcher manifest URL: {manifest_url}"
));
return Err(format!("拒绝拉取非官方启动器 manifest URL{manifest_url}"));
}
let output = self.run_curl_with_retry(
@@ -186,16 +180,16 @@ impl OfficialLauncherBootstrapService {
|output| {
let stderr = String::from_utf8_lossy(&output.stderr);
format!(
"curl failed for launcher manifest {manifest_url}: {}",
"curl 拉取启动器 manifest 失败 {manifest_url}{}",
stderr.trim()
)
},
)?;
let manifest: YostarJpLauncherRemoteManifest = serde_json::from_slice(&output.stdout)
.map_err(|error| format!("Failed to parse official launcher manifest: {error}"))?;
.map_err(|error| format!("解析官方启动器 manifest 失败:{error}"))?;
if manifest.files.is_empty() {
return Err("Official launcher remote manifest has no files".into());
return Err("官方启动器远端 manifest 没有文件条目".into());
}
Ok(manifest)
@@ -246,22 +240,22 @@ impl OfficialLauncherBootstrapService {
},
|output| {
let stderr = String::from_utf8_lossy(&output.stderr);
format!("curl failed for {url}: {}", stderr.trim())
format!("curl 拉取失败 {url}{}", stderr.trim())
},
)?;
let envelope: LauncherEnvelope = serde_json::from_slice(&output.stdout)
.map_err(|error| format!("Failed to parse official launcher API response: {error}"))?;
.map_err(|error| format!("解析官方启动器 API 响应失败:{error}"))?;
if envelope.code != 200 {
return Err(format!(
"Official launcher API returned code {}: {}",
"官方启动器 API 返回错误码 {}{}",
envelope.code,
envelope
.message
.as_deref()
.or(envelope.msg.as_deref())
.unwrap_or("<no message>")
.unwrap_or("<无消息>")
));
}
@@ -280,20 +274,20 @@ impl OfficialLauncherBootstrapService {
Ok(output) if output.status.success() => return Ok(output),
Ok(output) => {
last_error = Some(format!(
"attempt {attempt}/{attempts}: {}",
" {attempt}/{attempts} 次尝试失败:{}",
failure_message(&output)
));
}
Err(error) => {
last_error = Some(format!(
"attempt {attempt}/{attempts}: Failed to launch curl command {}: {error}",
" {attempt}/{attempts} 次尝试失败:启动 curl 命令失败 {}{error}",
self.curl_command
));
}
}
}
Err(last_error.unwrap_or_else(|| format!("curl command {} did not run", self.curl_command)))
Err(last_error.unwrap_or_else(|| format!("curl 命令未执行:{}", self.curl_command)))
}
}
@@ -315,7 +309,7 @@ pub struct LauncherEnvelope {
/// Builds an official launcher API URL from a path.
pub fn launcher_api_url(path: &str) -> Result<String, String> {
if !path.starts_with('/') || path.contains("..") || path.contains('\\') {
return Err(format!("Invalid official launcher API path: {path}"));
return Err(format!("官方启动器 API 路径无效:{path}"));
}
Ok(format!("{YOSTAR_JP_LAUNCHER_API_ROOT}{path}"))
@@ -337,7 +331,7 @@ pub fn is_official_launcher_package_url(url: &str) -> bool {
/// relative package path.
pub fn launcher_package_url(cdn_root: &str, file_path: &str) -> Result<String, String> {
if !is_official_launcher_package_url(cdn_root) {
return Err(format!("Launcher CDN root is not official: {cdn_root}"));
return Err(format!("启动器 CDN 根地址不是官方地址:{cdn_root}"));
}
validate_launcher_package_path(file_path)?;
@@ -368,15 +362,15 @@ fn normalize_relative_path(path: &str) -> String {
fn validate_launcher_package_path(path: &str) -> Result<(), String> {
if path.is_empty() || path.starts_with('/') || path.starts_with('\\') {
return Err(format!("Invalid launcher package path: {path}"));
return Err(format!("启动器包路径无效:{path}"));
}
for segment in path.split('/') {
if segment.is_empty() || segment == "." || segment == ".." {
return Err(format!("Invalid launcher package path: {path}"));
return Err(format!("启动器包路径无效:{path}"));
}
if segment.contains('\\') || segment.contains('?') || segment.contains('#') {
return Err(format!("Invalid launcher package path: {path}"));
return Err(format!("启动器包路径无效:{path}"));
}
}
@@ -394,14 +388,14 @@ pub fn launcher_authorization_header(
timestamp: Option<u64>,
) -> Result<String, String> {
if launcher_version.is_empty() {
return Err("Launcher version must not be empty".into());
return Err("启动器版本不能为空".into());
}
let timestamp = match timestamp {
Some(timestamp) => timestamp,
None => SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|error| format!("System time is before Unix epoch: {error}"))?
.map_err(|error| format!("系统时间早于 Unix epoch{error}"))?
.as_secs(),
};
@@ -411,7 +405,7 @@ pub fn launcher_authorization_header(
"version": launcher_version,
});
let head_json = serde_json::to_string(&head)
.map_err(|error| format!("Failed to serialize launcher auth head: {error}"))?;
.map_err(|error| format!("序列化启动器认证 head 失败:{error}"))?;
let mut hasher = Md5::new();
hasher.update(head_json.as_bytes());
hasher.update(body.as_bytes());
@@ -422,7 +416,7 @@ pub fn launcher_authorization_header(
"head": head,
"sign": sign,
}))
.map_err(|error| format!("Failed to serialize launcher auth header: {error}"))
.map_err(|error| format!("序列化启动器认证 header 失败:{error}"))
}
#[cfg(test)]