#!/usr/bin/env python3 """Kimi (Moonshot AI) deep hunter. Loads Moonshot keys from results/extracted_keys.txt and verifies them against api.moonshot.cn using three layers: 1. GET /v1/users/me/balance — returns balance + cash_balance (CNY) 2. POST /v1/chat/completions — model moonshot-v1-8k 3. POST /v1/chat/completions — model kimi-k2-0905-preview (K2 long-context) Classification rule (anything NOT 401 is kept): - 200 with balance > 0 / chat OK → USABLE - balance 0 / 402 / 429 → NO_BALANCE (key valid) - 400 / 403 / 404 / 5xx → NO_ACCESS (key valid, model issue) - network / timeout → UNKNOWN - 401 → DEAD (discard only this) Outputs in results/kimi/. """ import argparse import json import re import sys import time import urllib.request import urllib.error from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path HERE = Path(__file__).parent OUT = HERE / "results" / "kimi" OUT.mkdir(parents=True, exist_ok=True) SOURCE = HERE / "results" / "extracted_keys.txt" UA = "curl/8.5.0" BASE = "https://api.moonshot.cn" def http(method, path, key, body=None, timeout=20): url = f"{BASE}{path}" headers = { "User-Agent": UA, "Accept": "*/*", "Authorization": f"Bearer {key}", } data = None if body is not None: data = json.dumps(body).encode() headers["Content-Type"] = "application/json" req = urllib.request.Request(url, data=data, headers=headers, method=method) try: with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.getcode(), resp.read().decode("utf-8", errors="replace") except urllib.error.HTTPError as e: try: return e.code, e.read().decode("utf-8", errors="replace") except Exception: return e.code, "" except Exception as e: return 0, f"network: {type(e).__name__}: {e}" def classify_balance(code, body): if code == 200: try: data = json.loads(body) except json.JSONDecodeError: return "USABLE", f"balance: {body[:120]}" # Moonshot returns: {"balance":xxx,"cash_balance":xxx,...} or # {"success":true,"data":{"balance":...}} on some accounts bal = None if isinstance(data, dict): inner = data.get("data") if isinstance(data.get("data"), dict) else data avail = inner.get("available_balance") if avail is None: avail = inner.get("balance") cash = inner.get("cash_balance") if avail is not None: try: val = float(avail) if val > 0: return "USABLE", f"balance: CNY {val}" # available=0 — try voucher balance or check arrears voucher = inner.get("voucher_balance", 0) try: if float(voucher) > 0: return "USABLE", f"voucher: CNY {voucher}" except (TypeError, ValueError): pass return "NO_BALANCE", f"balance=0 cash={cash}: {body[:120]}" except (TypeError, ValueError): pass return "USABLE", f"balance: {body[:120]}" if code in (402, 429): return "NO_BALANCE", f"HTTP {code}: {body[:140]}" if code == 401: return "DEAD", "401 unauthorized" if code == 0: return "UNKNOWN", body[:160] return "NO_ACCESS", f"balance HTTP {code}: {body[:140]}" def classify_chat(code, body, model): bl = (body or "").lower() if code == 200: if '"choices"' in bl or '"id"' in bl: return "USABLE", f"chat {model}: 200" if any(x in bl for x in ("balance", "quota", "arrearage", "insufficient")): return "NO_BALANCE", f"chat {model}: {body[:120]}" return "USABLE", f"chat {model}: {body[:120]}" if code in (402, 429): return "NO_BALANCE", f"chat {model} HTTP {code}: {body[:120]}" if code == 401: return "DEAD", f"chat {model}: 401" if code == 0: return "UNKNOWN", body[:160] return "NO_ACCESS", f"chat {model} HTTP {code}: {body[:140]}" def verify(key): rank = {"USABLE": 4, "NO_BALANCE": 3, "NO_ACCESS": 2, "UNKNOWN": 1, "DEAD": 0} best = "DEAD" best_detail = "" def update(v, d): nonlocal best, best_detail if rank[v] > rank[best]: best, best_detail = v, d # 1) balance code, body = http("GET", "/v1/users/me/balance", key) update(*classify_balance(code, body)) if best == "USABLE": return best, best_detail # 2) chat — moonshot-v1-8k (default cheap model) code, body = http("POST", "/v1/chat/completions", key, body={ "model": "moonshot-v1-8k", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 1, }) update(*classify_chat(code, body, "v1-8k")) if best == "USABLE": return best, best_detail # 3) chat — kimi-k2 (long-context / reasoning-capable, some keys only have this) code, body = http("POST", "/v1/chat/completions", key, body={ "model": "kimi-k2-0905-preview", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 1, }) update(*classify_chat(code, body, "k2")) return best, best_detail def load_keys(): keys = {} fakes = ("xxxx", "your-", "example", "test-key", "placeholder", "sk-00000000") # Moonshot keys: sk- + 48 chars (mixed case letters/digits). Some newer # keys are hex-only 32 chars like DeepSeek, but to avoid false positives # with OpenAI sk- keys we require the documented 48-char form OR a 32-hex # form that did not match OpenAI patterns. pat_long = re.compile(r"^sk-[A-Za-z0-9]{48}$") pat_hex = re.compile(r"^sk-[a-f0-9]{32}$") if not SOURCE.exists(): print(f"ERROR: {SOURCE} not found", file=sys.stderr) sys.exit(1) with open(SOURCE) as f: for line in f: line = line.strip() if not line.startswith("Moonshot|"): continue parts = line.split("|", 3) if len(parts) < 3: continue key = parts[1] url = parts[2] if len(parts) > 2 else "" low = key.lower() if any(x in low for x in fakes): continue if "T3BlbkFJ" in key: # this is actually an OpenAI key continue if not (pat_long.match(key) or pat_hex.match(key)): continue if key not in keys: keys[key] = url with open(OUT / "extracted_keys.txt", "w") as f: for k, src in sorted(keys.items()): f.write(f"{k}|{src}\n") print(f"Loaded {len(keys)} unique Moonshot keys") return keys def main(): ap = argparse.ArgumentParser() ap.add_argument("--workers", type=int, default=15) ap.add_argument("--limit", type=int, default=0) args = ap.parse_args() keys = load_keys() if args.limit: keys = dict(list(keys.items())[:args.limit]) print(f" (limited to first {args.limit})") if not keys: print("No keys to verify.") return print(f"\nDeep verifying {len(keys)} Kimi keys against api.moonshot.cn (workers={args.workers})...") print("Rule: only 401 = dead; everything else kept.\n") buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "UNKNOWN": [], "DEAD": []} start = time.time() processed = 0 with ThreadPoolExecutor(max_workers=args.workers) as pool: futs = {pool.submit(verify, k): (k, s) for k, s in keys.items()} for fut in as_completed(futs): k, s = futs[fut] processed += 1 try: verdict, detail = fut.result() except Exception as e: verdict, detail = "UNKNOWN", str(e) buckets[verdict].append((k, s, detail)) if processed % 50 == 0: el = time.time() - start print(f" [{processed}/{len(keys)}] " f"usable={len(buckets['USABLE'])} " f"nobal={len(buckets['NO_BALANCE'])} " f"noacc={len(buckets['NO_ACCESS'])} " f"unk={len(buckets['UNKNOWN'])} " f"dead={len(buckets['DEAD'])} " f"({processed/el:.1f}/s)") el = time.time() - start print(f"\nDone in {el:.1f}s") for n in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD"): print(f" {n:11s}: {len(buckets[n])}") for name, path in [ ("USABLE", OUT / "usable.txt"), ("NO_BALANCE", OUT / "no_balance.txt"), ("NO_ACCESS", OUT / "no_access.txt"), ("UNKNOWN", OUT / "unknown.txt"), ("DEAD", OUT / "dead.txt"), ]: with open(path, "w") as f: for k, s, d in sorted(buckets[name]): f.write(f"{k}|{s}|{d}\n") print(f" written: {path}") with open(OUT / "all_non_401.txt", "w") as f: for n in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN"): for k, s, d in sorted(buckets[n]): f.write(f"{n}|{k}|{s}|{d}\n") print(f" written: {OUT / 'all_non_401.txt'}") if buckets["USABLE"]: print("\n=== USABLE keys ===") for k, s, d in sorted(buckets["USABLE"]): print(f" {k} {d}") print(f" src: {s}") if __name__ == "__main__": main()