#!/bin/bash # ═══════════════════════════════════════════════════════════════ # deep_verify.sh — 深度验证模块 # 发实际 chat completion 请求,区分: # USABLE (200) — key 能用,有额度 # VALID_NO_BALANCE (402/429) — key 有效但没额度/欠费 # VALID_NO_ACCESS (404) — key 有效但模型不可用 # DEAD (401/403) — key 无效/已吊销 # ═══════════════════════════════════════════════════════════════ set -euo pipefail KEYS_FILE="${1:-results/live_keys_raw.txt}" REPORT_DIR="${2:-results}" 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_usable() { echo -e "${GREEN}[USABLE]${NC} $1"; } log_nobal() { echo -e "${YELLOW}[NO_BALANCE]${NC} $1"; } log_dead() { echo -e "${RED}[DEAD]${NC} $1"; } mkdir -p "$REPORT_DIR/deep_verify" # ── 深度验证函数 ────────────────────────────────────────── # 每个函数发一条真实 chat completion,返回: USABLE/NO_BALANCE/NO_ACCESS/DEAD deep_verify() { local provider="$1" local key="$2" local resp code body case "$provider" in OpenAI) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.openai.com/v1/chat/completions 2>/dev/null || echo $'\n000') ;; Anthropic) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "x-api-key: $key" -H "anthropic-version: 2023-06-01" -H "Content-Type: application/json" \ -d '{"model":"claude-3-5-haiku-20241022","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.anthropic.com/v1/messages 2>/dev/null || echo $'\n000') ;; Google) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Content-Type: application/json" \ -d '{"contents":[{"parts":[{"text":"hi"}]}],"generationConfig":{"maxOutputTokens":1}}' \ "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=$key" 2>/dev/null || echo $'\n000') ;; Groq) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"llama-3.1-8b-instant","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.groq.com/openai/v1/chat/completions 2>/dev/null || echo $'\n000') ;; DeepSeek) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.deepseek.com/v1/chat/completions 2>/dev/null || echo $'\n000') ;; OpenRouter) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"meta-llama/llama-3.1-8b-instruct","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://openrouter.ai/api/v1/chat/completions 2>/dev/null || echo $'\n000') ;; Replicate) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://api.replicate.com/v1/account 2>/dev/null || echo $'\n000') ;; Perplexity) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"llama-3.1-8b-online","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.perplexity.ai/chat/completions 2>/dev/null || echo $'\n000') ;; DashScope) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"qwen-plus","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions 2>/dev/null || echo $'\n000') ;; Moonshot) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"moonshot-v1-8k","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.moonshot.cn/v1/chat/completions 2>/dev/null || echo $'\n000') ;; VolcanoArk) # 火山引擎 — 先试标准 API (doubao-seed-2-0-pro), 再试 Coding Plan (claude-sonnet-4-6) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"doubao-seed-2-0-pro-260215","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://ark.cn-beijing.volces.com/api/v3/chat/completions 2>/dev/null || echo $'\n000') code=$(echo "$resp" | tail -1) if [ "$code" = "200" ]; then : # USABLE else # 标准API不行, 试 Coding Plan (Anthropic-compatible) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "x-api-key: $key" -H "anthropic-version: 2023-06-01" -H "Content-Type: application/json" \ -d '{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://ark.cn-beijing.volces.com/api/coding/v1/messages 2>/dev/null || echo $'\n000') fi ;; ZhipuAI) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"glm-4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://open.bigmodel.cn/api/paas/v4/chat/completions 2>/dev/null || echo $'\n000') ;; TogetherAI) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"meta-llama/Llama-3.2-3B-Instruct-Turbo","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.together.xyz/v1/chat/completions 2>/dev/null || echo $'\n000') ;; LingyiWanwu) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"yi-large","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.lingyiwanwu.com/v1/chat/completions 2>/dev/null || echo $'\n000') ;; StepFun) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"step-1-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.stepfun.com/v1/chat/completions 2>/dev/null || echo $'\n000') ;; SiliconFlow) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" -H "Content-Type: application/json" \ -d '{"model":"Qwen/Qwen2.5-7B-Instruct","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \ https://api.siliconflow.cn/v1/chat/completions 2>/dev/null || echo $'\n000') ;; HuggingFace) resp=$(curl -s -w "\n%{http_code}" --max-time 10 \ -H "Authorization: Bearer $key" \ https://huggingface.co/api/whoami-v2 2>/dev/null || echo $'\n000') ;; *) return 1 ;; esac code=$(echo "$resp" | tail -1) body=$(echo "$resp" | head -n -1) case "$code" in 200) # 检查 body 里有没有 error if echo "$body" | grep -qiE '"error"|"code":\s*"[A-Z]' 2>/dev/null; then # 有 error 字段,可能是 Arrearage 等 if echo "$body" | grep -qiE 'Arrearage|insufficient|balance|quota|429|limit' 2>/dev/null; then echo "NO_BALANCE" else echo "NO_ACCESS" fi else echo "USABLE" fi ;; 402|429) echo "NO_BALANCE" ;; 404) # 404 可能是 model 不存在或 key 无效 if echo "$body" | grep -qiE 'invalid|unauthorized|forbidden|api.key' 2>/dev/null; then echo "DEAD" else echo "NO_ACCESS" fi ;; 401|403) echo "DEAD" ;; 400) # 400 可能是 Arrearage (阿里云) 或其他 if echo "$body" | grep -qiE 'Arrearage|insufficient|balance|overdue|payment' 2>/dev/null; then echo "NO_BALANCE" elif echo "$body" | grep -qiE 'suspended|limit|quota|rate' 2>/dev/null; then echo "NO_BALANCE" else echo "NO_ACCESS" fi ;; *) echo "UNKNOWN" ;; esac } # ── 主流程 ──────────────────────────────────────────────── [[ ! -f "$KEYS_FILE" ]] && { echo "key 文件不存在: $KEYS_FILE"; exit 1; } total=$(wc -l < "$KEYS_FILE" | tr -d ' ') log_info "深度验证 $total 个 key (发真实 chat completion)..." usable_file="${REPORT_DIR}/deep_verify/usable.txt" nobal_file="${REPORT_DIR}/deep_verify/valid_no_balance.txt" noacc_file="${REPORT_DIR}/deep_verify/valid_no_access.txt" dead_file="${REPORT_DIR}/deep_verify/dead.txt" > "$usable_file" > "$nobal_file" > "$noacc_file" > "$dead_file" count=0 usable=0 nobal=0 noacc=0 dead=0 while IFS='|' read -r st provider key url desc; do [[ "$st" != "LIVE" ]] && continue [[ -z "$provider" || -z "$key" ]] && continue count=$((count + 1)) result=$(deep_verify "$provider" "$key") line="${provider}|${key}|${url}|${desc}" case "$result" in USABLE) echo "$line" >> "$usable_file" log_usable "$provider | ${key:0:25}..." usable=$((usable + 1)) ;; NO_BALANCE) echo "$line" >> "$nobal_file" nobal=$((nobal + 1)) ;; NO_ACCESS) echo "$line" >> "$noacc_file" noacc=$((noacc + 1)) ;; DEAD) echo "$line" >> "$dead_file" dead=$((dead + 1)) ;; *) echo "$line" >> "$dead_file" dead=$((dead + 1)) ;; esac done < "$KEYS_FILE" echo "" echo "══════════════════════════════════════════════════════════════" echo " 深度验证结果" echo "══════════════════════════════════════════════════════════════" log_usable "USABLE (能用): $usable" log_nobal "VALID_NO_BALANCE (没额度): $nobal" log_info "VALID_NO_ACCESS (没权限): $noacc" log_dead "DEAD (已吊销): $dead" log_info "总计: $count" echo "" echo "文件:" echo " $usable_file" echo " $nobal_file" echo " $noacc_file" echo " $dead_file"