Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
42 changes: 40 additions & 2 deletions .github/scripts/build_git_over_cdn_eo_esa.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { spawn, spawnSync } from "node:child_process";
import yazl from "yazl";

const env = process.env;
const MAIN_SITE_URL = "https://alas.nanoda.work/";
const MAIN_SITE_HOST = "alas.nanoda.work";
const SEO_TITLE = "AzurPilot 更新 CDN - 碧蓝航线自动化更新镜像";
const SEO_DESCRIPTION = "AzurPilot 更新 CDN 提供 Git over CDN 静态更新文件、latest.json、更新包状态与最近提交信息,主站为 https://alas.nanoda.work/。";

function printHelp() {
console.log(`构建 EO/ESA/Pages 可用的 git-over-cdn 静态更新文件。
Expand Down Expand Up @@ -301,6 +305,10 @@ function escapeHtml(value) {
.replaceAll("'", "'");
}

function escapeScriptJson(value) {
return String(value).replaceAll("<", "\\u003c");
}

function shortCommit(commit) {
return commit.slice(0, 8);
}
Expand Down Expand Up @@ -348,6 +356,20 @@ function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos) {
const generatedAtTimestamp = Date.now();
const generatedAt = new Date(generatedAtTimestamp).toISOString();
const commitAge = formatDuration(generatedAtTimestamp - latestCommitInfo.committedAtTimestamp);
const structuredData = escapeScriptJson(JSON.stringify({
"@context": "https://schema.org",
"@type": "WebPage",
name: SEO_TITLE,
description: SEO_DESCRIPTION,
isPartOf: {
"@type": "WebSite",
name: "AzurPilot",
url: MAIN_SITE_URL,
},
about: "AzurPilot Git over CDN 更新镜像",
relatedLink: MAIN_SITE_URL,
dateModified: generatedAt,
}, null, 2));
const packRows = oldCommits.map((commit) => {
const filename = `${latest}/${commit}.zip`;
return `
Expand All @@ -373,7 +395,21 @@ function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos) {
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AzurPilot 更新 CDN</title>
<title>${escapeHtml(SEO_TITLE)}</title>
<meta name="description" content="${escapeHtml(SEO_DESCRIPTION)}">
<meta name="robots" content="index,follow">
<link rel="canonical" href="${escapeHtml(MAIN_SITE_URL)}">
<link rel="home" href="${escapeHtml(MAIN_SITE_URL)}">
<meta property="og:type" content="website">
<meta property="og:site_name" content="AzurPilot">
<meta property="og:title" content="${escapeHtml(SEO_TITLE)}">
<meta property="og:description" content="${escapeHtml(SEO_DESCRIPTION)}">
<meta property="og:url" content="${escapeHtml(MAIN_SITE_URL)}">
<meta property="og:see_also" content="${escapeHtml(MAIN_SITE_URL)}">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="${escapeHtml(SEO_TITLE)}">
<meta name="twitter:description" content="${escapeHtml(SEO_DESCRIPTION)}">
<script type="application/ld+json">${structuredData}</script>
<style>
:root {
color-scheme: light dark;
Expand Down Expand Up @@ -478,14 +514,16 @@ function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos) {
<body>
<main>
<h1>AzurPilot 更新 CDN</h1>
<p>此页面由构建脚本自动生成,用于确认静态更新文件已发布。</p>
<p>此页面由构建脚本自动生成,用于确认静态更新文件已发布。项目主站:<a href="${escapeHtml(MAIN_SITE_URL)}">${escapeHtml(MAIN_SITE_HOST)}</a></p>

<section>
<dl>
<dt>最新版本</dt>
<dd><code>${escapeHtml(latest)}</code></dd>
<dt>构建分支</dt>
<dd><code>${escapeHtml(options.branch)}</code></dd>
<dt>项目主站</dt>
<dd><a href="${escapeHtml(MAIN_SITE_URL)}">${escapeHtml(MAIN_SITE_URL)}</a></dd>
<dt>更新包数量</dt>
<dd>${oldCommits.length}</dd>
<dt>生成时间</dt>
Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@

# AzurPilot
# AzurPilot — 碧蓝航线自动化辅助工具

<p align="center">
<img src="doc/logo.webp" alt="AzurPilot Logo" width="400">
</p>

<p align="center">
<strong><a href="https://alas.nanoda.work/">AzurPilot 官网</a></strong> | 碧蓝航线自动化脚本 · 大世界侵蚀循环 · 多平台支持
</p>

<p align="center">
<a href="https://deepwiki.com/wess09/AzurPilot">
<img src="https://deepwiki.com/badge.svg" alt="DeepWiki" height="22">
Expand Down Expand Up @@ -48,7 +52,7 @@ AzurPilot 是基于 AzurLaneAutoScript 修改而来的碧蓝航线自动化辅
- 其他社区贡献的实用 [Pull Request](https://github.com/LmeSzinc/AzurLaneAutoScript/pulls)

<div align="center">
<a href="https://alas.nanoda.work">
<a href="https://alas.nanoda.work/">
<img src="https://img.shields.io/badge/Web-下载-blue?style=for-the-badge&logo=google-chrome&logoColor=white" />
</a>
&nbsp;&nbsp;&nbsp;&nbsp;
Expand All @@ -57,13 +61,17 @@ AzurPilot 是基于 AzurLaneAutoScript 修改而来的碧蓝航线自动化辅
</a>
</div>

访问 **[AzurPilot 官网](https://alas.nanoda.work/)** 了解更多功能详情,或前往 **[下载页面](https://alas.nanoda.work/download.html)** 获取最新版本。

## GUI
<div align="center">
<img src="doc/GUI.png" alt="GUI Preview" width="800">
</div>

## 依赖与启动

> 💡 **推荐方式**:直接从 [AzurPilot 官网下载页](https://alas.nanoda.work/download.html) 下载对应平台的启动器,内置 Python 环境,开箱即用。

本项目使用 `uv` 和项目根目录 `.venv` 管理 Python 运行环境。发布版启动器会自带 uv、Python、ADB、Git,并在 `.venv` 中同步依赖;源码开发时可安装 uv 后运行:

```bash
Expand Down Expand Up @@ -195,6 +203,8 @@ curl -fsSL https://alas.nanoda.work/install/deploy-image.sh | sudo -E bash

## 多平台启动器

> 📥 从 [AzurPilot 官网](https://alas.nanoda.work/download.html) 下载 Windows / macOS / Linux 启动器

<div align="center">
<img src="doc/loading.png" alt="loading" width="500" />
<p>启动加载界面</p>
Expand Down Expand Up @@ -733,6 +743,14 @@ adb shell wm density reset
<img src="https://contrib.rocks/image?repo=wess09/alas-launcher&max=1000" alt="Launcher Contributors">
</a>

## 相关链接

- [AzurPilot 官网](https://alas.nanoda.work/) — 项目介绍、功能详情、碧蓝航线自动化方案
- [AzurPilot 下载页](https://alas.nanoda.work/download.html) — 下载 Windows / macOS / Linux 版本的碧蓝航线脚本工具
- [GitHub 仓库](https://github.com/wess09/AzurPilot) — 源码、Issue、Pull Request
- [QQ 交流群](https://join.nanoda.work/#/) — 碧蓝航线自动化社区交流
- [AzurLaneAutoScript 上游项目](https://github.com/LmeSzinc/AzurLaneAutoScript) — ALAS 原版

## 开发说明

本项目基本完全是 VibeCoding 产物 不足之处请见谅
Expand Down
36 changes: 19 additions & 17 deletions alas.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ def run(self, command, skip_first_screenshot=False):
action='通常无需处理;若反复发生,请检查游戏包名、模拟器状态和登录流程。',
exc=e,
level=30,
# 预期恢复路径仅保留异常摘要,避免堆栈淹没后续重启日志。
with_traceback=False,
)
self._check_sensitive_exit(command, e)
handle_notify(
Expand Down Expand Up @@ -340,7 +342,7 @@ def run(self, command, skip_first_screenshot=False):
if self.config.Error_GameStuckRestart:
self.consecutive_game_stuck += 1
limit = int(self.config.Error_GameStuckThreshold)
logger.warning(f'GameStuckError: {self.consecutive_game_stuck}/{limit}')
logger.warning(f'[Alas] GameStuckError: {self.consecutive_game_stuck}/{limit}')
if self.consecutive_game_stuck >= limit:
logger.warning('[Alas] 游戏卡住次数过多,正在重启模拟器...')
if self._try_restart_emulator():
Expand Down Expand Up @@ -588,7 +590,7 @@ def save_error_log(self):
image = handle_sensitive_image(data['image'])
save_image(image, f'{folder}/{image_time}.png')
except Exception as e:
logger.error(f"Save error screenshot failed: {e}")
logger.error(f"[Alas] 保存错误截图失败: {e}")

try:
with open(logger.log_file, 'r', encoding='utf-8') as f:
Expand All @@ -603,7 +605,7 @@ def save_error_log(self):
with open(f'{folder}/log.txt', 'w', encoding='utf-8') as f:
f.writelines(lines)
except Exception as e:
logger.error(f"Save error logs failed: {e}")
logger.error(f"[Alas] 保存错误日志失败: {e}")

self.keep_last_errlog(config_folder, getattr(self.config, 'Error_SaveErrorCount', 0))

Expand Down Expand Up @@ -1062,7 +1064,7 @@ def emulator_manager(self):
# 回退到 EmulatorManager 配置
enable = deep_get(self.config.data, 'EmulatorManager.EmulatorManager.EnableRemoteSSH', False)
if not enable:
logger.warning('Remote SSH is not enabled in EmulatorManager settings.')
logger.warning('[Alas-SSH] 模拟器管理器设置中未启用远程SSH')
return

host = deep_get(self.config.data, 'EmulatorManager.EmulatorManager.RemoteSSHHost', '')
Expand All @@ -1074,10 +1076,10 @@ def emulator_manager(self):
key = deep_get(self.config.data, 'EmulatorManager.EmulatorManager.RemoteSSHPublicKey', '')

if not host or not command:
logger.warning(f'RemoteSSHHost ({host}) or RemoteStartCommand ({command}) is empty, skip remote SSH command')
logger.warning(f'[Alas-SSH] 远程SSH主机 ({host}) 或远程启动命令 ({command}) 为空,跳过远程SSH命令')
return

logger.hr('Remote SSH Command', level=1)
logger.hr('远程SSH命令', level=1)
target = f'{user}@{host}' if user else host
clear_ssh_host_key(host, port)
# -n: 禁用标准输入 -T: 禁用伪终端分配 BatchMode: 避免密码提示导致挂起
Expand Down Expand Up @@ -1107,12 +1109,12 @@ def emulator_manager(self):
os.chmod(key_file, 0o600)

cmd += ['-i', key_file]
logger.info(f'Using provided private key for authentication')
logger.info(f'[Alas-SSH] 使用提供的私钥进行认证')
except Exception as e:
logger.error(f'Failed to create or secure temporary key file: {e}')
logger.error(f'[Alas-SSH] 创建或保护临时密钥文件失败: {e}')

cmd += [target, command]
logger.info(f'Executing remote command: {" ".join(cmd)}')
logger.info(f'[Alas-SSH] 执行远程命令: {" ".join(cmd)}')

try:
process = subprocess.Popen(
Expand All @@ -1134,8 +1136,8 @@ def collect_stderr():

def collect_stdout():
for line in process.stdout:
logger.info(f'Remote: {line.strip()}')
logger.info(f'[Alas-SSH] 远程输出: {line.strip()}')

stderr_thread = threading.Thread(target=collect_stderr)
stdout_thread = threading.Thread(target=collect_stdout)
stderr_thread.start()
Expand All @@ -1146,20 +1148,20 @@ def collect_stdout():
process.wait(timeout=30)
except subprocess.TimeoutExpired:
process.kill()
logger.error('Remote SSH command timed out after 30 seconds')
logger.error('[Alas-SSH] 远程SSH命令超时(30秒)')
return
finally:
stderr_thread.join(timeout=5)
stdout_thread.join(timeout=5)

if process.returncode == 0:
logger.info('Remote command executed successfully')
logger.info('[Alas-SSH] 远程命令执行成功')
else:
logger.error(f'Remote command failed with return code {process.returncode}')
logger.error(f'[Alas-SSH] 远程命令失败,返回码 {process.returncode}')
for line in stderr_content:
logger.error(f'Remote Error: {line}')
logger.error(f'[Alas-SSH] 远程错误: {line}')
except Exception as e:
logger.error(f'Failed to execute remote SSH command: {e}')
logger.error(f'[Alas-SSH] 执行远程SSH命令失败: {e}')
finally:
if key_file and os.path.exists(key_file):
try:
Expand Down Expand Up @@ -1454,7 +1456,7 @@ def loop(self):
from module.llm import analyze_exception
analyze_exception(self.config, e)
except Exception as ex:
logger.error(f'LLM Analysis failed: {ex}')
logger.error(f'[Alas] LLM错误分析失败: {ex}')

logger.warning(
f">>> 这是第 {consecutive_global_failures} 次连续全局失败,共 {MAX_GLOBAL_FAILURES} 次。"
Expand Down
1 change: 1 addition & 0 deletions config/template.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"ServerName": "disabled",
"ScreenshotMethod": "auto",
"ControlMethod": "MaaTouch",
"GameSettings": false,
"ScreenshotDedithering": false,
"AdbRestart": false
},
Expand Down
Loading
Loading