- tools/scripts/llm-key-hunter: GitHub leak hunting pipeline (hunt_*, pivot miner, two-layer verify/content caches, per-provider verification) - usable_keys: verified key vault across 12 providers (deepseek, minimax, volcanoark, longcat, codingplan, zhipu free-tier, mimo, siliconflow, etc.) - .grok/skills/llm-key-hunter: operator skill for the hunt/verify/vault flow - NewAPI channel import scripts and CDP capture helpers - Result verdict buckets (excluding multi-GB blob caches and dedup dumps)
199 lines
6.3 KiB
Python
199 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Add newly-hunted keys (FreeModel / DeepSeek / Kimi) to NewAPI.
|
|
|
|
Reads each provider's non-401 key file, probes both endpoints to find
|
|
which one actually accepts the key, creates a channel for every
|
|
working combination, tests it, and auto-disables channels that fail
|
|
(so they don't break routing, but can be re-enabled later).
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from newapi_client import (
|
|
add_channel, list_channels, update_channel, test_channel,
|
|
TYPE_OPENAI, TYPE_ANTHROPIC,
|
|
)
|
|
|
|
HERE = Path(__file__).parent
|
|
HUNTER = HERE / "llm-key-hunter" / "results"
|
|
|
|
# Each entry: provider label, key file, list of endpoint specs
|
|
# Endpoint spec: (channel_name_suffix, type, base_url, models, header_template)
|
|
PROVIDERS = [
|
|
{
|
|
"name": "FreeModel",
|
|
"file": HUNTER / "freemodel" / "all_non_401.txt",
|
|
"parse": lambda line: line.split("|", 2)[1], # USABLE|key|src
|
|
"endpoints": [
|
|
{
|
|
"suffix": "Anthropic",
|
|
"type": TYPE_ANTHROPIC,
|
|
"base_url": "https://cc.freemodel.dev",
|
|
"models": (
|
|
"claude-sonnet-4-6,claude-opus-4-6,claude-opus-5,"
|
|
"claude-opus-4-8,claude-opus-4-7,claude-fable-5,"
|
|
"claude-haiku-4-5,claude-sonnet-4-20250514,"
|
|
"claude-opus-4-20250514,claude-3-5-haiku-20241022,"
|
|
"claude-3-7-sonnet-20250219"
|
|
),
|
|
},
|
|
{
|
|
"suffix": "OpenAI",
|
|
"type": TYPE_OPENAI,
|
|
"base_url": "https://api.freemodel.dev",
|
|
"models": (
|
|
"gpt-5.6-luna,gpt-5.6-sol,gpt-5.6-terra,"
|
|
"claude-sonnet-4-20250514"
|
|
),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"name": "DeepSeek",
|
|
"file": HUNTER / "deepseek" / "all_non_401.txt",
|
|
"parse": lambda line: line.split("|", 2)[1],
|
|
"endpoints": [
|
|
{
|
|
"suffix": "",
|
|
"type": TYPE_OPENAI,
|
|
"base_url": "https://api.deepseek.com",
|
|
"models": "deepseek-chat,deepseek-reasoner",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"name": "Kimi",
|
|
"file": HUNTER / "kimi" / "all_non_401.txt",
|
|
"parse": lambda line: line.split("|", 2)[1],
|
|
"endpoints": [
|
|
{
|
|
"suffix": "",
|
|
"type": TYPE_OPENAI,
|
|
"base_url": "https://api.moonshot.cn",
|
|
"models": (
|
|
"moonshot-v1-8k,moonshot-v1-32k,moonshot-v1-128k,"
|
|
"kimi-k2-0905-preview,kimi-k2-turbo-preview,"
|
|
"kimi-latest"
|
|
),
|
|
},
|
|
],
|
|
},
|
|
]
|
|
|
|
|
|
def existing_channel_keys():
|
|
"""Return set of keys already present in NewAPI (from full channel details)."""
|
|
keys = set()
|
|
for ch in list_channels():
|
|
cid = ch["id"]
|
|
try:
|
|
from newapi_client import get_channel
|
|
d = get_channel(cid)
|
|
k = (d or {}).get("key", "")
|
|
if k:
|
|
keys.add(k)
|
|
except Exception:
|
|
pass
|
|
return keys
|
|
|
|
|
|
def main():
|
|
print("Loading existing channels to avoid duplicates...")
|
|
existing_keys = existing_channel_keys()
|
|
print(f" {len(existing_keys)} unique keys already configured.\n")
|
|
|
|
added = []
|
|
skipped = 0
|
|
failed_test = 0
|
|
|
|
for prov in PROVIDERS:
|
|
path = prov["file"]
|
|
if not path.exists():
|
|
print(f"[{prov['name']}] no file at {path}, skipping")
|
|
continue
|
|
|
|
raw_keys = []
|
|
seen = set()
|
|
for line in path.read_text().splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
try:
|
|
k = prov["parse"](line)
|
|
except IndexError:
|
|
continue
|
|
if k and k not in seen:
|
|
seen.add(k)
|
|
raw_keys.append(k)
|
|
|
|
print(f"[{prov['name']}] {len(raw_keys)} keys from {path.name}")
|
|
|
|
for idx, key in enumerate(raw_keys, 1):
|
|
if key in existing_keys:
|
|
print(f" [{idx}/{len(raw_keys)}] {key[:18]}... already in NewAPI, skip")
|
|
skipped += 1
|
|
continue
|
|
|
|
for ep in prov["endpoints"]:
|
|
suffix = ep["suffix"]
|
|
name = f"{prov['name']}_{key[-6:]}"
|
|
if suffix:
|
|
name = f"{name}-{suffix}"
|
|
|
|
channel = {
|
|
"name": name,
|
|
"type": ep["type"],
|
|
"key": key,
|
|
"base_url": ep["base_url"],
|
|
"models": ep["models"],
|
|
"groups": ["default"],
|
|
"model_mapping": "",
|
|
"priority": 0,
|
|
"weight": 0,
|
|
"auto_ban": 1,
|
|
}
|
|
success, msg = add_channel(channel)
|
|
if not success:
|
|
print(f" [{idx}] {name} ADD FAIL: {msg}")
|
|
continue
|
|
|
|
# find new channel id
|
|
new_id = None
|
|
for ch in list_channels():
|
|
if ch["name"] == name:
|
|
new_id = ch["id"]
|
|
break
|
|
if new_id is None:
|
|
print(f" [{idx}] {name} added but couldn't find id")
|
|
continue
|
|
|
|
# test it
|
|
t_ok, t_msg, _ = test_channel(new_id)
|
|
if t_ok:
|
|
print(f" [{idx}] {name} ID={new_id} ✅ {t_msg[:80]}")
|
|
added.append((new_id, name, key[:18], t_msg[:80]))
|
|
else:
|
|
# auto-disable so it doesn't burn retries
|
|
update_channel(new_id, status=2)
|
|
print(f" [{idx}] {name} ID={new_id} ❌ DISABLED ({t_msg[:80]})")
|
|
failed_test += 1
|
|
time.sleep(0.3)
|
|
|
|
print()
|
|
|
|
print("=== Summary ===")
|
|
print(f" Added & test-passed: {len(added)}")
|
|
print(f" Added but auto-disabled: {failed_test}")
|
|
print(f" Skipped (already present): {skipped}")
|
|
if added:
|
|
print("\nLive channels:")
|
|
for cid, name, k, msg in added:
|
|
print(f" ID={cid:3d} {name:35s} key={k}... {msg}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|