#!/usr/bin/env python3 """Test Zhipu (BigModel) keys against GLM-5.2 with a REAL chat completion. Loads the candidate pool from results/deep_verify/zhipu_glm52.json and any zhipu-format keys in the pivot candidate files, then hits https://open.bigmodel.cn/api/paas/v4/chat/completions with model glm-5.2. Classification: only HTTP 401/400 invalid-key => DEAD. A 200 with choices is USABLE. 429/1113 = no balance/package but still an authenticated, real key. Direct connection (no proxy). """ import json, os, re, sys, time import urllib.request, urllib.error from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path HERE = Path(__file__).resolve().parent POOL = HERE / "results" / "deep_verify" / "zhipu_glm52.json" OUT = HERE / "results" / "zhipu_glm52_live" OUT.mkdir(parents=True, exist_ok=True) BASE = "https://open.bigmodel.cn/api/paas/v4" # Zhipu key shape: 32hex.16alnum (id.secret) KEY_RE = re.compile(r"\b[0-9a-f]{32}\.[A-Za-z0-9]{16}\b") UA = "Mozilla/5.0 key-hunter" _opener = None def opener(): global _opener if _opener is None: _opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) return _opener def load_candidates(): keys = {} if POOL.exists(): d = json.loads(POOL.read_text()) for bucket in d.values(): for row in bucket: if isinstance(row, (list, tuple)) and len(row) >= 2: k, src = row[0], row[1] else: continue if KEY_RE.fullmatch(k): keys[k] = src # also sweep pivot candidates + extracted pools for zhipu-format keys for f in list((HERE / "results" / "pivot").glob("*.txt")) + \ list((HERE / "results").rglob("extracted_keys.txt")): try: for line in f.read_text(errors="ignore").splitlines(): for m in KEY_RE.findall(line): keys.setdefault(m, str(f)) except Exception: pass return keys def chat(key, model="glm-5.2", timeout=30): body = json.dumps({ "model": model, "messages": [{"role": "user", "content": "回复两个字:你好"}], "max_tokens": 16, "temperature": 0.1, }).encode() req = urllib.request.Request( f"{BASE}/chat/completions", data=body, headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json", "User-Agent": UA}, method="POST") try: with opener().open(req, timeout=timeout) as r: return r.getcode(), r.read().decode("utf-8", "replace") except urllib.error.HTTPError as e: try: return e.code, e.read().decode("utf-8", "replace") except Exception: return e.code, "" except Exception as e: return 0, f"network: {type(e).__name__}: {e}" def verify(item): key, src = item code, body = chat(key) bl = (body or "").lower() if code == 200 and ('"choices"' in bl or '"id"' in bl): return "USABLE", key, src, f"chat 200: {body[:160]}" if code in (401,): return "DEAD", key, src, f"{code}: {body[:120]}" # 400 with invalid-key style message -> dead if code == 400 and ("invalid" in bl or "api key" in bl or "token" in bl): return "DEAD", key, src, f"{code}: {body[:120]}" if code in (429,): # 1113 = no balance/package; 1112/1115 etc = rate limited but real return "NO_BALANCE", key, src, f"{code}: {body[:140]}" if code == 0: return "UNKNOWN", key, src, body[:160] return "NO_ACCESS", key, src, f"{code}: {body[:140]}" def main(): workers = int(sys.argv[1]) if len(sys.argv) > 1 else 24 keys = load_candidates() print(f"candidates: {len(keys)} zhipu-format keys") buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "DEAD": [], "UNKNOWN": []} with ThreadPoolExecutor(max_workers=workers) as ex: futs = [ex.submit(verify, it) for it in keys.items()] for i, fut in enumerate(as_completed(futs), 1): v, k, src, d = fut.result() buckets[v].append((k, src, d)) if v == "USABLE": print(f" [+] USABLE {k[:22]}... {src}\n {d}", flush=True) if i % 25 == 0: print(f" {i}/{len(keys)} done", flush=True) for v, rows in buckets.items(): with open(OUT / f"{v.lower()}.txt", "w") as f: for k, src, d in rows: f.write(f"{k}\t{src}\t{d}\n") summary = {v: len(r) for v, r in buckets.items()} (OUT / "summary.json").write_text(json.dumps(summary, indent=2)) print("=== summary:", summary) if __name__ == "__main__": main()