Files
keyhunter-parser/revalidate_live.py

107 lines
3.3 KiB
Python

"""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)