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

458 lines
16 KiB
Bash
Executable File

#!/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 <UUID>
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 <APIKey.APISecret>
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 <sk-xxx>
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 <sk-xxx>
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 <key>
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 <key>
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 <key>
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 <APIKey> (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 <sk-xxx>
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 <sk-xxx>
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 <sk-xxx>
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 <fe_oa_...>
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"