Spaces:
Paused
Paused
| import fetch from 'node-fetch'; | |
| import { JSDOM } from 'jsdom'; | |
| import dotenv from 'dotenv'; | |
| import { randomUUID } from 'crypto'; | |
| import { fileURLToPath } from 'url'; | |
| import { dirname, join } from 'path'; | |
| import { PassThrough } from 'stream'; | |
| import chalk from 'chalk'; | |
| import { | |
| NotionTranscriptConfigValue, | |
| NotionTranscriptContextValue, NotionTranscriptItem, NotionDebugOverrides, | |
| NotionRequestBody, ChoiceDelta, Choice, ChatCompletionChunk, NotionTranscriptItemByuser | |
| } from './models.js'; | |
| import { proxyPool } from './ProxyPool.js'; | |
| import { proxyServer } from './ProxyServer.js'; | |
| import { cookieManager } from './CookieManager.js'; | |
| // 获取当前文件的目录路径 | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = dirname(__filename); | |
| // 加载环境变量 | |
| dotenv.config({ path: join(dirname(__dirname), '.env') }); | |
| // 日志配置 | |
| const logger = { | |
| info: (message) => console.log(chalk.blue(`[info] ${message}`)), | |
| error: (message) => console.error(chalk.red(`[error] ${message}`)), | |
| warning: (message) => console.warn(chalk.yellow(`[warn] ${message}`)), | |
| success: (message) => console.log(chalk.green(`[success] ${message}`)), | |
| }; | |
| // 配置 | |
| const NOTION_API_URL = "https://www.notion.so/api/v3/runInferenceTranscript"; | |
| // 这些变量将由cookieManager动态提供 | |
| let currentCookieData = null; | |
| const USE_NATIVE_PROXY_POOL = process.env.USE_NATIVE_PROXY_POOL === 'true'; | |
| const ENABLE_PROXY_SERVER = process.env.ENABLE_PROXY_SERVER === 'true'; | |
| let proxy = null; | |
| // 代理配置 | |
| const PROXY_URL = process.env.PROXY_URL || ""; | |
| // 标记是否成功初始化 | |
| let INITIALIZED_SUCCESSFULLY = false; | |
| // 注册进程退出事件,确保代理服务器在程序退出时关闭 | |
| process.on('exit', () => { | |
| try { | |
| if (proxyServer) { | |
| proxyServer.stop(); | |
| } | |
| } catch (error) { | |
| logger.error(`程序退出时关闭代理服务器出错: ${error.message}`); | |
| } | |
| }); | |
| // 捕获意外退出信号 | |
| ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach(signal => { | |
| process.on(signal, () => { | |
| logger.info(`收到${signal}信号,正在关闭代理服务器...`); | |
| try { | |
| if (proxyServer) { | |
| proxyServer.stop(); | |
| } | |
| } catch (error) { | |
| logger.error(`关闭代理服务器出错: ${error.message}`); | |
| } | |
| process.exit(0); | |
| }); | |
| }); | |
| // 构建Notion请求 | |
| function buildNotionRequest(requestData) { | |
| // 确保我们有当前的cookie数据 | |
| if (!currentCookieData) { | |
| currentCookieData = cookieManager.getNext(); | |
| if (!currentCookieData) { | |
| throw new Error('没有可用的cookie'); | |
| } | |
| } | |
| // 当前时间 | |
| const now = new Date(); | |
| // 格式化为ISO字符串,确保包含毫秒和时区 | |
| const isoString = now.toISOString(); | |
| // 生成随机名称,类似于Python版本 | |
| const randomWords = ["Project", "Workspace", "Team", "Studio", "Lab", "Hub", "Zone", "Space"]; | |
| const userName = `User${Math.floor(Math.random() * 900) + 100}`; // 生成100-999之间的随机数 | |
| const spaceName = `${randomWords[Math.floor(Math.random() * randomWords.length)]} ${Math.floor(Math.random() * 99) + 1}`; | |
| // 创建transcript数组 | |
| const transcript = []; | |
| // 添加配置项 | |
| if(requestData.model === 'anthropic-sonnet-3.x-stable'){ | |
| transcript.push(new NotionTranscriptItem({ | |
| type: "config", | |
| value: new NotionTranscriptConfigValue({ | |
| }) | |
| })); | |
| } else if(requestData.model === 'google-gemini-2.5-pro'){ | |
| transcript.push(new NotionTranscriptItem({ | |
| type: "config", | |
| value: new NotionTranscriptConfigValue({ | |
| model: 'vertex-gemini-2.5-pro' | |
| }) | |
| })); | |
| } else if (requestData.model === 'google-gemini-2.5-flash'){ | |
| transcript.push(new NotionTranscriptItem({ | |
| type: "config", | |
| value: new NotionTranscriptConfigValue({ | |
| model: 'vertex-gemini-2.5-flash' | |
| }) | |
| })); | |
| } | |
| else{ | |
| transcript.push(new NotionTranscriptItem({ | |
| type: "config", | |
| value: new NotionTranscriptConfigValue({ | |
| model: requestData.model | |
| }) | |
| })); | |
| } | |
| // 添加上下文项 | |
| transcript.push(new NotionTranscriptItem({ | |
| type: "context", | |
| value: new NotionTranscriptContextValue({ | |
| userId: currentCookieData.userId, | |
| spaceId: currentCookieData.spaceId, | |
| surface: "home_module", | |
| timezone: "America/Los_Angeles", | |
| userName: userName, | |
| spaceName: spaceName, | |
| spaceViewId: randomUUID(), | |
| currentDatetime: isoString | |
| }) | |
| })); | |
| // 添加agent-integration项 | |
| transcript.push(new NotionTranscriptItem({ | |
| type: "agent-integration" | |
| })); | |
| // 添加消息 | |
| for (const message of requestData.messages) { | |
| // 处理消息内容,确保格式一致 | |
| let content = message.content; | |
| // 处理内容为数组的情况 | |
| if (Array.isArray(content)) { | |
| let textContent = ""; | |
| for (const part of content) { | |
| if (part && typeof part === 'object' && part.type === 'text') { | |
| if (typeof part.text === 'string') { | |
| textContent += part.text; | |
| } | |
| } | |
| } | |
| content = textContent || ""; // 使用提取的文本或空字符串 | |
| } else if (typeof content !== 'string') { | |
| content = ""; // 如果不是字符串或数组,则默认为空字符串 | |
| } | |
| if (message.role === "system") { | |
| // 系统消息作为用户消息添加 | |
| transcript.push(new NotionTranscriptItemByuser({ | |
| type: "user", | |
| value: [[content]], | |
| userId: currentCookieData.userId, | |
| createdAt: message.createdAt || isoString | |
| })); | |
| } else if (message.role === "user") { | |
| // 用户消息 | |
| transcript.push(new NotionTranscriptItemByuser({ | |
| type: "user", | |
| value: [[content]], | |
| userId: currentCookieData.userId, | |
| createdAt: message.createdAt || isoString | |
| })); | |
| } else if (message.role === "assistant") { | |
| // 助手消息 | |
| transcript.push(new NotionTranscriptItem({ | |
| type: "markdown-chat", | |
| value: content, | |
| traceId: message.traceId || randomUUID(), | |
| createdAt: message.createdAt || isoString | |
| })); | |
| } | |
| } | |
| // 创建请求体 | |
| return new NotionRequestBody({ | |
| spaceId: currentCookieData.spaceId, | |
| transcript: transcript, | |
| createThread: true, | |
| traceId: randomUUID(), | |
| debugOverrides: new NotionDebugOverrides({ | |
| cachedInferences: {}, | |
| annotationInferences: {}, | |
| emitInferences: false | |
| }), | |
| generateTitle: false, | |
| saveAllThreadOperations: false | |
| }); | |
| } | |
| // 流式处理Notion响应 | |
| async function streamNotionResponse(notionRequestBody) { | |
| // 确保我们有当前的cookie数据 | |
| if (!currentCookieData) { | |
| currentCookieData = cookieManager.getNext(); | |
| if (!currentCookieData) { | |
| throw new Error('没有可用的cookie'); | |
| } | |
| } | |
| // 创建流 | |
| const stream = new PassThrough(); | |
| // 标记流状态 | |
| let streamClosed = false; | |
| // 重写stream.end方法,确保安全关闭 | |
| const originalEnd = stream.end; | |
| stream.end = function(...args) { | |
| if (streamClosed) return; // 避免重复关闭 | |
| streamClosed = true; | |
| return originalEnd.apply(this, args); | |
| }; | |
| // 添加初始数据,确保连接建立 | |
| stream.write(':\n\n'); // 发送一个空注释行,保持连接活跃 | |
| // 设置HTTP头模板 | |
| const headers = { | |
| 'Content-Type': 'application/json', | |
| 'accept': 'application/x-ndjson', | |
| 'accept-language': 'en-US,en;q=0.9', | |
| 'notion-audit-log-platform': 'web', | |
| 'notion-client-version': '23.13.0.3686', | |
| 'origin': 'https://www.notion.so', | |
| 'referer': 'https://www.notion.so/chat', | |
| 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36', | |
| 'x-notion-active-user-header': currentCookieData.userId, | |
| 'x-notion-space-id': currentCookieData.spaceId | |
| }; | |
| // 设置超时处理,确保流不会无限等待 | |
| const timeoutId = setTimeout(() => { | |
| if (streamClosed) return; | |
| logger.warning(`请求超时,30秒内未收到响应`); | |
| try { | |
| // 发送结束消息 | |
| const endChunk = new ChatCompletionChunk({ | |
| choices: [ | |
| new Choice({ | |
| delta: new ChoiceDelta({ content: "请求超时,未收到Notion响应。" }), | |
| finish_reason: "timeout" | |
| }) | |
| ] | |
| }); | |
| stream.write(`data: ${JSON.stringify(endChunk)}\n\n`); | |
| stream.write('data: [DONE]\n\n'); | |
| stream.end(); | |
| } catch (error) { | |
| logger.error(`发送超时消息时出错: ${error}`); | |
| if (!streamClosed) stream.end(); | |
| } | |
| }, 30000); // 30秒超时 | |
| // 启动fetch处理 | |
| fetchNotionResponse( | |
| stream, | |
| notionRequestBody, | |
| headers, | |
| NOTION_API_URL, | |
| currentCookieData.cookie, | |
| timeoutId | |
| ).catch((error) => { | |
| if (streamClosed) return; | |
| logger.error(`流处理出错: ${error}`); | |
| clearTimeout(timeoutId); // 清除超时计时器 | |
| try { | |
| // 发送错误消息 | |
| const errorChunk = new ChatCompletionChunk({ | |
| choices: [ | |
| new Choice({ | |
| delta: new ChoiceDelta({ content: `处理请求时出错: ${error.message}` }), | |
| finish_reason: "error" | |
| }) | |
| ] | |
| }); | |
| stream.write(`data: ${JSON.stringify(errorChunk)}\n\n`); | |
| stream.write('data: [DONE]\n\n'); | |
| } catch (e) { | |
| logger.error(`发送错误消息时出错: ${e}`); | |
| } finally { | |
| if (!streamClosed) stream.end(); | |
| } | |
| }); | |
| return stream; | |
| } | |
| // 使用fetch调用Notion API并处理流式响应 | |
| async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notionApiUrl, notionCookie, timeoutId) { | |
| let responseReceived = false; | |
| let dom = null; | |
| // 检查流是否已关闭的辅助函数 | |
| const isStreamClosed = () => { | |
| return chunkQueue.destroyed || (typeof chunkQueue.closed === 'boolean' && chunkQueue.closed); | |
| }; | |
| // 安全写入函数,确保只向开启的流写入数据 | |
| const safeWrite = (data) => { | |
| if (!isStreamClosed()) { | |
| try { | |
| return chunkQueue.write(data); | |
| } catch (error) { | |
| logger.error(`流写入错误: ${error.message}`); | |
| return false; | |
| } | |
| } | |
| return false; | |
| }; | |
| try { | |
| // 创建JSDOM实例模拟浏览器环境 | |
| dom = new JSDOM("", { | |
| url: "https://www.notion.so", | |
| referrer: "https://www.notion.so/chat", | |
| contentType: "text/html", | |
| includeNodeLocations: true, | |
| storageQuota: 10000000, | |
| pretendToBeVisual: true, | |
| userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36" | |
| }); | |
| // 设置全局对象 | |
| const { window } = dom; | |
| // 使用更安全的方式设置全局对象 | |
| try { | |
| if (!global.window) { | |
| global.window = window; | |
| } | |
| if (!global.document) { | |
| global.document = window.document; | |
| } | |
| // 安全地设置navigator | |
| if (!global.navigator) { | |
| try { | |
| Object.defineProperty(global, 'navigator', { | |
| value: window.navigator, | |
| writable: true, | |
| configurable: true | |
| }); | |
| } catch (navError) { | |
| logger.warning(`无法设置navigator: ${navError.message},继续执行`); | |
| // 继续执行,不会中断流程 | |
| } | |
| } | |
| } catch (globalError) { | |
| logger.warning(`设置全局对象时出错: ${globalError.message}`); | |
| } | |
| // 设置cookie | |
| document.cookie = notionCookie; | |
| // 创建fetch选项 | |
| const fetchOptions = { | |
| method: 'POST', | |
| headers: { | |
| ...headers, | |
| 'user-agent': window.navigator.userAgent, | |
| 'Cookie': notionCookie | |
| }, | |
| body: JSON.stringify(notionRequestBody), | |
| }; | |
| // 添加代理配置(如果有) | |
| if (USE_NATIVE_PROXY_POOL && ENABLE_PROXY_SERVER && !PROXY_URL) { | |
| proxy = proxyPool.getProxy(); | |
| if (proxy !== null) | |
| { | |
| logger.info(`使用代理: ${proxy.full}`); | |
| } | |
| else{ | |
| logger.warning(`没有可用代理`); | |
| } | |
| } else if(USE_NATIVE_PROXY_POOL&&!PROXY_URL&&!ENABLE_PROXY_SERVER) { | |
| const { HttpsProxyAgent } = await import('https-proxy-agent'); | |
| proxy = proxyPool.getProxy(); | |
| fetchOptions.agent = new HttpsProxyAgent(proxy.full); | |
| logger.info(`使用代理: ${proxy.full}`); | |
| }else if(PROXY_URL){ | |
| const { HttpsProxyAgent } = await import('https-proxy-agent'); | |
| fetchOptions.agent = new HttpsProxyAgent(PROXY_URL); | |
| logger.info(`使用代理: ${PROXY_URL}`); | |
| } | |
| let response = null; | |
| // 发送请求 | |
| if (ENABLE_PROXY_SERVER && USE_NATIVE_PROXY_POOL){ | |
| response = await fetch('http://127.0.0.1:10655/proxy', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| method: 'POST', | |
| url: notionApiUrl, | |
| headers: fetchOptions.headers, | |
| body: fetchOptions.body, | |
| stream:true, | |
| proxy:proxy.full | |
| }), | |
| }); | |
| } | |
| else if (ENABLE_PROXY_SERVER && !USE_NATIVE_PROXY_POOL && PROXY_URL){ | |
| response = await fetch('http://127.0.0.1:10655/proxy', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| method: 'POST', | |
| url: notionApiUrl, | |
| headers: fetchOptions.headers, | |
| body: fetchOptions.body, | |
| proxy: PROXY_URL, | |
| stream:true, | |
| }), | |
| }); | |
| } | |
| else if(ENABLE_PROXY_SERVER && !USE_NATIVE_PROXY_POOL){ | |
| response = await fetch('http://127.0.0.1:10655/proxy', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| method: 'POST', | |
| url: notionApiUrl, | |
| headers: fetchOptions.headers, | |
| body: fetchOptions.body, | |
| stream:true, | |
| }), | |
| }); | |
| } | |
| else{ | |
| response = await fetch(notionApiUrl, fetchOptions); | |
| } | |
| // 检查是否收到401错误(未授权) | |
| if (response.status === 401) { | |
| logger.error(`收到401未授权错误,cookie可能已失效`); | |
| // 标记当前cookie为无效 | |
| cookieManager.markAsInvalid(currentCookieData.userId); | |
| // 尝试获取下一个cookie | |
| currentCookieData = cookieManager.getNext(); | |
| if (!currentCookieData) { | |
| throw new Error('所有cookie均已失效,无法继续请求'); | |
| } | |
| // 使用新cookie重新构建请求体 | |
| const newRequestBody = buildNotionRequest({ | |
| model: notionRequestBody.transcript[0]?.value?.model || '', | |
| messages: [] // 这里应该根据实际情况重构消息 | |
| }); | |
| // 使用新cookie重试请求 | |
| return fetchNotionResponse( | |
| chunkQueue, | |
| newRequestBody, | |
| { | |
| ...headers, | |
| 'x-notion-active-user-header': currentCookieData.userId, | |
| 'x-notion-space-id': currentCookieData.spaceId | |
| }, | |
| notionApiUrl, | |
| currentCookieData.cookie, | |
| timeoutId | |
| ); | |
| } | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| // 处理流式响应 | |
| if (!response.body) { | |
| throw new Error("Response body is null"); | |
| } | |
| // 创建流读取器 | |
| const reader = response.body; | |
| let buffer = ''; | |
| // 处理数据块 | |
| reader.on('data', (chunk) => { | |
| // 检查流是否已关闭 | |
| if (isStreamClosed()) { | |
| try { | |
| reader.destroy(); | |
| } catch (error) { | |
| logger.error(`销毁reader时出错: ${error.message}`); | |
| } | |
| return; | |
| } | |
| try { | |
| // 标记已收到响应 | |
| if (!responseReceived) { | |
| responseReceived = true; | |
| logger.info(`已连接Notion API`); | |
| clearTimeout(timeoutId); // 清除超时计时器 | |
| } | |
| // 解码数据 | |
| const text = chunk.toString('utf8'); | |
| buffer += text; | |
| // 按行分割并处理完整的JSON对象 | |
| const lines = buffer.split('\n'); | |
| buffer = lines.pop() || ''; // 保留最后一行(可能不完整) | |
| for (const line of lines) { | |
| if (!line.trim()) continue; | |
| try { | |
| const jsonData = JSON.parse(line); | |
| // 提取内容 | |
| if (jsonData?.type === "markdown-chat" && typeof jsonData?.value === "string") { | |
| const content = jsonData.value; | |
| if (!content) continue; | |
| // 创建OpenAI格式的块 | |
| const chunk = new ChatCompletionChunk({ | |
| choices: [ | |
| new Choice({ | |
| delta: new ChoiceDelta({ content }), | |
| finish_reason: null | |
| }) | |
| ] | |
| }); | |
| // 添加到队列 | |
| const dataStr = `data: ${JSON.stringify(chunk)}\n\n`; | |
| if (!safeWrite(dataStr)) { | |
| // 如果写入失败,结束处理 | |
| try { | |
| reader.destroy(); | |
| } catch (error) { | |
| logger.error(`写入失败后销毁reader时出错: ${error.message}`); | |
| } | |
| return; | |
| } | |
| } else if (jsonData?.recordMap) { | |
| // 忽略recordMap响应 | |
| } else { | |
| // 忽略其他类型响应 | |
| } | |
| } catch (jsonError) { | |
| logger.error(`解析JSON出错: ${jsonError}`); | |
| } | |
| } | |
| } catch (error) { | |
| logger.error(`处理数据块出错: ${error}`); | |
| } | |
| }); | |
| // 处理流结束 | |
| reader.on('end', () => { | |
| try { | |
| logger.info(`响应完成`); | |
| if (cookieManager.getValidCount() > 1){ | |
| // 尝试切换到下一个cookie | |
| currentCookieData = cookieManager.getNext(); | |
| logger.info(`切换到下一个cookie: ${currentCookieData.userId}`); | |
| } | |
| // 如果没有收到任何响应,发送一个提示消息 | |
| if (!responseReceived) { | |
| if (!ENABLE_PROXY_SERVER){ | |
| logger.warning(`未从Notion收到内容响应,请尝试启用tls代理服务`) | |
| }else if (USE_NATIVE_PROXY_POOL){ | |
| logger.warning(`未从Notion收到内容响应,请重roll,或者切换cookie`) | |
| }else{ | |
| logger.warning(`未从Notion收到内容响应,请更换ip重试`); | |
| } | |
| if (USE_NATIVE_PROXY_POOL) { | |
| proxyPool.removeProxy(proxy.ip, proxy.port); | |
| } | |
| const noContentChunk = new ChatCompletionChunk({ | |
| choices: [ | |
| new Choice({ | |
| delta: new ChoiceDelta({ content: "未从Notion收到内容响应,请更换ip重试。" }), | |
| finish_reason: "no_content" | |
| }) | |
| ] | |
| }); | |
| safeWrite(`data: ${JSON.stringify(noContentChunk)}\n\n`); | |
| } | |
| // 创建结束块 | |
| const endChunk = new ChatCompletionChunk({ | |
| choices: [ | |
| new Choice({ | |
| delta: new ChoiceDelta({ content: null }), | |
| finish_reason: "stop" | |
| }) | |
| ] | |
| }); | |
| // 添加到队列 | |
| safeWrite(`data: ${JSON.stringify(endChunk)}\n\n`); | |
| safeWrite('data: [DONE]\n\n'); | |
| // 清除超时计时器(如果尚未清除) | |
| if (timeoutId) clearTimeout(timeoutId); | |
| // 清理全局对象 | |
| try { | |
| if (global.window) delete global.window; | |
| if (global.document) delete global.document; | |
| // 安全地删除navigator | |
| if (global.navigator) { | |
| try { | |
| delete global.navigator; | |
| } catch (navError) { | |
| // 如果无法删除,尝试将其设置为undefined | |
| try { | |
| Object.defineProperty(global, 'navigator', { | |
| value: undefined, | |
| writable: true, | |
| configurable: true | |
| }); | |
| } catch (defineError) { | |
| logger.warning(`无法清理navigator: ${defineError.message}`); | |
| } | |
| } | |
| } | |
| } catch (cleanupError) { | |
| logger.warning(`清理全局对象时出错: ${cleanupError.message}`); | |
| } | |
| // 结束流 | |
| if (!isStreamClosed()) { | |
| chunkQueue.end(); | |
| } | |
| } catch (error) { | |
| logger.error(`Error in stream end handler: ${error}`); | |
| if (timeoutId) clearTimeout(timeoutId); | |
| // 清理全局对象 | |
| try { | |
| if (global.window) delete global.window; | |
| if (global.document) delete global.document; | |
| // 安全地删除navigator | |
| if (global.navigator) { | |
| try { | |
| delete global.navigator; | |
| } catch (navError) { | |
| // 如果无法删除,尝试将其设置为undefined | |
| try { | |
| Object.defineProperty(global, 'navigator', { | |
| value: undefined, | |
| writable: true, | |
| configurable: true | |
| }); | |
| } catch (defineError) { | |
| logger.warning(`无法清理navigator: ${defineError.message}`); | |
| } | |
| } | |
| } | |
| } catch (cleanupError) { | |
| logger.warning(`清理全局对象时出错: ${cleanupError.message}`); | |
| } | |
| if (!isStreamClosed()) { | |
| chunkQueue.end(); | |
| } | |
| } | |
| }); | |
| // 处理错误 | |
| reader.on('error', (error) => { | |
| logger.error(`Stream error: ${error}`); | |
| if (timeoutId) clearTimeout(timeoutId); | |
| // 清理全局对象 | |
| try { | |
| if (global.window) delete global.window; | |
| if (global.document) delete global.document; | |
| // 安全地删除navigator | |
| if (global.navigator) { | |
| try { | |
| delete global.navigator; | |
| } catch (navError) { | |
| // 如果无法删除,尝试将其设置为undefined | |
| try { | |
| Object.defineProperty(global, 'navigator', { | |
| value: undefined, | |
| writable: true, | |
| configurable: true | |
| }); | |
| } catch (defineError) { | |
| logger.warning(`无法清理navigator: ${defineError.message}`); | |
| } | |
| } | |
| } | |
| } catch (cleanupError) { | |
| logger.warning(`清理全局对象时出错: ${cleanupError.message}`); | |
| } | |
| try { | |
| const errorChunk = new ChatCompletionChunk({ | |
| choices: [ | |
| new Choice({ | |
| delta: new ChoiceDelta({ content: `流读取错误: ${error.message}` }), | |
| finish_reason: "error" | |
| }) | |
| ] | |
| }); | |
| safeWrite(`data: ${JSON.stringify(errorChunk)}\n\n`); | |
| safeWrite('data: [DONE]\n\n'); | |
| } catch (e) { | |
| logger.error(`Error sending error message: ${e}`); | |
| } finally { | |
| if (!isStreamClosed()) { | |
| chunkQueue.end(); | |
| } | |
| } | |
| }); | |
| } catch (error) { | |
| logger.error(`Notion API请求失败: ${error}`); | |
| // 清理全局对象 | |
| try { | |
| if (global.window) delete global.window; | |
| if (global.document) delete global.document; | |
| // 安全地删除navigator | |
| if (global.navigator) { | |
| try { | |
| delete global.navigator; | |
| } catch (navError) { | |
| // 如果无法删除,尝试将其设置为undefined | |
| try { | |
| Object.defineProperty(global, 'navigator', { | |
| value: undefined, | |
| writable: true, | |
| configurable: true | |
| }); | |
| } catch (defineError) { | |
| logger.warning(`无法清理navigator: ${defineError.message}`); | |
| } | |
| } | |
| } | |
| } catch (cleanupError) { | |
| logger.warning(`清理全局对象时出错: ${cleanupError.message}`); | |
| } | |
| if (timeoutId) clearTimeout(timeoutId); | |
| // 确保在错误情况下也触发流结束 | |
| try { | |
| if (!responseReceived && !isStreamClosed()) { | |
| const errorChunk = new ChatCompletionChunk({ | |
| choices: [ | |
| new Choice({ | |
| delta: new ChoiceDelta({ content: `Notion API请求失败: ${error.message}` }), | |
| finish_reason: "error" | |
| }) | |
| ] | |
| }); | |
| safeWrite(`data: ${JSON.stringify(errorChunk)}\n\n`); | |
| safeWrite('data: [DONE]\n\n'); | |
| } | |
| } catch (e) { | |
| logger.error(`发送错误消息时出错: ${e}`); | |
| } | |
| if (!isStreamClosed()) { | |
| chunkQueue.end(); | |
| } | |
| throw error; // 重新抛出错误以便上层捕获 | |
| } | |
| } | |
| // 应用初始化 | |
| async function initialize() { | |
| logger.info(`初始化Notion配置...`); | |
| // 启动代理服务器 | |
| try { | |
| await proxyServer.start(); | |
| } catch (error) { | |
| logger.error(`启动代理服务器失败: ${error.message}`); | |
| } | |
| // 初始化cookie管理器 | |
| let initResult = false; | |
| // 检查是否配置了cookie文件 | |
| const cookieFilePath = process.env.COOKIE_FILE; | |
| if (cookieFilePath) { | |
| logger.info(`检测到COOKIE_FILE配置: ${cookieFilePath}`); | |
| initResult = await cookieManager.loadFromFile(cookieFilePath); | |
| if (!initResult) { | |
| logger.error(`从文件加载cookie失败,尝试使用环境变量中的NOTION_COOKIE`); | |
| } | |
| } | |
| // 如果文件加载失败或未配置文件,尝试从环境变量加载 | |
| if (!initResult) { | |
| const cookiesString = process.env.NOTION_COOKIE; | |
| if (!cookiesString) { | |
| logger.error(`错误: 未设置NOTION_COOKIE环境变量或COOKIE_FILE路径,应用无法正常工作`); | |
| logger.error(`请在.env文件中设置有效的NOTION_COOKIE值或COOKIE_FILE路径`); | |
| INITIALIZED_SUCCESSFULLY = false; | |
| return; | |
| } | |
| logger.info(`正在从环境变量初始化cookie管理器...`); | |
| initResult = await cookieManager.initialize(cookiesString); | |
| if (!initResult) { | |
| logger.error(`初始化cookie管理器失败,应用无法正常工作`); | |
| INITIALIZED_SUCCESSFULLY = false; | |
| return; | |
| } | |
| } | |
| // 获取第一个可用的cookie数据 | |
| currentCookieData = cookieManager.getNext(); | |
| if (!currentCookieData) { | |
| logger.error(`没有可用的cookie,应用无法正常工作`); | |
| INITIALIZED_SUCCESSFULLY = false; | |
| return; | |
| } | |
| logger.success(`成功初始化cookie管理器,共有 ${cookieManager.getValidCount()} 个有效cookie`); | |
| logger.info(`当前使用的cookie对应的用户ID: ${currentCookieData.userId}`); | |
| logger.info(`当前使用的cookie对应的空间ID: ${currentCookieData.spaceId}`); | |
| if (process.env.USE_NATIVE_PROXY_POOL === 'true') { | |
| logger.info(`正在初始化本地代理池...`); | |
| // 设置代理池的日志级别为warn,减少详细日志输出 | |
| proxyPool.logLevel = 'info'; | |
| // 启用进度条显示 | |
| proxyPool.showProgressBar = true; | |
| if (['us', 'uk', 'jp', 'de', 'fr', 'ca'].includes(process.env.PROXY_COUNTRY)) { | |
| proxyPool.setCountry(process.env.PROXY_COUNTRY); | |
| } else { | |
| logger.warning(`未设置正确PROXY_COUNTRY,使用默认代理国家: us`); | |
| proxyPool.setCountry('us'); | |
| } | |
| await proxyPool.initialize(); | |
| await new Promise(resolve => setTimeout(resolve, 1000)); | |
| logger.success(`代理池初始化完成,当前代理国家: ${proxyPool.proxyCountry}`); | |
| } | |
| INITIALIZED_SUCCESSFULLY = true; | |
| } | |
| // 导出函数 | |
| export { | |
| initialize, | |
| streamNotionResponse, | |
| buildNotionRequest, | |
| INITIALIZED_SUCCESSFULLY | |
| }; |