Migrate to Python-first architecture: implement mailbox and cafe tasks, replace Bash scripts with Python CLI, and establish configuration and driver modules.
This commit is contained in:
parent
ebce31156b
commit
398b936c64
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
.claude/settings.local.json
|
||||
graphify-out/cost.json
|
||||
graphify-out/cache/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
15
CLAUDE.md
15
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:
|
||||
|
||||
|
||||
111
README.md
Normal file
111
README.md
Normal file
@ -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/<command>.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.
|
||||
1
ba_auto/__init__.py
Normal file
1
ba_auto/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Python-first Blue Archive daily-automation package."""
|
||||
16
ba_auto/config.py
Normal file
16
ba_auto/config.py
Normal file
@ -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)
|
||||
3
ba_auto/detector.py
Normal file
3
ba_auto/detector.py
Normal file
@ -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.
|
||||
52
ba_auto/driver.py
Normal file
52
ba_auto/driver.py
Normal file
@ -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)
|
||||
3
ba_auto/navigation.py
Normal file
3
ba_auto/navigation.py
Normal file
@ -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.
|
||||
18
ba_auto/reference_notes/mapping.md
Normal file
18
ba_auto/reference_notes/mapping.md
Normal file
@ -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.
|
||||
1
ba_auto/tasks/__init__.py
Normal file
1
ba_auto/tasks/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Task modules dispatched by ba_daily.py."""
|
||||
6
ba_auto/tasks/cafe.py
Normal file
6
ba_auto/tasks/cafe.py
Normal file
@ -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"])
|
||||
58
ba_auto/tasks/mailbox.py
Normal file
58
ba_auto/tasks/mailbox.py
Normal file
@ -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.")
|
||||
128
ba_dailies.sh
128
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" "$@"
|
||||
|
||||
34
ba_daily.py
Normal file
34
ba_daily.py
Normal file
@ -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))
|
||||
24
plan.md
24
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:
|
||||
|
||||
```
|
||||
|
||||
122
scripts/ba_dailies_legacy.sh
Normal file
122
scripts/ba_dailies_legacy.sh
Normal file
@ -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
|
||||
13
setup.sh
13
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]"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user