# AMT - ARinera Minecraft Tool Minecraft 实用 TUI 工具。Go + bubbletea,前后端分离。 ## 文件结构 ``` main.go 入口,解析 exe 目录,tea.NewProgram(WithAltScreen) model.go 顶层 model 定义,page 枚举,Init/Update/View 路由 pages.go 4 个页面的 update/view + resetExecState + describeAction scan.go 扫描 /*/.minecraft/versions/* 目录 api.go HTTP 客户端 + Action/StepResponse 类型定义 actions.go action 执行引擎(download/unzip/delete/copy/move/new/backup) items.go list.Item 实现:versionItem, menuItem, mirrorItem styles.go lipgloss 样式常量 backend/ Python FastAPI 后端(独立进程,端口 3131) ``` 全部 Go 文件在项目根目录,package main。`go build -o dist/amt.exe .` ## 依赖 - `bubbletea` v1.3.10 / `bubbles` v1.0.0 / `lipgloss` v1.1.0 - 标准库:`net/http`, `archive/zip`, `encoding/json`, `io`, `os`, `path/filepath` ## 架构:页面状态机 ``` pageVersionSelect ──Enter──▶ pageMainMenu ──"输入数字码"──▶ pageCodeInput ──Enter(4位)──▶ pageExecuting ▲ │ │ │ │ │ Esc/q=退出 │ Esc=返回 任意键=返回 └──"切换版本"─────────────┘ └──────────────────────────┘◀──────────────────────────┘ ``` ### model 核心字段 | 字段 | 类型 | 用途 | |------|------|------| | `currentPage` | `page` (iota 枚举) | 当前页面 | | `exeDir` | string | 可执行文件所在目录 | | `versionDir` | string | 选中的版本完整路径 | | `versionName` | string | 版本目录名 | | `versionList` | list.Model | 版本选择列表(Init 时预初始化,避免零值 panic) | | `menuList` | list.Model | 主菜单列表(同上) | | `codeInput` | textinput.Model | 4 位数字码输入框 | | `actions` | []Action | 当前执行的 action 列表 | | `actionIdx` | int | 当前执行到第几个 | | `logLines` | []string | 执行日志(也被 viewVersionSelect 用于显示扫描错误) | | `execErr` / `execDone` | error / bool | 执行状态 | | `choosingMirror` | bool | 是否在 mirror 选择子页面 | | `pendingAction` | *Action | mirror 选择时暂存的 action | | `backupDir` | string | 当次执行的备份相对路径 `amt/backup/` | | `progressCh` | chan float64 | 下载进度 channel(预留) | ### 消息类型 | 消息 | 来源 | 处理页面 | |------|------|----------| | `versionsFoundMsg` | `scanVersions()` | pageVersionSelect | | `scanErrorMsg` | `scanVersions()` | pageVersionSelect | | `actionsReceivedMsg` | `fetchActions()` | pageExecuting | | `apiErrorMsg` | `fetchActions()` | pageExecuting | | `actionCompleteMsg` | `executeAction()` | pageExecuting | | `actionErrorMsg` | `executeAction()` | pageExecuting | | `actionProgressMsg` | `waitForProgress()` | pageExecuting | | `mirrorChoiceMsg` | `executeAdd()` | pageExecuting | ### 状态清理规则 **关键约束**:`logLines` 被 `viewVersionSelect` 用于判断是否显示错误,必须在离开相关上下文时清空。 - **进入 pageExecuting (T4)**:调用 `resetExecState(m)` 清空所有执行状态 - **离开 pageExecuting (T6)**:同上 - **进入 pageVersionSelect (T3)**:清空 `logLines` + 清空列表项 - **`versionsFoundMsg` 到达时**:清空 `logLines`(覆盖旧扫描错误) - **离开 pageVersionSelect (T1)**:清空 `logLines` - **mirror Esc 取消**:清空 `pendingAction` `resetExecState` 清空字段:`actions`, `actionIdx`, `logLines`, `execErr`, `execDone`, `choosingMirror`, `pendingAction`, `backupDir`, `progressCh` ## 后端 API 地址:`http://localhost:3131` ### GET /tools?code=XXXX 返回 step 结构: ```json { "actions": [ {"type": "add", "path": "相对路径", "unzip": false, "url": "https://...", "mirrors": ["https://..."]}, {"type": "delete", "path": "相对路径"}, {"type": "copy", "path": "源相对路径", "new_path": "目标相对路径"}, {"type": "move", "path": "源相对路径", "new_path": "目标相对路径"}, {"type": "new", "path": "相对路径", "is_dir": false} ] } ``` 所有 `path` 相对于 `versionDir`。 ## Action 执行逻辑 (actions.go) 顺序执行,任一失败立即中断。 | type | 行为 | 备份 | |------|------|------| | `add` | 下载 url→path,unzip=true 时解压后删 zip | 目标已存在则备份 | | `delete` | 删除 path | 删前备份 | | `copy` | 复制 path→new_path | new_path 已存在则备份 | | `move` | os.Rename,失败则 copy+delete | new_path 已存在则备份 | | `new` | is_dir=true 创建目录,否则创建空文件(父目录自动创建)。目标已存在则跳过 | 无 | ### 备份 目录:`/amt/backup//<原相对路径>` 每次执行创建一个时间戳目录,同次执行的所有备份共享。 ### 下载失败 Mirror 处理 主 URL 失败 → 发送 `mirrorChoiceMsg` → 设 `choosingMirror=true` → 显示 mirror 列表 → 用户选择后用新 URL 重试。用户 Esc 取消则中断执行。 ### 关键函数 ``` executeAction(versionDir, action, index, backupDir) → tea.Cmd → actionCompleteMsg | actionErrorMsg | mirrorChoiceMsg executeAdd(...) add 专用,处理下载/解压/mirror downloadFile(url, dest) 标准 http.Get + 临时文件 + rename unzipFile(zip, dir) archive/zip 解压,含 zip slip 防护 backupPath(versionDir, relPath, backupDir) 备份单个文件/目录 copyPath(src, dst) 递归复制文件/目录 copyFile(src, dst) 单文件复制 copyDir(src, dst) filepath.Walk 递归复制 ``` ## 版本扫描 (scan.go) `filepath.Glob(exeDir/*/.minecraft/versions/*)` → 过滤目录 → `versionsFoundMsg` ## 构建 ```bash go build -o dist/amt.exe . # 或 make make backend # 启动后端 (uvicorn, port 3131) ```