"""Re-validate all keys whose status claims WORKING/FREE/LIMITED. Root cause: validator_loop only re-checks NEW keys; stale live statuses rot. This script reconciles DB with reality using eternal_v10's m.validate(). No inner threads — m.validate already has per-request timeouts. """ import importlib.util import sqlite3 import sys import time from collections import Counter from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path import threading PROJ = Path(__file__).resolve().parent DB_PATH = PROJ / "data" / "keys.db" EV10 = PROJ / "src" / "eternal_v10.py" THREADS = 10 # conservative to avoid WAL contention with harvesters LIVE = {"WORKING", "LIMITED", "FREE"} _lock = threading.Lock() _statuses = Counter() _prov_live = Counter() _prov_dead = Counter() # --- import eternal_v10 --- spec = importlib.util.spec_from_file_location("ev10_reval", str(EV10)) m = importlib.util.module_from_spec(spec) spec.loader.exec_module(m) print("[init] eternal_v10 imported", flush=True) # Patch _DB_LOCK to no-op: each _connect() creates own connection, # WAL handles concurrent writes. Avoids contention with harvesters. class _NoopLock: def __enter__(self): return self def __exit__(self, *a): pass def acquire(self, *a): return True def release(self): pass m._DB_LOCK = _NoopLock() print("[init] _DB_LOCK patched to no-op", flush=True) # --- runtime GOOGLE patch --- GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models" def _v_google(_prov, key): try: r = m.requests.get(GEMINI_URL, params={"key": key}, timeout=15) if r.status_code == 200: n = len(r.json().get("models", [])) return "WORKING", "GEMINI API", f"{n} models", str(n) if r.status_code == 429: return "WORKING", "GEMINI API (rate-limited)", "429", "429" return "DEAD", "", "", "" except Exception: return "ERR", "", "", "" m.VALIDATORS["google"] = _v_google print("[init] GOOGLE runtime validator patched", flush=True) db = sqlite3.connect(str(DB_PATH), timeout=120) rows = db.execute( "SELECT val, prov FROM keys " "WHERE status IN ('WORKING','FREE','LIMITED') " "ORDER BY id" ).fetchall() db.close() total = len(rows) print(f"[init] revalidating {total} stale-live keys", flush=True) if not rows: print("[done] nothing to revalidate", flush=True) sys.exit(0) done = 0 def work(item): global done val, prov = item try: res = m.validate(val, prov) st = res[0] except Exception as e: st = "ERR" with _lock: _statuses[st] += 1 if st in LIVE: _prov_live[prov] += 1 else: _prov_dead[prov] += 1 done += 1 if done % 50 == 0 or done == total: print(f"[{done}/{total}] " + " ".join(f"{s}={c}" for s, c in _statuses.most_common()), flush=True) return st t0 = time.time() with ThreadPoolExecutor(max_workers=THREADS) as ex: futures = {ex.submit(work, r): r for r in rows} for f in as_completed(futures): pass # work() handles counters internally print(f"[done] {total} keys in {(time.time()-t0)/60:.1f} min", flush=True) print("=== live after ===", flush=True) for p, c in _prov_live.most_common(): print(f" {p}: {c}", flush=True) print("=== dead/err ===", flush=True) for p, c in _prov_dead.most_common(): print(f" {p}: {c}", flush=True)