270 lines
12 KiB
Bash
Executable File
270 lines
12 KiB
Bash
Executable File
#!/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"
|