"""Проверка ключей vault живыми запросами. Значения не печатаются — только маски. python tools/vault_check.py firecrawl --limit 20 python tools/vault_check.py 2captcha python tools/vault_check.py all Статусы в data/vault.json: live / dead / rate / nofund. firecrawl: GET /v2/team/credit-usage (200 = живой, 402 = без кредитов, 401 = мёртвый). 2captcha: GET res.php?action=getbalance (status:1 = живой, баланс в request). """ import argparse import json import sys import time from pathlib import Path import requests ROOT = Path(__file__).resolve().parents[1] VAULT = ROOT / "data" / "vault.json" def load(): return json.loads(VAULT.read_text(encoding="utf-8")) def save(d): VAULT.write_text(json.dumps(d, ensure_ascii=False, indent=0), encoding="utf-8") def mask(k): return k[:4] + "…" + k[-2:] if len(k) > 8 else "…" def check_firecrawl(key): for url in ("https://api.firecrawl.dev/v2/team/credit-usage", "https://api.firecrawl.dev/v1/credits"): try: r = requests.get(url, headers={"Authorization": "Bearer " + key}, timeout=20) except Exception as e: return "err", type(e).__name__ if r.status_code == 200: try: j = r.json() except Exception: j = {} rem = j.get("remainingCredits") or j.get("remaining_credits") or \ (j.get("data") or {}).get("remainingCredits") return "live", ("credits %s" % rem) if rem is not None else "ok" if r.status_code == 401: return "dead", "401" if r.status_code == 402: return "nofund", "402" if r.status_code == 429: return "rate", "429" if r.status_code == 403: return "dead", "403" return "dead", "no-endpoint" def check_2captcha(key): try: r = requests.get("https://2captcha.com/res.php", params={"key": key, "action": "getbalance", "json": 1}, timeout=20) j = r.json() except Exception as e: return "err", type(e).__name__ if j.get("status") == 1: return "live", "баланс %s" % j.get("request") err = str(j.get("request") or "") if "ERROR_WRONG_USER_KEY" in err or "ERROR_KEY_DOES_NOT_EXIST" in err: return "dead", err[:30] return "rate", err[:30] CHECKS = {"firecrawl": check_firecrawl, "2captcha": check_2captcha} def run(service, limit): d = load() rows = d.get(service) or [] if not rows: print("в vault нет сервиса", service) return fn = CHECKS[service] rows = rows[:limit] if limit else rows print("%s: проверяю %d ключей" % (service, len(rows))) stat = {} for r in rows: st, note = fn(r["key"]) r["status"] = st r["note"] = note stat[st] = stat.get(st, 0) + 1 mark = {"live": "+", "dead": "-", "nofund": "$", "rate": "~", "err": "!"}.get(st, "?") print(" %s %s %-7s %s" % (mark, mask(r["key"]), st, note)) time.sleep(0.35) save(d) print("итог %s: %s" % (service, stat)) if __name__ == "__main__": ap = argparse.ArgumentParser() ap.add_argument("service") ap.add_argument("--limit", type=int, default=0) a = ap.parse_args() if a.service == "all": for s in CHECKS: run(s, a.limit) elif a.service in CHECKS: run(a.service, a.limit) else: print("поддерживается:", ", ".join(CHECKS))