216 lines
7.0 KiB
Python
216 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Vault the 2026-08-03 CN coding-plan batch winners.
|
|
|
|
Reads results/cn_batch2_full/usable.txt + no_balance.txt and writes per-provider
|
|
vault directories under usable_keys/<provider>/ with keys.json, keys.env, and
|
|
per-key .txt files. Then rebuilds the root all_keys.json + README.md.
|
|
"""
|
|
import json, re, time
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
VAULT = HERE / "usable_keys"
|
|
SRC = HERE / "results/cn_batch2_full"
|
|
|
|
# Provider metadata: base_url, default model, auth, official site
|
|
META = {
|
|
"iflytek_maas": {
|
|
"dir": "iflytek_maas",
|
|
"base_url": "https://maas-api.cn-huabei-1.xf-yun.com/v1",
|
|
"auth": "Bearer",
|
|
"site": "https://www.xfyun.cn/",
|
|
"plan": "iFlytek Spark MaaS (讯飞星火 MaaS)",
|
|
},
|
|
"dmxapi": {
|
|
"dir": "dmxapi",
|
|
"base_url": "https://www.dmxapi.cn/v1",
|
|
"auth": "Bearer",
|
|
"site": "https://www.dmxapi.cn/",
|
|
"plan": "DMXAPI — one_api relay aggregator",
|
|
},
|
|
"aihubmix": {
|
|
"dir": "aihubmix",
|
|
"base_url": "https://aihubmix.com/v1",
|
|
"auth": "Bearer",
|
|
"site": "https://aihubmix.com/",
|
|
"plan": "AIHubMix — international+CN model aggregator",
|
|
},
|
|
"closeai": {
|
|
"dir": "closeai",
|
|
"base_url": "https://api.closeai-asia.com/v1",
|
|
"auth": "Bearer",
|
|
"site": "https://www.closeai-asia.com/",
|
|
"plan": "CloseAI-Asia relay",
|
|
},
|
|
"api2d": {
|
|
"dir": "api2d",
|
|
"base_url": "https://oa.api2d.net/v1",
|
|
"auth": "Bearer",
|
|
"site": "https://api2d.com/",
|
|
"plan": "API2D — Chinese OpenAI/Anthropic relay",
|
|
},
|
|
}
|
|
|
|
|
|
def parse_bucket(path):
|
|
"""Parse prov<TAB>key<TAB>url<TAB>detail, handling the prov+key concat bug."""
|
|
rows = []
|
|
if not path.exists():
|
|
return rows
|
|
for ln in path.read_text(errors="replace").splitlines():
|
|
parts = ln.split("\t")
|
|
if len(parts) < 3:
|
|
continue
|
|
prov, key, url = parts[0], parts[1], parts[2]
|
|
detail = parts[3] if len(parts) > 3 else ""
|
|
if not key.startswith("sk-") and prov.startswith(tuple(META.keys())):
|
|
# split concatenated "provsk-..."
|
|
for p in META:
|
|
if prov.startswith(p):
|
|
maybe_key = prov[len(p):]
|
|
if maybe_key.startswith("sk-"):
|
|
prov, key = p, maybe_key
|
|
break
|
|
if prov in META and key.startswith("sk-"):
|
|
rows.append((prov, key, url, detail))
|
|
return rows
|
|
|
|
|
|
def slug(s, n=6):
|
|
return s[:n]
|
|
|
|
|
|
def write_provider(prov, entries, verdict):
|
|
"""entries: list of (key, source, detail). verdict: USABLE|NO_BALANCE."""
|
|
meta = META[prov]
|
|
d = VAULT / meta["dir"]
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
# load existing keys.json if present
|
|
kf = d / "keys.json"
|
|
existing = []
|
|
if kf.exists():
|
|
try:
|
|
data = json.loads(kf.read_text())
|
|
existing = data if isinstance(data, list) else [data]
|
|
except Exception:
|
|
existing = []
|
|
existing_keys = {e.get("key") for e in existing}
|
|
|
|
# extract working model from detail: "model 200 ..." or "model 403 ..."
|
|
model_re = re.compile(r"^(\S+)\s+(200|40\d)")
|
|
added = 0
|
|
for key, source, detail in entries:
|
|
if key in existing_keys:
|
|
continue
|
|
m = model_re.match(detail or "")
|
|
model = m.group(1) if m else None
|
|
# try to pull content snippet
|
|
content = ""
|
|
if "200" in (detail or ""):
|
|
cm = re.search(r"200\s+(.*)", detail)
|
|
if cm:
|
|
content = cm.group(1)[:80]
|
|
entry = {
|
|
"key": key,
|
|
"source": source,
|
|
"detail": detail[:200],
|
|
"base_url": meta["base_url"],
|
|
"auth": meta["auth"],
|
|
"models": [model] if model else [],
|
|
"status": verdict,
|
|
"found": int(time.time()),
|
|
}
|
|
if verdict == "NO_BALANCE":
|
|
entry["note"] = "real credential, account balance exhausted"
|
|
existing.append(entry)
|
|
existing_keys.add(key)
|
|
added += 1
|
|
|
|
kf.write_text(json.dumps(existing, indent=2, ensure_ascii=False))
|
|
|
|
# rebuild keys.env
|
|
env = d / "keys.env"
|
|
lines = [f"# {meta['plan']}", f"# {meta['site']}",
|
|
f"# base_url={meta['base_url']}", ""]
|
|
for e in existing:
|
|
lines.append(f"{e['key']}")
|
|
env.write_text("\n".join(lines) + "\n")
|
|
|
|
# per-key .txt
|
|
for i, e in enumerate(existing, 1):
|
|
slug6 = slug(e["key"].replace("sk-", "").replace("-", ""), 6)
|
|
pf = d / f"key_{i:02d}_{slug6}.txt"
|
|
pf.write_text(
|
|
f"key: {e['key']}\n"
|
|
f"base_url: {e['base_url']}\n"
|
|
f"auth: {e['auth']}\n"
|
|
f"models: {','.join(e.get('models', []))}\n"
|
|
f"status: {e['status']}\n"
|
|
f"source: {e['source']}\n"
|
|
f"detail: {e.get('detail','')}\n"
|
|
)
|
|
return added, len(existing)
|
|
|
|
|
|
def rebuild_root():
|
|
ak = []
|
|
counts = {}
|
|
for d in sorted(p for p in VAULT.iterdir() if p.is_dir()):
|
|
f = d / "keys.json"
|
|
if not f.exists():
|
|
continue
|
|
try:
|
|
data = json.loads(f.read_text())
|
|
except Exception:
|
|
continue
|
|
data = data if isinstance(data, list) else [data]
|
|
for e in data:
|
|
if isinstance(e, dict) and e.get("key"):
|
|
ak.append(dict(e, provider=d.name))
|
|
counts[d.name] = len(data)
|
|
(VAULT / "all_keys.json").write_text(
|
|
json.dumps(ak, indent=2, ensure_ascii=False))
|
|
lines = ["# Usable LLM Keys Vault", "", "## Counts", ""]
|
|
for k in sorted(counts, key=lambda x: -counts[x]):
|
|
lines.append(f"- **{k}**: {counts[k]}")
|
|
lines.append("")
|
|
lines.append(f"**Total: {sum(counts.values())} keys across "
|
|
f"{len(counts)} providers**")
|
|
lines.append("")
|
|
(VAULT / "README.md").write_text("\n".join(lines))
|
|
return counts
|
|
|
|
|
|
def main():
|
|
usable = parse_bucket(SRC / "usable.txt")
|
|
nobal = parse_bucket(SRC / "no_balance.txt")
|
|
print(f"parsed {len(usable)} usable + {len(nobal)} no-balance keys")
|
|
|
|
by_prov_u = {}
|
|
by_prov_n = {}
|
|
for prov, key, url, detail in usable:
|
|
by_prov_u.setdefault(prov, []).append((key, url, detail))
|
|
for prov, key, url, detail in nobal:
|
|
by_prov_n.setdefault(prov, []).append((key, url, detail))
|
|
|
|
summary = {}
|
|
for prov in META:
|
|
u = by_prov_u.get(prov, [])
|
|
n = by_prov_n.get(prov, [])
|
|
if not u and not n:
|
|
continue
|
|
a1, t1 = write_provider(prov, u, "USABLE")
|
|
a2, t2 = write_provider(prov, n, "NO_BALANCE")
|
|
summary[prov] = (a1 + a2, t1)
|
|
print(f" {prov:16s}: +{a1+a2:3d} new (total {t1})")
|
|
|
|
counts = rebuild_root()
|
|
print("\nvault rebuild complete. counts:")
|
|
for k in sorted(counts, key=lambda x: -counts[x]):
|
|
print(f" {k:16s}: {counts[k]}")
|
|
print(f"\n TOTAL: {sum(counts.values())} keys across {len(counts)} providers")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|