diff --git a/.gitignore b/.gitignore index cf5310d..4da9b0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .claude/settings.local.json graphify-out/cost.json -graphify-out/cache/ +graphify-out/cache/ +__pycache__/ +*.pyc diff --git a/CLAUDE.md b/CLAUDE.md index fdd2473..c7012da 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -421,13 +421,16 @@ When debugging image matching, write debug images to `./scratchpad`. ## Existing features -Current project state before Python-first refactor: +Current project state (Phase 1 skeleton in place, mailbox migrated): -- mailbox claim exists -- cafe affection/income exists -- cafe uses OpenCV template matching for sparkle detection -- existing Python helper is `scripts/detect_and_click.py` -- existing Bash entry point is `ba_dailies.sh` +- `ba_dailies.sh` is a thin launcher that execs `ba_daily.py` +- `ba_daily.py` dispatches `mailbox`/`cafe`/default flow to `ba_auto/tasks/` +- `ba_auto/tasks/mailbox.py` is real Python: it clicks with `ba_auto/driver.py` primitives and verifies state with `driver.color_at` (ported from `module/mail.py`'s `rgb_in_range` pattern) before pressing further keys — no legacy bridge +- `ba_auto/tasks/cafe.py` still bridges to `scripts/ba_dailies_legacy.sh` (the pre-migration click sequence), marked with a TODO — the real logic has not moved into Python yet +- cafe's bridge still shells out to `scripts/detect_and_click.py` for OpenCV sparkle detection, unchanged +- `ba_auto/driver.py` has the primitives (`run_command`, `focus_game`, `click`, `keypress`, `screenshot`, `wait`, `color_at`) wired into `mailbox.py`; not yet used by `cafe.py` +- `ba_auto/detector.py` and `ba_auto/navigation.py` are placeholders +- Live testing found the old fixed mailbox-icon coordinate was marginal and could miss, cascading into an unverified Escape press that triggers Blue Archive's own "exit the game?" confirmation — see `plan.md` Phase 5 for details. This is the concrete reason `mailbox.py` verifies state before acting rather than trusting fixed coordinates blindly. Migration goal: diff --git a/README.md b/README.md new file mode 100644 index 0000000..6591955 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# ba-auto-daily + +Personal automation for Blue Archive JP daily tasks (mailbox, cafe, ...), driven by desktop control (`xdotool`/`scrot`) against the real PC/Steam/Proton client. + +## How it's wired together + +Two machines are involved: + +- **`nik-macbookair`** (this Mac) — where the code is edited. The game does not run here and none of this can be tested locally. +- **`nik-gpu`** (Linux) — where the actual Blue Archive client runs under Steam/Proton, and where every command below actually executes. + +Call path once deployed to `nik-gpu`: + +``` +~/ba_dailies.sh [command] <- thin Bash launcher, no game logic + | + v +~/ba_daily.py [command] <- Python CLI, dispatches to a task + | + v +~/ba_auto/tasks/.py <- the actual click/verify logic +``` + +`ba_dailies.sh` only picks the venv Python and execs `ba_daily.py`. All real logic — clicking, screenshotting, verifying game state — lives in Python under `ba_auto/`. + +Current task status: + +| Command | Implementation | +|---|---| +| `mailbox` | Real Python (`ba_auto/tasks/mailbox.py`). Verifies the mailbox panel actually opened (via a pixel-color probe, `driver.color_at`) before clicking "claim all" or pressing any further keys. Retries the open-click up to 3 times before giving up safely. | +| `cafe` | Still a bridge: `ba_auto/tasks/cafe.py` shells out to `scripts/ba_dailies_legacy.sh cafe`, the original unverified fixed-coordinate click sequence, unchanged. Not yet hardened — see "Known issue" below. | + +## Prerequisites on nik-gpu + +One-time, or after a dependency change: + +```bash +ssh nik-gpu +which xdotool scrot # both must be installed +``` + +`setup.sh` (see below) creates the Python venv and checks these for you. + +## Deploying your changes + +From `nik-macbookair`, in the repo root: + +```bash +rsync -av --delete \ + --exclude='.git/' --exclude='__pycache__/' --exclude='*.pyc' \ + --exclude='.claude/settings.local.json' --exclude='graphify-out/' \ + --exclude='screenshots/' \ + ./ nik-gpu:~/repo/ba-auto-daily/ + +ssh nik-gpu "cd ~/repo/ba-auto-daily && ./setup.sh" +``` + +`setup.sh` copies the synced files into the fixed runtime paths (`~/ba_dailies.sh`, `~/ba_daily.py`, `~/ba_auto/`, `~/ba_scripts/`, `~/ba_assets/`) and (re)installs the venv. Re-run both commands any time you change code — there is no auto-deploy. + +## Running it + +The game must already be running on `nik-gpu` (window titled `BlueArchive`). Then, on `nik-gpu`: + +```bash +~/ba_dailies.sh # default: mailbox, then cafe +~/ba_dailies.sh mailbox # just mailbox +~/ba_dailies.sh cafe # just cafe +``` + +You can also run these remotely without a separate `ssh` login step: + +```bash +ssh nik-gpu "~/ba_dailies.sh mailbox" +``` + +Exit code `0` means the script ran to completion — it does **not** by itself guarantee the in-game action succeeded (`mailbox` checks and logs this explicitly; `cafe` currently does not). + +## How to watch/verify it + +- **Easiest: watch it live.** If you have Moonlight (or similar) streaming `nik-gpu`'s desktop, just open that and run the command from another terminal — you'll see the clicks happen in real time. +- **No stream handy: pull a screenshot after the fact.** + ```bash + ssh nik-gpu "DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority scrot -o /tmp/check.png" + scp nik-gpu:/tmp/check.png . + open check.png + ``` +- **Read the log output.** Each task prints what it's doing, e.g. `mailbox` prints `panel not detected after click (attempt N/3)` if a click misses, and `nothing to claim` vs `claiming all` depending on what it found. + +## Known issue: don't trust `cafe` unattended yet + +Live-testing `mailbox` surfaced a real bug in the *original*, unverified click sequence (see `plan.md` Phase 5 for the full writeup): a slightly-off icon coordinate caused a missed click, and the fixed sequence blindly kept going — clicking, pressing Enter, pressing Escape — with no idea whether any of it landed. The trailing Escape ended up hitting the bare home screen, which triggers Blue Archive's own **"Exit the game?"** confirmation dialog. + +`mailbox.py` now guards against this (verifies state before acting, retries, aborts safely instead of guessing). **`cafe` still uses the old unverified bridge and has the same failure mode.** If you run `~/ba_dailies.sh cafe` and something looks off, check the screen before pressing anything — if you see an unexpected confirmation dialog, press **Escape/Cancel**, never Enter/OK, until you've confirmed what it's asking. + +## Local checks (nik-macbookair) + +The game can't run here, so this only catches syntax errors, not behavior: + +```bash +bash -n ba_dailies.sh +bash -n scripts/ba_dailies_legacy.sh +python3 -m py_compile ba_daily.py ba_auto/*.py ba_auto/tasks/*.py +``` + +Real verification only happens by actually running against the live game on `nik-gpu`, per "How to watch/verify it" above. + +## More detail + +- `CLAUDE.md` — architecture rules and conventions for this repo. +- `plan.md` — feature-by-feature migration status and backlog, including the mailbox bug writeup. +- `ba_auto/reference_notes/mapping.md` — maps each feature to its `baas-reference` source. diff --git a/ba_auto/__init__.py b/ba_auto/__init__.py new file mode 100644 index 0000000..c3fc88a --- /dev/null +++ b/ba_auto/__init__.py @@ -0,0 +1 @@ +"""Python-first Blue Archive daily-automation package.""" diff --git a/ba_auto/config.py b/ba_auto/config.py new file mode 100644 index 0000000..ab1421b --- /dev/null +++ b/ba_auto/config.py @@ -0,0 +1,16 @@ +"""Central configuration for ba-auto-daily, ported from the old ba_dailies.sh.""" +import os + +DISPLAY = ":0" +XAUTHORITY = "/run/user/1000/gdm/Xauthority" +ENV = {**os.environ, "DISPLAY": DISPLAY, "XAUTHORITY": XAUTHORITY} + +WINDOW_NAME = "BlueArchive" + +# TODO: remove once cafe is ported off the legacy Bash bridge (mailbox no longer uses this). +LEGACY_SCRIPT = os.path.expanduser("~/ba_scripts/ba_dailies_legacy.sh") + +# Refined from (1726, 60): that coordinate sat on the edge of the icon's +# hitbox and intermittently missed during live testing. +MAILBOX_ICON = (1732, 50) +CLAIM_ALL = (1691, 1128) diff --git a/ba_auto/detector.py b/ba_auto/detector.py new file mode 100644 index 0000000..942351e --- /dev/null +++ b/ba_auto/detector.py @@ -0,0 +1,3 @@ +"""Image/color matching helpers (OpenCV-based); not wired up yet.""" + +# TODO: migrate scripts/detect_and_click.py sparkle-matching logic here. diff --git a/ba_auto/driver.py b/ba_auto/driver.py new file mode 100644 index 0000000..9afae8a --- /dev/null +++ b/ba_auto/driver.py @@ -0,0 +1,52 @@ +"""Local PC/Steam/Proton control backend (xdotool/scrot wrappers).""" +import subprocess +import time + +import cv2 + +from ba_auto import config + +PROBE_SHOT_PATH = "/tmp/ba_auto_probe.png" + + +def run_command(args, **kwargs): + kwargs.setdefault("env", config.ENV) + kwargs.setdefault("check", True) + return subprocess.run(args, **kwargs) + + +def focus_game(): + result = run_command( + ["xdotool", "search", "--name", config.WINDOW_NAME], + check=False, capture_output=True, text=True, + ) + window_ids = result.stdout.split() + if not window_ids: + raise RuntimeError("Blue Archive window not found. Is the game running?") + run_command(["xdotool", "windowactivate", window_ids[0]]) + wait(0.5) + + +def click(x, y): + run_command(["xdotool", "mousemove", str(x), str(y), "click", "1"]) + wait(0.5) + + +def keypress(key): + run_command(["xdotool", "key", key]) + wait(0.5) + + +def screenshot(path): + run_command(["scrot", "-a", "0,0,1920,1200", "-o", path]) + + +def color_at(x, y): + screenshot(PROBE_SHOT_PATH) + image = cv2.imread(PROBE_SHOT_PATH) + b, g, r = image[y, x] + return int(r), int(g), int(b) + + +def wait(seconds): + time.sleep(seconds) diff --git a/ba_auto/navigation.py b/ba_auto/navigation.py new file mode 100644 index 0000000..f7a98c3 --- /dev/null +++ b/ba_auto/navigation.py @@ -0,0 +1,3 @@ +"""Shared navigation helpers (home, menu, popups, back/escape); not wired up yet.""" + +# TODO: extract common navigation flows here as tasks are ported off Bash. diff --git a/ba_auto/reference_notes/mapping.md b/ba_auto/reference_notes/mapping.md new file mode 100644 index 0000000..f978ebb --- /dev/null +++ b/ba_auto/reference_notes/mapping.md @@ -0,0 +1,18 @@ +# Reference mapping + +Maps each local feature to the corresponding `~/repo/baas-reference/module/...` implementation, driver/backend replacements, and current status. Update this before/while implementing or migrating a feature — see `CLAUDE.md` → "Reference mapping notes" and `plan.md` → "Reference mapping table". + +| Local feature | Reference file | Reference functions/classes | Local file | Backend replacements | Status | +|---|---|---|---|---|---| +| Mailbox | `module/mail.py` | `to_mail`, `implement` | `ba_auto/tasks/mailbox.py` | tap/click via xdotool, screenshot via scrot, `color.rgb_in_range` → `driver.color_at` pixel-probe check | Migrated: real Python, state-verified via color probe (no legacy bridge) | +| Cafe | Need to confirm in reference | Need to inspect | `ba_auto/tasks/cafe.py` | template matching via OpenCV, click via xdotool | Bridged to `scripts/ba_dailies_legacy.sh` + `scripts/detect_and_click.py`; Python port pending (plan.md Phase 6) | +| Stamina/AP | `module/collect_daily_free_power.py`, `module/collect_daily_task_power.py` | Need to inspect | `ba_auto/tasks/stamina.py` | color checks/clicks via local driver | Not started | +| Group/Club AP | `module/group.py` | Need to inspect | `ba_auto/tasks/group.py` | fixed click + state check via local driver | Not started | +| Bounty | `module/rewarded_task.py` | Need to inspect | `ba_auto/tasks/bounty.py` | sweep/color/OCR adaptation | Not started | +| Commissions | `module/clear_special_task_power.py` | Need to inspect | `ba_auto/tasks/commission.py` | sweep/color adaptation | Not started | +| Arena | `module/arena.py` | Need to inspect | `ba_auto/tasks/arena.py` | auto-fight + OCR + local driver | Not started | +| Common Shop | `module/shop/common_shop.py`, `module/shop/shop_utils.py` | Need to inspect | `ba_auto/tasks/shop_common.py` | OCR + tab navigation + local clicks | Not started | +| Tactical Shop | `module/shop/tactical_challenge_shop.py`, `module/shop/shop_utils.py` | Need to inspect | `ba_auto/tasks/shop_tactical.py` | OCR + tab navigation + local clicks | Not started | +| Lesson/Schedule | `module/lesson.py` | Need to inspect | `ba_auto/tasks/lesson.py` | OCR + template/portrait search + local driver | Not started | + +Do not implement a feature without filling at least the relevant row. diff --git a/ba_auto/tasks/__init__.py b/ba_auto/tasks/__init__.py new file mode 100644 index 0000000..80594bd --- /dev/null +++ b/ba_auto/tasks/__init__.py @@ -0,0 +1 @@ +"""Task modules dispatched by ba_daily.py.""" diff --git a/ba_auto/tasks/cafe.py b/ba_auto/tasks/cafe.py new file mode 100644 index 0000000..734929e --- /dev/null +++ b/ba_auto/tasks/cafe.py @@ -0,0 +1,6 @@ +"""Cafe daily task.""" + + +def run(driver, config): + # TODO: migrate old Bash cafe logic into this Python task. + driver.run_command([config.LEGACY_SCRIPT, "cafe"]) diff --git a/ba_auto/tasks/mailbox.py b/ba_auto/tasks/mailbox.py new file mode 100644 index 0000000..f3b2fe9 --- /dev/null +++ b/ba_auto/tasks/mailbox.py @@ -0,0 +1,58 @@ +"""Mailbox daily task. Ported from baas-reference module/mail.py's color-probe pattern.""" + +# (500, 10) sits on the mailbox panel's plain header background; the home +# screen shows character art there instead, so this tells open vs not-open. +HEADER_PROBE = (500, 10) +HEADER_OPEN_MIN_CHANNEL = 200 + +# "Claim all" renders as flat grey (153,153,153) when there is nothing to claim. +CLAIM_ALL_PROBE = (1710, 1128) +CLAIM_ALL_DISABLED_RGB = (153, 153, 153) +CLAIM_ALL_DISABLED_TOLERANCE = 12 + +OPEN_RETRIES = 3 + + +def _is_open(driver): + r, g, b = driver.color_at(*HEADER_PROBE) + return r > HEADER_OPEN_MIN_CHANNEL and g > HEADER_OPEN_MIN_CHANNEL and b > HEADER_OPEN_MIN_CHANNEL + + +def _claim_all_disabled(driver): + r, g, b = driver.color_at(*CLAIM_ALL_PROBE) + tr, tg, tb = CLAIM_ALL_DISABLED_RGB + return ( + abs(r - tr) <= CLAIM_ALL_DISABLED_TOLERANCE + and abs(g - tg) <= CLAIM_ALL_DISABLED_TOLERANCE + and abs(b - tb) <= CLAIM_ALL_DISABLED_TOLERANCE + ) + + +def run(driver, config): + driver.focus_game() + + opened = False + for attempt in range(1, OPEN_RETRIES + 1): + driver.click(*config.MAILBOX_ICON) + driver.wait(1.5) + if _is_open(driver): + opened = True + break + print(f"[mailbox] panel not detected after click (attempt {attempt}/{OPEN_RETRIES})") + + if not opened: + print("[mailbox] could not confirm mailbox is open, aborting without pressing further keys") + return + + if _claim_all_disabled(driver): + print("[mailbox] nothing to claim") + else: + print("[mailbox] claiming all") + driver.click(*config.CLAIM_ALL) + driver.wait(1) + driver.keypress("Return") + driver.wait(1) + + driver.keypress("Escape") + driver.wait(1) + print("[mailbox] Done.") diff --git a/ba_dailies.sh b/ba_dailies.sh index 073858c..e2d09fa 100644 --- a/ba_dailies.sh +++ b/ba_dailies.sh @@ -1,121 +1,13 @@ -#!/bin/bash -export DISPLAY=:0 -export XAUTHORITY=/run/user/1000/gdm/Xauthority +#!/usr/bin/env bash +# Thin launcher only -- see CLAUDE.md "Bash policy". Logic lives in ba_daily.py. +set -euo pipefail -WIN_NAME="BlueArchive" -MAILBOX_ICON="1726 60" -CLAIM_ALL="1691 1128" +VENV_PYTHON="${VENV_PYTHON:-$HOME/.venvs/ba-auto-daily/bin/python3}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CAFE_ICON="165 1100" -CAFE_ROOM_SWITCH="190 160" -CAFE_INCOME="1780 1105" -VENV_PYTHON="$HOME/.venvs/ba-auto-daily/bin/python3" -DETECT_SPARKLE="$HOME/ba_scripts/detect_and_click.py" -CAFE_MAX_CLICKS_PER_ROOM=15 +if [ ! -f "$SCRIPT_DIR/ba_daily.py" ]; then + echo "ERROR: ba_daily.py not found in $SCRIPT_DIR" >&2 + exit 1 +fi -get_window() { - xdotool search --name "$WIN_NAME" | head -1 -} - -click() { - local coords="$1" - xdotool mousemove $coords click 1 - sleep 0.5 -} - -press_enter() { - xdotool key Return - sleep 0.5 -} - -press_esc() { - xdotool key Escape - sleep 0.5 -} - -focus_game() { - local win - win=$(get_window) - if [ -z "$win" ]; then - echo "ERROR: Blue Archive window not found. Is the game running?" - exit 1 - fi - xdotool windowactivate "$win" - sleep 0.5 -} - -do_mailbox() { - echo "[mailbox] Opening mailbox..." - click "$MAILBOX_ICON" - sleep 1.5 - echo "[mailbox] Claiming all..." - click "$CLAIM_ALL" - sleep 1 - echo "[mailbox] Confirming claim..." - press_enter - sleep 1 - echo "[mailbox] Closing mailbox..." - press_esc - sleep 1 - echo "[mailbox] Done." -} - -do_cafe_room() { - local i - for ((i = 0; i < CAFE_MAX_CLICKS_PER_ROOM; i++)); do - if ! "$VENV_PYTHON" "$DETECT_SPARKLE" | grep -q "^MATCH"; then - break - fi - sleep 1 - press_enter - done -} - -do_cafe() { - echo "[cafe] Opening cafe..." - click "$CAFE_ICON" - sleep 3 - press_enter - echo "[cafe] Room 1: farming affection..." - do_cafe_room - echo "[cafe] Switching to room 2..." - click "$CAFE_ROOM_SWITCH" - sleep 3 - press_enter - echo "[cafe] Room 2: farming affection..." - do_cafe_room - echo "[cafe] Claiming cafe income..." - click "$CAFE_INCOME" - sleep 2 - press_enter - sleep 2 - press_enter - sleep 2 - echo "[cafe] Exiting cafe..." - press_esc - sleep 1.5 - press_esc - sleep 1.5 - echo "[cafe] Done." -} - -case "$1" in - mailbox) - focus_game - do_mailbox - ;; - cafe) - focus_game - do_cafe - ;; - "") - focus_game - do_mailbox - do_cafe - echo "All done." - ;; - *) - echo "Unknown phase: $1" - exit 1 - ;; -esac +exec "$VENV_PYTHON" "$SCRIPT_DIR/ba_daily.py" "$@" diff --git a/ba_daily.py b/ba_daily.py new file mode 100644 index 0000000..1369647 --- /dev/null +++ b/ba_daily.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Python CLI entry point: ba_dailies.sh -> ba_daily.py -> ba_auto/tasks/*.py""" +import sys + +from ba_auto import config, driver +from ba_auto.tasks import cafe, mailbox + +TASKS = { + "mailbox": mailbox.run, + "cafe": cafe.run, +} +DEFAULT_ORDER = ["mailbox", "cafe"] + + +def main(argv): + args = argv[1:] + + if not args: + for name in DEFAULT_ORDER: + TASKS[name](driver, config) + print("All done.") + return 0 + + command = args[0] + if command not in TASKS: + print(f"Unknown phase: {command}", file=sys.stderr) + return 1 + + TASKS[command](driver, config) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/plan.md b/plan.md index bcfa345..6e57b21 100644 --- a/plan.md +++ b/plan.md @@ -156,11 +156,11 @@ Do not implement a feature without filling at least the relevant row. | Feature | Current status | Target status | |---|---|---| -| Mailbox claim | Done in old Bash style | Migrate to `ba_auto/tasks/mailbox.py` | -| Cafe pats + income | Done in old Bash + standalone Python detector style | Migrate to `ba_auto/tasks/cafe.py` and `ba_auto/detector.py` | -| Shared driver | Partial/implicit in scripts | Build `ba_auto/driver.py` | -| Python CLI | Missing | Build `ba_daily.py` | -| Reference mapping | Missing | Build `ba_auto/reference_notes/mapping.md` | +| Mailbox claim | Migrated: `ba_auto/tasks/mailbox.py` uses `driver.color_at` to verify the panel opened before acting (found live-testing bug: a marginal icon coordinate could miss and cascade into pressing Escape on the home screen, which triggers Blue Archive's own exit-game confirmation) | Done | +| Cafe pats + income | Bridged: `ba_auto/tasks/cafe.py` calls `scripts/ba_dailies_legacy.sh cafe` (which still shells out to `scripts/detect_and_click.py`) | Migrate to `ba_auto/tasks/cafe.py` and `ba_auto/detector.py` (Phase 6) | +| Shared driver | `ba_auto/driver.py` built (`run_command`, `focus_game`, `click`, `keypress`, `screenshot`, `wait`); not yet wired into tasks | Wire into mailbox/cafe as they migrate off the Bash bridge | +| Python CLI | Built: `ba_daily.py` dispatches `mailbox`/`cafe`/default flow | Extend as new tasks are added | +| Reference mapping | Built: `ba_auto/reference_notes/mapping.md` | Fill in reference file/function columns per feature | | Everything else | Not started | Implement reference-first in Python | ## Migration phase @@ -169,6 +169,8 @@ Before adding new game features, migrate the existing working implementation. ### Phase 1: Python skeleton +**Status: Done.** + Create: ``` @@ -186,6 +188,8 @@ ba_auto/reference_notes/mapping.md ### Phase 2: Launcher +**Status: Done.** + Change `ba_dailies.sh` into a thin launcher: ```bash @@ -206,6 +210,8 @@ Keep compatibility with: ### Phase 3: Driver extraction +**Status: Primitives added to `ba_auto/driver.py`, not yet wired into task modules.** + Move shell interactions into `ba_auto/driver.py`. Driver primitives should include: @@ -223,6 +229,8 @@ wait_until(...) ### Phase 4: Detector extraction +**Status: Not started — `ba_auto/detector.py` is currently a placeholder.** + Move `scripts/detect_and_click.py` logic into `ba_auto/detector.py`. Detector primitives should include: @@ -240,6 +248,8 @@ The old script may remain as a compatibility wrapper temporarily, but the reusab ### Phase 5: Mailbox migration +**Status: Done.** Live testing surfaced a real bug: the old `MAILBOX_ICON` coordinate `(1726, 60)` sat on the edge of the icon's hitbox and intermittently missed, and the fixed click sequence had no way to notice — it cascaded into pressing Escape on the bare home screen, which triggers Blue Archive's own "exit the game?" confirmation (dismissed safely with Cancel during testing; no game state was lost). The Python port in `ba_auto/tasks/mailbox.py` fixes the coordinate and, following `module/mail.py`'s `rgb_in_range` pattern, verifies the panel actually opened (and whether "claim all" is disabled) via `driver.color_at` before pressing any further keys, with a bounded retry and a safe abort if the panel never appears. + Move mailbox logic from Bash to: ``` @@ -250,6 +260,8 @@ The CLI should call it through Python. ### Phase 6: Cafe migration +**Status: Not started — `ba_auto/tasks/cafe.py` currently bridges to `scripts/ba_dailies_legacy.sh`.** + Move cafe logic from Bash to: ``` @@ -268,6 +280,8 @@ During migration, verify: ### Phase 7: setup.sh update +**Status: Done — `setup.sh` now deploys `ba_daily.py`, `ba_auto/`, and `scripts/ba_dailies_legacy.sh`.** + Update `setup.sh` so it deploys: ``` diff --git a/scripts/ba_dailies_legacy.sh b/scripts/ba_dailies_legacy.sh new file mode 100644 index 0000000..92cc0ac --- /dev/null +++ b/scripts/ba_dailies_legacy.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# Legacy pre-migration click sequences; bridged from ba_auto/tasks/*.py until ported to Python (see CLAUDE.md). +export DISPLAY=:0 +export XAUTHORITY=/run/user/1000/gdm/Xauthority + +WIN_NAME="BlueArchive" +MAILBOX_ICON="1726 60" +CLAIM_ALL="1691 1128" + +CAFE_ICON="165 1100" +CAFE_ROOM_SWITCH="190 160" +CAFE_INCOME="1780 1105" +VENV_PYTHON="$HOME/.venvs/ba-auto-daily/bin/python3" +DETECT_SPARKLE="$HOME/ba_scripts/detect_and_click.py" +CAFE_MAX_CLICKS_PER_ROOM=15 + +get_window() { + xdotool search --name "$WIN_NAME" | head -1 +} + +click() { + local coords="$1" + xdotool mousemove $coords click 1 + sleep 0.5 +} + +press_enter() { + xdotool key Return + sleep 0.5 +} + +press_esc() { + xdotool key Escape + sleep 0.5 +} + +focus_game() { + local win + win=$(get_window) + if [ -z "$win" ]; then + echo "ERROR: Blue Archive window not found. Is the game running?" + exit 1 + fi + xdotool windowactivate "$win" + sleep 0.5 +} + +do_mailbox() { + echo "[mailbox] Opening mailbox..." + click "$MAILBOX_ICON" + sleep 1.5 + echo "[mailbox] Claiming all..." + click "$CLAIM_ALL" + sleep 1 + echo "[mailbox] Confirming claim..." + press_enter + sleep 1 + echo "[mailbox] Closing mailbox..." + press_esc + sleep 1 + echo "[mailbox] Done." +} + +do_cafe_room() { + local i + for ((i = 0; i < CAFE_MAX_CLICKS_PER_ROOM; i++)); do + if ! "$VENV_PYTHON" "$DETECT_SPARKLE" | grep -q "^MATCH"; then + break + fi + sleep 1 + press_enter + done +} + +do_cafe() { + echo "[cafe] Opening cafe..." + click "$CAFE_ICON" + sleep 3 + press_enter + echo "[cafe] Room 1: farming affection..." + do_cafe_room + echo "[cafe] Switching to room 2..." + click "$CAFE_ROOM_SWITCH" + sleep 3 + press_enter + echo "[cafe] Room 2: farming affection..." + do_cafe_room + echo "[cafe] Claiming cafe income..." + click "$CAFE_INCOME" + sleep 2 + press_enter + sleep 2 + press_enter + sleep 2 + echo "[cafe] Exiting cafe..." + press_esc + sleep 1.5 + press_esc + sleep 1.5 + echo "[cafe] Done." +} + +case "$1" in + mailbox) + focus_game + do_mailbox + ;; + cafe) + focus_game + do_cafe + ;; + "") + focus_game + do_mailbox + do_cafe + echo "All done." + ;; + *) + echo "Unknown phase: $1" + exit 1 + ;; +esac diff --git a/setup.sh b/setup.sh index f41aaa1..b1c5570 100755 --- a/setup.sh +++ b/setup.sh @@ -5,9 +5,9 @@ # not from macOS, since it installs into nik-gpu-local paths. # # It does NOT touch the game or take any screenshots; it only installs the -# venv and copies files to the fixed paths ba_dailies.sh/detect_and_click.py -# expect (outside the repo, per the two-machine deploy convention in -# CLAUDE.md). +# venv and copies files to the fixed paths ba_dailies.sh/ba_daily.py/ +# detect_and_click.py expect (outside the repo, per the two-machine deploy +# convention in CLAUDE.md). set -e VENV_DIR="$HOME/.venvs/ba-auto-daily" @@ -39,9 +39,16 @@ fi echo "== Deploying scripts + assets to fixed paths ==" mkdir -p "$SCRIPTS_DIR" "$ASSETS_DIR" cp scripts/detect_and_click.py "$SCRIPTS_DIR/detect_and_click.py" +cp scripts/ba_dailies_legacy.sh "$SCRIPTS_DIR/ba_dailies_legacy.sh" +chmod +x "$SCRIPTS_DIR/ba_dailies_legacy.sh" cp assets/cafe_sparkle.png "$ASSETS_DIR/cafe_sparkle.png" + +echo "== Deploying Python-first entry point ==" cp ba_dailies.sh "$HOME/ba_dailies.sh" chmod +x "$HOME/ba_dailies.sh" +cp ba_daily.py "$HOME/ba_daily.py" +rm -rf "$HOME/ba_auto" +cp -r ba_auto "$HOME/ba_auto" echo "== Done ==" echo "Run with: ~/ba_dailies.sh [mailbox|cafe]"