#!/usr/bin/env python3 """Import all usable keys into NewAPI - one channel per key. Channel name format: _ 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()