/// import { serve, crypto, base64Decode } from "./deps.ts"; // 声明 Deno 命名空间 declare namespace Deno { interface Kv { get(key: unknown[]): Promise<{ value: any }>; set(key: unknown[], value: unknown, options?: { expireIn?: number }): Promise; delete(key: unknown[]): Promise; } interface Env { get(key: string): string | undefined; } const env: Env; const exit: (code: number) => never; const openKv: () => Promise; } // 类型定义 interface Vendor { id: number; name: string; icon: string; } interface VendorResponse { data: { [key: string]: Vendor; }; } interface Price { id?: string; model: string; billing_type: 'tokens' | 'times'; channel_type: number; currency: 'CNY' | 'USD'; input_price: number; output_price: number; input_ratio: number; output_ratio: number; price_source: string; status: 'pending' | 'approved' | 'rejected'; created_by: string; created_at: string; reviewed_by?: string; reviewed_at?: string; } // 声明全局变量 declare const vendors: { [key: string]: Vendor }; // 缓存供应商数据 let vendorsCache: VendorResponse | null = null; let vendorsCacheTime: number = 0; const CACHE_DURATION = 1000 * 60 * 5; // 5分钟缓存 // 初始化 KV 存储 let kv: Deno.Kv; try { kv = await Deno.openKv(); } catch (error) { console.error('初始化 KV 存储失败:', error); Deno.exit(1); } // 获取供应商数据 async function getVendors(): Promise { const now = Date.now(); if (vendorsCache && (now - vendorsCacheTime) < CACHE_DURATION) { return vendorsCache; } try { const response = await fetch('https://oapi.czl.net/api/ownedby'); const data = await response.json() as VendorResponse; vendorsCache = data; vendorsCacheTime = now; return data; } catch (error) { console.error('获取供应商数据失败:', error); throw new Error('获取供应商数据失败'); } } // 计算倍率 function calculateRatio(price: number, currency: 'CNY' | 'USD'): number { return currency === 'USD' ? price / 2 : price / 14; } // 修改验证价格数据函数 function validatePrice(data: any): string | null { console.log('验证数据:', data); // 添加日志 if (!data.model || !data.billing_type || !data.channel_type || !data.currency || data.input_price === undefined || data.output_price === undefined || !data.price_source) { return "所有字段都是必需的"; } if (data.billing_type !== 'tokens' && data.billing_type !== 'times') { return "计费类型必须是 tokens 或 times"; } if (data.currency !== 'CNY' && data.currency !== 'USD') { return "币种必须是 CNY 或 USD"; } const channel_type = Number(data.channel_type); const input_price = Number(data.input_price); const output_price = Number(data.output_price); if (isNaN(channel_type) || isNaN(input_price) || isNaN(output_price)) { return "价格和供应商ID必须是数字"; } if (channel_type < 0 || input_price < 0 || output_price < 0) { return "价格和供应商ID不能为负数"; } return null; } // 添加 Discourse SSO 配置 const DISCOURSE_URL = Deno.env.get('DISCOURSE_URL') || 'https://discourse.czl.net'; const DISCOURSE_SSO_SECRET = Deno.env.get('DISCOURSE_SSO_SECRET'); // 验证必需的环境变量 if (!DISCOURSE_SSO_SECRET) { console.error('错误: 必须设置 DISCOURSE_SSO_SECRET 环境变量'); Deno.exit(1); } // 添加认证相关函数 async function verifyDiscourseSSO(request: Request): Promise { const cookie = request.headers.get('cookie'); if (!cookie) return null; const sessionMatch = cookie.match(/session=([^;]+)/); if (!sessionMatch) return null; const sessionId = sessionMatch[1]; const session = await kv.get(['sessions', sessionId]); if (!session.value) return null; return session.value.username; } // 修改 generateSSO 函数 async function generateSSO(returnUrl: string): Promise { const encoder = new TextEncoder(); const nonce = crypto.randomUUID(); const rawPayload = `nonce=${nonce}&return_sso_url=${encodeURIComponent(returnUrl)}`; const base64Payload = btoa(rawPayload); const key = await crypto.subtle.importKey( "raw", encoder.encode(DISCOURSE_SSO_SECRET), { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ); const signature = await crypto.subtle.sign( "HMAC", key, encoder.encode(base64Payload) ); const sig = Array.from(new Uint8Array(signature)) .map(b => b.toString(16).padStart(2, '0')) .join(''); // 存储 nonce 用于验证回调 await kv.set(['sso_nonce', nonce], { return_url: returnUrl, created_at: new Date().toISOString() }, { expireIn: 5 * 60 * 1000 }); // 5分钟过期 return `${DISCOURSE_URL}/session/sso_provider?sso=${encodeURIComponent(base64Payload)}&sig=${sig}`; } // HTML 页面 const html = ` AI Models Price API AI Models Price 价格json: https://aimodels-price.deno.dev/api/prices 已复制 价格列表 提交价格 模型名称 计费类型 供应商 币种 输入价格(M) 输出价格(M) 输入倍率 输出倍率 价格依据 状态 操作 加载中... 状态说明: 待审核 新提交的价格记录 已通过 管理员审核通过 已拒绝 管理员拒绝 模型名称 计费类型 按量计费(tokens) 按次计费(times) 供应商 选择供应商... 币种 人民币 美元 输入价格(M) 输出价格(M) 价格依据(官方文档链接) 提交价格 `; // 读取价格数据 async function readPrices(): Promise { try { const prices = await kv.get(["prices"]); return prices.value || []; } catch (error) { console.error('读取价格数据失败:', error); return []; } } // 写入价格数据 async function writePrices(prices: Price[]): Promise { try { await kv.set(["prices"], prices); } catch (error) { console.error('写入价格数据失败:', error); throw new Error('写入价格数据失败'); } } // 修改验证函数 function validateData(data: any): string | null { if (!data.model || !data.type || data.channel_type === undefined || data.input === undefined || data.output === undefined) { return "所有字段都是必需的"; } // 确保数字字段是数字类型 const channel_type = Number(data.channel_type); const input = Number(data.input); const output = Number(data.output); if (isNaN(channel_type) || isNaN(input) || isNaN(output)) { return "数字字段格式无效"; } // 验证数字范围(允许等于0) if (channel_type < 0 || input < 0 || output < 0) { return "数字不能小于0"; } return null; } // 修改处理函数 async function handler(req: Request): Promise { const headers = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, DELETE", "Access-Control-Allow-Headers": "Content-Type, Cookie, Authorization", "Access-Control-Allow-Credentials": "true", "Access-Control-Max-Age": "86400" }; const jsonHeaders = { ...headers, "Content-Type": "application/json" }; const htmlHeaders = { ...headers, "Content-Type": "text/html; charset=utf-8" }; try { const url = new URL(req.url); console.log('Received request:', req.method, url.pathname); // 处理预检请求 if (req.method === "OPTIONS") { return new Response(null, { status: 204, headers }); } // 获取价格列表 if (url.pathname === "/api/prices" && req.method === "GET") { try { console.log('Reading prices from KV store...'); const prices = await readPrices(); console.log('Prices read successfully:', prices.length); return new Response(JSON.stringify(prices), { headers: jsonHeaders }); } catch (error) { console.error('获取价格列表失败:', error); return new Response(JSON.stringify({ error: "获取价格列表失败", details: error.message }), { status: 500, headers: jsonHeaders }); } } // 认证状态检查 if (url.pathname === "/api/auth/status") { try { const username = await verifyDiscourseSSO(req); return new Response(JSON.stringify({ authenticated: !!username, user: username }), { headers: jsonHeaders }); } catch (error) { console.error('验证用户状态失败:', error); return new Response(JSON.stringify({ error: "验证用户状态失败", details: error.message }), { status: 500, headers: jsonHeaders }); } } // 登录处理 if (url.pathname === "/api/auth/login") { const params = new URLSearchParams(url.search); const returnUrl = params.get('return_url'); if (!returnUrl) { return new Response(JSON.stringify({ error: "缺少 return_url 参数" }), { status: 400, headers: jsonHeaders }); } const ssoUrl = await generateSSO(returnUrl); return new Response(null, { status: 302, headers: { ...headers, "Location": ssoUrl } }); } // SSO 回调处理 if (url.pathname === "/auth/callback") { const params = new URLSearchParams(url.search); const sso = params.get('sso'); const sig = params.get('sig'); if (!sso || !sig) { return new Response("Invalid SSO parameters", { status: 400, headers: { "Content-Type": "text/plain", ...headers } }); } try { // 验证签名 const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(DISCOURSE_SSO_SECRET), { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ); const signature = await crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(sso) ); const expectedSig = Array.from(new Uint8Array(signature)) .map(b => b.toString(16).padStart(2, '0')) .join(''); if (sig !== expectedSig) { throw new Error('Invalid signature'); } // 解码 payload const payload = atob(sso); const payloadParams = new URLSearchParams(payload); // 验证 nonce const nonce = payloadParams.get('nonce'); if (!nonce) { throw new Error('Missing nonce'); } const nonceData = await kv.get(['sso_nonce', nonce]); if (!nonceData.value) { throw new Error('Invalid or expired nonce'); } // 删除已使用的 nonce await kv.delete(['sso_nonce', nonce]); const username = payloadParams.get('username'); if (!username) { throw new Error('Missing username'); } // 设置 session cookie const sessionId = crypto.randomUUID(); await kv.set(['sessions', sessionId], { username, created_at: new Date().toISOString() }, { expireIn: 24 * 60 * 60 * 1000 }); // 24小时过期 return new Response(null, { status: 302, headers: { ...headers, "Location": "/", "Set-Cookie": `session=${sessionId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400` } }); } catch (error) { console.error('SSO 回调处理失败:', error); return new Response("SSO verification failed: " + error.message, { status: 400, headers: { "Content-Type": "text/plain", ...headers } }); } } // 登出处理 if (url.pathname === "/api/auth/logout" && req.method === "POST") { const cookie = req.headers.get('cookie'); if (cookie) { const sessionMatch = cookie.match(/session=([^;]+)/); if (sessionMatch) { const sessionId = sessionMatch[1]; await kv.delete(['sessions', sessionId]); } } return new Response(JSON.stringify({ success: true }), { headers: { ...jsonHeaders, "Set-Cookie": "session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0" } }); } // 价格审核 if (url.pathname.match(/^\/api\/prices\/\d+\/review$/)) { const username = await verifyDiscourseSSO(req); if (!username || username !== 'wood') { return new Response(JSON.stringify({ error: "未授权" }), { status: 403, headers: jsonHeaders }); } if (req.method === "POST") { try { const id = url.pathname.split('/')[3]; const { status } = await req.json(); if (status !== 'approved' && status !== 'rejected') { throw new Error("无效的状态"); } const prices = await readPrices(); const priceIndex = prices.findIndex(p => p.id === id); if (priceIndex === -1) { throw new Error("价格记录不存在"); } prices[priceIndex].status = status; prices[priceIndex].reviewed_by = username; prices[priceIndex].reviewed_at = new Date().toISOString(); await writePrices(prices); return new Response(JSON.stringify({ success: true }), { headers: jsonHeaders }); } catch (error) { return new Response(JSON.stringify({ error: error.message || "审核失败" }), { status: 400, headers: jsonHeaders }); } } } // 提交新价格 if (url.pathname === "/api/prices" && req.method === "POST") { const username = await verifyDiscourseSSO(req); if (!username) { return new Response(JSON.stringify({ error: "请先登录" }), { status: 401, headers: jsonHeaders }); } try { let rawData; const contentType = req.headers.get("content-type") || ""; if (contentType.includes("application/json")) { rawData = await req.json(); } else if (contentType.includes("application/x-www-form-urlencoded")) { const formData = await req.formData(); rawData = {}; for (const [key, value] of formData.entries()) { rawData[key] = value; } } else { throw new Error("不支持的内容类型"); } console.log('接收到的数据:', rawData); // 添加日志 // 处理数据 const newPrice: Price = { model: String(rawData.model).trim(), billing_type: rawData.billing_type as 'tokens' | 'times', channel_type: Number(rawData.channel_type), currency: rawData.currency as 'CNY' | 'USD', input_price: Number(rawData.input_price), output_price: Number(rawData.output_price), input_ratio: calculateRatio(Number(rawData.input_price), rawData.currency as 'CNY' | 'USD'), output_ratio: calculateRatio(Number(rawData.output_price), rawData.currency as 'CNY' | 'USD'), price_source: String(rawData.price_source), status: 'pending', created_by: username, created_at: new Date().toISOString() }; console.log('处理后的数据:', newPrice); // 添加日志 // 验证数据 const error = validatePrice(newPrice); if (error) { return new Response(JSON.stringify({ error }), { status: 400, headers: jsonHeaders }); } // 读取现有数据 const prices = await readPrices(); // 生成唯一ID newPrice.id = Date.now().toString(); // 添加新数据 prices.push(newPrice); // 保存数据 await writePrices(prices); return new Response(JSON.stringify({ success: true, data: newPrice }), { headers: jsonHeaders }); } catch (error) { console.error("处理价格提交失败:", error); return new Response(JSON.stringify({ error: error.message, details: "数据处理失败,请检查输入格式" }), { status: 500, headers: jsonHeaders }); } } // 提供静态页面 if (url.pathname === "/" || url.pathname === "/index.html") { return new Response(html, { headers: htmlHeaders }); } return new Response("Not Found", { status: 404, headers: { ...headers, "Content-Type": "text/plain" } }); } catch (error) { console.error('处理请求失败:', error); return new Response(JSON.stringify({ error: "处理请求失败", details: error.message }), { status: 500, headers: jsonHeaders }); } } // 启动服务器 serve(handler);
https://aimodels-price.deno.dev/api/prices