458 lines
16 KiB
Bash
Executable File
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"
|