Spaces:
Sleeping
Sleeping
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 = 'error'; | |
// 启用进度条显示 | |
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 | |
}; |