""" Is the bot allowed to run right now, and if not, when? python tools\\check_guard.py Exists because "SystemExit: 3" kept coming up and the only way to find out why was to start a whole run — which then refused, told you nothing about the clock, and left you guessing whether to try again in one minute or twenty. This reads `Logs\\attempts.json` and prints the answer. It starts no browser, touches eCW not at all, and **records no attempt**, so running it is free. Exit code mirrors the bot's: 0 = a run would proceed, 3 = it would be refused. """ import sys as _sys from pathlib import Path as _Path _sys.path.insert(0, str(_Path(__file__).resolve().parent.parent / "src")) # Windows hands these scripts a cp1252 console, which cannot encode the arrows # below. See runlog._force_utf8_console. for _s in (_sys.stdout, _sys.stderr): try: _s.reconfigure(encoding="utf-8", errors="replace") except Exception: pass from datetime import datetime, timezone, timedelta from ecw_bot import settings as cfg from ecw_bot.core import guard def _local(dt): return dt.astimezone().strftime("%H:%M:%S") if dt else "-" def _ago(dt, now): if not dt: return "" secs = (now - dt).total_seconds() return f"{secs/60:.0f} min ago" if secs >= 90 else f"{secs:.0f}s ago" def main() -> int: state = guard._read() now = datetime.now(timezone.utc) print(f"Now : {_local(now)}") print(f"State file : {guard.STATE_PATH}") print() last = guard._parse(state.get("last_attempt")) success = guard._parse(state.get("last_success")) locked = guard._parse(state.get("locked_until")) window = now - timedelta(hours=1) recent = sorted(d for d in (guard._parse(x) for x in state.get("recent", [])) if d and d > window) print(f"Last attempt : {_local(last)} {_ago(last, now)}") print(f"Last success : {_local(success)} {_ago(success, now)}") print() # Rule 1 — the 5 minute cooldown between attempts. Waited out, not refused. print(f"Cooldown ({cfg.LOGIN_COOLDOWN_SECONDS}s between attempts)") if last: left = cfg.LOGIN_COOLDOWN_SECONDS - (now - last).total_seconds() if left > 0: waits = left <= cfg.COOLDOWN_MAX_WAIT_SECONDS print(f" {left:.0f}s left" + (" → the bot WAITS this out and then runs" if waits else " → too long to wait, it would exit 3")) else: print(" clear") else: print(" clear (no attempt recorded yet)") print() # Rule 2 — the rolling hour. This one refuses; it is the real protection. print(f"Hourly limit ({len(recent)} of {cfg.LOGIN_MAX_PER_HOUR} in the " f"last hour)") for d in recent: print(f" {_local(d)} leaves the window at " f"{_local(d + timedelta(hours=1))}") if len(recent) >= cfg.LOGIN_MAX_PER_HOUR: clears = min(recent) + timedelta(hours=1) print(f" FULL. Clears at {_local(clears)} — " f"{(clears - now).total_seconds()/60:.0f} min from now.") else: print(f" room for {cfg.LOGIN_MAX_PER_HOUR - len(recent)} more") print() # Rule 3 — eCW showed its restriction page. print("Restriction lockout") if locked and now < locked: print(f" eCW refused us at " f"{state.get('restricted_at', '?')[:19]} UTC. " f"Locked until {_local(locked)}.") print(" Usual cause is the browser losing the proxy, not the account " "— check tools\\check_net.py first.") else: print(" none") print() refusal = guard.check() if refusal: print(f"VERDICT: a run would be REFUSED (exit 3)") print(f" {refusal}") return 3 print("VERDICT: clear — a run would proceed") return 0 if __name__ == "__main__": _sys.exit(main())