isididiidid commited on
Commit
2dff040
·
verified ·
1 Parent(s): 5a813db

Create src/lightweight-client.js

Browse files
Files changed (1) hide show
  1. src/lightweight-client.js +773 -0
src/lightweight-client.js ADDED
@@ -0,0 +1,773 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fetch from 'node-fetch';
2
+ import { JSDOM } from 'jsdom';
3
+ import dotenv from 'dotenv';
4
+ import { randomUUID } from 'crypto';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+ import { PassThrough } from 'stream';
8
+ import chalk from 'chalk';
9
+ import fs from 'fs'; // 确保在文件顶部导入fs模块
10
+ import {
11
+ NotionTranscriptConfigValue,
12
+ NotionTranscriptContextValue, NotionTranscriptItem, NotionDebugOverrides,
13
+ NotionRequestBody, ChoiceDelta, Choice, ChatCompletionChunk, NotionTranscriptItemByuser
14
+ } from './models.js';
15
+ import { proxyPool } from './ProxyPool.js';
16
+ import { proxyServer } from './ProxyServer.js';
17
+ import { cookieManager } from './CookieManager.js';
18
+
19
+ // 获取当前文件的目录路径
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+
23
+ // 加载环境变量
24
+ dotenv.config({ path: join(dirname(__dirname), '.env') });
25
+
26
+ // 日志配置
27
+ const logger = {
28
+ info: (message) => console.log(chalk.blue(`[info] ${message}`)),
29
+ error: (message) => console.error(chalk.red(`[error] ${message}`)),
30
+ warning: (message) => console.warn(chalk.yellow(`[warn] ${message}`)),
31
+ success: (message) => console.log(chalk.green(`[success] ${message}`)),
32
+ };
33
+
34
+ // 配置
35
+ const NOTION_API_URL = "https://www.notion.so/api/v3/runInferenceTranscript";
36
+ // 这些变量将由cookieManager动态提供
37
+ let currentCookieData = null;
38
+ const USE_NATIVE_PROXY_POOL = process.env.USE_NATIVE_PROXY_POOL === 'true';
39
+ const ENABLE_PROXY_SERVER = process.env.ENABLE_PROXY_SERVER === 'true';
40
+ let proxy = null;
41
+
42
+ // 代理配置
43
+ const PROXY_URL = process.env.PROXY_URL || "";
44
+
45
+ // 标记是否成功初始化
46
+ let INITIALIZED_SUCCESSFULLY = false;
47
+
48
+ // 注册进程退出事件,确保代理服务器在程序退出时关闭
49
+ process.on('exit', () => {
50
+ try {
51
+ if (proxyServer) {
52
+ proxyServer.stop();
53
+ }
54
+ } catch (error) {
55
+ logger.error(`程序退出时关闭代理服务器出错: ${error.message}`);
56
+ }
57
+ });
58
+
59
+ // 捕获意外退出信号
60
+ ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach(signal => {
61
+ process.on(signal, () => {
62
+ logger.info(`收到${signal}信号,正在关闭代理服务器...`);
63
+ try {
64
+ if (proxyServer) {
65
+ proxyServer.stop();
66
+ }
67
+ } catch (error) {
68
+ logger.error(`关闭代理服务器出错: ${error.message}`);
69
+ }
70
+ process.exit(0);
71
+ });
72
+ });
73
+
74
+ // 构建Notion请求
75
+ function buildNotionRequest(requestData) {
76
+ // 确保我们有当前的cookie数据
77
+ if (!currentCookieData) {
78
+ currentCookieData = cookieManager.getNext();
79
+ if (!currentCookieData) {
80
+ throw new Error('没有可用的cookie');
81
+ }
82
+ }
83
+
84
+ // 当前时间
85
+ const now = new Date();
86
+ // 格式化为ISO字符串,确保包含毫秒和时区
87
+ const isoString = now.toISOString();
88
+
89
+ // 生成随机名称,类似于Python版本
90
+ const randomWords = ["Project", "Workspace", "Team", "Studio", "Lab", "Hub", "Zone", "Space"];
91
+ const userName = `User${Math.floor(Math.random() * 900) + 100}`; // 生成100-999之间的随机数
92
+ const spaceName = `${randomWords[Math.floor(Math.random() * randomWords.length)]} ${Math.floor(Math.random() * 99) + 1}`;
93
+
94
+ // 创建transcript数组
95
+ const transcript = [];
96
+
97
+ // 添加配置项
98
+ if(requestData.model === 'anthropic-sonnet-3.x-stable'){
99
+ transcript.push(new NotionTranscriptItem({
100
+ type: "config",
101
+ value: new NotionTranscriptConfigValue({
102
+ })
103
+ }));
104
+ }else{
105
+ transcript.push(new NotionTranscriptItem({
106
+ type: "config",
107
+ value: new NotionTranscriptConfigValue({
108
+ model: requestData.model
109
+ })
110
+ }));
111
+ }
112
+
113
+
114
+ // 添加上下文项
115
+ transcript.push(new NotionTranscriptItem({
116
+ type: "context",
117
+ value: new NotionTranscriptContextValue({
118
+ userId: currentCookieData.userId,
119
+ spaceId: currentCookieData.spaceId,
120
+ surface: "home_module",
121
+ timezone: "America/Los_Angeles",
122
+ userName: userName,
123
+ spaceName: spaceName,
124
+ spaceViewId: randomUUID(),
125
+ currentDatetime: isoString
126
+ })
127
+ }));
128
+
129
+ // 添加agent-integration项
130
+ transcript.push(new NotionTranscriptItem({
131
+ type: "agent-integration"
132
+ }));
133
+
134
+ // 添加消息
135
+ for (const message of requestData.messages) {
136
+ // 处理消息内容,确保格式一致
137
+ let content = message.content;
138
+
139
+ // 处理内容为数组的情况
140
+ if (Array.isArray(content)) {
141
+ let textContent = "";
142
+ for (const part of content) {
143
+ if (part && typeof part === 'object' && part.type === 'text') {
144
+ if (typeof part.text === 'string') {
145
+ textContent += part.text;
146
+ }
147
+ }
148
+ }
149
+ content = textContent || ""; // 使用提取的文本或空字符串
150
+ } else if (typeof content !== 'string') {
151
+ content = ""; // 如果不是字符串或数组,则默认为空字符串
152
+ }
153
+
154
+ if (message.role === "system") {
155
+ // 系统消息作为用户消息添加
156
+ transcript.push(new NotionTranscriptItemByuser({
157
+ type: "user",
158
+ value: [[content]],
159
+ userId: currentCookieData.userId,
160
+ createdAt: message.createdAt || isoString
161
+ }));
162
+ } else if (message.role === "user") {
163
+ // 用户消息
164
+ transcript.push(new NotionTranscriptItemByuser({
165
+ type: "user",
166
+ value: [[content]],
167
+ userId: currentCookieData.userId,
168
+ createdAt: message.createdAt || isoString
169
+ }));
170
+ } else if (message.role === "assistant") {
171
+ // 助手消息
172
+ transcript.push(new NotionTranscriptItem({
173
+ type: "markdown-chat",
174
+ value: content,
175
+ traceId: message.traceId || randomUUID(),
176
+ createdAt: message.createdAt || isoString
177
+ }));
178
+ }
179
+ }
180
+
181
+ // 创建请求体
182
+ return new NotionRequestBody({
183
+ spaceId: currentCookieData.spaceId,
184
+ transcript: transcript,
185
+ createThread: true,
186
+ traceId: randomUUID(),
187
+ debugOverrides: new NotionDebugOverrides({
188
+ cachedInferences: {},
189
+ annotationInferences: {},
190
+ emitInferences: false
191
+ }),
192
+ generateTitle: false,
193
+ saveAllThreadOperations: false
194
+ });
195
+ }
196
+
197
+ // 流式处理Notion响应
198
+ async function streamNotionResponse(notionRequestBody) {
199
+ // 确保我们有当前的cookie数据
200
+ if (!currentCookieData) {
201
+ currentCookieData = cookieManager.getNext();
202
+ if (!currentCookieData) {
203
+ throw new Error('没有可用的cookie');
204
+ }
205
+ }
206
+
207
+ // 创建流
208
+ const stream = new PassThrough();
209
+
210
+ // 添加初始数据,确保连接建立
211
+ stream.write(':\n\n'); // 发送一个空注释行,保持连接活跃
212
+
213
+ // 设置HTTP头模板
214
+ const headers = {
215
+ 'Content-Type': 'application/json',
216
+ 'accept': 'application/x-ndjson',
217
+ 'accept-language': 'en-US,en;q=0.9',
218
+ 'notion-audit-log-platform': 'web',
219
+ 'notion-client-version': '23.13.0.3686',
220
+ 'origin': 'https://www.notion.so',
221
+ 'referer': 'https://www.notion.so/chat',
222
+ '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',
223
+ 'x-notion-active-user-header': currentCookieData.userId,
224
+ 'x-notion-space-id': currentCookieData.spaceId
225
+ };
226
+
227
+ // 设置超时处理,确保流不会无限等待
228
+ const timeoutId = setTimeout(() => {
229
+ logger.warning(`请求超时,30秒内未收到响应`);
230
+ try {
231
+ // 发送结束消息
232
+ const endChunk = new ChatCompletionChunk({
233
+ choices: [
234
+ new Choice({
235
+ delta: new ChoiceDelta({ content: "请求超时,未收到Notion响应。" }),
236
+ finish_reason: "timeout"
237
+ })
238
+ ]
239
+ });
240
+ stream.write(`data: ${JSON.stringify(endChunk)}\n\n`);
241
+ stream.write('data: [DONE]\n\n');
242
+ stream.end();
243
+ } catch (error) {
244
+ logger.error(`发送超时消息时出错: ${error}`);
245
+ stream.end();
246
+ }
247
+ }, 30000); // 30秒超时
248
+
249
+ // 启动fetch处理
250
+ fetchNotionResponse(
251
+ stream,
252
+ notionRequestBody,
253
+ headers,
254
+ NOTION_API_URL,
255
+ currentCookieData.cookie,
256
+ timeoutId
257
+ ).catch((error) => {
258
+ logger.error(`流处理出错: ${error}`);
259
+ clearTimeout(timeoutId); // 清除超时计时器
260
+
261
+ try {
262
+ // 发送错误消息
263
+ const errorChunk = new ChatCompletionChunk({
264
+ choices: [
265
+ new Choice({
266
+ delta: new ChoiceDelta({ content: `处理请求时出错: ${error.message}` }),
267
+ finish_reason: "error"
268
+ })
269
+ ]
270
+ });
271
+ stream.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
272
+ stream.write('data: [DONE]\n\n');
273
+ } catch (e) {
274
+ logger.error(`发送错误消息时出错: ${e}`);
275
+ } finally {
276
+ stream.end();
277
+ }
278
+ });
279
+
280
+ return stream;
281
+ }
282
+
283
+ // 使用fetch调用Notion API并处理流式响应
284
+ async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notionApiUrl, notionCookie, timeoutId) {
285
+ let responseReceived = false;
286
+ let dom = null;
287
+
288
+ try {
289
+ // 创建JSDOM实例模拟浏览器环境
290
+ dom = new JSDOM("", {
291
+ url: "https://www.notion.so",
292
+ referrer: "https://www.notion.so/chat",
293
+ contentType: "text/html",
294
+ includeNodeLocations: true,
295
+ storageQuota: 10000000,
296
+ pretendToBeVisual: true,
297
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
298
+ });
299
+
300
+ // 设置全局对象
301
+ const { window } = dom;
302
+
303
+ // 使用更安全的方式设置全局对象
304
+ try {
305
+ if (!global.window) {
306
+ global.window = window;
307
+ }
308
+
309
+ if (!global.document) {
310
+ global.document = window.document;
311
+ }
312
+
313
+ // 安全地设置navigator
314
+ if (!global.navigator) {
315
+ try {
316
+ Object.defineProperty(global, 'navigator', {
317
+ value: window.navigator,
318
+ writable: true,
319
+ configurable: true
320
+ });
321
+ } catch (navError) {
322
+ logger.warning(`无法设置navigator: ${navError.message},继续执行`);
323
+ // 继续执行,不会中断流程
324
+ }
325
+ }
326
+ } catch (globalError) {
327
+ logger.warning(`设置全局对象时出错: ${globalError.message}`);
328
+ }
329
+
330
+ // 设置cookie
331
+ document.cookie = notionCookie;
332
+
333
+ // 创建fetch选项
334
+ const fetchOptions = {
335
+ method: 'POST',
336
+ headers: {
337
+ ...headers,
338
+ 'user-agent': window.navigator.userAgent,
339
+ 'Cookie': notionCookie
340
+ },
341
+ body: JSON.stringify(notionRequestBody),
342
+ };
343
+
344
+ // 添加代理配置(如果有)
345
+ if (USE_NATIVE_PROXY_POOL) {
346
+ proxy = proxyPool.getProxy();
347
+ if (proxy !== null)
348
+ {
349
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
350
+ fetchOptions.agent = new HttpsProxyAgent(proxy.full);
351
+ logger.info(`使用代理: ${proxy.full}`);
352
+ }
353
+ else{
354
+ logger.warning(`没有可用代理`);
355
+ }
356
+ } else if(PROXY_URL) {
357
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
358
+ fetchOptions.agent = new HttpsProxyAgent(PROXY_URL);
359
+ logger.info(`使用代理: ${PROXY_URL}`);
360
+ }
361
+ let response = null;
362
+ // 发送请求
363
+ if (ENABLE_PROXY_SERVER){
364
+ response = await fetch('http://127.0.0.1:10655/proxy', {
365
+ method: 'POST',
366
+ body: JSON.stringify({
367
+ method: 'POST',
368
+ url: notionApiUrl,
369
+ headers: fetchOptions.headers,
370
+ body: fetchOptions.body,
371
+ stream:true
372
+ }),
373
+ });
374
+ }else{
375
+ response = await fetch(notionApiUrl, fetchOptions);
376
+ }
377
+
378
+ // 检查是否收到401错误(未授权)
379
+ if (response.status === 401) {
380
+ logger.error(`收到401未授权错误,cookie可能已失效`);
381
+ // 标记当前cookie为无效
382
+ cookieManager.markAsInvalid(currentCookieData.userId);
383
+ // 尝试获取下一个cookie
384
+ currentCookieData = cookieManager.getNext();
385
+
386
+ if (!currentCookieData) {
387
+ throw new Error('所有cookie均已失效,无法继续请求');
388
+ }
389
+
390
+ // 使用新cookie重新构建请求体
391
+ const newRequestBody = buildNotionRequest({
392
+ model: notionRequestBody.transcript[0]?.value?.model || '',
393
+ messages: [] // 这里应该根据实际情况重构消息
394
+ });
395
+
396
+ // 使用新cookie重试请求
397
+ return fetchNotionResponse(
398
+ chunkQueue,
399
+ newRequestBody,
400
+ {
401
+ ...headers,
402
+ 'x-notion-active-user-header': currentCookieData.userId,
403
+ 'x-notion-space-id': currentCookieData.spaceId
404
+ },
405
+ notionApiUrl,
406
+ currentCookieData.cookie,
407
+ timeoutId
408
+ );
409
+ }
410
+
411
+ if (!response.ok) {
412
+ throw new Error(`HTTP error! status: ${response.status}`);
413
+ }
414
+
415
+ // 处理流式响应
416
+ if (!response.body) {
417
+ throw new Error("Response body is null");
418
+ }
419
+
420
+ // 创建流读取器
421
+ const reader = response.body;
422
+ let buffer = '';
423
+
424
+ // 处理数据块
425
+ reader.on('data', (chunk) => {
426
+ try {
427
+ // 标记已收到响应
428
+ if (!responseReceived) {
429
+ responseReceived = true;
430
+ logger.info(`已连接Notion API`);
431
+ clearTimeout(timeoutId); // 清除超时计时器
432
+ }
433
+
434
+ // 解码数据
435
+ const text = chunk.toString('utf8');
436
+ buffer += text;
437
+
438
+ // 按行分割并处理完整的JSON对象
439
+ const lines = buffer.split('\n');
440
+ buffer = lines.pop() || ''; // 保留最后一行(可能不完整)
441
+
442
+ for (const line of lines) {
443
+ if (!line.trim()) continue;
444
+
445
+ try {
446
+ const jsonData = JSON.parse(line);
447
+
448
+ // 提取内容
449
+ if (jsonData?.type === "markdown-chat" && typeof jsonData?.value === "string") {
450
+ const content = jsonData.value;
451
+ if (!content) continue;
452
+
453
+ // 创建OpenAI格式的块
454
+ const chunk = new ChatCompletionChunk({
455
+ choices: [
456
+ new Choice({
457
+ delta: new ChoiceDelta({ content }),
458
+ finish_reason: null
459
+ })
460
+ ]
461
+ });
462
+
463
+ // 添加到队列
464
+ const dataStr = `data: ${JSON.stringify(chunk)}\n\n`;
465
+ chunkQueue.write(dataStr);
466
+ } else if (jsonData?.recordMap) {
467
+ // 忽略recordMap响应
468
+ } else {
469
+ // 忽略其他类型响应
470
+ }
471
+ } catch (jsonError) {
472
+ logger.error(`解析JSON出错: ${jsonError}`);
473
+ }
474
+ }
475
+ } catch (error) {
476
+ logger.error(`处理数据块出错: ${error}`);
477
+ }
478
+ });
479
+
480
+ // 处理流结束
481
+ reader.on('end', () => {
482
+ try {
483
+ logger.info(`响应完成`);
484
+ if (cookieManager.getValidCount() > 1){
485
+ // 尝试切换到下一个cookie
486
+ currentCookieData = cookieManager.getNext();
487
+ logger.info(`切换到下一个cookie: ${currentCookieData.userId}`);
488
+ }
489
+
490
+ // 如果没有收到任何响应,发送一个提示消息
491
+ if (!responseReceived) {
492
+ logger.warning(`未从Notion收到内容响应,请更换ip重试`);
493
+ if (USE_NATIVE_PROXY_POOL) {
494
+ proxyPool.removeProxy(proxy.ip, proxy.port);
495
+ }
496
+
497
+ const noContentChunk = new ChatCompletionChunk({
498
+ choices: [
499
+ new Choice({
500
+ delta: new ChoiceDelta({ content: "未从Notion收到内容响应,请更换ip重试。" }),
501
+ finish_reason: "no_content"
502
+ })
503
+ ]
504
+ });
505
+ chunkQueue.write(`data: ${JSON.stringify(noContentChunk)}\n\n`);
506
+ }
507
+
508
+ // 创建结束块
509
+ const endChunk = new ChatCompletionChunk({
510
+ choices: [
511
+ new Choice({
512
+ delta: new ChoiceDelta({ content: null }),
513
+ finish_reason: "stop"
514
+ })
515
+ ]
516
+ });
517
+
518
+ // 添加到队列
519
+ chunkQueue.write(`data: ${JSON.stringify(endChunk)}\n\n`);
520
+ chunkQueue.write('data: [DONE]\n\n');
521
+
522
+ // 清除超时计时器(如果尚未清除)
523
+ if (timeoutId) clearTimeout(timeoutId);
524
+
525
+ // 清理全局对象
526
+ try {
527
+ if (global.window) delete global.window;
528
+ if (global.document) delete global.document;
529
+
530
+ // 安全地删除navigator
531
+ if (global.navigator) {
532
+ try {
533
+ delete global.navigator;
534
+ } catch (navError) {
535
+ // 如果无法删除,尝试将其设置为undefined
536
+ try {
537
+ Object.defineProperty(global, 'navigator', {
538
+ value: undefined,
539
+ writable: true,
540
+ configurable: true
541
+ });
542
+ } catch (defineError) {
543
+ logger.warning(`无法清理navigator: ${defineError.message}`);
544
+ }
545
+ }
546
+ }
547
+ } catch (cleanupError) {
548
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
549
+ }
550
+
551
+ // 结束流
552
+ chunkQueue.end();
553
+ } catch (error) {
554
+ logger.error(`Error in stream end handler: ${error}`);
555
+ if (timeoutId) clearTimeout(timeoutId);
556
+
557
+ // 清理全局对象
558
+ try {
559
+ if (global.window) delete global.window;
560
+ if (global.document) delete global.document;
561
+
562
+ // 安全地删除navigator
563
+ if (global.navigator) {
564
+ try {
565
+ delete global.navigator;
566
+ } catch (navError) {
567
+ // 如果无法删除,尝试将其设置为undefined
568
+ try {
569
+ Object.defineProperty(global, 'navigator', {
570
+ value: undefined,
571
+ writable: true,
572
+ configurable: true
573
+ });
574
+ } catch (defineError) {
575
+ logger.warning(`无法清理navigator: ${defineError.message}`);
576
+ }
577
+ }
578
+ }
579
+ } catch (cleanupError) {
580
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
581
+ }
582
+
583
+ chunkQueue.end();
584
+ }
585
+ });
586
+
587
+ // 处理错误
588
+ reader.on('error', (error) => {
589
+ logger.error(`Stream error: ${error}`);
590
+ if (timeoutId) clearTimeout(timeoutId);
591
+
592
+ // 清理全局对象
593
+ try {
594
+ if (global.window) delete global.window;
595
+ if (global.document) delete global.document;
596
+
597
+ // 安全地删除navigator
598
+ if (global.navigator) {
599
+ try {
600
+ delete global.navigator;
601
+ } catch (navError) {
602
+ // 如果无法删除,尝试将其设置为undefined
603
+ try {
604
+ Object.defineProperty(global, 'navigator', {
605
+ value: undefined,
606
+ writable: true,
607
+ configurable: true
608
+ });
609
+ } catch (defineError) {
610
+ logger.warning(`无法清理navigator: ${defineError.message}`);
611
+ }
612
+ }
613
+ }
614
+ } catch (cleanupError) {
615
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
616
+ }
617
+
618
+ try {
619
+ const errorChunk = new ChatCompletionChunk({
620
+ choices: [
621
+ new Choice({
622
+ delta: new ChoiceDelta({ content: `流读取错误: ${error.message}` }),
623
+ finish_reason: "error"
624
+ })
625
+ ]
626
+ });
627
+ chunkQueue.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
628
+ chunkQueue.write('data: [DONE]\n\n');
629
+ } catch (e) {
630
+ logger.error(`Error sending error message: ${e}`);
631
+ } finally {
632
+ chunkQueue.end();
633
+ }
634
+ });
635
+ } catch (error) {
636
+ logger.error(`Notion API请求失败: ${error}`);
637
+ // 清理全局对象
638
+ try {
639
+ if (global.window) delete global.window;
640
+ if (global.document) delete global.document;
641
+
642
+ // 安全地删除navigator
643
+ if (global.navigator) {
644
+ try {
645
+ delete global.navigator;
646
+ } catch (navError) {
647
+ // 如果无法删除,尝试将其设置为undefined
648
+ try {
649
+ Object.defineProperty(global, 'navigator', {
650
+ value: undefined,
651
+ writable: true,
652
+ configurable: true
653
+ });
654
+ } catch (defineError) {
655
+ logger.warning(`无法清理navigator: ${defineError.message}`);
656
+ }
657
+ }
658
+ }
659
+ } catch (cleanupError) {
660
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
661
+ }
662
+
663
+ if (timeoutId) clearTimeout(timeoutId);
664
+ if (chunkQueue) chunkQueue.end();
665
+
666
+ // 确保在错误情况下也触发流结束
667
+ try {
668
+ if (!responseReceived && chunkQueue) {
669
+ const errorChunk = new ChatCompletionChunk({
670
+ choices: [
671
+ new Choice({
672
+ delta: new ChoiceDelta({ content: `Notion API请求失败: ${error.message}` }),
673
+ finish_reason: "error"
674
+ })
675
+ ]
676
+ });
677
+ chunkQueue.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
678
+ chunkQueue.write('data: [DONE]\n\n');
679
+ }
680
+ } catch (e) {
681
+ logger.error(`发送错误消息时出错: ${e}`);
682
+ }
683
+
684
+ throw error; // 重新抛出错误以便上层捕获
685
+ }
686
+ }
687
+
688
+ // ====================================================================
689
+ // 【核心修改】应用初始化函数,以适应Hugging Face Secrets
690
+ // ====================================================================
691
+ async function initialize() {
692
+ logger.info(`初始化Notion配置...`);
693
+
694
+ // 如果启用了完整的代理功能,则启动代理服务器
695
+ if (ENABLE_PROXY_SERVER) {
696
+ try {
697
+ await proxyServer.start();
698
+ } catch (error) {
699
+ logger.error(`启动代理服务器失败: ${error.message}`);
700
+ }
701
+ }
702
+
703
+ let initResult = false;
704
+ const cookieFileContent = process.env.COOKIE_FILE_CONTENT;
705
+ const cookieFilePath = process.env.COOKIE_FILE_PATH || 'cookies.txt';
706
+
707
+ // 1. 优先从环境变量 COOKIE_FILE_CONTENT 加载 (Hugging Face部署模式)
708
+ if (cookieFileContent) {
709
+ logger.info(`检测到 COOKIE_FILE_CONTENT,正在从环境变量内容加载...`);
710
+ try {
711
+ // 将Secret内容写入容器内的一个临时文件
712
+ fs.writeFileSync(cookieFilePath, cookieFileContent, 'utf8');
713
+ // 让CookieManager从这个刚创建的文件加载
714
+ initResult = await cookieManager.loadFromFile(cookieFilePath);
715
+ } catch (e) {
716
+ logger.error(`从Secret创建或加载cookie失败: ${e.message}`);
717
+ initResult = false;
718
+ }
719
+ }
720
+ // 2. 如果没有,则回退到从环境变量 COOKIE_FILE 指定的路径加载 (本地开发模式1)
721
+ else if (process.env.COOKIE_FILE) {
722
+ logger.info(`检测到COOKIE_FILE配置: ${process.env.COOKIE_FILE}`);
723
+ initResult = await cookieManager.loadFromFile(process.env.COOKIE_FILE);
724
+ }
725
+
726
+ // 3. 如果以上都失败或未配置,则回退到从 NOTION_COOKIE 字符串加载 (本地开发模式2)
727
+ if (!initResult) {
728
+ const cookiesString = process.env.NOTION_COOKIE;
729
+ if (!cookiesString) {
730
+ logger.error(`错误: 未在Secrets或环境变量中设置 COOKIE_FILE_CONTENT, COOKIE_FILE 或 NOTION_COOKIE。应用无法启动。`);
731
+ INITIALIZED_SUCCESSFULLY = false;
732
+ return;
733
+ }
734
+ logger.info(`正在从环境变量 NOTION_COOKIE 初始化...`);
735
+ initResult = await cookieManager.initialize(cookiesString);
736
+ }
737
+
738
+ // 检查最终初始化结果
739
+ if (!initResult) {
740
+ logger.error(`初始化cookie管理器失败,应用无法正常工作。`);
741
+ INITIALIZED_SUCCESSFULLY = false;
742
+ return;
743
+ }
744
+
745
+ // 获取第一个可用的cookie数据用于日志记录
746
+ currentCookieData = cookieManager.getNext();
747
+ if (!currentCookieData) {
748
+ logger.error(`没有可用的有效cookie,应用无法正常工作。`);
749
+ INITIALIZED_SUCCESSFULLY = false;
750
+ return;
751
+ }
752
+
753
+ logger.success(`成功初始化cookie管理器,共有 ${cookieManager.getValidCount()} 个有效cookie`);
754
+ logger.info(`当前使用的cookie对应的用户ID: ${currentCookieData.userId}`);
755
+ logger.info(`当前使用的cookie对应的空间ID: ${currentCookieData.spaceId}`);
756
+
757
+ // 如果启用了完整的代理功能,则初始化代理池
758
+ if (USE_NATIVE_PROXY_POOL) {
759
+ logger.info(`正在初始化本地代理池...`);
760
+ await proxyPool.initialize();
761
+ }
762
+
763
+ INITIALIZED_SUCCESSFULLY = true;
764
+ }
765
+
766
+
767
+ // 导出函数
768
+ export {
769
+ initialize,
770
+ streamNotionResponse,
771
+ buildNotionRequest,
772
+ INITIALIZED_SUCCESSFULLY
773
+ };