"""Gem shop (青輝石購入) daily free package. Ports module/collect_daily_free_power.py. Reference flow (`implement`): to_main_page -> to_purchase_pyroxenes_menu (open the dialog via the home-screen 青輝石購入 icon) -> to_purchase_type ("package") -> detect_free_power_availability -> if purchasable, collect_daily_free_power (click the free card, confirm the 0-yen purchase, wait for the reward) else log "already collected" -> return_to_main_page (close the dialog). Skips entirely on CN server (JP/Global only) -- no equivalent server concept in this project, so not ported. The reference detects every step via image template matching (no OCR anywhere in this flow) -- this port uses plain color probes for the same states instead (see config.py's "Gem shop daily free package" section for the full live-calibration writeup and exact pixel values), matching this project's own established equivalent for a simple, high-contrast state-A/state-B visual difference, same as cafe.py's CLAIM_DISABLED_RGB or stamina.py's MISSION_CLAIM_PROBE. Live-calibrated 2026-07-15 by manually driving the real dialog end-to-end on nik-gpu, which genuinely claimed the account's real free package for the day (0 yen, confirmed +10 AP / +10,000 credits via before/after counter values) -- see scratchpad/gem_shop_*.png for the captures. That exercised the "available -> claim it" path for real, via raw xdotool/scrot rather than this actual module. RECALIBRATED 2026-07-29 after a real Blue Archive client UI update replaced the old 3-top-tab overlay dialog (期間限定/青輝石/パッケージ) with a real full subscreen (left sidebar + デイリー/ウィークリー/マンスリー sub-tabs under 一般商品). See config.py's "Gem shop daily free package" section for the full writeup of what was re-verified live and what's still an inferred value. The old `_dialog_open`/`_close_gem_shop` custom probes existed only because the pre-redesign overlay dialog was invisible to navigation.is_on_subscreen/is_modal_open; the redesigned screen is a real subscreen, confirmed live, so this module now shares the same is_on_subscreen/return_to_home machinery every other subscreen-based task (mailbox/cafe/shop/lesson) already uses, and those two custom probes are gone. A `reference-parity-reviewer` pass on the original version caught a real bug: the final dialog-close step verified success via `navigation.is_on_subscreen`, which the pre-redesign overlay dialog read identically whether open or closed -- so that check could never fail, making a stuck-open dialog unrecoverable. That specific blindness no longer applies post-redesign (is_on_subscreen now genuinely distinguishes this screen from home, confirmed live), which is what made switching to the shared navigation.return_to_home safe here. """ from ba_auto import navigation def _open_gem_shop(driver, config): for attempt in range(1, config.GEM_SHOP_ICON_RETRIES + 1): driver.click(*config.GEM_SHOP_ICON) driver.wait(2) if navigation.is_on_subscreen(driver): return True print(f"[gem_shop] subscreen not detected after click (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})") return False def _open_daily_general_tab(driver, config): for attempt in range(1, config.GEM_SHOP_ICON_RETRIES + 1): driver.click(*config.GEM_SHOP_GENERAL_PRODUCTS_TAB) driver.wait(1) driver.click(*config.GEM_SHOP_DAILY_SUBTAB) driver.wait(1.5) status = _read_free_card_status(driver, config) if status != "unknown": return status print(f"[gem_shop] free card status not recognized after click (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})") return "unknown" def _color_matches(rgb, target, tolerance): return all(abs(c - t) <= tolerance for c, t in zip(rgb, target)) def _read_free_card_status(driver, config): rgb = driver.color_at(*config.GEM_SHOP_FREE_CARD_STATUS_PROBE) if _color_matches(rgb, config.GEM_SHOP_STATUS_CLAIMED_RGB, config.GEM_SHOP_STATUS_TOLERANCE): return "claimed" if _color_matches(rgb, config.GEM_SHOP_STATUS_AVAILABLE_RGB, config.GEM_SHOP_STATUS_TOLERANCE): return "available" return "unknown" def _claim_free_package(driver, config): driver.click(*config.GEM_SHOP_FREE_CARD_BUY_BUTTON) driver.wait(2) for _ in range(config.GEM_SHOP_CLAIM_MAX_ATTEMPTS): if _read_free_card_status(driver, config) == "claimed": return True driver.keypress("Return") driver.wait(2) return _read_free_card_status(driver, config) == "claimed" def run(driver, config): driver.focus_game() if not _open_gem_shop(driver, config): print("[gem_shop] could not confirm the gem shop screen opened, aborting without pressing further keys") return status = _open_daily_general_tab(driver, config) if status == "unknown": print("[gem_shop] could not read the free package card's status, aborting without pressing further keys") if not navigation.return_to_home(driver): print("[gem_shop] warning: could not confirm return to home") return if status == "claimed": print("[gem_shop] daily free package already collected today") else: print("[gem_shop] claiming daily free package (+10 AP, +10,000 credits)") if _claim_free_package(driver, config): print("[gem_shop] claimed daily free package") else: print("[gem_shop] warning: could not confirm the free package was claimed after retries") if not navigation.return_to_home(driver): print("[gem_shop] warning: could not confirm return to home") print("[gem_shop] Done.")