#!/bin/bash # ═══════════════════════════════════════════════════════════════ # verify.sh — LLM API Key 验证模块 # 对每个候选 key 调用对应 provider API 确认是否 live # ═══════════════════════════════════════════════════════════════ set -euo pipefail KEYS_FILE="${1:-}" REPORT_DIR="${2:-./results}" VERIFIED_ONLY="${3:-false}" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' log_info() { echo -e "${CYAN}[*]${NC} $1"; } log_live() { echo -e "${GREEN}[LIVE]${NC} $1"; } log_dead() { echo -e "${RED}[DEAD]${NC} $1"; } log_warn() { echo -e "${YELLOW}[!]${NC} $1"; } mkdir -p "$REPORT_DIR" LIVE_FILE="${REPORT_DIR}/live_keys.txt" DEAD_FILE="${REPORT_DIR}/dead_keys.txt" ALL_FILE="${REPORT_DIR}/verified_all.txt" > "$LIVE_FILE" > "$DEAD_FILE" > "$ALL_FILE" # ── 按厂商分类的输出目录 ────────────────────────────────── LIVE_DIR="${REPORT_DIR}/live" mkdir -p "${LIVE_DIR}/international" "${LIVE_DIR}/china" # provider → 分类映射 get_category() { case "$1" in OpenAI|Anthropic|Google|HuggingFace|Groq|Replicate|TogetherAI|Mistral|Cohere|DeepSeek|Perplexity|OpenRouter|AzureOpenAI) echo "international" ;; VolcanoArk|ZhipuAI|DashScope|Moonshot|MiniMax|TencentHunyuan|BaiduQianfan|iFlytekSpark|iFlytekCodingPlan|LingyiWanwu|StepFun|SiliconFlow|FreeModel) echo "china" ;; *) echo "unknown" ;; esac } # provider → 文件名映射 get_filename() { case "$1" in OpenAI) echo "openai.txt" ;; Anthropic) echo "anthropic.txt" ;; Google) echo "google_gemini.txt" ;; HuggingFace) echo "huggingface.txt" ;; Groq) echo "groq.txt" ;; Replicate) echo "replicate.txt" ;; TogetherAI) echo "together_ai.txt" ;; Mistral) echo "mistral.txt" ;; Cohere) echo "cohere.txt" ;; DeepSeek) echo "deepseek.txt" ;; Perplexity) echo "perplexity.txt" ;; OpenRouter) echo "openrouter.txt" ;; AzureOpenAI) echo "azure_openai.txt" ;; VolcanoArk) echo "volcano_ark.txt" ;; ZhipuAI) echo "zhipu_chatglm.txt" ;; DashScope) echo "alibaba_dashscope.txt" ;; Moonshot) echo "kimi_moonshot.txt" ;; MiniMax) echo "minimax.txt" ;; TencentHunyuan) echo "tencent_hunyuan.txt" ;; BaiduQianfan) echo "baidu_qianfan.txt" ;; iFlytekSpark) echo "iflytek_spark.txt" ;; iFlytekCodingPlan) echo "iflytek_coding_plan.txt" ;; LingyiWanwu) echo "lingyiwanwu_01ai.txt" ;; StepFun) echo "stepfun.txt" ;; SiliconFlow) echo "siliconflow.txt" ;; FreeModel) echo "freemodel.txt" ;; *) echo "unknown.txt" ;; esac } # ── 验证函数 ────────────────────────────────────────────── verify_openai() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.openai.com/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_anthropic() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "x-api-key: $key" \ -H "anthropic-version: 2023-06-01" \ https://api.anthropic.com/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_google() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ "https://generativelanguage.googleapis.com/v1/models?key=$key" 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_huggingface() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://huggingface.co/api/whoami-v2 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_groq() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.groq.com/openai/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_replicate() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Token $key" \ https://api.replicate.com/v1/account 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_together() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.together.xyz/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_mistral() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.mistral.ai/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_cohere() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.cohere.ai/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_deepseek() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.deepseek.com/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } verify_perplexity() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.perplexity.ai/chat/completions 2>/dev/null || echo "000") # perplexity might return 400 for empty body, 401 for bad key [[ "$resp" != "401" && "$resp" != "000" ]] } verify_openrouter() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://openrouter.ai/api/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # ── 中国平台验证 ────────────────────────────────────────── # 火山引擎 (Volcano Engine Ark) # Endpoint: https://ark.cn-beijing.volces.com/api/v3/models # Auth: Bearer verify_volcanoark() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://ark.cn-beijing.volces.com/api/v3/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # 智谱 AI (Zhipu / ChatGLM) # Endpoint: https://open.bigmodel.cn/api/paas/v4/models # Auth: Bearer verify_zhipuai() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://open.bigmodel.cn/api/paas/v4/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # 阿里云通义 (DashScope / Qwen) # Endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1/models # Auth: Bearer verify_dashscope() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://dashscope.aliyuncs.com/compatible-mode/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # Kimi / Moonshot AI # Endpoint: https://api.moonshot.cn/v1/models # Auth: Bearer verify_moonshot() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.moonshot.cn/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # MiniMax # Endpoint: https://api.minimaxi.com/v1/models # Auth: Bearer verify_minimax() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.minimaxi.com/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # 腾讯混元 (Tencent Hunyuan) # Endpoint: https://api.hunyuan.cloud.tencent.com/v1/models # Auth: Bearer verify_tencent_hunyuan() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.hunyuan.cloud.tencent.com/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # 百度千帆 (Baidu Qianfan / ERNIE) # v2 Endpoint: https://qianfan.baidubce.com/v2/models # Auth: Bearer verify_baidu_qianfan() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://qianfan.baidubce.com/v2/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # 讯飞星火 (iFlytek Spark) # Endpoint: https://spark-api.xf-yun.com/v1/chat/completions # Auth: HMAC signature (APIKey:APISecret) # 注: 讯飞用 HMAC 签名,无法简单 Bearer 验证 # 降级: 检查 key 格式是否为 UUID:secret 格式 verify_iflytek_spark() { local key="$1" # 讯飞 key 格式: APIKey:APISecret # 无法直接验证,标记为需手动检查 if [[ "$key" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}:[a-zA-Z0-9]{16,}$ ]]; then return 0 # 格式正确,但无法验证 live fi return 1 } # 讯飞星辰 (iFlytek Astron Coding Plan) # Endpoint: https://maas-coding-api.cn-huabei-1.xf-yun.com/v2 (OpenAI 协议) # Auth: Bearer (OpenAI 兼容) # Model: astron-code-latest verify_iflytek_codingplan() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ -H "Content-Type: application/json" \ -X POST \ -d '{"model":"astron-code-latest","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://maas-coding-api.cn-huabei-1.xf-yun.com/v2/chat/completions 2>/dev/null || echo "000") # 200 = valid key, 401/403 = invalid key [[ "$resp" != "401" && "$resp" != "403" && "$resp" != "000" ]] } # 零一万物 (01.AI / Yi) # Endpoint: https://api.lingyiwanwu.com/v1/models # Auth: Bearer verify_lingyiwanwu() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.lingyiwanwu.com/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # 阶跃星辰 (StepFun) # Endpoint: https://api.stepfun.com/v1/models # Auth: Bearer verify_stepfun() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.stepfun.com/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # SiliconFlow (硅基流动) # Endpoint: https://api.siliconflow.cn/v1/models # Auth: Bearer verify_siliconflow() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.siliconflow.cn/v1/models 2>/dev/null || echo "000") [[ "$resp" == "200" ]] } # FreeModel.dev # Endpoint: https://api.freemodel.dev/v1/models # Auth: Bearer verify_freemodel() { local key="$1" local resp resp=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.freemodel.dev/v1/models 2>/dev/null || echo "000") # 200 = live, 401/403 = dead/no balance, 402 = rate limited but valid [[ "$resp" != "401" && "$resp" != "000" ]] } # ── 调度 ────────────────────────────────────────────────── dispatch_verify() { local provider="$1" local key="$2" case "$provider" in # 国际平台 OpenAI) verify_openai "$key" ;; Anthropic) verify_anthropic "$key" ;; Google) verify_google "$key" ;; HuggingFace) verify_huggingface "$key" ;; Groq) verify_groq "$key" ;; Replicate) verify_replicate "$key" ;; TogetherAI) verify_together "$key" ;; Mistral) verify_mistral "$key" ;; Cohere) verify_cohere "$key" ;; DeepSeek) verify_deepseek "$key" ;; Perplexity) verify_perplexity "$key" ;; OpenRouter) verify_openrouter "$key" ;; AzureOpenAI) verify_openai "$key" ;; # 中国平台 VolcanoArk) verify_volcanoark "$key" ;; ZhipuAI) verify_zhipuai "$key" ;; DashScope) verify_dashscope "$key" ;; Moonshot) verify_moonshot "$key" ;; MiniMax) verify_minimax "$key" ;; TencentHunyuan) verify_tencent_hunyuan "$key" ;; BaiduQianfan) verify_baidu_qianfan "$key" ;; iFlytekSpark) verify_iflytek_spark "$key" ;; iFlytekCodingPlan) verify_iflytek_codingplan "$key" ;; LingyiWanwu) verify_lingyiwanwu "$key" ;; StepFun) verify_stepfun "$key" ;; SiliconFlow) verify_siliconflow "$key" ;; FreeModel) verify_freemodel "$key" ;; *) return 1 ;; esac } # ── 主流程 ──────────────────────────────────────────────── [[ ! -f "$KEYS_FILE" ]] && { log_warn "key 文件不存在: $KEYS_FILE"; exit 1; } total=$(wc -l < "$KEYS_FILE" | tr -d ' ') log_info "开始验证 $total 个候选 key..." count=0 live=0 dead=0 while IFS='|' read -r provider key url desc; do [[ -z "$provider" || -z "$key" ]] && continue count=$((count + 1)) printf " [%d/%d] %s ... " "$count" "$total" "$provider" if dispatch_verify "$provider" "$key"; then echo "LIVE" log_live "$provider | $key" echo "${provider}|${key}|${url}|${desc}" >> "$LIVE_FILE" echo "${provider}|${key}|${url}|${desc}|LIVE" >> "$ALL_FILE" # 按厂商/类型分类写入 category=$(get_category "$provider") filename=$(get_filename "$provider") category_file="${LIVE_DIR}/${category}/${filename}" echo "${provider}|${key}|${url}|${desc}" >> "$category_file" live=$((live + 1)) else echo "DEAD" echo "${provider}|${key}|${url}|${desc}" >> "$DEAD_FILE" echo "${provider}|${key}|${url}|${desc}|DEAD" >> "$ALL_FILE" dead=$((dead + 1)) fi done < "$KEYS_FILE" echo "" log_info "═══ 验证结果 ═══" log_live "Live keys: $live" log_dead "Dead keys: $dead" log_info "总计: $count" echo "" log_info "═══ 按厂商分类的 Live Keys ═══" if [[ -d "${LIVE_DIR}/international" ]]; then echo " 国际平台:" for f in "${LIVE_DIR}/international"/*.txt; do [[ -f "$f" ]] || continue n=$(wc -l < "$f" | tr -d ' ') [[ "$n" -gt 0 ]] && echo " $(basename "$f"): $n 个" done fi if [[ -d "${LIVE_DIR}/china" ]]; then echo " 中国平台:" for f in "${LIVE_DIR}/china"/*.txt; do [[ -f "$f" ]] || continue n=$(wc -l < "$f" | tr -d ' ') [[ "$n" -gt 0 ]] && echo " $(basename "$f"): $n 个" done fi echo "" log_info "Live keys 总表: $LIVE_FILE" log_info "分类目录: ${LIVE_DIR}/" log_info "完整结果: $ALL_FILE"