Spaces:
Paused
Paused
| // ================================================================================= | |
| // 项目: toolbaz-2api (Puppeteer 版本) | |
| // 版本: 2.1.3 (代号: Hybrid Optimization - 混合优化版) | |
| // 日期: 2025-11-26 | |
| // | |
| // [v2.1.3 核心变更 - 混合优化] | |
| // 1. [混合认证] 集成Cloudflare Worker的TDF时间差计算,提高认证准确性 | |
| // 2. [响应修复] 修复AJAX成功响应被误判为失败的关键问题 | |
| // 3. [验证优化] 改进响应验证逻辑,减少误判,提高识别精度 | |
| // 4. [数据结构] 统一所有AJAX函数的返回数据结构,确保一致性 | |
| // 5. [智能回退] 优化UI回退机制,只在真正需要时启用 | |
| // | |
| // [v2.1.2 历史变更] | |
| // 1. [精准优化] 参考思路保持优势: 参考Cloudflare Worker的优化思路,但保持Docker版本的成功机制 | |
| // 2. [认证优化] 行为数据策略: 保持mM9wZ: [], kP8jY: []关键优化,避免机器人检测 | |
| // 3. [请求优化] 参考直接POST: 优化向writing.php的直接请求流程,减少中间环节 | |
| // 4. [时序优化] 智能延迟: 在关键步骤添加合理延迟,模拟真实用户行为 | |
| // 5. [错误处理] 增强容错: 改进错误处理和重试机制,提高成功率 | |
| // [v2.1.1 历史变更] | |
| // 1. [新增] 全面的测试套件: 添加了大量测试函数和调试工具 | |
| // 2. [新增] 性能监控: 实时监控请求响应时间和成功率 | |
| // 3. [新增] 错误分析: 详细分析失败原因并提供解决方案建议 | |
| // 4. [新增] 会话验证: 多层次验证session有效性 | |
| // 5. [新增] 网络请求监控: 监控所有网络请求,分析问题 | |
| // 6. [增强] 更丰富的日志输出: 彩色日志、分级显示、结构化输出 | |
| // 7. [新增] 自动重试机制: 智能重试失败的请求 | |
| // 8. [新增] 缓存优化: 优化浏览器启动和页面加载 | |
| // | |
| // [v2.1.0 核心变更] | |
| // 1. [重大改进] 直接模拟AJAX请求: 新增 bypass 前端JavaScript的解决方案 | |
| // 通过 Puppeteer 直接向 https://data.toolbaz.com/writing.php 发送请求,避免网站自动添加前缀 | |
| // 2. [智能回退] 保持原有UI方法作为备用方案,确保100%可靠性 | |
| // 3. [会话管理] 自动检测和获取session_id,支持无session场景 | |
| // 4. [错误处理] 增强的错误处理和日志记录 | |
| // ================================================================================= | |
| import http from 'http'; | |
| import puppeteer from 'puppeteer'; | |
| const CONFIG = { | |
| PROJECT_NAME: "toolbaz-2api", | |
| PROJECT_VERSION: "2.1.2", | |
| API_MASTER_KEY: "1", | |
| PORT: 7860, | |
| CHAT_MODELS: ["gemini-2.5-flash", "gemini-2.5-pro", "claude-sonnet-4", "gpt-5", "grok-4-fast", "gpt-oss-120b", "gemini-2.0-flash-thinking", "grok-4.1-fast", "Llama-4-Maverick", "deepseek-v3.1"], | |
| IMAGE_MODELS: ["FLUX-1-schnell", "FLUX-1-dev", "FLUX-1-uncensored"], | |
| DEFAULT_CHAT_MODEL: "gemini-2.5-flash", | |
| // 新增测试配置 | |
| TESTING_MODE: true, | |
| DEBUG_LEVEL: "verbose", // error, warn, info, debug, verbose | |
| MAX_RETRIES: 3, | |
| RETRY_DELAY: 1000, | |
| PERFORMANCE_MONITORING: true, | |
| }; | |
| let browser = null; | |
| // 全局凭据缓存 | |
| let credentialCache = { | |
| data: null, | |
| timestamp: null, | |
| expiresAt: null, | |
| isValid: function() { | |
| return this.data && this.expiresAt && Date.now() < this.expiresAt; | |
| }, | |
| set: function(credentials, ttlMinutes = 30) { | |
| this.data = credentials; | |
| this.timestamp = Date.now(); | |
| this.expiresAt = Date.now() + (ttlMinutes * 60 * 1000); | |
| log('info', `🔐 凭据已缓存,有效期 ${ttlMinutes} 分钟`); | |
| }, | |
| clear: function() { | |
| this.data = null; | |
| this.timestamp = null; | |
| this.expiresAt = null; | |
| log('info', '🔐 凭据缓存已清空'); | |
| } | |
| }; | |
| // 新增:性能监控和统计 | |
| const PERFORMANCE_STATS = { | |
| totalRequests: 0, | |
| successfulRequests: 0, | |
| failedRequests: 0, | |
| ajaxSuccess: 0, | |
| ajaxFailed: 0, | |
| uiFallback: 0, | |
| averageResponseTime: 0, | |
| sessionDetectionStats: {}, | |
| lastResetTime: Date.now() | |
| }; | |
| // 新增:彩色日志输出 | |
| const COLORS = { | |
| reset: '\x1b[0m', | |
| red: '\x1b[31m', | |
| green: '\x1b[32m', | |
| yellow: '\x1b[33m', | |
| blue: '\x1b[34m', | |
| magenta: '\x1b[35m', | |
| cyan: '\x1b[36m', | |
| white: '\x1b[37m', | |
| bright_red: '\x1b[91m', | |
| bright_green: '\x1b[92m', | |
| bright_yellow: '\x1b[93m', | |
| bright_blue: '\x1b[94m', | |
| bright_magenta: '\x1b[95m', | |
| bright_cyan: '\x1b[96m' | |
| }; | |
| function log(level, message, data = null) { | |
| const levels = { error: 0, warn: 1, info: 2, debug: 3, verbose: 4 }; | |
| const currentLevel = levels[CONFIG.DEBUG_LEVEL] || 2; | |
| const messageLevel = levels[level] || 2; | |
| if (messageLevel <= currentLevel) { | |
| const timestamp = new Date().toISOString(); | |
| const coloredLevel = { | |
| error: `${COLORS.bright_red}[ERROR]${COLORS.reset}`, | |
| warn: `${COLORS.bright_yellow}[WARN]${COLORS.reset}`, | |
| info: `${COLORS.bright_blue}[INFO]${COLORS.reset}`, | |
| debug: `${COLORS.bright_cyan}[DEBUG]${COLORS.reset}`, | |
| verbose: `${COLORS.white}[VERBOSE]${COLORS.reset}` | |
| }[level] || `[${level.toUpperCase()}]`; | |
| console.log(`${timestamp} ${coloredLevel} ${message}`); | |
| if (data && level === 'verbose') { | |
| console.log(`${COLORS.cyan}数据详情:${COLORS.reset}`, JSON.stringify(data, null, 2)); | |
| } | |
| } | |
| } | |
| // 新增:测试函数 - 验证基本配置 | |
| function testBasicConfiguration() { | |
| log('info', '🧪 开始基本配置测试...'); | |
| const tests = [ | |
| { name: '项目名称', value: CONFIG.PROJECT_NAME, expected: 'toolbaz-2api' }, | |
| { name: '端口配置', value: CONFIG.PORT, expected: 7860 }, | |
| { name: 'API密钥', value: CONFIG.API_MASTER_KEY, expected: '1' }, | |
| { name: '默认模型', value: CONFIG.DEFAULT_CHAT_MODEL, expected: 'gemini-2.5-flash' }, | |
| { name: '聊天模型数量', value: CONFIG.CHAT_MODELS.length, min: 1 }, | |
| { name: '图片模型数量', value: CONFIG.IMAGE_MODELS.length, min: 1 } | |
| ]; | |
| let passed = 0; | |
| let failed = 0; | |
| tests.forEach(test => { | |
| const result = test.expected ? test.value === test.expected : test.value >= (test.min || 0); | |
| if (result) { | |
| log('verbose', `✅ ${test.name}: ${test.value}`); | |
| passed++; | |
| } else { | |
| log('error', `❌ ${test.name}: 期望 ${test.expected || `>=${test.min || 0}`},实际 ${test.value}`); | |
| failed++; | |
| } | |
| }); | |
| log('info', `📊 配置测试结果: ${passed} 通过, ${failed} 失败`); | |
| return { passed, failed }; | |
| } | |
| // 修改:测试函数 - 网络连接性(使用单个浏览器实例) | |
| async function testNetworkConnectivity() { | |
| log('info', '🌐 开始网络连接性测试(使用单个浏览器实例)...'); | |
| const testUrls = [ | |
| { name: '主站', url: 'https://toolbaz.com' }, | |
| { name: '写作页面', url: 'https://toolbaz.com/writer/ai-writer' }, | |
| { name: '图片生成', url: 'https://toolbaz.com/image/ai-image-generator' }, | |
| { name: 'API端点', url: 'https://data.toolbaz.com/writing.php' } | |
| ]; | |
| const results = []; | |
| let testBrowser = null; | |
| let testPage = null; | |
| try { | |
| // 启动单个浏览器实例 | |
| testBrowser = await puppeteer.launch({ | |
| headless: true, | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--disable-gpu' | |
| ] | |
| }); | |
| testPage = await testBrowser.newPage(); | |
| // 设置更长的超时时间以应对网络问题 | |
| testPage.setDefaultTimeout(30000); | |
| for (const test of testUrls) { | |
| try { | |
| const startTime = Date.now(); | |
| // 重用同一个页面进行测试 | |
| await testPage.goto(test.url, { | |
| waitUntil: 'networkidle2', | |
| timeout: 25000 // 增加到25秒 | |
| }); | |
| const responseTime = Date.now() - startTime; | |
| const title = await testPage.title(); | |
| results.push({ | |
| name: test.name, | |
| url: test.url, | |
| status: 'success', | |
| responseTime, | |
| title: title.substring(0, 50) | |
| }); | |
| log('verbose', `✅ ${test.name}: ${responseTime}ms - ${title.substring(0, 30)}...`); | |
| // 在测试之间添加短暂延迟,避免过于频繁的请求 | |
| await new Promise(resolve => setTimeout(resolve, 1000)); | |
| } catch (error) { | |
| results.push({ | |
| name: test.name, | |
| url: test.url, | |
| status: 'failed', | |
| error: error.message | |
| }); | |
| log('error', `❌ ${test.name}: ${error.message}`); | |
| // 错误后也短暂延迟 | |
| await new Promise(resolve => setTimeout(resolve, 500)); | |
| } | |
| } | |
| } catch (error) { | |
| log('error', `❌ 网络测试初始化失败: ${error.message}`); | |
| } finally { | |
| // 清理资源 | |
| if (testPage) { | |
| await testPage.close().catch(e => log('warn', `页面关闭失败: ${e.message}`)); | |
| } | |
| if (testBrowser) { | |
| await testBrowser.close().catch(e => log('warn', `浏览器关闭失败: ${e.message}`)); | |
| } | |
| } | |
| const successCount = results.filter(r => r.status === 'success').length; | |
| log('info', `📊 网络测试结果: ${successCount}/${results.length} 连接成功`); | |
| return results; | |
| } | |
| // 新增:测试函数 - 前缀过滤测试 | |
| async function testPromptPrefixFiltering() { | |
| log('info', '🧹 开始前缀过滤测试...'); | |
| const testCases = [ | |
| { | |
| input: 'Generate an original and engaging piece of writing on the following topic : 寫一個1萬字的小說ㅤ', | |
| expected: '寫一個1萬字的小說ㅤ' | |
| }, | |
| { | |
| input: 'Write a code snippet that does the following in the specified language: Python function to sort array', | |
| expected: 'Python function to sort array' | |
| }, | |
| { | |
| input: '正常文本,无需过滤', | |
| expected: '正常文本,无需过滤' | |
| } | |
| ]; | |
| const results = []; | |
| for (const testCase of testCases) { | |
| const filtered = cleanPromptPrefix(testCase.input); | |
| const passed = filtered === testCase.expected; | |
| results.push({ | |
| input: testCase.input, | |
| expected: testCase.expected, | |
| actual: filtered, | |
| passed | |
| }); | |
| if (passed) { | |
| log('verbose', `✅ 前缀过滤测试通过: "${testCase.input}" -> "${filtered}"`); | |
| } else { | |
| log('error', `❌ 前缀过滤测试失败: "${testCase.input}" -> "${filtered}" (期望: "${testCase.expected}")`); | |
| } | |
| } | |
| const passCount = results.filter(r => r.passed).length; | |
| log('info', `📊 前缀过滤测试结果: ${passCount}/${results.length} 通过`); | |
| return { results, passed: passCount, failed: results.length - passCount }; | |
| } | |
| // 修改:测试函数 - 浏览器环境(使用全局浏览器实例) | |
| async function testBrowserEnvironment() { | |
| log('info', '🌍 开始浏览器环境测试...'); | |
| let testPage = null; | |
| try { | |
| // 尝试使用现有的浏览器实例,如果没有则创建 | |
| const testBrowser = browser || await initBrowser(); | |
| testPage = await testBrowser.newPage(); | |
| // 设置更长的超时时间 | |
| testPage.setDefaultTimeout(20000); | |
| // 测试基本功能 | |
| await testPage.goto('https://example.com', { waitUntil: 'networkidle2', timeout: 15000 }); | |
| const browserInfo = await testPage.evaluate(() => { | |
| return { | |
| userAgent: navigator.userAgent, | |
| language: navigator.language, | |
| cookieEnabled: navigator.cookieEnabled, | |
| localStorage: !!localStorage, | |
| sessionStorage: !!sessionStorage, | |
| platform: navigator.platform | |
| }; | |
| }); | |
| // 测试JavaScript执行 | |
| const jsTest = await testPage.evaluate(() => { | |
| try { | |
| return { | |
| fetch: typeof fetch !== 'undefined', | |
| formData: typeof FormData !== 'undefined', | |
| urlSearchParams: typeof URLSearchParams !== 'undefined', | |
| promise: typeof Promise !== 'undefined', | |
| async: typeof (async () => {}) === 'function' | |
| }; | |
| } catch (e) { | |
| return { error: e.message }; | |
| } | |
| }); | |
| log('verbose', '✅ 浏览器环境测试通过'); | |
| log('verbose', '📋 浏览器信息:', browserInfo); | |
| log('verbose', '📋 JavaScript功能:', jsTest); | |
| return { success: true, browserInfo, jsTest }; | |
| } catch (error) { | |
| log('error', `❌ 浏览器环境测试失败: ${error.message}`); | |
| return { success: false, error: error.message }; | |
| } finally { | |
| // 清理页面资源 | |
| if (testPage) { | |
| await testPage.close().catch(e => log('warn', `页面关闭失败: ${e.message}`)); | |
| } | |
| } | |
| } | |
| // 修改:测试函数 - 深度会话检测(使用全局浏览器实例) | |
| async function testSessionDetection() { | |
| log('info', '🔍 开始深度会话检测测试...'); | |
| let testPage = null; | |
| try { | |
| // 使用现有的浏览器实例或创建新的 | |
| const testBrowser = browser || await initBrowser(); | |
| testPage = await testBrowser.newPage(); | |
| // 设置更长的超时时间 | |
| testPage.setDefaultTimeout(30000); | |
| await testPage.goto('https://toolbaz.com/writer/ai-writer', { | |
| waitUntil: 'networkidle2', | |
| timeout: 30000 | |
| }); | |
| // 简化的会话检测(减少资源消耗) | |
| const sessionAnalysis = await testPage.evaluate(() => { | |
| const analysis = { | |
| timestamp: Date.now(), | |
| url: window.location.href, | |
| title: document.title, | |
| elementsFound: [], | |
| hiddenInputs: [], | |
| cookies: {}, | |
| sessionStorage: {}, | |
| localStorage: {} | |
| }; | |
| // 专注于关键元素,避免过度遍历 | |
| const sessionKeywords = ['session', 'sess', 'sid', 'token', 'csrf', 'auth']; | |
| // 只检查可能包含会话信息的元素 | |
| const relevantSelectors = [ | |
| 'input[type="hidden"]', | |
| 'input[name*="session"]', | |
| 'input[name*="token"]', | |
| 'input[id*="session"]', | |
| 'input[id*="token"]', | |
| '[data-session]', | |
| '[data-token]' | |
| ]; | |
| relevantSelectors.forEach(selector => { | |
| try { | |
| const elements = document.querySelectorAll(selector); | |
| elements.forEach(elem => { | |
| const info = { | |
| tag: elem.tagName, | |
| id: elem.id || '', | |
| name: elem.name || '', | |
| value: elem.value || '' | |
| }; | |
| const searchText = [info.id, info.name, info.value].join(' ').toLowerCase(); | |
| if (sessionKeywords.some(keyword => searchText.includes(keyword))) { | |
| analysis.elementsFound.push(info); | |
| } | |
| }); | |
| } catch (e) { | |
| // 忽略选择器错误 | |
| } | |
| }); | |
| // 分析隐藏输入 | |
| const hiddenInputs = document.querySelectorAll('input[type="hidden"]'); | |
| hiddenInputs.forEach(input => { | |
| if (input.name || input.value) { | |
| analysis.hiddenInputs.push({ | |
| name: input.name, | |
| id: input.id, | |
| value: input.value.substring(0, 100) | |
| }); | |
| } | |
| }); | |
| // 简化存储分析 | |
| try { | |
| for (let i = 0; i < Math.min(sessionStorage.length, 10); i++) { | |
| const key = sessionStorage.key(i); | |
| if (key && sessionKeywords.some(k => key.toLowerCase().includes(k))) { | |
| analysis.sessionStorage[key] = sessionStorage.getItem(key); | |
| } | |
| } | |
| for (let i = 0; i < Math.min(localStorage.length, 10); i++) { | |
| const key = localStorage.key(i); | |
| if (key && sessionKeywords.some(k => key.toLowerCase().includes(k))) { | |
| analysis.localStorage[key] = localStorage.getItem(key); | |
| } | |
| } | |
| } catch (e) { | |
| // 忽略存储访问错误 | |
| } | |
| // 简化cookie分析 | |
| try { | |
| document.cookie.split(';').forEach(cookie => { | |
| const [name, value] = cookie.trim().split('='); | |
| if (name && value && sessionKeywords.some(k => name.toLowerCase().includes(k))) { | |
| analysis.cookies[name] = value; | |
| } | |
| }); | |
| } catch (e) { | |
| // 忽略cookie访问错误 | |
| } | |
| return analysis; | |
| }); | |
| log('verbose', `📊 会话分析结果:`); | |
| log('verbose', ` - 页面: ${sessionAnalysis.title}`); | |
| log('verbose', ` - 相关元素: ${sessionAnalysis.elementsFound.length}`); | |
| log('verbose', ` - 隐藏输入: ${sessionAnalysis.hiddenInputs.length}`); | |
| log('verbose', ` - Cookie数量: ${Object.keys(sessionAnalysis.cookies).length}`); | |
| log('verbose', ` - SessionStorage: ${Object.keys(sessionAnalysis.sessionStorage).length}`); | |
| log('verbose', ` - LocalStorage: ${Object.keys(sessionAnalysis.localStorage).length}`); | |
| if (sessionAnalysis.elementsFound.length > 0) { | |
| log('info', '🎯 找到潜在的会话相关元素:'); | |
| sessionAnalysis.elementsFound.forEach(elem => { | |
| log('verbose', ` - ${elem.tag}#${elem.id} (${elem.name}): ${elem.value.substring(0, 50)}`); | |
| }); | |
| } | |
| return { success: true, analysis: sessionAnalysis }; | |
| } catch (error) { | |
| log('error', `❌ 深度会话检测失败: ${error.message}`); | |
| return { success: false, error: error.message }; | |
| } finally { | |
| // 清理页面资源 | |
| if (testPage) { | |
| await testPage.close().catch(e => log('warn', `页面关闭失败: ${e.message}`)); | |
| } | |
| } | |
| } | |
| // 修改:测试函数 - AJAX请求模拟(使用全局浏览器实例) | |
| async function testAjaxRequest() { | |
| log('info', '📡 开始AJAX请求模拟测试...'); | |
| let testPage = null; | |
| try { | |
| // 使用现有的浏览器实例或创建新的 | |
| const testBrowser = browser || await initBrowser(); | |
| testPage = await testBrowser.newPage(); | |
| // 设置更长的超时时间 | |
| testPage.setDefaultTimeout(30000); | |
| await testPage.goto('https://toolbaz.com/writer/ai-writer', { | |
| waitUntil: 'networkidle2', | |
| timeout: 30000 | |
| }); | |
| // 简化的请求监听 | |
| const networkRequests = []; | |
| try { | |
| await testPage.setRequestInterception(true); | |
| testPage.on('request', request => { | |
| if (request.url().includes('writing.php')) { | |
| networkRequests.push({ | |
| url: request.url(), | |
| method: request.method(), | |
| headers: request.headers(), | |
| postData: request.postData() | |
| }); | |
| } | |
| request.continue(); | |
| }); | |
| } catch (e) { | |
| log('warn', `请求拦截设置失败,继续测试: ${e.message}`); | |
| } | |
| // 测试AJAX请求 | |
| const ajaxTest = await testPage.evaluate(async () => { | |
| return new Promise((resolve) => { | |
| const testData = { | |
| text: 'Hello, this is a test message', | |
| model: 'gemini-2.5-flash', | |
| test: true | |
| }; | |
| const startTime = Date.now(); | |
| fetch('https://data.toolbaz.com/writing.php', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| 'Accept': '*/*', | |
| 'Origin': 'https://toolbaz.com', | |
| 'Referer': 'https://toolbaz.com/writer/ai-writer' | |
| }, | |
| body: new URLSearchParams(testData).toString() | |
| }) | |
| .then(response => { | |
| return { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers: Object.fromEntries(response.headers.entries()), | |
| url: response.url, | |
| responseTime: Date.now() - startTime | |
| }; | |
| }) | |
| .then(result => { | |
| resolve({ success: true, result }); | |
| }) | |
| .catch(error => { | |
| resolve({ success: false, error: error.message, responseTime: Date.now() - startTime }); | |
| }); | |
| }); | |
| }); | |
| log('verbose', '📊 AJAX测试结果:'); | |
| log('verbose', ` - 请求状态: ${ajaxTest.success ? '成功' : '失败'}`); | |
| if (ajaxTest.success) { | |
| log('verbose', ` - HTTP状态: ${ajaxTest.result.status}`); | |
| log('verbose', ` - 响应时间: ${ajaxTest.result.responseTime}ms`); | |
| log('verbose', ` - 响应URL: ${ajaxTest.result.url}`); | |
| } else { | |
| log('error', ` - 错误信息: ${ajaxTest.error}`); | |
| } | |
| log('verbose', ` - 网络请求数量: ${networkRequests.length}`); | |
| return { success: ajaxTest.success, data: ajaxTest, networkRequests }; | |
| } catch (error) { | |
| log('error', `❌ AJAX测试失败: ${error.message}`); | |
| return { success: false, error: error.message }; | |
| } finally { | |
| // 清理页面资源 | |
| if (testPage) { | |
| await testPage.close().catch(e => log('warn', `页面关闭失败: ${e.message}`)); | |
| } | |
| } | |
| } | |
| // 新增:性能统计更新 | |
| function updatePerformanceStats(type, success = true, responseTime = 0) { | |
| if (!CONFIG.PERFORMANCE_MONITORING) return; | |
| PERFORMANCE_STATS.totalRequests++; | |
| if (success) { | |
| PERFORMANCE_STATS.successfulRequests++; | |
| if (type === 'ajax') { | |
| PERFORMANCE_STATS.ajaxSuccess++; | |
| } | |
| } else { | |
| PERFORMANCE_STATS.failedRequests++; | |
| if (type === 'ajax') { | |
| PERFORMANCE_STATS.ajaxFailed++; | |
| } else if (type === 'ui') { | |
| PERFORMANCE_STATS.uiFallback++; | |
| } | |
| } | |
| if (responseTime > 0) { | |
| const total = PERFORMANCE_STATS.averageResponseTime * (PERFORMANCE_STATS.totalRequests - 1) + responseTime; | |
| PERFORMANCE_STATS.averageResponseTime = Math.round(total / PERFORMANCE_STATS.totalRequests); | |
| } | |
| } | |
| // 新增:显示性能统计 | |
| function showPerformanceStats() { | |
| if (!CONFIG.PERFORMANCE_MONITORING) return; | |
| const uptime = Date.now() - PERFORMANCE_STATS.lastResetTime; | |
| const uptimeMinutes = Math.round(uptime / 60000); | |
| log('info', '📊 性能统计报告:'); | |
| log('info', ` - 运行时间: ${uptimeMinutes} 分钟`); | |
| log('info', ` - 总请求数: ${PERFORMANCE_STATS.totalRequests}`); | |
| log('info', ` - 成功请求: ${PERFORMANCE_STATS.successfulRequests}`); | |
| log('info', ` - 失败请求: ${PERFORMANCE_STATS.failedRequests}`); | |
| log('info', ` - 成功率: ${PERFORMANCE_STATS.totalRequests > 0 ? Math.round(PERFORMANCE_STATS.successfulRequests / PERFORMANCE_STATS.totalRequests * 100) : 0}%`); | |
| log('info', ` - AJAX成功: ${PERFORMANCE_STATS.ajaxSuccess}`); | |
| log('info', ` - AJAX失败: ${PERFORMANCE_STATS.ajaxFailed}`); | |
| log('info', ` - UI回退: ${PERFORMANCE_STATS.uiFallback}`); | |
| log('info', ` - 平均响应时间: ${PERFORMANCE_STATS.averageResponseTime}ms`); | |
| } | |
| // 修改:综合测试函数(优化浏览器资源管理) | |
| async function runComprehensiveTests() { | |
| log('info', '🧪 开始综合测试套件(优化版)...'); | |
| const results = { | |
| configuration: null, | |
| network: null, | |
| browser: null, | |
| session: null, | |
| ajax: null, | |
| overall: { passed: 0, failed: 0 } | |
| }; | |
| let sharedBrowser = null; | |
| try { | |
| // 1. 基本配置测试(不需要浏览器) | |
| results.configuration = testBasicConfiguration(); | |
| if (results.configuration.failed === 0) { | |
| results.overall.passed++; | |
| } else { | |
| results.overall.failed++; | |
| } | |
| // 2. 网络连接性测试(已优化为使用单个浏览器) | |
| results.network = await testNetworkConnectivity(); | |
| if (results.network.filter(r => r.status === 'success').length >= 3) { | |
| results.overall.passed++; | |
| } else { | |
| results.overall.failed++; | |
| } | |
| // 3. 浏览器环境测试(使用全局浏览器) | |
| results.browser = await testBrowserEnvironment(); | |
| if (results.browser.success) { | |
| results.overall.passed++; | |
| } else { | |
| results.overall.failed++; | |
| } | |
| // 4. 会话检测测试(使用全局浏览器) | |
| results.session = await testSessionDetection(); | |
| if (results.session.success) { | |
| results.overall.passed++; | |
| } else { | |
| results.overall.failed++; | |
| } | |
| // 5. AJAX请求测试(使用全局浏览器) | |
| results.ajax = await testAjaxRequest(); | |
| if (results.ajax.success) { | |
| results.overall.passed++; | |
| } else { | |
| results.overall.failed++; | |
| } | |
| // 6. 前缀过滤测试(不需要浏览器) | |
| results.prefixFiltering = await testPromptPrefixFiltering(); | |
| if (results.prefixFiltering.failed === 0) { | |
| results.overall.passed++; | |
| } else { | |
| results.overall.failed++; | |
| } | |
| log('info', `🎉 综合测试完成: ${results.overall.passed}/6 测试通过`); | |
| if (results.overall.failed > 0) { | |
| log('warn', '⚠️ 部分测试失败,请检查相关配置'); | |
| } | |
| // 添加资源使用统计 | |
| const browserInstance = browser || sharedBrowser; | |
| if (browserInstance) { | |
| const pages = await browserInstance.pages().catch(() => []); | |
| log('info', `📊 浏览器资源统计: ${pages.length} 个页面`); | |
| } | |
| return results; | |
| } catch (error) { | |
| log('error', `❌ 综合测试失败: ${error.message}`); | |
| // 确保清理共享浏览器资源 | |
| if (sharedBrowser) { | |
| await sharedBrowser.close().catch(e => log('warn', `共享浏览器关闭失败: ${e.message}`)); | |
| } | |
| return { ...results, error }; | |
| } | |
| } | |
| async function initBrowser() { | |
| if (!browser) { | |
| log('info', '🚀 启动浏览器...'); | |
| const launchOptions = { | |
| headless: true, | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--disable-gpu', | |
| '--disable-blink-features=AutomationControlled', | |
| '--disable-web-security', | |
| '--disable-features=VizDisplayCompositor' | |
| ], | |
| protocolTimeout: 60000, | |
| defaultViewport: { | |
| width: 1920, | |
| height: 1080 | |
| } | |
| }; | |
| try { | |
| browser = await puppeteer.launch(launchOptions); | |
| log('info', '✅ 浏览器启动成功'); | |
| // 如果启用测试模式,运行快速测试 | |
| if (CONFIG.TESTING_MODE) { | |
| log('debug', '🧪 执行浏览器启动测试...'); | |
| const testPage = await browser.newPage(); | |
| await testPage.goto('https://example.com', { waitUntil: 'networkidle2', timeout: 10000 }); | |
| const testResult = await testPage.evaluate(() => ({ | |
| title: document.title, | |
| readyState: document.readyState | |
| })); | |
| log('debug', '📋 浏览器测试结果:', testResult); | |
| await testPage.close(); | |
| } | |
| } catch (error) { | |
| log('error', `❌ 浏览器启动失败: ${error.message}`); | |
| throw error; | |
| } | |
| } | |
| return browser; | |
| } | |
| // 混合优化版本:集成Cloudflare Worker的TDF认证 + Docker版本的UI回退 | |
| async function performAjaxRequest(page, prompt, model, sessionId = null, retryCount = 0) { | |
| const startTime = Date.now(); | |
| log('info', '📡 尝试直接AJAX请求(混合优化模式)...'); | |
| log('info', '🔐 第一步:获取TDF值和SessionID...'); | |
| log('debug', '📋 请求参数:'); | |
| log('debug', ` - prompt: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`); | |
| log('debug', ` - model: ${model}`); | |
| log('debug', ` - 重试次数: ${retryCount}`); | |
| try { | |
| // 步骤1:获取TDF值(Cloudflare Worker方法) | |
| const tdf = await page.evaluate(async () => { | |
| return new Promise((resolve, reject) => { | |
| const url = `https://data.toolbaz.com/info.php?v=1&_v=j101&a=1786349895&t=pageview&_s=1`; | |
| fetch(url, { method: 'POST' }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (!data.t) { | |
| reject(new Error('TDF响应无效')); | |
| return; | |
| } | |
| const serverTime = data.t; | |
| const clientTime = Math.floor(Date.now() / 1000); | |
| const realTdf = serverTime - clientTime; | |
| // 模拟真实用户的时钟偏移 | |
| const fakeClockDrift = Math.floor(Math.random() * 58) + 2; | |
| const humanizedTdf = realTdf + fakeClockDrift; | |
| resolve(humanizedTdf); | |
| }) | |
| .catch(error => reject(error)); | |
| }); | |
| }); | |
| log('info', `✅ TDF获取成功: ${tdf}`); | |
| // 步骤2:生成SessionID和基础Token(保持Docker版本的成功结构) | |
| const authData = await page.evaluate((tdfValue) => { | |
| // 模拟 updateC() 函数生成SessionID | |
| function gRS(length) { | |
| const c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |
| let result = ""; | |
| const cLength = c.length; | |
| for (let i = 0; i < length; i++) { | |
| result += c.charAt(Math.floor(Math.random() * cLength)); | |
| } | |
| return result; | |
| } | |
| function setC(name, value, days) { | |
| const d = new Date(); | |
| d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000); | |
| const expires = "expires=" + d.toUTCString(); | |
| document.cookie = name + "=" + (value || "") + ";" + expires + ";path=/"; | |
| } | |
| function getCookie(name) { | |
| const cookies = document.cookie.split(";"); | |
| for (let i = 0; i < cookies.length; i++) { | |
| const cookie = cookies[i].trim(); | |
| if (cookie.startsWith(name + "=")) { | |
| return cookie.substring(name.length + 1); | |
| } | |
| } | |
| return null; | |
| } | |
| function cTZ() { | |
| return Math.floor(new Date().getTime() / 1000); | |
| } | |
| // 生成新的SessionID(保持原有36位格式,但添加变化) | |
| const session_id = gRS(36); | |
| setC("SessionID", session_id, 1); | |
| // 模拟 xA1pY() 函数生成Token(保持原有优势,添加小幅优化) | |
| function xA1pY() { | |
| const bR6wF = { | |
| nV5kP: navigator.userAgent, | |
| lQ9jX: navigator.language, | |
| sD2zR: `${window.screen.width}x${window.screen.height}`, | |
| tY4hL: Intl.DateTimeFormat().resolvedOptions().timeZone, | |
| pL8mC: navigator.platform, | |
| cQ3vD: window.screen.colorDepth, | |
| hK7jN: navigator.hardwareConcurrency || "unknown", | |
| }; | |
| const uT4bX = { | |
| mM9wZ: [], // 保持关键:行为数据置空 | |
| kP8jY: [], // 保持关键:键盘行为置空 | |
| }; | |
| // 简化版:不收集实际的行为数据 | |
| function hA9kQ(tQ8zY) { | |
| return gRS(6) + tQ8zY; | |
| } | |
| function gH7wN(dA3vR) { | |
| const jS9mY = JSON.stringify(dA3vR); | |
| return btoa(unescape(encodeURIComponent(jS9mY))); | |
| } | |
| function gF2jX() { | |
| const cD8yL = { | |
| bR6wF, | |
| uT4bX, | |
| tuTcS: cTZ(), | |
| tDfxy: tdfValue || getCookie("tdf"), // 使用传入的tdf值 | |
| RtyJt: gRS(36), | |
| }; | |
| const tN5wX = hA9kQ(gH7wN(cD8yL)); | |
| return tN5wX; | |
| } | |
| return gF2jX(); | |
| } | |
| const token = xA1pY(); | |
| return { | |
| session_id, | |
| token | |
| }; | |
| }, tdf); | |
| log('info', `✅ 获取认证信息成功 - SessionID: ${authData.session_id.substring(0, 8)}...`); | |
| // 第二步:请求CAPTCHA token(添加智能延迟) | |
| log('info', '🔑 第二步:请求CAPTCHA token...'); | |
| // 模拟真实用户的思考延迟(优化:减少延迟) | |
| await new Promise(resolve => setTimeout(resolve, 50 + Math.random() * 100)); | |
| const captchaResponse = await page.evaluate((authData) => { | |
| return new Promise((resolve, reject) => { | |
| const formData = new FormData(); | |
| formData.append('session_id', authData.session_id); | |
| formData.append('token', authData.token); | |
| // 模拟网络延迟 | |
| setTimeout(() => { | |
| fetch('https://data.toolbaz.com/token.php', { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => response.json()) | |
| .then(response => { | |
| resolve(response); | |
| }) | |
| .catch(error => { | |
| reject(error); | |
| }); | |
| }, 50 + Math.random() * 50); // 50-100ms随机延迟 | |
| }); | |
| }, authData); | |
| if (!captchaResponse || !captchaResponse.token) { | |
| throw new Error('CAPTCHA token获取失败: ' + JSON.stringify(captchaResponse)); | |
| } | |
| log('info', `✅ CAPTCHA token获取成功: ${captchaResponse.token.substring(0, 20)}...`); | |
| // 第三步:处理文本并提交(添加用户行为模拟) | |
| log('info', '📝 第三步:处理文本并提交...'); | |
| // 模拟用户输入思考和准备时间(优化:减少延迟) | |
| const thinkingDelay = 200 + Math.random() * 300; // 200-500ms | |
| log('debug', `🤔 模拟用户思考时间: ${thinkingDelay}ms`); | |
| await new Promise(resolve => setTimeout(resolve, thinkingDelay)); | |
| const finalResponse = await page.evaluate((promptText, modelText, captchaToken, authData, debugMode) => { | |
| const debug = debugMode || false; | |
| const startTime = Date.now(); | |
| // 模拟 encodeHtmlEntities 函数 | |
| function encodeHtmlEntities(text) { | |
| const entityMap = { | |
| '"': """, | |
| "&": "&", | |
| "'": "'", | |
| "<": "<", | |
| ">": ">", | |
| "`": "`", | |
| }; | |
| return text.replace(/[&<>"'`]/g, function (match) { | |
| return entityMap[match]; | |
| }) + "ㅤ"; // 添加特殊字符 | |
| } | |
| // 处理输入文本 | |
| const inp_val = encodeHtmlEntities(promptText.trim()); | |
| const text = inp_val; // 因为没有advanced_fields_text,直接使用处理后的文本 | |
| if (debug) { | |
| console.log('📝 最终处理的文本:', text); | |
| console.log('🔑 使用CAPTCHA token:', captchaToken); | |
| console.log('🆔 使用Session ID:', authData.session_id); | |
| } | |
| // 构造最终请求数据 | |
| const requestData = { | |
| text: text, | |
| capcha: captchaToken, // 注意是 capcha 不是 captcha | |
| model: modelText || 'gemini-2.5-flash', | |
| session_id: authData.session_id | |
| }; | |
| const urlParams = new URLSearchParams(); | |
| Object.entries(requestData).forEach(([key, value]) => { | |
| urlParams.append(key, value); | |
| }); | |
| const headers = { | |
| 'Accept': '*/*', | |
| 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', | |
| 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', | |
| 'Origin': 'https://toolbaz.com', | |
| 'Referer': 'https://toolbaz.com/writer/ai-writer', | |
| 'User-Agent': navigator.userAgent, | |
| 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"', | |
| 'sec-ch-ua-mobile': '?0', | |
| 'sec-ch-ua-platform': '"Windows"', | |
| 'Sec-Fetch-Dest': 'empty', | |
| 'Sec-Fetch-Mode': 'cors', | |
| 'Sec-Fetch-Site': 'same-site', | |
| 'X-Requested-With': 'XMLHttpRequest' | |
| }; | |
| if (debug) { | |
| console.log('🌐 发送最终请求到 writing.php'); | |
| console.log('📋 请求参数:', urlParams.toString()); | |
| } | |
| return fetch('https://data.toolbaz.com/writing.php', { | |
| method: 'POST', | |
| headers: headers, | |
| body: urlParams.toString(), | |
| credentials: 'include' | |
| }) | |
| .then(response => { | |
| const responseTime = Date.now() - startTime; | |
| if (debug) { | |
| console.log('📡 响应状态:', response.status); | |
| console.log('📡 响应时间:', responseTime + 'ms'); | |
| } | |
| if (!response.ok) { | |
| throw new Error(`HTTP ${response.status}: ${response.statusText}`); | |
| } | |
| return response.text(); | |
| }) | |
| .then(text => { | |
| const responseTime = Date.now() - startTime; | |
| if (debug) { | |
| console.log('📡 响应长度:', text.length); | |
| console.log('📡 响应前200字符:', text.substring(0, 200)); | |
| } | |
| return { | |
| success: true, | |
| data: text, | |
| responseTime | |
| }; | |
| }) | |
| .catch(error => { | |
| const responseTime = Date.now() - startTime; | |
| if (debug) { | |
| console.error('❌ 请求失败:', error); | |
| } | |
| return { | |
| success: false, | |
| error: error.message, | |
| responseTime | |
| }; | |
| }); | |
| }, prompt, model, captchaResponse.token, authData, CONFIG.DEBUG_LEVEL === 'verbose'); | |
| const totalTime = Date.now() - startTime; | |
| if (finalResponse.success) { | |
| log('info', '✅ 双重认证AJAX请求成功!'); | |
| log('info', `📋 响应长度: ${finalResponse.data.length}`); | |
| log('info', `⏱️ 总响应时间: ${totalTime}ms`); | |
| updatePerformanceStats('ajax', true, totalTime); | |
| // 返回一致的数据结构:包含success属性的对象 | |
| return { | |
| success: true, | |
| data: finalResponse.data, | |
| responseTime: totalTime | |
| }; | |
| } else { | |
| // 返回包含错误信息的对象,而不是抛出异常 | |
| return { | |
| success: false, | |
| error: finalResponse.error, | |
| responseTime: totalTime | |
| }; | |
| } | |
| } catch (error) { | |
| const totalTime = Date.now() - startTime; | |
| log('error', `❌ 双重认证AJAX请求失败: ${error.message}`); | |
| log('debug', `⏱️ 失败响应时间: ${totalTime}ms`); | |
| updatePerformanceStats('ajax', false, totalTime); | |
| if (retryCount < CONFIG.MAX_RETRIES) { | |
| log('info', `🔄 重试第 ${retryCount + 1} 次...`); | |
| await new Promise(resolve => setTimeout(resolve, CONFIG.RETRY_DELAY)); | |
| return await performAjaxRequest(page, prompt, model, sessionId, retryCount + 1); | |
| } | |
| throw error; | |
| } | |
| try { | |
| // 设置网络监听 | |
| const networkRequests = []; | |
| const originalFetch = page.evaluateHandle(() => { | |
| const originalFetch = window.fetch; | |
| const requests = []; | |
| window.fetch = function(...args) { | |
| const [url, options] = args; | |
| const startTime = Date.now(); | |
| requests.push({ | |
| url, | |
| method: options?.method || 'GET', | |
| headers: options?.headers, | |
| body: options?.body, | |
| startTime | |
| }); | |
| return originalFetch.apply(this, args).then(response => { | |
| const req = requests[requests.length - 1]; | |
| req.responseTime = Date.now() - startTime; | |
| req.status = response.status; | |
| req.statusText = response.statusText; | |
| req.responseURL = response.url; | |
| return response; | |
| }); | |
| }; | |
| return { requests, originalFetch }; | |
| }); | |
| const response = await page.evaluate(async (promptText, modelText, sessId, debugMode) => { | |
| const debug = debugMode || false; | |
| const startTime = Date.now(); // 将 startTime 移到函数开始处 | |
| if (debug) { | |
| console.log('🔍 浏览器端调试开始'); | |
| console.log(' promptText:', promptText); | |
| console.log(' modelText:', modelText); | |
| console.log(' sessId:', sessId); | |
| } | |
| try { | |
| // 增强的请求验证 | |
| const requestData = { | |
| text: promptText, | |
| model: modelText || 'gemini-2.5-flash' | |
| }; | |
| if (sessId) { | |
| requestData.session_id = sessId; | |
| if (debug) console.log('✅ 添加了session_id:', sessId); | |
| } else { | |
| if (debug) console.log('⚠️ 没有session_id,将发送无session请求'); | |
| } | |
| // 转换为URLSearchParams | |
| const urlParams = new URLSearchParams(); | |
| Object.entries(requestData).forEach(([key, value]) => { | |
| urlParams.append(key, value); | |
| }); | |
| if (debug) { | |
| console.log('📤 最终发送的URL参数:'); | |
| for (const [key, value] of urlParams.entries()) { | |
| console.log(` - ${key}:`, value); | |
| } | |
| } | |
| // 构造增强的headers | |
| const headers = { | |
| 'Accept': '*/*', | |
| 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', | |
| 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', | |
| 'Origin': 'https://toolbaz.com', | |
| 'Referer': 'https://toolbaz.com/writer/ai-writer', | |
| 'User-Agent': navigator.userAgent, | |
| 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"', | |
| 'sec-ch-ua-mobile': '?0', | |
| 'sec-ch-ua-platform': '"Windows"', | |
| 'Sec-Fetch-Dest': 'empty', | |
| 'Sec-Fetch-Mode': 'cors', | |
| 'Sec-Fetch-Site': 'same-site', | |
| 'X-Requested-With': 'XMLHttpRequest' | |
| }; | |
| if (debug) { | |
| console.log('🌐 发送fetch请求到 https://data.toolbaz.com/writing.php'); | |
| console.log('📋 请求Headers:', headers); | |
| console.log('📋 请求Body:', urlParams.toString()); | |
| } | |
| // 发送请求 | |
| const response = await fetch('https://data.toolbaz.com/writing.php', { | |
| method: 'POST', | |
| headers: headers, | |
| body: urlParams.toString(), | |
| credentials: 'include' | |
| }); | |
| const responseTime = Date.now() - startTime; | |
| if (debug) { | |
| console.log('📡 AJAX响应状态:', response.status); | |
| console.log('📡 AJAX响应类型:', response.type); | |
| console.log('📡 AJAX响应URL:', response.url); | |
| console.log('📡 响应时间:', responseTime + 'ms'); | |
| } | |
| // 详细响应分析 | |
| const responseHeaders = {}; | |
| for (const [key, value] of response.headers.entries()) { | |
| responseHeaders[key] = value; | |
| } | |
| if (debug) { | |
| console.log('📡 AJAX响应头:', responseHeaders); | |
| } | |
| if (!response.ok) { | |
| const errorText = await response.text(); | |
| if (debug) { | |
| console.error('❌ HTTP错误响应内容:', errorText); | |
| } | |
| // 分析错误类型 | |
| let errorAnalysis = 'Unknown Error'; | |
| if (errorText.includes('CAPTCHA')) { | |
| errorAnalysis = 'CAPTCHA Token Error'; | |
| } else if (errorText.includes('session')) { | |
| errorAnalysis = 'Session Error'; | |
| } else if (response.status === 429) { | |
| errorAnalysis = 'Rate Limited'; | |
| } else if (response.status >= 500) { | |
| errorAnalysis = 'Server Error'; | |
| } | |
| throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorAnalysis} - ${errorText}`); | |
| } | |
| const text = await response.text(); | |
| if (debug) { | |
| console.log('📡 AJAX响应长度:', text.length); | |
| console.log('📡 AJAX响应前200字符:', text.substring(0, 200)); | |
| } | |
| // 响应内容分析 | |
| const responseAnalysis = { | |
| length: text.length, | |
| isEmpty: text.length === 0, | |
| containsHtml: /<[^>]+>/.test(text), | |
| containsError: /error|fail|invalid|unauthorized/i.test(text), | |
| containsSuccess: /success|ok|complete/i.test(text), | |
| preview: text.substring(0, 500) | |
| }; | |
| if (debug) { | |
| console.log('📊 响应分析:', responseAnalysis); | |
| } | |
| return { | |
| success: true, | |
| data: text, | |
| analysis: responseAnalysis, | |
| responseTime, | |
| headers: responseHeaders | |
| }; | |
| } catch (error) { | |
| if (debug) { | |
| console.error('❌ AJAX请求失败:', error); | |
| console.error('❌ 错误堆栈:', error.stack); | |
| } | |
| return { | |
| success: false, | |
| error: error.message, | |
| stack: error.stack, | |
| responseTime: Date.now() - startTime | |
| }; | |
| } | |
| }, prompt, model, sessionId, CONFIG.DEBUG_LEVEL === 'verbose'); | |
| const totalTime = Date.now() - startTime; | |
| log('verbose', '🏁 浏览器端调试结束'); | |
| log('verbose', '📋 Node.js收到的响应:', response); | |
| log('debug', `⏱️ 总响应时间: ${totalTime}ms`); | |
| // 更新性能统计 | |
| updatePerformanceStats('ajax', response.success, totalTime); | |
| return response; | |
| } catch (error) { | |
| log('error', `❌ AJAX请求执行失败: ${error.message}`); | |
| updatePerformanceStats('ajax', false, Date.now() - startTime); | |
| // 如果是重试范围内,尝试重试 | |
| if (retryCount < CONFIG.MAX_RETRIES) { | |
| log('info', `🔄 准备重试 (${retryCount + 1}/${CONFIG.MAX_RETRIES})...`); | |
| await new Promise(resolve => setTimeout(resolve, CONFIG.RETRY_DELAY)); | |
| return performAjaxRequest(page, prompt, model, sessionId, retryCount + 1); | |
| } | |
| return { success: false, error: error.message, stack: error.stack }; | |
| } | |
| } | |
| async function captureAuthCredentials(page) { | |
| log('info', '🔐 捕获UI成功后的认证凭据...'); | |
| const credentials = await page.evaluate(() => { | |
| const results = { | |
| timestamp: Date.now(), | |
| cookies: document.cookie, | |
| localStorage: {}, | |
| sessionStorage: {}, | |
| hiddenInputs: {}, | |
| metaTags: {}, | |
| captchaToken: null, | |
| sessionId: null, | |
| otherTokens: {} | |
| }; | |
| // 捕获localStorage | |
| try { | |
| for (let i = 0; i < localStorage.length; i++) { | |
| const key = localStorage.key(i); | |
| if (key && (key.includes('token') || key.includes('session') || key.includes('auth') || key.includes('sid') || key.includes('csrf'))) { | |
| results.localStorage[key] = localStorage.getItem(key); | |
| } | |
| } | |
| } catch (e) {} | |
| // 捕获sessionStorage | |
| try { | |
| for (let i = 0; i < sessionStorage.length; i++) { | |
| const key = sessionStorage.key(i); | |
| if (key && (key.includes('token') || key.includes('session') || key.includes('auth') || key.includes('sid') || key.includes('csrf'))) { | |
| results.sessionStorage[key] = sessionStorage.getItem(key); | |
| } | |
| } | |
| } catch (e) {} | |
| // 捕获所有hidden input | |
| const hiddenInputs = document.querySelectorAll('input[type="hidden"]'); | |
| hiddenInputs.forEach(input => { | |
| if (input.name && (input.name.includes('token') || input.name.includes('session') || input.name.includes('auth') || input.name.includes('sid') || input.name.includes('csrf') || input.name.includes('captcha'))) { | |
| results.hiddenInputs[input.name] = input.value; | |
| } | |
| // 特殊查找常见的token字段 | |
| if (input.name === 'g-recaptcha-response' || input.id === 'g-recaptcha-response') { | |
| results.captchaToken = input.value; | |
| } | |
| if (input.name === 'session_id' || input.id === 'session_id' || input.name === 'sid' || input.id === 'sid') { | |
| results.sessionId = input.value; | |
| } | |
| }); | |
| // 捕获meta tags中的token | |
| const metaTags = document.querySelectorAll('meta[name*="token"], meta[name*="csrf"], meta[property*="token"]'); | |
| metaTags.forEach(meta => { | |
| results.metaTags[meta.name || meta.property] = meta.content; | |
| }); | |
| // 查找window对象中的全局变量 | |
| try { | |
| if (window.recaptchaToken) results.captchaToken = window.recaptchaToken; | |
| if (window.session_id) results.sessionId = window.session_id; | |
| if (window.csrfToken) results.otherTokens.csrfToken = window.csrfToken; | |
| } catch (e) {} | |
| return results; | |
| }); | |
| log('info', `🔐 捕获到凭据: cookies=${!!credentials.cookies}, captcha=${!!credentials.captchaToken}, sessionId=${!!credentials.sessionId}`); | |
| log('debug', '🔐 详细凭据:', credentials); | |
| return credentials; | |
| } | |
| async function performAjaxRequestWithCredentials(page, prompt, model, credentials) { | |
| log('info', '🚀 使用捕获的凭据执行AJAX请求...'); | |
| const response = await page.evaluate(async (promptText, modelText, authData, debugMode) => { | |
| const debug = debugMode || false; | |
| const startTime = Date.now(); | |
| if (debug) { | |
| console.log('🔐 使用凭据的AJAX请求开始'); | |
| console.log(' authData:', authData); | |
| } | |
| try { | |
| const requestData = { | |
| text: promptText, | |
| model: modelText || 'gemini-2.5-flash' | |
| }; | |
| // 使用捕获的凭据 | |
| if (authData.sessionId) { | |
| requestData.session_id = authData.sessionId; | |
| if (debug) console.log('✅ 使用捕获的session_id:', authData.sessionId); | |
| } | |
| // 如果有captcha token,添加到请求中 | |
| if (authData.captchaToken) { | |
| requestData['g-recaptcha-response'] = authData.captchaToken; | |
| if (debug) console.log('✅ 使用捕获的captcha token'); | |
| } | |
| // 添加其他可能的token | |
| Object.entries(authData.otherTokens).forEach(([key, value]) => { | |
| requestData[key] = value; | |
| }); | |
| const urlParams = new URLSearchParams(); | |
| Object.entries(requestData).forEach(([key, value]) => { | |
| urlParams.append(key, value); | |
| }); | |
| const headers = { | |
| 'Accept': '*/*', | |
| 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', | |
| 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', | |
| 'Origin': 'https://toolbaz.com', | |
| 'Referer': 'https://toolbaz.com/writer/ai-writer', | |
| 'User-Agent': navigator.userAgent, | |
| 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"', | |
| 'sec-ch-ua-mobile': '?0', | |
| 'sec-ch-ua-platform': '"Windows"', | |
| 'Sec-Fetch-Dest': 'empty', | |
| 'Sec-Fetch-Mode': 'cors', | |
| 'Sec-Fetch-Site': 'same-site', | |
| 'X-Requested-With': 'XMLHttpRequest' | |
| }; | |
| if (debug) { | |
| console.log('🌐 发送带凭据的fetch请求'); | |
| console.log('📋 请求参数:', urlParams.toString()); | |
| } | |
| const response = await fetch('https://data.toolbaz.com/writing.php', { | |
| method: 'POST', | |
| headers: headers, | |
| body: urlParams.toString(), | |
| credentials: 'include' | |
| }); | |
| const responseTime = Date.now() - startTime; | |
| if (debug) { | |
| console.log('📡 响应状态:', response.status); | |
| console.log('📡 响应时间:', responseTime + 'ms'); | |
| } | |
| if (!response.ok) { | |
| const errorText = await response.text(); | |
| throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`); | |
| } | |
| const text = await response.text(); | |
| return { | |
| success: true, | |
| data: text, | |
| responseTime | |
| }; | |
| } catch (error) { | |
| return { | |
| success: false, | |
| error: error.message, | |
| responseTime: Date.now() - startTime | |
| }; | |
| } | |
| }, prompt, model, credentials, CONFIG.DEBUG_LEVEL === 'verbose'); | |
| return response; | |
| } | |
| async function cleanupHtmlResponse(html) { | |
| return html | |
| .replace(/<br\s*\/?>/gi, '\n') | |
| .replace(/<\/p>/gi, '\n\n') | |
| .replace(/<[^>]+>/g, '') | |
| .replace(/ /g, ' ') | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/"/g, '"') | |
| .replace(/'/g, "'") | |
| .trim(); | |
| } | |
| function cleanPromptPrefix(prompt) { | |
| const prefixPatterns = [ | |
| /^Write a code snippet that does the following in the specified language\s*:\s*/i, | |
| /^Generate an original and engaging piece of writing on the following topic\s*:\s*/i, | |
| /^Translate the following text to\s*\w+\s*:\s*/i, | |
| /^Summarize the following text\s*:\s*/i, | |
| /^Explain the following concept\s*:\s*/i, | |
| /^Write a function that\s*:\s*/i, | |
| /^Create a\s*\w+\s*that\s*:\s*/i | |
| ]; | |
| let cleanedPrompt = prompt; | |
| for (const pattern of prefixPatterns) { | |
| if (pattern.test(cleanedPrompt)) { | |
| cleanedPrompt = cleanedPrompt.replace(pattern, ''); | |
| break; | |
| } | |
| } | |
| return cleanedPrompt.trim(); | |
| } | |
| async function chatWithBrowser(prompt, model) { | |
| const startTime = Date.now(); | |
| let ajaxSuccess = false; | |
| let uiFallback = false; | |
| try { | |
| log('info', `📝 开始聊天请求: ${prompt.substring(0, 50)}...`); | |
| const browser = await initBrowser(); | |
| const page = await browser.newPage(); | |
| // 设置页面配置 | |
| await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36'); | |
| // 过滤前缀文本 | |
| const cleanedPrompt = cleanPromptPrefix(prompt); | |
| if (cleanedPrompt !== prompt) { | |
| log('info', `🧹 已过滤前缀: ${prompt.substring(0, 50)}... -> ${cleanedPrompt.substring(0, 50)}...`); | |
| } | |
| try { | |
| // 访问页面获取必要的会话信息 | |
| log('info', '🌐 访问页面获取会话信息...'); | |
| await page.goto('https://toolbaz.com/writer/ai-writer', { | |
| waitUntil: 'networkidle2', | |
| timeout: 30000 | |
| }); | |
| // 等待页面完全加载 | |
| await page.waitForSelector('#input', { timeout: 10000 }); | |
| log('debug', '✅ 页面加载完成'); | |
| log('info', '🔍 开始增强的session ID检测...'); | |
| // 简化的session检测(优化:减少复杂检测) | |
| const sessionInfo = await page.evaluate((debugMode) => { | |
| const debug = debugMode || false; | |
| if (debug) { | |
| console.log('🔍 简化版session ID检测开始'); | |
| } | |
| const results = { | |
| timestamp: Date.now(), | |
| url: window.location.href, | |
| finalSessionId: null, | |
| detectionMethods: [] | |
| }; | |
| // 仅保留最有效的方法,注释掉复杂的检测 | |
| let sessionId = null; | |
| let detectionMethod = null; | |
| // 仅保留最可能成功的方法 | |
| const simpleMethods = [ | |
| () => { | |
| const input = document.querySelector('input[name="session_id"]'); | |
| return input?.value || null; | |
| }, | |
| () => { | |
| const elem = document.querySelector('#session_id'); | |
| return elem?.value || null; | |
| }, | |
| () => sessionStorage.getItem('session_id'), | |
| () => window.session_id | |
| ]; | |
| for (let i = 0; i < simpleMethods.length; i++) { | |
| const result = simpleMethods[i](); | |
| if (result && result.length > 0) { | |
| sessionId = result; | |
| detectionMethod = `简化方法${i + 1}`; | |
| if (debug) console.log(`✅ ${detectionMethod}成功:`, result); | |
| break; | |
| } | |
| } | |
| // 注释掉:复杂的检测逻辑(当前占用大量资源但效果不佳) | |
| /* | |
| // 原有的复杂检测逻辑已注释 | |
| const allElements = document.querySelectorAll('*'); | |
| const sessionElements = []; | |
| ...省略... | |
| */ | |
| results.finalSessionId = sessionId; | |
| if (detectionMethod) { | |
| results.detectionMethods.push(detectionMethod); | |
| } | |
| if (debug) { | |
| console.log('🔍 简化版session ID结果:', sessionId); | |
| } | |
| return results; | |
| }, CONFIG.DEBUG_LEVEL === 'verbose'); | |
| log('info', '📊 简化版Session检测结果:'); | |
| if (sessionInfo.finalSessionId) { | |
| log('info', `✅ 找到session ID: "${sessionInfo.finalSessionId.substring(0, 20)}..."`); | |
| log('info', `✅ session ID长度: ${sessionInfo.finalSessionId.length}`); | |
| log('info', `✅ 检测方法: ${sessionInfo.detectionMethods.join(', ')}`); | |
| } else { | |
| log('warn', '❌ 未找到有效的session ID(简化检测)'); | |
| log('debug', '📋 简化检测结果:', sessionInfo); | |
| } | |
| const sessionId = sessionInfo.finalSessionId; | |
| // 尝试直接AJAX请求(混合优化模式) | |
| log('info', '📡 尝试直接AJAX请求(混合优化模式)...'); | |
| const ajaxResponse = await performAjaxRequest(page, cleanedPrompt, model, sessionId); | |
| // 修复后的响应处理逻辑 | |
| if (ajaxResponse && ajaxResponse.success) { | |
| // 清理HTML响应 | |
| const cleanedResponse = await cleanupHtmlResponse(ajaxResponse.data); | |
| // 优化的响应验证:更宽松但准确的验证逻辑 | |
| const isRealError = (text) => { | |
| const errorPatterns = [ | |
| /session.*invalid/i, | |
| /session.*expired/i, | |
| /error.*session/i, | |
| /unauthorized/i, | |
| /^<[^>]*>$/ // 纯HTML标签无内容 | |
| ]; | |
| // 只有真正包含错误信息的响应才被认为是错误 | |
| return errorPatterns.some(pattern => pattern.test(text)) || text.trim().length === 0; | |
| }; | |
| if (!isRealError(cleanedResponse)) { | |
| ajaxSuccess = true; | |
| const totalTime = Date.now() - startTime; | |
| log('info', `✅ AJAX响应成功: ${cleanedResponse.substring(0, 100)}...`); | |
| log('info', `✅ 响应长度: ${cleanedResponse.length}`); | |
| log('info', `⏱️ 总处理时间: ${totalTime}ms`); | |
| updatePerformanceStats('ajax', true, totalTime); | |
| return cleanedResponse; | |
| } else { | |
| log('warn', '⚠️ AJAX响应包含真实错误,准备回退到UI方法...'); | |
| log('warn', ' 响应预览:', cleanedResponse.substring(0, 200)); | |
| } | |
| } else { | |
| log('warn', '❌ AJAX请求失败:', ajaxResponse?.error || '未知错误'); | |
| if (ajaxResponse?.responseTime) { | |
| log('debug', `⏱️ 失败请求耗时: ${ajaxResponse.responseTime}ms`); | |
| } | |
| log('info', '🔄 准备回退到UI方法...'); | |
| } | |
| // 混合模式:优先使用缓存的凭据,再获取新凭据 | |
| uiFallback = true; | |
| log('info', '🔄 混合模式:智能凭据管理...'); | |
| let credentialsToUse = null; | |
| // 1. 首先检查缓存的凭据 | |
| if (credentialCache.isValid()) { | |
| credentialsToUse = credentialCache.data; | |
| log('info', '🔐 使用缓存的凭据尝试AJAX请求...'); | |
| const cachedAjaxResponse = await performAjaxRequestWithCredentials(page, cleanedPrompt, model, credentialsToUse); | |
| if (cachedAjaxResponse && cachedAjaxResponse.success) { | |
| const totalTime = Date.now() - startTime; | |
| log('info', `✅ 缓存凭据AJAX请求成功: ${cachedAjaxResponse.data.substring(0, 100)}...`); | |
| log('info', `⏱️ 缓存模式处理时间: ${totalTime}ms`); | |
| updatePerformanceStats('ajax', true, totalTime); | |
| return cachedAjaxResponse.data; | |
| } else { | |
| log('warn', '❌ 缓存凭据失效,清空缓存并重新获取...'); | |
| credentialCache.clear(); | |
| } | |
| } | |
| // 2. 捕获当前页面的认证凭据 | |
| const currentCredentials = await captureAuthCredentials(page); | |
| // 3. 尝试使用当前凭据进行AJAX请求 | |
| if (currentCredentials && (currentCredentials.captchaToken || currentCredentials.sessionId)) { | |
| log('info', '🔐 发现新凭据,尝试凭据AJAX请求...'); | |
| const credAjaxResponse = await performAjaxRequestWithCredentials(page, cleanedPrompt, model, currentCredentials); | |
| if (credAjaxResponse && credAjaxResponse.success) { | |
| const totalTime = Date.now() - startTime; | |
| log('info', `✅ 新凭据AJAX请求成功: ${credAjaxResponse.data.substring(0, 100)}...`); | |
| log('info', `⏱️ 混合模式处理时间: ${totalTime}ms`); | |
| // 缓存有效的凭据 | |
| credentialCache.set(currentCredentials, 30); | |
| updatePerformanceStats('ajax', true, totalTime); | |
| return credAjaxResponse.data; | |
| } else { | |
| log('warn', '❌ 新凭据AJAX请求失败,继续执行UI交互...'); | |
| } | |
| } else { | |
| log('info', '🔐 未发现有效凭据,直接执行UI交互...'); | |
| } | |
| // 执行UI交互获取响应 | |
| log('info', '🖱️ 执行UI交互...'); | |
| // 增强的UI交互 | |
| await page.evaluate(() => { | |
| const input = document.querySelector('#input'); | |
| if (input) { | |
| input.value = ''; | |
| input.focus(); | |
| } | |
| }); | |
| await page.type('#input', cleanedPrompt, { delay: 50 }); | |
| await page.click('#main_btn'); | |
| log('debug', '⏳ 等待UI响应...'); | |
| // 等待响应出现,增加超时检测 | |
| const uiResponse = await Promise.race([ | |
| page.waitForFunction(() => { | |
| const output = document.querySelector('#output'); | |
| return output && output.innerText && output.innerText.trim().length > 0; | |
| }, { timeout: 60000 }), | |
| new Promise((_, reject) => | |
| setTimeout(() => reject(new Error('UI响应超时')), 60000) | |
| ) | |
| ]).then(async () => { | |
| const text = await page.evaluate(() => { | |
| const output = document.querySelector('#output'); | |
| return output ? output.innerText.trim() : ''; | |
| }); | |
| return text; | |
| }); | |
| if (uiResponse && uiResponse.length > 0) { | |
| const totalTime = Date.now() - startTime; | |
| log('info', `✅ UI响应成功: ${uiResponse.substring(0, 100)}...`); | |
| log('info', `✅ 响应长度: ${uiResponse.length}`); | |
| log('info', `⏱️ 总处理时间: ${totalTime}ms`); | |
| // UI成功后,再次尝试用这个页面的凭据进行AJAX请求,以备后续使用 | |
| log('info', '🔐 UI成功后,捕获并缓存凭据...'); | |
| const postUICredentials = await captureAuthCredentials(page); | |
| // 如果捕获到有效凭据,进行缓存 | |
| if (postUICredentials && (postUICredentials.captchaToken || postUICredentials.sessionId)) { | |
| credentialCache.set(postUICredentials, 60); // UI成功的凭据缓存60分钟 | |
| log('info', '✅ UI成功后的凭据已缓存'); | |
| } | |
| updatePerformanceStats('ui', true, totalTime); | |
| return uiResponse; | |
| } else { | |
| throw new Error('UI方法也未能获取有效响应'); | |
| } | |
| } catch (error) { | |
| log('error', `❌ 聊天请求失败: ${error.message}`); | |
| log('error', `❌ 错误详情:`, error.stack); | |
| // 记录失败统计 | |
| updatePerformanceStats(uiFallback ? 'ui' : 'ajax', false, Date.now() - startTime); | |
| // 尝试最后一次紧急恢复 | |
| log('warn', '🚨 尝试紧急恢复...'); | |
| try { | |
| await page.goto('https://toolbaz.com/writer/ai-writer', { | |
| waitUntil: 'networkidle2', | |
| timeout: 15000 | |
| }); | |
| await page.waitForSelector('#input', { timeout: 5000 }); | |
| await page.type('#input', cleanedPrompt, { delay: 100 }); | |
| await page.click('#main_btn'); | |
| const emergencyResponse = await page.waitForFunction(() => { | |
| const output = document.querySelector('#output'); | |
| return output && output.innerText && output.innerText.trim().length > 0; | |
| }, { timeout: 30000 }).then(async () => { | |
| return await page.evaluate(() => document.querySelector('#output')?.innerText?.trim() || ''); | |
| }); | |
| if (emergencyResponse && emergencyResponse.length > 0) { | |
| log('warn', '✅ 紧急恢复成功:', emergencyResponse.substring(0, 100) + '...'); | |
| return emergencyResponse; | |
| } | |
| } catch (recoveryError) { | |
| log('error', `❌ 紧急恢复也失败了: ${recoveryError.message}`); | |
| } | |
| throw error; | |
| } finally { | |
| await page.close(); | |
| } | |
| } catch (error) { | |
| const totalTime = Date.now() - startTime; | |
| log('error', `💥 完全聊天失败: ${error.message}`); | |
| log('error', `💥 总耗时: ${totalTime}ms`); | |
| updatePerformanceStats('unknown', false, totalTime); | |
| throw error; | |
| } | |
| } | |
| async function generateImageWithBrowser(prompt, model, size) { | |
| const browser = await initBrowser(); | |
| const page = await browser.newPage(); | |
| try { | |
| console.log(`🎨 图片生成: ${prompt.substring(0, 50)}...`); | |
| await page.goto('https://toolbaz.com/image/ai-image-generator', { waitUntil: 'networkidle2', timeout: 30000 }); | |
| // 等待输入框 | |
| await page.waitForSelector('#prompt', { timeout: 10000 }); | |
| // 输入提示词 | |
| await page.click('#prompt', { clickCount: 3 }); | |
| await page.type('#prompt', prompt); | |
| // 选择模型(如果需要) | |
| if (model) { | |
| try { | |
| await page.select('#model', model); | |
| } catch (e) { | |
| console.log('⚠️ 模型选择失败,使用默认'); | |
| } | |
| } | |
| // 选择尺寸(如果需要) | |
| if (size) { | |
| try { | |
| await page.select('#size', size); | |
| } catch (e) { | |
| console.log('⚠️ 尺寸选择失败,使用默认'); | |
| } | |
| } | |
| // 点击生成按钮 | |
| await page.click('#generat'); | |
| // 等待图片生成(等待结果区域出现图片) | |
| await page.waitForSelector('img[src*="generated"], img[src*="image"]', { timeout: 120000 }); | |
| // 提取图片 URL | |
| const imageUrl = await page.evaluate(() => { | |
| const imgs = document.querySelectorAll('img[src*="generated"], img[src*="image"], img[src*="toolbaz"]'); | |
| for (let img of imgs) { | |
| if (img.src && img.src.startsWith('http') && !img.src.includes('logo')) { | |
| return img.src; | |
| } | |
| } | |
| return null; | |
| }); | |
| if (!imageUrl) { | |
| throw new Error('未找到生成的图片'); | |
| } | |
| console.log(`✅ 图片: ${imageUrl}`); | |
| return imageUrl; | |
| } catch (error) { | |
| console.error(`❌ 图片生成失败: ${error.message}`); | |
| throw error; | |
| } finally { | |
| await page.close(); | |
| } | |
| } | |
| async function handleChatCompletions(req, res) { | |
| if (!verifyAuth(req)) return sendError(res, 401, 'Unauthorized'); | |
| let body = ''; | |
| req.on('data', chunk => body += chunk); | |
| req.on('end', async () => { | |
| try { | |
| const data = JSON.parse(body); | |
| const messages = data.messages || []; | |
| const lastMessage = messages[messages.length - 1]?.content || "Hello"; | |
| const model = data.model || CONFIG.DEFAULT_CHAT_MODEL; | |
| const responseText = await chatWithBrowser(lastMessage, model); | |
| res.writeHead(200, { | |
| 'Content-Type': 'text/event-stream', | |
| 'Cache-Control': 'no-cache', | |
| 'Connection': 'keep-alive', | |
| 'Access-Control-Allow-Origin': '*' | |
| }); | |
| const chunkSize = 10; | |
| for (let i = 0; i < responseText.length; i += chunkSize) { | |
| const content = responseText.slice(i, i + chunkSize); | |
| res.write(`data: ${JSON.stringify({ | |
| id: `chatcmpl-${Date.now()}`, | |
| object: 'chat.completion.chunk', | |
| created: Math.floor(Date.now() / 1000), | |
| model, | |
| choices: [{ index: 0, delta: { content }, finish_reason: null }] | |
| })}\n\n`); | |
| await new Promise(r => setTimeout(r, 20)); | |
| } | |
| res.write('data: [DONE]\n\n'); | |
| res.end(); | |
| } catch (error) { | |
| console.error('聊天请求失败:', error); | |
| sendError(res, 500, error.message); | |
| } | |
| }); | |
| } | |
| async function handleImageGenerations(req, res) { | |
| if (!verifyAuth(req)) return sendError(res, 401, 'Unauthorized'); | |
| let body = ''; | |
| req.on('data', chunk => body += chunk); | |
| req.on('end', async () => { | |
| try { | |
| const data = JSON.parse(body); | |
| const prompt = data.prompt; | |
| const model = data.model || "FLUX-1-schnell"; | |
| const imageUrl = await generateImageWithBrowser(prompt, model); | |
| res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); | |
| res.end(JSON.stringify({ created: Math.floor(Date.now() / 1000), data: [{ url: imageUrl }] })); | |
| } catch (error) { | |
| console.error('图片请求失败:', error); | |
| sendError(res, 500, error.message); | |
| } | |
| }); | |
| } | |
| // 模型缓存(避免频繁重新提取) | |
| let modelCache = { | |
| data: null, | |
| timestamp: null, | |
| expiresAt: null, | |
| isValid: function() { | |
| return this.data && this.expiresAt && Date.now() < this.expiresAt; | |
| }, | |
| set: function(models, ttlMinutes = 60) { | |
| this.data = models; | |
| this.timestamp = Date.now(); | |
| this.expiresAt = Date.now() + (ttlMinutes * 60 * 1000); | |
| log('info', `🔐 模型列表已缓存,有效期 ${ttlMinutes} 分钟`); | |
| }, | |
| clear: function() { | |
| this.data = null; | |
| this.timestamp = null; | |
| this.expiresAt = null; | |
| log('info', '🔐 模型缓存已清空'); | |
| } | |
| }; | |
| async function handleModels(req, res) { | |
| try { | |
| log('info', '🔍 开始提取网站模型列表...'); | |
| // 检查缓存 | |
| if (modelCache.isValid()) { | |
| log('info', `✅ 使用缓存模型列表 (${modelCache.data.length} 个模型)`); | |
| res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); | |
| res.end(JSON.stringify({ | |
| object: 'list', | |
| data: modelCache.data | |
| })); | |
| return; | |
| } | |
| // 尝试多种方法获取模型列表 | |
| let models = null; | |
| let extractionMethod = ''; | |
| // 方法1: 尝试访问网站获取最新模型 | |
| try { | |
| log('info', '🌐 方法1: 从网站提取模型列表...'); | |
| models = await extractModelsFromWebsite(); | |
| extractionMethod = 'website'; | |
| } catch (websiteError) { | |
| log('warn', `⚠️ 网站提取失败: ${websiteError.message}`); | |
| // 方法2: 尝试AJAX方式获取模型 | |
| try { | |
| log('info', '📡 方法2: 尝试AJAX方式获取模型...'); | |
| models = await extractModelsViaAjax(); | |
| extractionMethod = 'ajax'; | |
| } catch (ajaxError) { | |
| log('warn', `⚠️ AJAX提取失败: ${ajaxError.message}`); | |
| // 方法3: 使用增强的静态配置 | |
| try { | |
| log('info', '🔧 方法3: 使用增强静态配置...'); | |
| models = await getEnhancedStaticModels(); | |
| extractionMethod = 'enhanced-static'; | |
| } catch (staticError) { | |
| log('error', `❌ 所有方法都失败了,使用基础静态配置: ${staticError.message}`); | |
| models = getBasicStaticModels(); | |
| extractionMethod = 'basic-static'; | |
| } | |
| } | |
| } | |
| // 更新缓存 | |
| if (models && models.length > 0) { | |
| modelCache.set(models, 60); // 缓存60分钟 | |
| log('info', `✅ 成功提取 ${models.length} 个模型 (方法: ${extractionMethod})`); | |
| // 记录提取到的模型(调试用) | |
| if (CONFIG.DEBUG_LEVEL === 'verbose') { | |
| models.forEach((model, index) => { | |
| log('verbose', ` ${index + 1}. ${model.name} (${model.id})`); | |
| }); | |
| } | |
| res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); | |
| res.end(JSON.stringify({ | |
| object: 'list', | |
| data: models, | |
| metadata: { | |
| extraction_method: extractionMethod, | |
| extracted_at: new Date().toISOString(), | |
| total_models: models.length | |
| } | |
| })); | |
| } else { | |
| throw new Error('没有获取到任何模型数据'); | |
| } | |
| } catch (error) { | |
| log('error', `❌ 模型提取完全失败: ${error.message}`); | |
| // 最后的兜底策略 | |
| const fallbackModels = getBasicStaticModels(); | |
| res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); | |
| res.end(JSON.stringify({ | |
| object: 'list', | |
| data: fallbackModels, | |
| metadata: { | |
| extraction_method: 'fallback', | |
| error: error.message, | |
| extracted_at: new Date().toISOString() | |
| } | |
| })); | |
| } | |
| } | |
| // 新增:从网站提取模型的专用函数 | |
| async function extractModelsFromWebsite() { | |
| const browser = await initBrowser(); | |
| const page = await browser.newPage(); | |
| try { | |
| // 设置更长的超时时间 | |
| page.setDefaultTimeout(30000); | |
| log('info', '🌐 访问写作页面...'); | |
| await page.goto('https://toolbaz.com/writer/ai-writer', { | |
| waitUntil: 'networkidle2', | |
| timeout: 30000 | |
| }); | |
| // 等待页面加载 | |
| await page.waitForTimeout(2000); // 给页面一些加载时间 | |
| log('info', '🔍 查找模型选择器...'); | |
| // 尝试多种选择器 | |
| const modelSelectors = [ | |
| '#selected-model', | |
| '.model-selector', | |
| '[data-model]', | |
| '.model-dropdown-trigger' | |
| ]; | |
| let selectorFound = null; | |
| for (const selector of modelSelectors) { | |
| try { | |
| await page.waitForSelector(selector, { timeout: 5000 }); | |
| selectorFound = selector; | |
| log('info', `✅ 找到模型选择器: ${selector}`); | |
| break; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| if (!selectorFound) { | |
| throw new Error('未找到模型选择器'); | |
| } | |
| // 尝试点击选择器 | |
| try { | |
| await page.click(selectorFound); | |
| log('info', '✅ 成功点击模型选择器'); | |
| } catch (e) { | |
| log('warn', `⚠️ 点击选择器失败,尝试直接查找模型: ${e.message}`); | |
| } | |
| // 等待下拉列表或模型列表出现 | |
| await page.waitForTimeout(1000); | |
| // 查找模型选项 | |
| const models = await page.evaluate(() => { | |
| const modelList = []; | |
| // 尝试多种模型选项选择器 | |
| const modelSelectors = [ | |
| '.model-dropdown .model-option', | |
| '.model-list .model-item', | |
| '[data-model-option]', | |
| '.model-selector option', | |
| 'select[name="model"] option', | |
| '.ai-models .model', | |
| '.available-models .model-item' | |
| ]; | |
| for (const selector of modelSelectors) { | |
| try { | |
| const elements = document.querySelectorAll(selector); | |
| if (elements.length > 0) { | |
| log(`✅ 使用选择器 ${selector} 找到 ${elements.length} 个模型选项`); | |
| elements.forEach((element, index) => { | |
| let name = ''; | |
| let id = ''; | |
| // 尝试获取模型名称 | |
| const nameElement = element.querySelector('.model-name') || | |
| element.querySelector('.name') || | |
| element.querySelector('span') || | |
| element; | |
| name = nameElement.innerText || nameElement.textContent || | |
| nameElement.value || nameElement.getAttribute('data-model') || ''; | |
| name = name.trim(); | |
| if (name && name.length > 0) { | |
| id = name.toLowerCase() | |
| .replace(/[^a-z0-9\s]/g, '') | |
| .replace(/\s+/g, '-') | |
| .substring(0, 50); | |
| modelList.push({ | |
| id: id || `model-${index}`, | |
| name: name, | |
| icon: element.querySelector('.model-icon, .icon')?.src || '', | |
| object: 'model', | |
| created: Math.floor(Date.now() / 1000), | |
| owned_by: 'toolbaz' | |
| }); | |
| } | |
| }); | |
| // 如果找到了模型,就不再尝试其他选择器 | |
| if (modelList.length > 0) { | |
| break; | |
| } | |
| } | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| return modelList; | |
| }); | |
| if (models.length === 0) { | |
| throw new Error('页面中没有找到模型选项'); | |
| } | |
| return models; | |
| } finally { | |
| await page.close().catch(e => log('warn', `页面关闭失败: ${e.message}`)); | |
| } | |
| } | |
| // 新增:通过AJAX方式获取模型 | |
| async function extractModelsViaAjax() { | |
| const browser = await initBrowser(); | |
| const page = await browser.newPage(); | |
| try { | |
| await page.goto('https://toolbaz.com/writer/ai-writer', { | |
| waitUntil: 'networkidle2', | |
| timeout: 20000 | |
| }); | |
| const models = await page.evaluate(async () => { | |
| return new Promise((resolve) => { | |
| // 尝试通过AJAX获取模型列表 | |
| const modelEndpoints = [ | |
| 'https://data.toolbaz.com/models.php', | |
| 'https://data.toolbaz.com/api/models', | |
| '/api/models', | |
| '/models' | |
| ]; | |
| let requestCount = 0; | |
| const tryFetch = (url) => { | |
| return fetch(url, { | |
| method: 'GET', | |
| headers: { | |
| 'Accept': 'application/json', | |
| 'X-Requested-With': 'XMLHttpRequest', | |
| 'Referer': window.location.href | |
| } | |
| }) | |
| .then(response => { | |
| if (response.ok) { | |
| return response.json(); | |
| } | |
| throw new Error(`HTTP ${response.status}`); | |
| }) | |
| .then(data => { | |
| if (Array.isArray(data) && data.length > 0) { | |
| return data.map((model, index) => ({ | |
| id: model.id || model.name?.toLowerCase().replace(/[^a-z0-9]/g, '-') || `model-${index}`, | |
| name: model.name || model.displayName || `Model ${index}`, | |
| icon: model.icon || '', | |
| object: 'model', | |
| created: Math.floor(Date.now() / 1000), | |
| owned_by: 'toolbaz' | |
| })); | |
| } | |
| throw new Error('无效的模型数据'); | |
| }); | |
| }; | |
| // 尝试所有端点 | |
| const promises = modelEndpoints.map(url => | |
| tryFetch(url).catch(err => { | |
| requestCount++; | |
| if (requestCount === modelEndpoints.length) { | |
| throw new Error('所有AJAX端点都失败了'); | |
| } | |
| return null; | |
| }) | |
| ); | |
| Promise.all(promises) | |
| .then(results => { | |
| const validResults = results.filter(r => r !== null); | |
| if (validResults.length > 0) { | |
| resolve(validResults[0]); // 使用第一个成功的结果 | |
| } else { | |
| throw new Error('没有成功的AJAX请求'); | |
| } | |
| }) | |
| .catch(() => resolve([])); // 返回空数组表示失败 | |
| }); | |
| }); | |
| if (models.length === 0) { | |
| throw new Error('AJAX方式未获取到模型'); | |
| } | |
| return models; | |
| } finally { | |
| await page.close().catch(e => log('warn', `页面关闭失败: ${e.message}`)); | |
| } | |
| } | |
| // 新增:增强的静态模型配置 | |
| async function getEnhancedStaticModels() { | |
| // 结合配置中的模型和常见的AI模型 | |
| const enhancedModels = [ | |
| // 聊天模型 | |
| ...CONFIG.CHAT_MODELS.map(name => ({ | |
| id: name.toLowerCase().replace(/[^a-z0-9]/g, '-'), | |
| name: name, | |
| object: 'model', | |
| created: Math.floor(Date.now() / 1000), | |
| owned_by: 'toolbaz-2api' | |
| })), | |
| // 图片模型 | |
| ...CONFIG.IMAGE_MODELS.map(name => ({ | |
| id: name.toLowerCase().replace(/[^a-z0-9]/g, '-'), | |
| name: name, | |
| object: 'model', | |
| created: Math.floor(Date.now() / 1000), | |
| owned_by: 'toolbaz-2api' | |
| })), | |
| // 额外的常见模型 | |
| { | |
| id: 'gpt-4', | |
| name: 'GPT-4', | |
| object: 'model', | |
| created: Math.floor(Date.now() / 1000), | |
| owned_by: 'toolbaz-2api' | |
| }, | |
| { | |
| id: 'gpt-3.5-turbo', | |
| name: 'GPT-3.5 Turbo', | |
| object: 'model', | |
| created: Math.floor(Date.now() / 1000), | |
| owned_by: 'toolbaz-2api' | |
| }, | |
| { | |
| id: 'claude-3-sonnet', | |
| name: 'Claude 3 Sonnet', | |
| object: 'model', | |
| created: Math.floor(Date.now() / 1000), | |
| owned_by: 'toolbaz-2api' | |
| } | |
| ]; | |
| // 去重 | |
| const uniqueModels = []; | |
| const seenIds = new Set(); | |
| for (const model of enhancedModels) { | |
| if (!seenIds.has(model.id)) { | |
| seenIds.add(model.id); | |
| uniqueModels.push(model); | |
| } | |
| } | |
| return uniqueModels; | |
| } | |
| // 新增:基础静态模型配置 | |
| function getBasicStaticModels() { | |
| return [...CONFIG.CHAT_MODELS, ...CONFIG.IMAGE_MODELS].map(id => ({ | |
| id: id.toLowerCase().replace(/[^a-z0-9]/g, '-'), | |
| name: id, | |
| object: 'model', | |
| created: Math.floor(Date.now() / 1000), | |
| owned_by: 'toolbaz-2api' | |
| })); | |
| } | |
| // 新增:测试API端点 | |
| async function handleTests(req, res) { | |
| if (!verifyAuth(req)) return sendError(res, 401, 'Unauthorized'); | |
| const url = new URL(req.url, `http://${req.headers.host}`); | |
| const testType = url.searchParams.get('type') || 'comprehensive'; | |
| log('info', `🧪 收到测试请求: ${testType}`); | |
| try { | |
| let result; | |
| switch (testType) { | |
| case 'config': | |
| result = testBasicConfiguration(); | |
| break; | |
| case 'network': | |
| result = await testNetworkConnectivity(); | |
| break; | |
| case 'browser': | |
| result = await testBrowserEnvironment(); | |
| break; | |
| case 'session': | |
| result = await testSessionDetection(); | |
| break; | |
| case 'ajax': | |
| result = await testAjaxRequest(); | |
| break; | |
| case 'prefix': | |
| result = await testPromptPrefixFiltering(); | |
| break; | |
| case 'comprehensive': | |
| default: | |
| result = await runComprehensiveTests(); | |
| break; | |
| } | |
| // 包含性能统计 | |
| result.performance = PERFORMANCE_STATS; | |
| result.timestamp = new Date().toISOString(); | |
| res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); | |
| res.end(JSON.stringify(result, null, 2)); | |
| } catch (error) { | |
| log('error', `❌ 测试执行失败: ${error.message}`); | |
| sendError(res, 500, error.message); | |
| } | |
| } | |
| // 新增:状态API端点 | |
| async function handleStatus(req, res) { | |
| try { | |
| const status = { | |
| service: CONFIG.PROJECT_NAME, | |
| version: CONFIG.PROJECT_VERSION, | |
| uptime: Math.floor(process.uptime()), | |
| timestamp: new Date().toISOString(), | |
| performance: PERFORMANCE_STATS, | |
| config: { | |
| testing_mode: CONFIG.TESTING_MODE, | |
| debug_level: CONFIG.DEBUG_LEVEL, | |
| max_retries: CONFIG.MAX_RETRIES, | |
| performance_monitoring: CONFIG.PERFORMANCE_MONITORING | |
| }, | |
| browser: browser ? 'running' : 'stopped', | |
| cache: { | |
| models: modelCache.isValid() ? 'valid' : 'expired', | |
| credentials: credentialCache.isValid() ? 'valid' : 'expired' | |
| } | |
| }; | |
| res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); | |
| res.end(JSON.stringify(status, null, 2)); | |
| } catch (error) { | |
| log('error', `❌ 状态查询失败: ${error.message}`); | |
| sendError(res, 500, error.message); | |
| } | |
| } | |
| // 新增:模型刷新处理函数 | |
| async function handleModelsRefresh(req, res) { | |
| if (!verifyAuth(req)) return sendError(res, 401, 'Unauthorized'); | |
| try { | |
| log('info', '🔄 收到模型刷新请求...'); | |
| // 清除模型缓存 | |
| modelCache.clear(); | |
| // 强制重新提取模型 | |
| const response = await handleModels(req, res); | |
| log('info', '✅ 模型刷新完成'); | |
| } catch (error) { | |
| log('error', `❌ 模型刷新失败: ${error.message}`); | |
| sendError(res, 500, error.message); | |
| } | |
| } | |
| function handleUI(req, res) { | |
| const html = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>${CONFIG.PROJECT_NAME}</title><style>:root{--bg:#121212;--panel:#1E1E1E;--border:#333;--text:#E0E0E0;--primary:#00FF9D;--secondary:#00BFA5;--error:#F44336;--success:#4CAF50;--warning:#FF9800}body{font-family:Consolas,monospace;background:var(--bg);color:var(--text);margin:0;height:100vh;display:flex;overflow:hidden;font-size:13px}.sidebar{width:320px;background:var(--panel);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:10px;gap:10px}.main{flex:1;display:flex;flex-direction:column}.log-panel{height:40%;border-top:1px solid var(--border);background:#000;overflow-y:auto;padding:10px}.box{background:#252525;padding:15px;border-radius:6px;border:1px solid var(--border)}.label{font-size:11px;color:#888;display:block;margin-bottom:5px;text-transform:uppercase}input,select,textarea{width:100%;background:#333;border:1px solid #444;color:#fff;padding:8px;border-radius:4px;margin-bottom:10px;box-sizing:border-box;font-family:inherit}button{width:100%;padding:10px;background:var(--primary);border:none;border-radius:4px;font-weight:bold;cursor:pointer;color:#000;margin-bottom:5px;transition:all 0.2s}button:disabled{background:#555;cursor:not-allowed}button.secondary{background:#333;color:#fff;border:1px solid #555}button.danger{background:var(--error);color:#fff}button.warning{background:var(--warning);color:#000}.chat-window{flex:1;padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:15px}.msg{max-width:85%;padding:10px 15px;border-radius:6px;line-height:1.5;word-wrap:break-word}.msg.user{align-self:flex-end;background:#333;color:#fff}.msg.ai{align-self:flex-start;background:#1a1a1a;border:1px solid #333;width:100%}.msg.img img{max-width:100%;border-radius:4px}.log-entry{margin-bottom:8px;border-bottom:1px solid #222;padding-bottom:8px}.log-time{color:#666;margin-right:10px}.log-data{color:#ccc;white-space:pre-wrap;word-break:break-all;margin-top:4px;display:block}.tabs{display:flex;border-bottom:1px solid var(--border);margin-bottom:10px}.tab{flex:1;text-align:center;padding:8px;cursor:pointer;color:#888;border-bottom:2px solid transparent;transition:all 0.2s}.tab.active{color:var(--primary);border-bottom-color:var(--primary)}.status-item{display:flex;justify-content:space-between;margin-bottom:5px;font-size:12px}.status-value{color:var(--primary)}.test-result{margin:5px 0;padding:5px;border-radius:3px;font-size:11px}.test-pass{background:var(--success);color:#000}.test-fail{background:var(--error);color:#fff}.performance-grid{display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-top:10px}.perf-item{text-align:center;padding:5px;background:#333;border-radius:3px}.perf-value{font-size:16px;color:var(--primary)}.perf-label{font-size:10px;color:#888}</style></head><body><div class="sidebar"><div class="box"><h2 style="margin:0;color:var(--primary)">${CONFIG.PROJECT_NAME}</h2><span style="font-size:11px;color:#666">v${CONFIG.PROJECT_VERSION} | Enhanced Testing</span></div><div class="box"><div class="tabs"><div class="tab active" onclick="switchMode('chat')">聊天</div><div class="tab" onclick="switchMode('image')">绘图</div><div class="tab" onclick="switchMode('test')">测试</div></div><div id="chat-controls"><span class="label">Model</span><select id="chat-model">${CONFIG.CHAT_MODELS.map(m => `<option value="${m}">${m}</option>`).join('')}</select><span class="label">Prompt</span><textarea id="chat-prompt" rows="5" placeholder="输入内容...">你好,请介绍一下你自己</textarea></div><div id="image-controls" style="display:none"><span class="label">Model</span><select id="image-model">${CONFIG.IMAGE_MODELS.map(m => `<option value="${m}">${m}</option>`).join('')}</select><span class="label">Prompt</span><textarea id="image-prompt" rows="5" placeholder="输入图片描述...">A futuristic city at sunset</textarea></div><div id="test-controls" style="display:none"><span class="label">测试类型</span><select id=\"test-type\"><option value=\"comprehensive\">综合测试</option><option value=\"config\">配置测试</option><option value=\"network\">网络测试</option><option value=\"browser\">浏览器测试</option><option value=\"session\">会话测试</option><option value=\"ajax\">AJAX测试</option><option value=\"prefix\">前缀过滤测试</option></select><button class="warning" onclick="runTest()">🧪 运行测试</button></div><button id="btn-send" onclick="handleSend()">发送请求</button></div><div class="box"><div class="status-item"><span>服务状态:</span><span class="status-value" id="service-status">检查中...</span></div><div class="status-item"><span>浏览器:</span><span class="status-value" id="browser-status">未知</span></div><div class="status-item"><span>总请求:</span><span class="status-value" id="total-requests">0</span></div><div class="status-item"><span>成功率:</span><span class="status-value" id="success-rate">0%</span></div><div class="performance-grid"><div class="perf-item"><div class="perf-value" id="ajax-success">0</div><div class="perf-label">AJAX成功</div></div><div class="perf-item"><div class="perf-value" id="ui-fallback">0</div><div class="perf-label">UI回退</div></div><div class="perf-item"><div class="perf-value" id="avg-response">0ms</div><div class="perf-label">平均响应</div></div><div class="perf-item"><div class="perf-value" id="uptime">0m</div><div class="perf-label">运行时间</div></div></div></div><div class="box" style="margin-top:auto"><button class="secondary" onclick="copyLogs()">📋 复制日志</button><button class="secondary" onclick="clearLogs()">🗑️ 清空日志</button><button class="secondary" onclick="refreshStatus()">🔄 刷新状态</button></div></div><div class="main"><div class="chat-window" id="chat"><div class="msg ai">🎉 ${CONFIG.PROJECT_NAME} v${CONFIG.PROJECT_VERSION} 已就绪<br><br>✨ 新功能:<br>• 🧪 增强的测试套件<br>• 📊 性能监控和统计<br>• 🔍 深度调试信息<br>• 🔄 智能重试机制<br><br>请选择聊天或测试模式开始使用!</div></div><div class="log-panel" id="log-container"></div></div><script>const API_KEY="${CONFIG.API_MASTER_KEY}";const ORIGIN=window.location.origin;let currentMode='chat';let statusInterval;function switchMode(mode){currentMode=mode;document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));event.target.classList.add('active');document.getElementById('chat-controls').style.display=mode==='chat'?'block':'none';document.getElementById('image-controls').style.display=mode==='image'?'block':'none';document.getElementById('test-controls').style.display=mode==='test'?'block':'none';const sendBtn=document.getElementById('btn-send');sendBtn.style.display=mode==='test'?'none':'block';}function appendLog(msg,type='info'){const div=document.createElement('div');div.className='log-entry';let color='#ccc';switch(type){case 'error':color='#F44336';break;case 'warn':color='#FF9800';break;case 'success':color='#4CAF50';break;case 'info':color='#2196F3';break;}div.innerHTML=\`<span class="log-time">\${new Date().toLocaleTimeString()}</span><span class="log-data" style="color:\${color}">\${msg}</span>\`;document.getElementById('log-container').appendChild(div);div.scrollIntoView()}function clearLogs(){document.getElementById('log-container').innerHTML=''}function copyLogs(){const logs=Array.from(document.querySelectorAll('.log-entry')).map(e=>e.innerText).join('\\n');navigator.clipboard.writeText(logs).then(()=>appendLog('日志已复制到剪贴板','success'))}function appendMsg(role,content){const div=document.createElement('div');div.className=\`msg \${role}\`;div.innerHTML=content;document.getElementById('chat').appendChild(div);div.scrollIntoView({behavior:"smooth"});return div}async function handleSend(){const btn=document.getElementById('btn-send');btn.disabled=true;clearLogs();appendLog('🚀 开始请求...');if(currentMode==='chat'){const prompt=document.getElementById('chat-prompt').value;const model=document.getElementById('chat-model').value;if(!prompt){btn.disabled=false;return}appendMsg('user',prompt);const aiMsg=appendMsg('ai','...');let fullText='';try{appendLog('📡 发送到浏览器...');const res=await fetch(ORIGIN+'/v1/chat/completions',{method:'POST',headers:{'Authorization':'Bearer '+API_KEY,'Content-Type':'application/json'},body:JSON.stringify({model,messages:[{role:'user',content:prompt}],stream:true})});appendLog('✅ 接收响应...');const reader=res.body.getReader();const decoder=new TextDecoder();while(true){const{done,value}=await reader.read();if(done)break;const chunk=decoder.decode(value);const lines=chunk.split('\\n');for(const line of lines){if(line.startsWith('data: ')){const dataStr=line.slice(6);if(dataStr==='[DONE]')break;try{const json=JSON.parse(dataStr);if(json.choices&&json.choices[0].delta.content){fullText+=json.choices[0].delta.content;aiMsg.innerText=fullText}}catch(e){}}}}appendLog('🎉 完成!','success');refreshStatus();}catch(e){aiMsg.innerText+='\\n[错误]: '+e.message;appendLog('❌ 错误: '+e.message,'error')}}else{const prompt=document.getElementById('image-prompt').value;const model=document.getElementById('image-model').value;if(!prompt){btn.disabled=false;return}appendMsg('user',prompt);const aiMsg=appendMsg('ai','生成中...');try{appendLog('🎨 发送图片请求...');const res=await fetch(ORIGIN+'/v1/images/generations',{method:'POST',headers:{'Authorization':'Bearer '+API_KEY,'Content-Type':'application/json'},body:JSON.stringify({model,prompt})});const data=await res.json();if(data.error)throw new Error(data.error.message);const imgUrl=data.data[0].url;aiMsg.innerHTML=\`<img src="\${imgUrl}" onclick="window.open(this.src)">\`;aiMsg.className='msg ai img';appendLog('✅ 图片生成成功!','success');refreshStatus();}catch(e){aiMsg.innerText='生成失败: '+e.message;appendLog('❌ 错误: '+e.message,'error')}}btn.disabled=false}async function runTest(){const testType=document.getElementById('test-type').value;appendLog(\`🧪 开始运行\${testType}测试...\`,'info');const testMsg=appendMsg('ai','<div id="test-results">正在运行测试...</div>');try{const res=await fetch(ORIGIN+'/v1/tests?type='+testType,{headers:{'Authorization':'Bearer '+API_KEY}});const data=await res.json();let html='<h3>测试结果</h3>';if(data.overall){html+=\`<p><strong>综合测试:</strong> \${data.overall.passed}/\${data.overall.passed+data.overall.failed} 通过</p>\`;if(data.configuration){html+=\`<div class="test-result test-pass">配置测试: \${data.configuration.passed} 通过, \${data.configuration.failed} 失败</div>\`;}}if(data.success===false){html+=\`<div class="test-result test-fail">测试失败: \${data.error}</div>\`;}else if(data.success){html+=\`<div class="test-result test-pass">测试通过</div>\`;}if(data.analysis){html+=\`<p><strong>会话分析:</strong> 找到 \${data.analysis.elementsFound.length} 个相关元素</p>\`;}if(data.length>0){html+='<h4>详细结果:</h4><ul>';data.forEach(result=>{const status=result.status==='success'?'pass':'fail';html+=\`<li class="test-result test-\${status}">\${result.name}: \${result.status}</li>\`;});html+='</ul>';}testMsg.querySelector('#test-results').innerHTML=html;appendLog('测试完成','success');refreshStatus();}catch(e){testMsg.innerHTML='<div class="test-result test-fail">测试执行失败: '+e.message+'</div>';appendLog('测试执行失败: '+e.message,'error');}}async function refreshStatus(){try{const res=await fetch(ORIGIN+'/v1/status',{headers:{'Authorization':'Bearer '+API_KEY}});const data=await res.json();document.getElementById('service-status').textContent='运行中';document.getElementById('browser-status').textContent=data.browser||'未知';document.getElementById('total-requests').textContent=data.performance.totalRequests||0;const successRate=data.performance.totalRequests>0?Math.round(data.performance.successfulRequests/data.performance.totalRequests*100):0;document.getElementById('success-rate').textContent=successRate+'%';document.getElementById('ajax-success').textContent=data.performance.ajaxSuccess||0;document.getElementById('ui-fallback').textContent=data.performance.uiFallback||0;document.getElementById('avg-response').textContent=(data.performance.averageResponseTime||0)+'ms';document.getElementById('uptime').textContent=Math.floor(data.uptime/60)+'m';}catch(e){appendLog('状态刷新失败: '+e.message,'error');}}function init(){refreshStatus();statusInterval=setInterval(refreshStatus,30000);}init();</script></body></html>`; | |
| res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); | |
| res.end(html); | |
| } | |
| function verifyAuth(req) { | |
| return req.headers['authorization'] === `Bearer ${CONFIG.API_MASTER_KEY}`; | |
| } | |
| function sendError(res, status, message) { | |
| res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); | |
| res.end(JSON.stringify({ error: { message, type: 'api_error' } })); | |
| } | |
| function handleCORS(req, res) { | |
| res.writeHead(204, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' }); | |
| res.end(); | |
| } | |
| const server = http.createServer(async (req, res) => { | |
| const url = new URL(req.url, `http://${req.headers.host}`); | |
| log('info', `📨 ${req.method} ${url.pathname}`); | |
| if (req.method === 'OPTIONS') return handleCORS(req, res); | |
| if (url.pathname === '/') return handleUI(req, res); | |
| if (url.pathname === '/v1/chat/completions') return handleChatCompletions(req, res); | |
| if (url.pathname === '/v1/images/generations') return handleImageGenerations(req, res); | |
| if (url.pathname === '/v1/models') return await handleModels(req, res); | |
| // 新增的测试和状态端点 | |
| if (url.pathname === '/v1/tests') return await handleTests(req, res); | |
| if (url.pathname === '/v1/status') return await handleStatus(req, res); | |
| if (url.pathname === '/v1/models/refresh') return await handleModelsRefresh(req, res); | |
| sendError(res, 404, 'Not Found'); | |
| }); | |
| process.on('uncaughtException', (error) => { | |
| console.error('💥 未捕获异常:', error.message); | |
| console.error(error.stack); | |
| }); | |
| process.on('unhandledRejection', (reason) => { | |
| console.error('💥 未处理Promise:', reason); | |
| }); | |
| server.listen(CONFIG.PORT, async () => { | |
| log('info', `🚀 ${CONFIG.PROJECT_NAME} v${CONFIG.PROJECT_VERSION} 运行在 http://localhost:${CONFIG.PORT}`); | |
| log('info', `📝 使用 Puppeteer 控制真实浏览器`); | |
| log('info', `✅ 聊天和绘图功能已就绪`); | |
| // 如果启用测试模式,运行启动测试 | |
| if (CONFIG.TESTING_MODE) { | |
| log('info', '🧪 测试模式已启用,开始启动时测试...'); | |
| try { | |
| // 运行基本配置测试 | |
| const configTest = testBasicConfiguration(); | |
| if (configTest.failed === 0) { | |
| log('info', '✅ 配置测试通过'); | |
| } else { | |
| log('warn', `⚠️ 配置测试发现 ${configTest.failed} 个问题`); | |
| } | |
| // 可选:运行其他测试(异步) | |
| if (CONFIG.DEBUG_LEVEL === 'verbose') { | |
| setTimeout(async () => { | |
| try { | |
| log('info', '🌐 运行网络连接性测试...'); | |
| const networkTest = await testNetworkConnectivity(); | |
| const successCount = networkTest.filter(r => r.status === 'success').length; | |
| log('info', `📊 网络测试完成: ${successCount}/${networkTest.length} 连接成功`); | |
| } catch (error) { | |
| log('error', `❌ 网络测试失败: ${error.message}`); | |
| } | |
| }, 2000); | |
| } | |
| } catch (error) { | |
| log('error', `❌ 启动测试失败: ${error.message}`); | |
| } | |
| } | |
| // 显示可用的API端点 | |
| log('info', '📚 可用的API端点:'); | |
| log('info', ' - GET / : Web界面'); | |
| log('info', ' - POST /v1/chat/completions : 聊天API'); | |
| log('info', ' - POST /v1/images/generations : 图片生成API'); | |
| log('info', ' - GET /v1/models : 模型列表API(带60分钟缓存)'); | |
| log('info', ' - GET /v1/models/refresh : 强制刷新模型列表API'); | |
| log('info', ' - GET /v1/status : 服务状态API(包含缓存状态)'); | |
| log('info', ' - GET /v1/tests : 测试API (?type=config|network|browser|session|ajax|comprehensive)'); | |
| // 启动性能统计定时显示 | |
| if (CONFIG.PERFORMANCE_MONITORING) { | |
| setInterval(() => { | |
| showPerformanceStats(); | |
| }, 300000); // 每5分钟显示一次统计 | |
| } | |
| }); | |
| process.on('SIGTERM', async () => { | |
| console.log('📴 关闭中...'); | |
| if (browser) await browser.close(); | |
| server.close(() => process.exit(0)); | |
| }); | |