Files
hack/tools/scripts/llm-key-hunter/deep_verify.sh
T

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"