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'; import { gotScraping } from 'got-scraping'; // 获取当前文件的目录路径 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{ 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(); // 添加初始数据,确保连接建立 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(() => { logger.warning(`请求超时,30秒内未收到响应`); try { if (!responseReceived) { stream.end(); } } catch (e) { logger.error(`关闭超时流时出错: ${e.message}`); } }, 30000); let responseReceived = false; try { const notionCookie = cookieManager.getCookieString(currentCookieData.userId); // 配置 got-scraping const options = { method: 'POST', headers: headers, body: JSON.stringify(notionRequestBody), isStream: true, cookieJar: await cookieManager.getGotCookieJar(notionCookie), timeout: { request: 30000 }, http2: true, // 使用HTTP/2,这对于模拟浏览器很重要 headerGeneratorOptions: { // 伪造浏览器头 browsers: ['chrome'], devices: ['desktop'], locales: ['en-US'], operatingSystems: ['windows'], }, }; // 如果配置了代理,则使用它 if (PROXY_URL) { options.proxyUrl = PROXY_URL; logger.info(`使用代理: ${PROXY_URL}`); } const responseStream = gotScraping(NOTION_API_URL, options); // 监听流事件 fetchNotionResponse(stream, responseStream, timeoutId); } catch (error) { logger.error(`创建 got-scraping 请求时出错: ${error.message}`); if (timeoutId) clearTimeout(timeoutId); stream.end(); } return stream; } // 使用got-scraping的流处理函数 async function fetchNotionResponse(chunkQueue, responseStream, timeoutId) { let responseReceived = false; try { responseStream.on('data', (chunk) => { responseReceived = true; chunkQueue.write(chunk); }); responseStream.on('end', () => { if (timeoutId) clearTimeout(timeoutId); if (!responseReceived) { logger.warning('流已结束但未收到任何数据'); } chunkQueue.end(); }); responseStream.on('error', (error) => { logger.error(`got-scraping 响应流错误: ${error.message}`); if (timeoutId) clearTimeout(timeoutId); chunkQueue.end(); }); } catch (error) { logger.error(`处理 got-scraping 响应时出错: ${error.message}`); if (timeoutId) clearTimeout(timeoutId); chunkQueue.end(); } } // 应用初始化 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(`正在初始化本地代理池...`); // await proxyPool.initialize(); // } INITIALIZED_SUCCESSFULLY = true; } // 导出函数 export { initialize, streamNotionResponse, buildNotionRequest, INITIALIZED_SUCCESSFULLY };