// AnyRouter - API Proxy Service // Built at: 2025-12-02T13:55:08.917Z // https://github.com/dext7r/anyrouter var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/config.js var config_exports = {}; __export(config_exports, { BUILD_TIME: () => BUILD_TIME, CACHE_KEY: () => CACHE_KEY, CONFIG_CACHE_TTL_MS: () => CONFIG_CACHE_TTL_MS, DEFAULT_ADMIN_PASSWORD: () => DEFAULT_ADMIN_PASSWORD, FALLBACK_CONFIG: () => FALLBACK_CONFIG, KV_CACHE_TTL_SECONDS: () => KV_CACHE_TTL_SECONDS, REDIS_CACHE_TTL_SECONDS: () => REDIS_CACHE_TTL_SECONDS }); var BUILD_TIME, FALLBACK_CONFIG, CONFIG_CACHE_TTL_MS, REDIS_CACHE_TTL_SECONDS, KV_CACHE_TTL_SECONDS, CACHE_KEY, DEFAULT_ADMIN_PASSWORD; var init_config = __esm({ "src/config.js"() { BUILD_TIME = "2025-12-02T13:55:08.917Z"; FALLBACK_CONFIG = {}; CONFIG_CACHE_TTL_MS = 10 * 60 * 1e3; REDIS_CACHE_TTL_SECONDS = 5 * 60; KV_CACHE_TTL_SECONDS = 5 * 60; CACHE_KEY = "anyrouter:api_configs"; DEFAULT_ADMIN_PASSWORD = "123456"; } }); // src/utils/helpers.js init_config(); function getAdminPassword(env) { return env.ADMIN_PASSWORD || DEFAULT_ADMIN_PASSWORD; } function verifyAdmin(request, env) { const authHeader = request.headers.get("Authorization"); if (!authHeader || !authHeader.startsWith("Bearer ")) { return false; } const token = authHeader.substring(7).trim(); return token === getAdminPassword(env).trim(); } function isValidUrl(apiUrl) { if (typeof apiUrl !== "string" || apiUrl.length === 0) { return false; } try { const parsed = new URL(apiUrl); return parsed.protocol === "http:" || parsed.protocol === "https:"; } catch { return false; } } function isValidToken(token) { return typeof token === "string" && token.length > 0 && token.length <= 1e3 && !/[\s\0\n\r]/.test(token); } function validateConfigPayload(body, options = {}) { const { partial = false } = options; if (!body || typeof body !== "object") { return { valid: false, error: "Invalid payload" }; } if (!partial || "api_url" in body) { if (!isValidUrl(body.api_url)) { return { valid: false, error: "api_url is required and must be a valid URL" }; } } if (!partial || "token" in body) { if (!isValidToken(body.token)) { return { valid: false, error: "token is required and must not contain special characters" }; } } if ("enabled" in body && typeof body.enabled !== "boolean") { return { valid: false, error: "enabled must be a boolean" }; } if ("remark" in body) { if (body.remark !== null && typeof body.remark !== "string") { return { valid: false, error: "remark must be a string or null" }; } if (body.remark && body.remark.length > 255) { return { valid: false, error: "remark must be 255 characters or less" }; } } if (partial && !("api_url" in body || "token" in body || "enabled" in body || "remark" in body)) { return { valid: false, error: "No fields provided for update" }; } return { valid: true }; } function jsonResponse(data, status = 200) { return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" } }); } function handleCORS() { return new Response(null, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization" } }); } // src/db/supabase.js init_config(); // src/cache/index.js init_config(); // src/cache/redis.js var RedisClient = class { constructor(url, token) { this.baseUrl = url; this.token = token; } async request(command) { const response = await fetch(`${this.baseUrl}`, { method: "POST", headers: { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json" }, body: JSON.stringify(command) }); const data = await response.json(); if (data.error) throw new Error(data.error); return data.result; } async get(key) { return this.request(["GET", key]); } async set(key, value, ttlSeconds) { if (ttlSeconds) { return this.request(["SET", key, value, "EX", ttlSeconds]); } return this.request(["SET", key, value]); } async del(key) { return this.request(["DEL", key]); } }; function getRedisClient(env) { if (!env.UPSTASH_REDIS_URL || !env.UPSTASH_REDIS_TOKEN) { return null; } return new RedisClient(env.UPSTASH_REDIS_URL, env.UPSTASH_REDIS_TOKEN); } // src/cache/index.js var configCache = { value: null, expiresAt: 0 }; function getCachedConfig() { if (configCache.value && configCache.expiresAt > Date.now()) { return configCache.value; } return null; } function setConfigCache(config) { configCache = { value: config, expiresAt: Date.now() + CONFIG_CACHE_TTL_MS }; } async function invalidateAllCache(env) { configCache = { value: null, expiresAt: 0 }; const redis = getRedisClient(env); if (redis) { try { await redis.del(CACHE_KEY); } catch { } } if (env && env.CONFIG_KV) { try { await env.CONFIG_KV.delete(CACHE_KEY); } catch { } } } async function warmupCache(env) { const result = { success: false, cached: [], keysCount: 0 }; if (!env.SUPABASE_URL || !env.SUPABASE_KEY) { return { ...result, error: "Database not configured" }; } try { let response = await fetch( `${env.SUPABASE_URL}/rest/v1/api_configs?select=*&deleted_at=is.null&order=created_at.desc`, { headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}` } } ); if (!response.ok) { response = await fetch( `${env.SUPABASE_URL}/rest/v1/api_configs?select=*&order=created_at.desc`, { headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}` } } ); } if (!response.ok) { return { ...result, error: `Database query failed: HTTP ${response.status}` }; } const data = await response.json(); const config = {}; data.forEach((item) => { if (!config[item.api_url]) { config[item.api_url] = { keys: [] }; } config[item.api_url].keys.push({ id: item.id, key_id: item.key_id, token: item.token, enabled: item.enabled, remark: item.remark || "", created_at: item.created_at, updated_at: item.updated_at }); }); result.keysCount = data.length; setConfigCache(config); result.cached.push("memory"); const redis = getRedisClient(env); if (redis) { try { const { REDIS_CACHE_TTL_SECONDS: REDIS_CACHE_TTL_SECONDS2 } = await Promise.resolve().then(() => (init_config(), config_exports)); await redis.set(CACHE_KEY, JSON.stringify(config), REDIS_CACHE_TTL_SECONDS2); result.cached.push("redis"); } catch (e) { result.redisError = e.message; } } if (env.CONFIG_KV) { try { const { KV_CACHE_TTL_SECONDS: KV_CACHE_TTL_SECONDS2 } = await Promise.resolve().then(() => (init_config(), config_exports)); await env.CONFIG_KV.put(CACHE_KEY, JSON.stringify(config), { expirationTtl: KV_CACHE_TTL_SECONDS2 }); result.cached.push("kv"); } catch (e) { result.kvError = e.message; } } result.success = true; return result; } catch (error) { return { ...result, error: error.message }; } } // src/db/supabase.js async function clearAllCache(env) { await invalidateAllCache(env); } async function getConfigFromDB(env) { const memoryCached = getCachedConfig(); if (memoryCached) { return memoryCached; } const redis = getRedisClient(env); if (redis) { try { const redisCached = await redis.get(CACHE_KEY); if (redisCached) { const parsed = JSON.parse(redisCached); setConfigCache(parsed); return parsed; } } catch { } } if (env.CONFIG_KV) { try { const kvCached = await env.CONFIG_KV.get(CACHE_KEY, { type: "json" }); if (kvCached) { setConfigCache(kvCached); return kvCached; } } catch { } } if (!env.SUPABASE_URL || !env.SUPABASE_KEY) { setConfigCache(FALLBACK_CONFIG); return FALLBACK_CONFIG; } try { let response = await fetch( `${env.SUPABASE_URL}/rest/v1/api_configs?select=*&deleted_at=is.null&order=created_at.desc`, { headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}` } } ); if (!response.ok) { response = await fetch( `${env.SUPABASE_URL}/rest/v1/api_configs?select=*&order=created_at.desc`, { headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}` } } ); } if (!response.ok) { setConfigCache(FALLBACK_CONFIG); return FALLBACK_CONFIG; } const data = await response.json(); const config = {}; data.forEach((item) => { if (!config[item.api_url]) { config[item.api_url] = { keys: [] }; } config[item.api_url].keys.push({ id: item.id, key_id: item.key_id, sk_alias: item.sk_alias || null, token: item.token, enabled: item.enabled, remark: item.remark || "", expires_at: item.expires_at || null, created_at: item.created_at, updated_at: item.updated_at }); }); const finalizedConfig = Object.keys(config).length > 0 ? config : FALLBACK_CONFIG; setConfigCache(finalizedConfig); if (redis) { redis.set(CACHE_KEY, JSON.stringify(finalizedConfig), REDIS_CACHE_TTL_SECONDS).catch(() => { }); } if (env.CONFIG_KV) { env.CONFIG_KV.put(CACHE_KEY, JSON.stringify(finalizedConfig), { expirationTtl: KV_CACHE_TTL_SECONDS }).catch(() => { }); } return finalizedConfig; } catch { setConfigCache(FALLBACK_CONFIG); return FALLBACK_CONFIG; } } async function saveConfigToDB(env, apiUrl, token, enabled, remark = "", expiresAt = null) { if (!env.SUPABASE_URL || !env.SUPABASE_KEY) { return { success: false, error: "Database not configured" }; } try { const response = await fetch(`${env.SUPABASE_URL}/rest/v1/api_configs`, { method: "POST", headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}`, "Content-Type": "application/json", Prefer: "return=representation" }, body: JSON.stringify({ api_url: apiUrl, token, enabled, remark: remark || null, expires_at: expiresAt || null }) }); if (!response.ok) { return { success: false, error: await response.text() }; } await clearAllCache(env); return { success: true, data: await response.json() }; } catch (error) { return { success: false, error: error.message }; } } async function updateConfigInDB(env, id, updates) { if (!env.SUPABASE_URL || !env.SUPABASE_KEY) { return { success: false, error: "Database not configured" }; } try { const data = { ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() }; const response = await fetch( `${env.SUPABASE_URL}/rest/v1/api_configs?id=eq.${id}`, { method: "PATCH", headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify(data) } ); if (!response.ok) { return { success: false, error: await response.text() }; } await clearAllCache(env); return { success: true }; } catch (error) { return { success: false, error: error.message }; } } async function deleteConfigFromDB(env, id) { if (!env.SUPABASE_URL || !env.SUPABASE_KEY) { return { success: false, error: "Database not configured" }; } try { const response = await fetch( `${env.SUPABASE_URL}/rest/v1/api_configs?id=eq.${id}`, { method: "PATCH", headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ deleted_at: (/* @__PURE__ */ new Date()).toISOString() }) } ); if (!response.ok) { return { success: false, error: await response.text() }; } await clearAllCache(env); return { success: true }; } catch (error) { return { success: false, error: error.message }; } } function generateSkAlias() { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let result = "sk-ar-"; for (let i = 0; i < 32; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } async function updateSkAlias(env, id, skAlias = null) { if (!env.SUPABASE_URL || !env.SUPABASE_KEY) { return { success: false, error: "Database not configured" }; } const newAlias = skAlias || generateSkAlias(); try { const response = await fetch( `${env.SUPABASE_URL}/rest/v1/api_configs?id=eq.${id}`, { method: "PATCH", headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}`, "Content-Type": "application/json", Prefer: "return=representation" }, body: JSON.stringify({ sk_alias: newAlias, updated_at: (/* @__PURE__ */ new Date()).toISOString() }) } ); if (!response.ok) { return { success: false, error: await response.text() }; } await clearAllCache(env); return { success: true, sk_alias: newAlias }; } catch (error) { return { success: false, error: error.message }; } } function findBySkAlias(config, skAlias) { for (const [apiUrl, apiConfig] of Object.entries(config)) { if (!apiConfig.keys) continue; const key = apiConfig.keys.find((k) => k.sk_alias === skAlias); if (key) { return { apiUrl, key }; } } return null; } // src/cache/stats.js var STATS_PREFIX = "anyrouter:stats"; var BLACKLIST_KEY = "anyrouter:blacklist:ips"; var STATS_SAMPLE_PERCENT = 100; function getTodayKey() { return (/* @__PURE__ */ new Date()).toISOString().split("T")[0]; } function getHourKey() { const now = /* @__PURE__ */ new Date(); return `${now.toISOString().split("T")[0]}-${now.getUTCHours().toString().padStart(2, "0")}`; } async function recordRequest(env, data) { const redis = getRedisClient(env); if (!redis) return; const shouldRecord = Math.random() * 100 < STATS_SAMPLE_PERCENT; if (!shouldRecord) return; const multiplier = Math.round(100 / STATS_SAMPLE_PERCENT); const { apiUrl, keyId, success, ip } = data; const today = getTodayKey(); const hour = getHourKey(); try { await redis.request(["INCRBY", `${STATS_PREFIX}:daily:${today}:total`, multiplier]); await redis.request(["INCRBY", `${STATS_PREFIX}:daily:${today}:${success ? "success" : "error"}`, multiplier]); await redis.request(["INCRBY", `${STATS_PREFIX}:hourly:${hour}:total`, multiplier]); if (apiUrl) { await redis.request(["HINCRBY", `${STATS_PREFIX}:daily:${today}:urls`, apiUrl, multiplier]); } if (keyId) { await redis.request(["HINCRBY", `${STATS_PREFIX}:daily:${today}:keys`, keyId, multiplier]); await redis.request(["HSET", `${STATS_PREFIX}:lastused`, keyId, (/* @__PURE__ */ new Date()).toISOString()]); } if (ip && ip !== "unknown") { await redis.request(["HINCRBY", `${STATS_PREFIX}:daily:${today}:ips`, ip, multiplier]); } const ttl = 7 * 24 * 60 * 60; await redis.request(["EXPIRE", `${STATS_PREFIX}:daily:${today}:total`, ttl]); await redis.request(["EXPIRE", `${STATS_PREFIX}:hourly:${hour}:total`, ttl]); } catch { } } async function getStats(env, days = 7) { const redis = getRedisClient(env); if (!redis) { return { enabled: false, message: "Redis not configured" }; } try { const stats = { enabled: true, daily: [], hourly: [], topUrls: {}, topKeys: {}, topIps: {}, summary: { total: 0, success: 0, error: 0 } }; const dates = []; for (let i = 0; i < days; i++) { const d = /* @__PURE__ */ new Date(); d.setDate(d.getDate() - i); dates.push(d.toISOString().split("T")[0]); } for (const date of dates) { const total = await redis.get(`${STATS_PREFIX}:daily:${date}:total`) || 0; const success = await redis.get(`${STATS_PREFIX}:daily:${date}:success`) || 0; const error = await redis.get(`${STATS_PREFIX}:daily:${date}:error`) || 0; stats.daily.push({ date, total: parseInt(total), success: parseInt(success), error: parseInt(error) }); stats.summary.total += parseInt(total); stats.summary.success += parseInt(success); stats.summary.error += parseInt(error); } const today = getTodayKey(); const urlStats = await redis.request(["HGETALL", `${STATS_PREFIX}:daily:${today}:urls`]); if (urlStats && Array.isArray(urlStats)) { for (let i = 0; i < urlStats.length; i += 2) { stats.topUrls[urlStats[i]] = parseInt(urlStats[i + 1]); } } const keyStats = await redis.request(["HGETALL", `${STATS_PREFIX}:daily:${today}:keys`]); if (keyStats && Array.isArray(keyStats)) { for (let i = 0; i < keyStats.length; i += 2) { stats.topKeys[keyStats[i]] = parseInt(keyStats[i + 1]); } } const ipStats = await redis.request(["HGETALL", `${STATS_PREFIX}:daily:${today}:ips`]); if (ipStats && Array.isArray(ipStats)) { for (let i = 0; i < ipStats.length; i += 2) { stats.topIps[ipStats[i]] = parseInt(ipStats[i + 1]); } } for (let i = 0; i < 24; i++) { const d = /* @__PURE__ */ new Date(); d.setHours(d.getHours() - i); const hourKey = `${d.toISOString().split("T")[0]}-${d.getUTCHours().toString().padStart(2, "0")}`; const hourTotal = await redis.get(`${STATS_PREFIX}:hourly:${hourKey}:total`) || 0; stats.hourly.push({ hour: hourKey, total: parseInt(hourTotal) }); } stats.daily.reverse(); stats.hourly.reverse(); return stats; } catch (error) { return { enabled: false, error: error.message }; } } async function getLastUsedTimes(env) { const redis = getRedisClient(env); if (!redis) return {}; try { const result = await redis.request(["HGETALL", `${STATS_PREFIX}:lastused`]); if (!result || !Array.isArray(result)) return {}; const lastUsed = {}; for (let i = 0; i < result.length; i += 2) { lastUsed[result[i]] = result[i + 1]; } return lastUsed; } catch { return {}; } } async function recordLogin(env, request) { const redis = getRedisClient(env); if (!redis) return; try { const ip = request.headers.get("CF-Connecting-IP") || request.headers.get("X-Forwarded-For")?.split(",")[0]?.trim() || "unknown"; const userAgent = request.headers.get("User-Agent") || "unknown"; const now = (/* @__PURE__ */ new Date()).toISOString(); const record = JSON.stringify({ time: now, ip, ua: userAgent }); await redis.request(["LPUSH", `${STATS_PREFIX}:logins`, record]); await redis.request(["LTRIM", `${STATS_PREFIX}:logins`, 0, 49]); } catch { } } async function getLoginRecords(env, limit = 20) { const redis = getRedisClient(env); if (!redis) return []; try { const records = await redis.request(["LRANGE", `${STATS_PREFIX}:logins`, 0, limit - 1]); if (!records || !Array.isArray(records)) return []; return records.map((r) => { try { return JSON.parse(r); } catch { return null; } }).filter(Boolean); } catch { return []; } } async function isIpBlocked(env, ip) { const redis = getRedisClient(env); if (!redis || !ip || ip === "unknown") return { blocked: false }; try { const reason = await redis.request(["HGET", BLACKLIST_KEY, ip]); if (reason) { return { blocked: true, reason: reason || "\u5DF2\u88AB\u7BA1\u7406\u5458\u5C01\u7981" }; } return { blocked: false }; } catch { return { blocked: false }; } } async function blockIp(env, ip, reason = "\u624B\u52A8\u5C01\u7981") { const redis = getRedisClient(env); if (!redis) return { success: false, error: "Redis not configured" }; try { const record = JSON.stringify({ reason, blocked_at: (/* @__PURE__ */ new Date()).toISOString() }); await redis.request(["HSET", BLACKLIST_KEY, ip, record]); return { success: true }; } catch (error) { return { success: false, error: error.message }; } } async function unblockIp(env, ip) { const redis = getRedisClient(env); if (!redis) return { success: false, error: "Redis not configured" }; try { await redis.request(["HDEL", BLACKLIST_KEY, ip]); return { success: true }; } catch (error) { return { success: false, error: error.message }; } } async function getBlockedIps(env) { const redis = getRedisClient(env); if (!redis) return []; try { const result = await redis.request(["HGETALL", BLACKLIST_KEY]); if (!result || !Array.isArray(result)) return []; const blockedIps = []; for (let i = 0; i < result.length; i += 2) { const ip = result[i]; let info = { reason: "\u624B\u52A8\u5C01\u7981", blocked_at: null }; try { info = JSON.parse(result[i + 1]); } catch { info.reason = result[i + 1] || "\u624B\u52A8\u5C01\u7981"; } blockedIps.push({ ip, ...info }); } return blockedIps; } catch { return []; } } // src/handlers/api.js async function handleApiRequest(request, env, url) { if (!verifyAdmin(request, env)) { return jsonResponse({ error: "Unauthorized" }, 401); } const path = url.pathname; if (path === "/api/configs" && request.method === "GET") { const config = await getConfigFromDB(env); const lastUsed = await getLastUsedTimes(env); return jsonResponse({ success: true, data: config, lastUsed }); } if (path === "/api/configs" && request.method === "POST") { const body = await request.json(); const validation = validateConfigPayload(body); if (!validation.valid) { return jsonResponse({ error: validation.error }, 400); } const result = await saveConfigToDB( env, body.api_url, body.token, body.enabled ?? true, body.remark || "", body.expires_at || null ); return jsonResponse(result, result.success ? 200 : 400); } if (path.match(/^\/api\/configs\/\d+$/) && request.method === "PATCH") { const id = path.split("/").pop(); const body = await request.json(); const validation = validateConfigPayload(body, { partial: true }); if (!validation.valid) { return jsonResponse({ error: validation.error }, 400); } const result = await updateConfigInDB(env, id, body); return jsonResponse(result, result.success ? 200 : 400); } if (path.match(/^\/api\/configs\/\d+$/) && request.method === "DELETE") { const id = path.split("/").pop(); const result = await deleteConfigFromDB(env, id); return jsonResponse(result, result.success ? 200 : 400); } if (path.match(/^\/api\/configs\/\d+\/sk-alias$/) && request.method === "POST") { const id = path.split("/")[3]; const result = await updateSkAlias(env, id); return jsonResponse(result, result.success ? 200 : 400); } if (path.match(/^\/api\/configs\/\d+\/sk-alias$/) && request.method === "DELETE") { const id = path.split("/")[3]; const result = await updateSkAlias(env, id, ""); return jsonResponse(result, result.success ? 200 : 400); } if (path === "/api/status" && request.method === "GET") { const hasDbConfig = Boolean(env.SUPABASE_URL && env.SUPABASE_KEY); const result = { success: true, storage_mode: hasDbConfig ? "database" : "passthrough", database_configured: hasDbConfig, database_connected: false }; if (hasDbConfig) { try { const response = await fetch( `${env.SUPABASE_URL}/rest/v1/api_configs?select=count&limit=1`, { headers: { apikey: env.SUPABASE_KEY, Authorization: `Bearer ${env.SUPABASE_KEY}` } } ); result.database_connected = response.ok; if (!response.ok) { result.database_error = `HTTP ${response.status}`; } } catch (error) { result.database_connected = false; result.database_error = error.message; } } return jsonResponse(result); } if (path === "/api/login" && request.method === "POST") { await recordLogin(env, request); return jsonResponse({ success: true }); } if (path === "/api/logins" && request.method === "GET") { const limit = parseInt(url.searchParams.get("limit") || "20"); const records = await getLoginRecords(env, limit); return jsonResponse({ success: true, data: records }); } if (path === "/api/stats" && request.method === "GET") { const days = parseInt(url.searchParams.get("days") || "7"); const stats = await getStats(env, days); return jsonResponse({ success: true, data: stats }); } if (path === "/api/blacklist" && request.method === "GET") { const blockedIps = await getBlockedIps(env); return jsonResponse({ success: true, data: blockedIps }); } if (path === "/api/blacklist" && request.method === "POST") { const body = await request.json(); if (!body.ip) { return jsonResponse({ error: "IP address is required" }, 400); } const result = await blockIp(env, body.ip, body.reason || "\u624B\u52A8\u5C01\u7981"); return jsonResponse(result, result.success ? 200 : 400); } if (path.startsWith("/api/blacklist/") && request.method === "DELETE") { const ip = decodeURIComponent(path.replace("/api/blacklist/", "")); if (!ip) { return jsonResponse({ error: "IP address is required" }, 400); } const result = await unblockIp(env, ip); return jsonResponse(result, result.success ? 200 : 400); } if (path === "/api/redis/test" && request.method === "GET") { const redis = getRedisClient(env); if (!redis) { return jsonResponse({ success: false, configured: false, error: "Redis not configured (missing UPSTASH_REDIS_URL or UPSTASH_REDIS_TOKEN)" }); } try { const testKey = "anyrouter:test:ping"; const testValue = Date.now().toString(); await redis.set(testKey, testValue, 60); const readValue = await redis.get(testKey); await redis.del(testKey); return jsonResponse({ success: true, configured: true, connected: true, latency_test: readValue === testValue ? "passed" : "failed", message: "Redis connection successful" }); } catch (error) { return jsonResponse({ success: false, configured: true, connected: false, error: error.message }); } } if (path === "/api/cache/warmup" && request.method === "POST") { const result = await warmupCache(env); return jsonResponse(result, result.success ? 200 : 400); } if (path === "/api/stats/test" && request.method === "POST") { const redis = getRedisClient(env); if (!redis) { return jsonResponse({ success: false, error: "Redis not configured" }); } const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0]; const key = `anyrouter:stats:daily:${today}:total`; try { const result = await redis.request(["INCR", key]); await redis.request(["EXPIRE", key, 604800]); return jsonResponse({ success: true, message: "\u5199\u5165\u6210\u529F", key, newValue: result }); } catch (error) { return jsonResponse({ success: false, error: error.message, stack: error.stack }); } } return jsonResponse({ error: "Not found" }, 404); } // src/handlers/proxy.js function errorResponse(code, message, hint) { return jsonResponse({ error: { code, message, hint, contact: "\u5982\u6709\u7591\u95EE\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458" } }, code === "UNAUTHORIZED" ? 401 : code === "BAD_REQUEST" ? 400 : code === "NOT_FOUND" ? 404 : code === "FORBIDDEN" ? 403 : code === "SERVICE_ERROR" ? 503 : 500); } async function handleProxyRequest(request, env, url, ctx) { const clientIp = request.headers.get("CF-Connecting-IP") || request.headers.get("X-Forwarded-For")?.split(",")[0]?.trim() || "unknown"; const blockCheck = await isIpBlocked(env, clientIp); if (blockCheck.blocked) { return jsonResponse({ error: { code: "IP_BLOCKED", message: "IP \u5DF2\u88AB\u5C01\u7981", reason: blockCheck.reason, ip: clientIp, contact: "\u5982\u6709\u7591\u95EE\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458" } }, 403); } const authHeader = request.headers.get("Authorization"); if (!authHeader || !authHeader.startsWith("Bearer ")) { return errorResponse( "UNAUTHORIZED", "\u7F3A\u5C11\u6388\u6743\u4FE1\u606F", "\u8BF7\u5728 Authorization header \u4E2D\u63D0\u4F9B Bearer token\uFF0C\u683C\u5F0F: Bearer : \u6216 Bearer sk-ar-xxx" ); } const authValue = authHeader.substring(7).trim(); const config = await getConfigFromDB(env); let tokenToUse; let targetApiUrl; let usedKeyId = null; if (authValue.startsWith("sk-ar-")) { const found = findBySkAlias(config, authValue); if (!found) { return errorResponse( "NOT_FOUND", "SK \u522B\u540D\u4E0D\u5B58\u5728", `\u627E\u4E0D\u5230 SK \u522B\u540D "${authValue}"\uFF0C\u8BF7\u68C0\u67E5\u662F\u5426\u8F93\u5165\u6B63\u786E\u6216\u8054\u7CFB\u7BA1\u7406\u5458\u83B7\u53D6\u6709\u6548\u7684 SK` ); } if (!found.key.enabled) { return errorResponse( "FORBIDDEN", "SK \u5DF2\u88AB\u7981\u7528", "\u6B64 SK \u522B\u540D\u5F53\u524D\u5904\u4E8E\u7981\u7528\u72B6\u6001\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u542F\u7528" ); } if (found.key.expires_at && new Date(found.key.expires_at) < /* @__PURE__ */ new Date()) { return errorResponse( "FORBIDDEN", "SK \u5DF2\u8FC7\u671F", `\u6B64 SK \u522B\u540D\u5DF2\u4E8E ${found.key.expires_at} \u8FC7\u671F\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u7EED\u671F\u6216\u83B7\u53D6\u65B0\u7684 SK` ); } tokenToUse = found.key.token; targetApiUrl = found.apiUrl; usedKeyId = found.key.key_id; } else { const lastColonIndex = authValue.lastIndexOf(":"); if (lastColonIndex === -1 || lastColonIndex < 8) { return errorResponse( "BAD_REQUEST", "\u6388\u6743\u683C\u5F0F\u9519\u8BEF", "\u6B63\u786E\u683C\u5F0F: : \u6216 sk-ar-xxx\uFF0C\u4F8B\u5982 https://api.openai.com:a3x9k2" ); } targetApiUrl = authValue.substring(0, lastColonIndex); const keyPart = authValue.substring(lastColonIndex + 1); if (!targetApiUrl.startsWith("http://") && !targetApiUrl.startsWith("https://")) { return errorResponse( "BAD_REQUEST", "API URL \u683C\u5F0F\u65E0\u6548", "URL \u5FC5\u987B\u4EE5 http:// \u6216 https:// \u5F00\u5934" ); } if (!keyPart) { return errorResponse( "BAD_REQUEST", "\u7F3A\u5C11 Key ID \u6216 Token", "\u8BF7\u5728 URL \u540E\u9762\u52A0\u4E0A\u5192\u53F7\u548C Key ID\uFF086\u4F4D\uFF09\u6216\u5B8C\u6574 Token" ); } const isKeyId = /^[a-z0-9]{6}$/.test(keyPart); if (isKeyId) { const keyId = keyPart; usedKeyId = keyId; if (!config[targetApiUrl]) { return errorResponse( "NOT_FOUND", "API \u5730\u5740\u672A\u914D\u7F6E", `\u76EE\u6807 API "${targetApiUrl}" \u5C1A\u672A\u5728\u7CFB\u7EDF\u4E2D\u6CE8\u518C\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u914D\u7F6E` ); } const keyConfig = config[targetApiUrl].keys.find((k) => k.key_id === keyId); if (!keyConfig) { return errorResponse( "NOT_FOUND", "Key ID \u4E0D\u5B58\u5728", `\u627E\u4E0D\u5230 Key ID "${keyId}"\uFF0C\u8BF7\u68C0\u67E5\u662F\u5426\u8F93\u5165\u6B63\u786E\u6216\u8054\u7CFB\u7BA1\u7406\u5458\u83B7\u53D6\u6709\u6548\u7684 Key ID` ); } if (!keyConfig.enabled) { return errorResponse( "FORBIDDEN", "Key \u5DF2\u88AB\u7981\u7528", `Key ID "${keyId}" \u5F53\u524D\u5904\u4E8E\u7981\u7528\u72B6\u6001\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u542F\u7528\u6216\u83B7\u53D6\u65B0\u7684 Key ID` ); } if (keyConfig.expires_at && new Date(keyConfig.expires_at) < /* @__PURE__ */ new Date()) { return errorResponse( "FORBIDDEN", "Key \u5DF2\u8FC7\u671F", `Key ID "${keyId}" \u5DF2\u4E8E ${keyConfig.expires_at} \u8FC7\u671F\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u7EED\u671F\u6216\u83B7\u53D6\u65B0\u7684 Key ID` ); } tokenToUse = keyConfig.token; } else { tokenToUse = keyPart; } } const targetUrl = new URL(targetApiUrl); const selfHostname = url.hostname.toLowerCase(); const targetHostname = targetUrl.hostname.toLowerCase(); if (targetHostname === selfHostname || targetHostname.endsWith("." + selfHostname) || selfHostname.endsWith("." + targetHostname)) { return errorResponse( "FORBIDDEN", "\u7981\u6B62\u53CD\u4EE3\u81EA\u8EAB", "\u4E0D\u5141\u8BB8\u5C06\u8BF7\u6C42\u4EE3\u7406\u5230\u4EE3\u7406\u670D\u52A1\u81EA\u8EAB\u7684\u57DF\u540D\uFF0C\u8FD9\u4F1A\u9020\u6210\u5FAA\u73AF\u8BF7\u6C42" ); } url.protocol = targetUrl.protocol; url.hostname = targetUrl.hostname; url.port = targetUrl.port || ""; const headers = new Headers(request.headers); headers.set("authorization", "Bearer " + tokenToUse); const modifiedRequest = new Request(url.toString(), { headers, method: request.method, body: request.body, redirect: "follow" }); try { const response = await fetch(modifiedRequest); const modifiedResponse = new Response(response.body, response); modifiedResponse.headers.set("Access-Control-Allow-Origin", "*"); const contentType = response.headers.get("content-type") || ""; const isStreaming = contentType.includes("text/event-stream") || contentType.includes("stream") || request.headers.get("accept")?.includes("text/event-stream"); if (isStreaming) { modifiedResponse.headers.set("Cache-Control", "no-cache, no-store, no-transform, must-revalidate"); modifiedResponse.headers.set("X-Accel-Buffering", "no"); modifiedResponse.headers.set("Connection", "keep-alive"); modifiedResponse.headers.set("Content-Encoding", "identity"); modifiedResponse.headers.delete("Content-Length"); } if (ctx && ctx.waitUntil) { ctx.waitUntil(recordRequest(env, { apiUrl: targetApiUrl, keyId: usedKeyId, success: response.ok, ip: clientIp })); } return modifiedResponse; } catch (error) { if (ctx && ctx.waitUntil) { ctx.waitUntil(recordRequest(env, { apiUrl: targetApiUrl, keyId: usedKeyId, success: false, ip: clientIp })); } console.error("Proxy request error:", error); return errorResponse( "SERVICE_ERROR", "\u4EE3\u7406\u8BF7\u6C42\u5931\u8D25", `\u65E0\u6CD5\u8FDE\u63A5\u5230\u76EE\u6807 API "${targetApiUrl}"\uFF0C\u53EF\u80FD\u662F\u7F51\u7EDC\u95EE\u9898\u6216\u76EE\u6807\u670D\u52A1\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5` ); } } // src/pages/status.js init_config(); function getStatusHtml() { const buildTimeFormatted = new Date(BUILD_TIME).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }); return ` AnyRouter - API Proxy Service

AnyRouter

\u8F7B\u91CF\u7EA7 API \u4EE3\u7406\u670D\u52A1

\u670D\u52A1\u8FD0\u884C\u4E2D
\u591A\u7AEF\u70B9\u4EE3\u7406
Token \u7BA1\u7406
\u5B89\u5168\u8F6C\u53D1
\u8FB9\u7F18\u52A0\u901F
`; } // src/pages/admin.js function getAdminHtml() { return ` API Proxy Admin