clash-linux commited on
Commit
c92fcc9
·
verified ·
1 Parent(s): c918646

Upload 16 files

Browse files
proxy_server.log CHANGED
@@ -86,3 +86,11 @@
86
  2025/06/11 17:54:09.357768 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
87
  2025/06/11 17:54:09.357768 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
88
  2025/06/11 17:54:09.357768 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
 
 
 
 
 
 
 
 
 
86
  2025/06/11 17:54:09.357768 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
87
  2025/06/11 17:54:09.357768 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
88
  2025/06/11 17:54:09.357768 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
89
+ 2025/06/11 18:05:05.408576 chrome_tls_proxy.go:706: Chrome TLS指纹代理服务器运行在 http://localhost:10655/proxy
90
+ 2025/06/11 18:05:05.413163 chrome_tls_proxy.go:707: 使用方法:向/proxy发送POST请求,请求体包含目标URL、方法、请求头和请求体
91
+ 2025/06/11 18:05:05.413163 chrome_tls_proxy.go:708: 支持流式响应,需要在请求体中添加 'stream': true
92
+ 2025/06/11 18:05:05.413163 chrome_tls_proxy.go:709: 已禁用速率限制:允许无限制请求
93
+ 2025/06/11 18:05:05.413163 chrome_tls_proxy.go:710: 已禁用并发限制:允许同一IP发起多个并发请求
94
+ 2025/06/11 18:05:05.413163 chrome_tls_proxy.go:711: 已强制使用IPv4连接,并使用8.8.8.8作为DNS服务器
95
+ 2025/06/11 18:05:05.413163 chrome_tls_proxy.go:712: 使用Chrome浏览器的TLS指纹进行连接
96
+ 2025/06/11 18:05:05.414212 chrome_tls_proxy.go:716: 服务器启动失败: listen tcp :10655: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
src/ProxyPool.js CHANGED
@@ -1,5 +1,14 @@
1
  import axios from 'axios';
2
- import { URL } from 'url';
 
 
 
 
 
 
 
 
 
3
 
4
  /**
5
  * 代理池类,用于管理和提供HTTP代理
@@ -21,25 +30,33 @@ class ProxyPool {
21
  * @param {number} options.retryDelay - 重试延迟(毫秒),默认1000
22
  * @param {boolean} options.useCache - 是否使用缓存,默认true
23
  * @param {number} options.cacheExpiry - 缓存过期时间(毫秒),默认3600000 (1小时)
24
- * @param {string} options.staticProxy - 静态代理地址,如果提供,将优先使用此代理
25
  */
26
  constructor(options = {}) {
27
- // 新增静态代理配置 - 优先使用传入的选项,否则从环境变量读取
28
- this.staticProxy = options.staticProxy || process.env.STATIC_PROXY || null;
29
 
30
  // 配置参数
31
- this.targetCount = options.targetCount || 20;
32
- this.batchSize = options.batchSize || 20;
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  this.testTimeout = options.testTimeout || 5000;
34
  this.requestTimeout = options.requestTimeout || 10000;
35
  this.targetUrl = options.targetUrl || 'https://www.notion.so';
36
  this.concurrentRequests = options.concurrentRequests || 10;
37
- this.minThreshold = options.minThreshold || 5;
38
  this.checkInterval = options.checkInterval || 30000; // 默认30秒检查一次
39
  this.proxyProtocol = options.proxyProtocol || 'http';
40
- this.maxRefillAttempts = options.maxRefillAttempts || 20; // 减少最大尝试次数
41
  this.retryDelay = options.retryDelay || 1000; // 减少重试延迟
42
- this.useCache = options.useCache !== undefined ? options.useCache : true;
43
  this.cacheExpiry = options.cacheExpiry || 3600000; // 默认1小时
44
 
45
  // 内部状态
@@ -63,45 +80,6 @@ class ProxyPool {
63
  async initialize() {
64
  if (this.isInitialized) return;
65
 
66
- // 如果配置了静态代理,则使用静态代理
67
- if (this.staticProxy) {
68
- console.log(`初始化代理池,使用静态代理: ${this.staticProxy}`);
69
- try {
70
- const url = new URL(this.staticProxy);
71
- const protocol = url.protocol.replace(':', '');
72
- const proxyUrlForTest = `${url.hostname}:${url.port}`;
73
-
74
- // 确保代理协议与配置一致
75
- if (protocol !== this.proxyProtocol) {
76
- console.log(`静态代理协议与配置不符,将使用代理地址中的协议: ${protocol}`);
77
- this.proxyProtocol = protocol;
78
- }
79
-
80
- const isValid = await this.testProxy(proxyUrlForTest);
81
-
82
- if (isValid) {
83
- const proxyObj = {
84
- ip: url.hostname,
85
- port: url.port,
86
- protocol: this.proxyProtocol,
87
- full: `${this.proxyProtocol}://${url.hostname}:${url.port}`,
88
- addedAt: new Date().toISOString()
89
- };
90
- this.availableProxies.push(proxyObj);
91
- console.log(`静态代理 ${this.staticProxy} 测试通过并已添加。`);
92
- } else {
93
- console.error(`静态代理 ${this.staticProxy} 测试失败,不可用。`);
94
- }
95
- } catch (e) {
96
- console.error(`提供的静态代理地址无效: ${this.staticProxy}`, e);
97
- }
98
-
99
- this.isInitialized = true;
100
- console.log(`代理池初始化完成,当前可用代理数量: ${this.availableProxies.length}`);
101
- // 静态代理模式下,不需要定时检查和补充
102
- return;
103
- }
104
-
105
  console.log(`初始化代理池,目标数量: ${this.targetCount}`);
106
  await this.refillProxies();
107
 
@@ -127,9 +105,6 @@ class ProxyPool {
127
  * 检查并补充代理
128
  */
129
  async checkAndRefill() {
130
- // 如果使用静态代理,则不进行检查和补充
131
- if (this.staticProxy) return;
132
-
133
  if (this.availableProxies.length <= this.minThreshold && !this.isRefilling) {
134
  console.log(`可用代理数量(${this.availableProxies.length})低于阈值(${this.minThreshold}),开始补充代理`);
135
  await this.refillProxies();
@@ -141,8 +116,41 @@ class ProxyPool {
141
  * @returns {Promise<void>}
142
  */
143
  async refillProxies() {
144
- // 如果使用静态代理,则不进行补充
145
- if (this.isRefilling || this.staticProxy) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
  this.isRefilling = true;
148
  console.log(`开始补充代理,当前数量: ${this.availableProxies.length},目标数量: ${this.targetCount}`);
@@ -378,13 +386,13 @@ class ProxyPool {
378
  * @param {string} proxyUrl - 代理URL
379
  * @returns {Promise<boolean>} - 代理是否可用
380
  */
381
- async testProxy(proxyUrl) {
382
  try {
383
  // 创建代理配置
384
  const proxyConfig = {
385
  host: proxyUrl.split(':')[0],
386
  port: parseInt(proxyUrl.split(':')[1]),
387
- protocol: this.proxyProtocol
388
  };
389
 
390
  // 发送请求到目标网站
@@ -536,7 +544,7 @@ async function example() {
536
  concurrentRequests: 15, // 增加并发请求数
537
  useCache: true, // 启用缓存
538
  maxRefillAttempts: 15, // 减少最大尝试次数
539
- retryDelay: 1000, // 减少重试延迟
540
  });
541
 
542
  // 初始化代理池
 
1
  import axios from 'axios';
2
+ import dotenv from 'dotenv';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ // 获取当前文件的目录路径
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ // 加载环境变量
11
+ dotenv.config({ path: join(dirname(__dirname), '.env') });
12
 
13
  /**
14
  * 代理池类,用于管理和提供HTTP代理
 
30
  * @param {number} options.retryDelay - 重试延迟(毫秒),默认1000
31
  * @param {boolean} options.useCache - 是否使用缓存,默认true
32
  * @param {number} options.cacheExpiry - 缓存过期时间(毫秒),默认3600000 (1小时)
 
33
  */
34
  constructor(options = {}) {
35
+ this.staticProxy = process.env.STATIC_PROXY;
 
36
 
37
  // 配置参数
38
+ if (this.staticProxy) {
39
+ console.log(`检测到静态代理配置,将使用静态代理模式: ${this.staticProxy}`);
40
+ this.targetCount = 1;
41
+ this.batchSize = 1;
42
+ this.minThreshold = 0;
43
+ this.maxRefillAttempts = 5;
44
+ this.useCache = false;
45
+ } else {
46
+ this.targetCount = options.targetCount || 20;
47
+ this.batchSize = options.batchSize || 20;
48
+ this.minThreshold = options.minThreshold || 5;
49
+ this.maxRefillAttempts = options.maxRefillAttempts || 20; // 减少最大尝试次数
50
+ this.useCache = options.useCache !== undefined ? options.useCache : true;
51
+ }
52
+
53
  this.testTimeout = options.testTimeout || 5000;
54
  this.requestTimeout = options.requestTimeout || 10000;
55
  this.targetUrl = options.targetUrl || 'https://www.notion.so';
56
  this.concurrentRequests = options.concurrentRequests || 10;
 
57
  this.checkInterval = options.checkInterval || 30000; // 默认30秒检查一次
58
  this.proxyProtocol = options.proxyProtocol || 'http';
 
59
  this.retryDelay = options.retryDelay || 1000; // 减少重试延迟
 
60
  this.cacheExpiry = options.cacheExpiry || 3600000; // 默认1小时
61
 
62
  // 内部状态
 
80
  async initialize() {
81
  if (this.isInitialized) return;
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  console.log(`初始化代理池,目标数量: ${this.targetCount}`);
84
  await this.refillProxies();
85
 
 
105
  * 检查并补充代理
106
  */
107
  async checkAndRefill() {
 
 
 
108
  if (this.availableProxies.length <= this.minThreshold && !this.isRefilling) {
109
  console.log(`可用代理数量(${this.availableProxies.length})低于阈值(${this.minThreshold}),开始补充代理`);
110
  await this.refillProxies();
 
116
  * @returns {Promise<void>}
117
  */
118
  async refillProxies() {
119
+ if (this.isRefilling) return;
120
+
121
+ if (this.staticProxy) {
122
+ if (this.availableProxies.length > 0) {
123
+ return;
124
+ }
125
+ this.isRefilling = true;
126
+ console.log(`正在配置静态代理: ${this.staticProxy}`);
127
+ try {
128
+ const url = new URL(this.staticProxy);
129
+ const proxyProtocol = url.protocol.slice(0, -1);
130
+ const proxyUrlForTest = `${url.hostname}:${url.port}`;
131
+
132
+ const isValid = await this.testProxy(proxyUrlForTest, proxyProtocol);
133
+
134
+ if (isValid) {
135
+ const proxyObj = {
136
+ ip: url.hostname,
137
+ port: url.port,
138
+ protocol: proxyProtocol,
139
+ full: this.staticProxy,
140
+ addedAt: new Date().toISOString()
141
+ };
142
+ this.availableProxies.push(proxyObj);
143
+ console.log(`静态代理 ${this.staticProxy} 已成功添加。`);
144
+ } else {
145
+ console.error(`静态代理 ${this.staticProxy} 测试失败,不可用。`);
146
+ }
147
+ } catch (error) {
148
+ console.error(`无效的静态代理URL '${this.staticProxy}': ${error.message}`);
149
+ } finally {
150
+ this.isRefilling = false;
151
+ }
152
+ return;
153
+ }
154
 
155
  this.isRefilling = true;
156
  console.log(`开始补充代理,当前数量: ${this.availableProxies.length},目标数量: ${this.targetCount}`);
 
386
  * @param {string} proxyUrl - 代理URL
387
  * @returns {Promise<boolean>} - 代理是否可用
388
  */
389
+ async testProxy(proxyUrl, protocol = this.proxyProtocol) {
390
  try {
391
  // 创建代理配置
392
  const proxyConfig = {
393
  host: proxyUrl.split(':')[0],
394
  port: parseInt(proxyUrl.split(':')[1]),
395
+ protocol: protocol,
396
  };
397
 
398
  // 发送请求到目标网站
 
544
  concurrentRequests: 15, // 增加并发请求数
545
  useCache: true, // 启用缓存
546
  maxRefillAttempts: 15, // 减少最大尝试次数
547
+ retryDelay: 1000 // 减少重试延迟
548
  });
549
 
550
  // 初始化代理池
src/ProxyServer.js CHANGED
@@ -119,10 +119,16 @@ class ProxyServer {
119
  // 将进程的输出重定向到日志文件
120
  if (this.proxyProcess.stdout) {
121
  this.proxyProcess.stdout.pipe(this.logStream);
 
 
 
122
  }
123
 
124
  if (this.proxyProcess.stderr) {
125
  this.proxyProcess.stderr.pipe(this.logStream);
 
 
 
126
  }
127
 
128
  // 设置进程事件处理
 
119
  // 将进程的输出重定向到日志文件
120
  if (this.proxyProcess.stdout) {
121
  this.proxyProcess.stdout.pipe(this.logStream);
122
+ this.proxyProcess.stdout.on('data', (data) => {
123
+ logger.info(`[ProxyExecutable STDOUT] ${data.toString().trim()}`);
124
+ });
125
  }
126
 
127
  if (this.proxyProcess.stderr) {
128
  this.proxyProcess.stderr.pipe(this.logStream);
129
+ this.proxyProcess.stderr.on('data', (data) => {
130
+ logger.error(`[ProxyExecutable STDERR] ${data.toString().trim()}`);
131
+ });
132
  }
133
 
134
  // 设置进程事件处理
src/lightweight-client-express.js CHANGED
@@ -145,9 +145,6 @@ app.post('/v1/chat/completions', authenticate, async (req, res) => {
145
  // 构建Notion请求
146
  const notionRequestBody = buildNotionRequest(requestData);
147
 
148
- // 获取代理
149
- const proxy = proxyPool.getProxy();
150
-
151
  // 处理流式响应
152
  if (requestData.stream) {
153
  res.setHeader('Content-Type', 'text/event-stream');
@@ -155,7 +152,7 @@ app.post('/v1/chat/completions', authenticate, async (req, res) => {
155
  res.setHeader('Connection', 'keep-alive');
156
 
157
  logger.info(`开始流式响应`);
158
- const stream = await streamNotionResponse(notionRequestBody, proxy);
159
  stream.pipe(res);
160
 
161
  // 处理客户端断开连接
@@ -167,7 +164,7 @@ app.post('/v1/chat/completions', authenticate, async (req, res) => {
167
  // 创建一个内部流来收集完整响应
168
  logger.info(`开始非流式响应`);
169
  const chunks = [];
170
- const stream = await streamNotionResponse(notionRequestBody, proxy);
171
 
172
  return new Promise((resolve, reject) => {
173
  stream.on('data', (chunk) => {
@@ -252,24 +249,21 @@ app.get('/cookies/status', authenticate, (req, res) => {
252
  // 启动服务器
253
  const PORT = process.env.PORT || 7860;
254
 
255
- (async () => {
256
- try {
257
- // 初始化代理池
258
- await proxyPool.initialize();
259
-
260
- // 初始化Notion客户端
261
- await initialize();
262
-
263
- // 启动服务器
264
- app.listen(PORT, '0.0.0.0', () => {
265
- logger.info(`服务已启动 - 端口: ${PORT}`);
266
- logger.info(`访问地址: http://localhost:${PORT}`);
267
- logger.success(`系统初始化状态: ${INITIALIZED_SUCCESSFULLY ? '✅' : '❌'}`);
268
  logger.success(`可用cookie数量: ${cookieManager.getValidCount()}`);
269
- });
270
-
271
- } catch (error) {
272
- logger.error(`启动失败: ${error.message}`);
273
- process.exit(1);
274
- }
275
- })();
 
 
 
145
  // 构建Notion请求
146
  const notionRequestBody = buildNotionRequest(requestData);
147
 
 
 
 
148
  // 处理流式响应
149
  if (requestData.stream) {
150
  res.setHeader('Content-Type', 'text/event-stream');
 
152
  res.setHeader('Connection', 'keep-alive');
153
 
154
  logger.info(`开始流式响应`);
155
+ const stream = await streamNotionResponse(notionRequestBody);
156
  stream.pipe(res);
157
 
158
  // 处理客户端断开连接
 
164
  // 创建一个内部流来收集完整响应
165
  logger.info(`开始非流式响应`);
166
  const chunks = [];
167
+ const stream = await streamNotionResponse(notionRequestBody);
168
 
169
  return new Promise((resolve, reject) => {
170
  stream.on('data', (chunk) => {
 
249
  // 启动服务器
250
  const PORT = process.env.PORT || 7860;
251
 
252
+ // 初始化并启动服务器
253
+ initialize().then(() => {
254
+ app.listen(PORT, () => {
255
+ logger.info(`服务已启动 - 端口: ${PORT}`);
256
+ logger.info(`访问地址: http://localhost:${PORT}`);
257
+
258
+ if (INITIALIZED_SUCCESSFULLY) {
259
+ logger.success(`系统初始化状态: ✅`);
 
 
 
 
 
260
  logger.success(`可用cookie数量: ${cookieManager.getValidCount()}`);
261
+ } else {
262
+ logger.warning(`系统初始化状态: ❌`);
263
+ logger.warning(`警告: 系统未成功初始化,API调用将无法正常工作`);
264
+ logger.warning(`请检查NOTION_COOKIE配置是否有效`);
265
+ }
266
+ });
267
+ }).catch((error) => {
268
+ logger.error(`初始化失败: ${error}`);
269
+ });
src/lightweight-client.js CHANGED
@@ -6,12 +6,12 @@ import { fileURLToPath } from 'url';
6
  import { dirname, join } from 'path';
7
  import { PassThrough } from 'stream';
8
  import chalk from 'chalk';
9
- import { HttpsProxyAgent } from 'https-proxy-agent';
10
  import {
11
  NotionTranscriptConfigValue,
12
  NotionTranscriptContextValue, NotionTranscriptItem, NotionDebugOverrides,
13
  NotionRequestBody, ChoiceDelta, Choice, ChatCompletionChunk, NotionTranscriptItemByuser
14
  } from './models.js';
 
15
  import { proxyServer } from './ProxyServer.js';
16
  import { cookieManager } from './CookieManager.js';
17
 
@@ -34,7 +34,12 @@ const logger = {
34
  const NOTION_API_URL = "https://www.notion.so/api/v3/runInferenceTranscript";
35
  // 这些变量将由cookieManager动态提供
36
  let currentCookieData = null;
 
37
  const ENABLE_PROXY_SERVER = process.env.ENABLE_PROXY_SERVER === 'true';
 
 
 
 
38
 
39
  // 标记是否成功初始化
40
  let INITIALIZED_SUCCESSFULLY = false;
@@ -189,7 +194,7 @@ function buildNotionRequest(requestData) {
189
  }
190
 
191
  // 流式处理Notion响应
192
- async function streamNotionResponse(notionRequestBody, proxy = null) {
193
  // 确保我们有当前的cookie数据
194
  if (!currentCookieData) {
195
  currentCookieData = cookieManager.getNext();
@@ -247,8 +252,7 @@ async function streamNotionResponse(notionRequestBody, proxy = null) {
247
  headers,
248
  NOTION_API_URL,
249
  currentCookieData.cookie,
250
- timeoutId,
251
- proxy
252
  ).catch((error) => {
253
  logger.error(`流处理出错: ${error}`);
254
  clearTimeout(timeoutId); // 清除超时计时器
@@ -275,133 +279,474 @@ async function streamNotionResponse(notionRequestBody, proxy = null) {
275
  return stream;
276
  }
277
 
278
- /**
279
- * 实际执行fetch请求的函数
280
- * @param {PassThrough} chunkQueue - 数据块队列
281
- * @param {NotionRequestBody} notionRequestBody - Notion请求体
282
- * @param {object} headers - 请求头
283
- * @param {string} notionApiUrl - Notion API URL
284
- * @param {string} notionCookie - Notion Cookie
285
- * @param {NodeJS.Timeout} timeoutId - 超时ID
286
- * @param {object|null} proxy - 代理对象 { ip, port, protocol }
287
- */
288
- async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notionApiUrl, notionCookie, timeoutId, proxy = null) {
289
- let agent = null;
290
- if (proxy && proxy.ip && proxy.port && proxy.protocol) {
291
- agent = new HttpsProxyAgent(`${proxy.protocol}://${proxy.ip}:${proxy.port}`);
292
- logger.info(`使用代理: ${proxy.protocol}://${proxy.ip}:${proxy.port}`);
293
- }
294
-
295
  try {
296
- const response = await fetch(notionApiUrl, {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  method: 'POST',
298
  headers: {
299
  ...headers,
 
300
  'Cookie': notionCookie
301
  },
302
  body: JSON.stringify(notionRequestBody),
303
- agent: agent // 应用代理
304
- });
305
-
306
- // 请求成功,清除超时
307
- clearTimeout(timeoutId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  if (!response.ok) {
310
- // 简化错误处理
311
- const errorBody = await response.text();
312
- throw new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`);
313
  }
314
-
 
 
 
 
 
 
315
  const reader = response.body;
316
  let buffer = '';
317
-
 
318
  reader.on('data', (chunk) => {
319
- buffer += chunk.toString('utf8');
320
- // 处理ndjson
321
- let boundary = buffer.indexOf('\n');
322
- while (boundary !== -1) {
323
- const line = buffer.substring(0, boundary).trim();
324
- buffer = buffer.substring(boundary + 1);
325
- if (line.startsWith('{"type":"chunk"')) {
 
 
 
 
 
 
 
 
 
 
 
 
326
  try {
327
- const parsed = JSON.parse(line);
328
- if (parsed.chunk) {
329
- const streamChunk = new ChatCompletionChunk({
 
 
 
 
 
 
330
  choices: [
331
  new Choice({
332
- delta: new ChoiceDelta({ content: parsed.chunk })
 
333
  })
334
  ]
335
  });
336
- chunkQueue.write(`data: ${JSON.stringify(streamChunk)}\n\n`);
 
 
 
 
 
 
 
337
  }
338
- } catch (e) {
339
- logger.warning(`解析JSON块失败: ${e.message}`);
340
  }
341
  }
342
- boundary = buffer.indexOf('\n');
 
343
  }
344
  });
345
-
 
346
  reader.on('end', () => {
347
- if (!chunkQueue.writableEnded) {
348
- if (buffer.trim() === '') {
349
- logger.warning(`未从Notion收到内容响应,请更换ip重试`);
350
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  const endChunk = new ChatCompletionChunk({
352
- choices: [new Choice({ finish_reason: 'stop' })]
 
 
 
 
 
353
  });
 
 
354
  chunkQueue.write(`data: ${JSON.stringify(endChunk)}\n\n`);
355
  chunkQueue.write('data: [DONE]\n\n');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  chunkQueue.end();
357
  }
358
  });
359
-
360
- reader.on('error', (err) => {
361
- if (!chunkQueue.writableEnded) {
362
- logger.error(`读取响应流时出错: ${err.message}`);
363
- chunkQueue.end();
364
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  });
366
-
367
  } catch (error) {
368
- clearTimeout(timeoutId);
369
- logger.error(`fetchNotionResponse出错: ${error.message}`);
370
- if (!chunkQueue.writableEnded) {
371
- chunkQueue.end();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  }
 
 
373
  }
374
  }
375
 
376
  // 应用初始化
377
  async function initialize() {
378
- if (ENABLE_PROXY_SERVER) {
379
- logger.info("正在启动内置代理服务器...");
380
- try {
381
- const started = await proxyServer.start();
382
- if (!started) {
383
- logger.warning('内置代理服务器启动失败,将尝试不使用代理。');
384
- }
385
- } catch (error) {
386
- logger.error(`启动内置代理服务器失败: ${error.message}`);
 
 
 
 
 
 
 
 
 
 
 
387
  }
388
  }
389
-
390
- logger.info("初始化Notion配置...");
391
-
392
- // 初始化Cookie管理器
393
- await cookieManager.initialize();
394
- if (cookieManager.getValidCount() > 0) {
395
- currentCookieData = cookieManager.getNext();
396
- INITIALIZED_SUCCESSFULLY = true;
397
- logger.success(`成功初始化cookie管理器,共有 ${cookieManager.getValidCount()} 个有效cookie`);
398
- logger.info(`当前使用的cookie对应的用户ID: ${currentCookieData.userId}`);
399
- logger.info(`当前使用的cookie对应的空间ID: ${currentCookieData.spaceId}`);
400
- } else {
401
- logger.error('初始化失败:没有有效的cookie。请检查您的.env或cookies.txt文件。');
 
 
 
 
 
 
 
 
 
 
 
 
402
  INITIALIZED_SUCCESSFULLY = false;
403
- return; // 没有cookie,无法继续
 
 
 
 
 
 
 
 
 
404
  }
 
 
405
  }
406
 
407
  // 导出函数
 
6
  import { dirname, join } from 'path';
7
  import { PassThrough } from 'stream';
8
  import chalk from 'chalk';
 
9
  import {
10
  NotionTranscriptConfigValue,
11
  NotionTranscriptContextValue, NotionTranscriptItem, NotionDebugOverrides,
12
  NotionRequestBody, ChoiceDelta, Choice, ChatCompletionChunk, NotionTranscriptItemByuser
13
  } from './models.js';
14
+ import { proxyPool } from './ProxyPool.js';
15
  import { proxyServer } from './ProxyServer.js';
16
  import { cookieManager } from './CookieManager.js';
17
 
 
34
  const NOTION_API_URL = "https://www.notion.so/api/v3/runInferenceTranscript";
35
  // 这些变量将由cookieManager动态提供
36
  let currentCookieData = null;
37
+ const USE_NATIVE_PROXY_POOL = process.env.USE_NATIVE_PROXY_POOL === 'true';
38
  const ENABLE_PROXY_SERVER = process.env.ENABLE_PROXY_SERVER === 'true';
39
+ let proxy = null;
40
+
41
+ // 代理配置
42
+ const PROXY_URL = process.env.PROXY_URL || "";
43
 
44
  // 标记是否成功初始化
45
  let INITIALIZED_SUCCESSFULLY = false;
 
194
  }
195
 
196
  // 流式处理Notion响应
197
+ async function streamNotionResponse(notionRequestBody) {
198
  // 确保我们有当前的cookie数据
199
  if (!currentCookieData) {
200
  currentCookieData = cookieManager.getNext();
 
252
  headers,
253
  NOTION_API_URL,
254
  currentCookieData.cookie,
255
+ timeoutId
 
256
  ).catch((error) => {
257
  logger.error(`流处理出错: ${error}`);
258
  clearTimeout(timeoutId); // 清除超时计时器
 
279
  return stream;
280
  }
281
 
282
+ // 使用fetch调用Notion API并处理流式响应
283
+ async function fetchNotionResponse(chunkQueue, notionRequestBody, headers, notionApiUrl, notionCookie, timeoutId) {
284
+ let responseReceived = false;
285
+ let dom = null;
286
+
 
 
 
 
 
 
 
 
 
 
 
 
287
  try {
288
+ // 创建JSDOM实例模拟浏览器环境
289
+ dom = new JSDOM("", {
290
+ url: "https://www.notion.so",
291
+ referrer: "https://www.notion.so/chat",
292
+ contentType: "text/html",
293
+ includeNodeLocations: true,
294
+ storageQuota: 10000000,
295
+ pretendToBeVisual: true,
296
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
297
+ });
298
+
299
+ // 设置全局对象
300
+ const { window } = dom;
301
+
302
+ // 使用更安全的方式设置全局对象
303
+ try {
304
+ if (!global.window) {
305
+ global.window = window;
306
+ }
307
+
308
+ if (!global.document) {
309
+ global.document = window.document;
310
+ }
311
+
312
+ // 安全地设置navigator
313
+ if (!global.navigator) {
314
+ try {
315
+ Object.defineProperty(global, 'navigator', {
316
+ value: window.navigator,
317
+ writable: true,
318
+ configurable: true
319
+ });
320
+ } catch (navError) {
321
+ logger.warning(`无法设置navigator: ${navError.message},继续执行`);
322
+ // 继续执行,不会中断流程
323
+ }
324
+ }
325
+ } catch (globalError) {
326
+ logger.warning(`设置全局对象时出错: ${globalError.message}`);
327
+ }
328
+
329
+ // 设置cookie
330
+ document.cookie = notionCookie;
331
+
332
+ // 创建fetch选项
333
+ const fetchOptions = {
334
  method: 'POST',
335
  headers: {
336
  ...headers,
337
+ 'user-agent': window.navigator.userAgent,
338
  'Cookie': notionCookie
339
  },
340
  body: JSON.stringify(notionRequestBody),
341
+ };
342
+
343
+ // 添加代理配置(如果有)
344
+ if (USE_NATIVE_PROXY_POOL) {
345
+ proxy = proxyPool.getProxy();
346
+ if (proxy !== null)
347
+ {
348
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
349
+ fetchOptions.agent = new HttpsProxyAgent(proxy.full);
350
+ logger.info(`使用代理: ${proxy.full}`);
351
+ }
352
+ else{
353
+ logger.warning(`没有可用代理`);
354
+ }
355
+ } else if(PROXY_URL) {
356
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
357
+ fetchOptions.agent = new HttpsProxyAgent(PROXY_URL);
358
+ logger.info(`使用代理: ${PROXY_URL}`);
359
+ }
360
+ let response = null;
361
+ // 发送请求
362
+ if (ENABLE_PROXY_SERVER){
363
+ response = await fetch('http://127.0.0.1:10655/proxy', {
364
+ method: 'POST',
365
+ body: JSON.stringify({
366
+ method: 'POST',
367
+ url: notionApiUrl,
368
+ headers: fetchOptions.headers,
369
+ body: fetchOptions.body,
370
+ stream:true
371
+ }),
372
+ });
373
+ }else{
374
+ response = await fetch(notionApiUrl, fetchOptions);
375
+ }
376
 
377
+ // 检查是否收到401错误(未授权)
378
+ if (response.status === 401) {
379
+ logger.error(`收到401未授权错误,cookie可能已失效`);
380
+ // 标记当前cookie为无效
381
+ cookieManager.markAsInvalid(currentCookieData.userId);
382
+ // 尝试获取下一个cookie
383
+ currentCookieData = cookieManager.getNext();
384
+
385
+ if (!currentCookieData) {
386
+ throw new Error('所有cookie均已失效,无法继续请求');
387
+ }
388
+
389
+ // 使用新cookie重新构建请求体
390
+ const newRequestBody = buildNotionRequest({
391
+ model: notionRequestBody.transcript[0]?.value?.model || '',
392
+ messages: [] // 这里应该根据实际情况重构消息
393
+ });
394
+
395
+ // 使用新cookie重试请求
396
+ return fetchNotionResponse(
397
+ chunkQueue,
398
+ newRequestBody,
399
+ {
400
+ ...headers,
401
+ 'x-notion-active-user-header': currentCookieData.userId,
402
+ 'x-notion-space-id': currentCookieData.spaceId
403
+ },
404
+ notionApiUrl,
405
+ currentCookieData.cookie,
406
+ timeoutId
407
+ );
408
+ }
409
+
410
  if (!response.ok) {
411
+ throw new Error(`HTTP error! status: ${response.status}`);
 
 
412
  }
413
+
414
+ // 处理流式响应
415
+ if (!response.body) {
416
+ throw new Error("Response body is null");
417
+ }
418
+
419
+ // 创建流读取器
420
  const reader = response.body;
421
  let buffer = '';
422
+
423
+ // 处理数据块
424
  reader.on('data', (chunk) => {
425
+ try {
426
+ // 标记已收到响应
427
+ if (!responseReceived) {
428
+ responseReceived = true;
429
+ logger.info(`已连接Notion API`);
430
+ clearTimeout(timeoutId); // 清除超时计时器
431
+ }
432
+
433
+ // 解码数据
434
+ const text = chunk.toString('utf8');
435
+ buffer += text;
436
+
437
+ // 按行分割并处理完整的JSON对象
438
+ const lines = buffer.split('\n');
439
+ buffer = lines.pop() || ''; // 保留最后一行(可能不完整)
440
+
441
+ for (const line of lines) {
442
+ if (!line.trim()) continue;
443
+
444
  try {
445
+ const jsonData = JSON.parse(line);
446
+
447
+ // 提取内容
448
+ if (jsonData?.type === "markdown-chat" && typeof jsonData?.value === "string") {
449
+ const content = jsonData.value;
450
+ if (!content) continue;
451
+
452
+ // 创建OpenAI格式的块
453
+ const chunk = new ChatCompletionChunk({
454
  choices: [
455
  new Choice({
456
+ delta: new ChoiceDelta({ content }),
457
+ finish_reason: null
458
  })
459
  ]
460
  });
461
+
462
+ // 添加到队列
463
+ const dataStr = `data: ${JSON.stringify(chunk)}\n\n`;
464
+ chunkQueue.write(dataStr);
465
+ } else if (jsonData?.recordMap) {
466
+ // 忽略recordMap响应
467
+ } else {
468
+ // 忽略其他类型响应
469
  }
470
+ } catch (jsonError) {
471
+ logger.error(`解析JSON出错: ${jsonError}`);
472
  }
473
  }
474
+ } catch (error) {
475
+ logger.error(`处理数据块出错: ${error}`);
476
  }
477
  });
478
+
479
+ // 处理流结束
480
  reader.on('end', () => {
481
+ try {
482
+ logger.info(`响应完成`);
483
+ if (cookieManager.getValidCount() > 1){
484
+ // 尝试切换到下一个cookie
485
+ currentCookieData = cookieManager.getNext();
486
+ logger.info(`切换到下一个cookie: ${currentCookieData.userId}`);
487
+ }
488
+
489
+ // 如果没有收到任何响应,发送一个提示消息
490
+ if (!responseReceived) {
491
+ logger.warning(`未从Notion收到内容响应,请更换ip重试`);
492
+ if (USE_NATIVE_PROXY_POOL) {
493
+ proxyPool.removeProxy(proxy.ip, proxy.port);
494
+ }
495
+
496
+ const noContentChunk = new ChatCompletionChunk({
497
+ choices: [
498
+ new Choice({
499
+ delta: new ChoiceDelta({ content: "未从Notion收到内容响应,请更换ip重试。" }),
500
+ finish_reason: "no_content"
501
+ })
502
+ ]
503
+ });
504
+ chunkQueue.write(`data: ${JSON.stringify(noContentChunk)}\n\n`);
505
+ }
506
+
507
+ // 创建结束块
508
  const endChunk = new ChatCompletionChunk({
509
+ choices: [
510
+ new Choice({
511
+ delta: new ChoiceDelta({ content: null }),
512
+ finish_reason: "stop"
513
+ })
514
+ ]
515
  });
516
+
517
+ // 添加到队列
518
  chunkQueue.write(`data: ${JSON.stringify(endChunk)}\n\n`);
519
  chunkQueue.write('data: [DONE]\n\n');
520
+
521
+ // 清除超时计时器(如果尚未清除)
522
+ if (timeoutId) clearTimeout(timeoutId);
523
+
524
+ // 清理全局对象
525
+ try {
526
+ if (global.window) delete global.window;
527
+ if (global.document) delete global.document;
528
+
529
+ // 安全地删除navigator
530
+ if (global.navigator) {
531
+ try {
532
+ delete global.navigator;
533
+ } catch (navError) {
534
+ // 如果无法删除,尝试将其设置为undefined
535
+ try {
536
+ Object.defineProperty(global, 'navigator', {
537
+ value: undefined,
538
+ writable: true,
539
+ configurable: true
540
+ });
541
+ } catch (defineError) {
542
+ logger.warning(`无法清理navigator: ${defineError.message}`);
543
+ }
544
+ }
545
+ }
546
+ } catch (cleanupError) {
547
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
548
+ }
549
+
550
+ // 结束流
551
+ chunkQueue.end();
552
+ } catch (error) {
553
+ logger.error(`Error in stream end handler: ${error}`);
554
+ if (timeoutId) clearTimeout(timeoutId);
555
+
556
+ // 清理全局对象
557
+ try {
558
+ if (global.window) delete global.window;
559
+ if (global.document) delete global.document;
560
+
561
+ // 安全地删除navigator
562
+ if (global.navigator) {
563
+ try {
564
+ delete global.navigator;
565
+ } catch (navError) {
566
+ // 如果无法删除,尝试将其设置为undefined
567
+ try {
568
+ Object.defineProperty(global, 'navigator', {
569
+ value: undefined,
570
+ writable: true,
571
+ configurable: true
572
+ });
573
+ } catch (defineError) {
574
+ logger.warning(`无法清理navigator: ${defineError.message}`);
575
+ }
576
+ }
577
+ }
578
+ } catch (cleanupError) {
579
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
580
+ }
581
+
582
  chunkQueue.end();
583
  }
584
  });
585
+
586
+ // 处理错误
587
+ reader.on('error', (error) => {
588
+ logger.error(`Stream error: ${error}`);
589
+ if (timeoutId) clearTimeout(timeoutId);
590
+
591
+ // 清理全局对象
592
+ try {
593
+ if (global.window) delete global.window;
594
+ if (global.document) delete global.document;
595
+
596
+ // 安全地删除navigator
597
+ if (global.navigator) {
598
+ try {
599
+ delete global.navigator;
600
+ } catch (navError) {
601
+ // 如果无法删除,尝试将其设置为undefined
602
+ try {
603
+ Object.defineProperty(global, 'navigator', {
604
+ value: undefined,
605
+ writable: true,
606
+ configurable: true
607
+ });
608
+ } catch (defineError) {
609
+ logger.warning(`无法清理navigator: ${defineError.message}`);
610
+ }
611
+ }
612
+ }
613
+ } catch (cleanupError) {
614
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
615
+ }
616
+
617
+ try {
618
+ const errorChunk = new ChatCompletionChunk({
619
+ choices: [
620
+ new Choice({
621
+ delta: new ChoiceDelta({ content: `流读取错误: ${error.message}` }),
622
+ finish_reason: "error"
623
+ })
624
+ ]
625
+ });
626
+ chunkQueue.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
627
+ chunkQueue.write('data: [DONE]\n\n');
628
+ } catch (e) {
629
+ logger.error(`Error sending error message: ${e}`);
630
+ } finally {
631
+ chunkQueue.end();
632
+ }
633
  });
 
634
  } catch (error) {
635
+ logger.error(`Notion API请求失败: ${error}`);
636
+ // 清理全局对象
637
+ try {
638
+ if (global.window) delete global.window;
639
+ if (global.document) delete global.document;
640
+
641
+ // 安全地删除navigator
642
+ if (global.navigator) {
643
+ try {
644
+ delete global.navigator;
645
+ } catch (navError) {
646
+ // 如果无法删除,尝试将其设置为undefined
647
+ try {
648
+ Object.defineProperty(global, 'navigator', {
649
+ value: undefined,
650
+ writable: true,
651
+ configurable: true
652
+ });
653
+ } catch (defineError) {
654
+ logger.warning(`无法清理navigator: ${defineError.message}`);
655
+ }
656
+ }
657
+ }
658
+ } catch (cleanupError) {
659
+ logger.warning(`清理全局对象时出错: ${cleanupError.message}`);
660
+ }
661
+
662
+ if (timeoutId) clearTimeout(timeoutId);
663
+ if (chunkQueue) chunkQueue.end();
664
+
665
+ // 确保在错误情况下也触发流结束
666
+ try {
667
+ if (!responseReceived && chunkQueue) {
668
+ const errorChunk = new ChatCompletionChunk({
669
+ choices: [
670
+ new Choice({
671
+ delta: new ChoiceDelta({ content: `Notion API请求失败: ${error.message}` }),
672
+ finish_reason: "error"
673
+ })
674
+ ]
675
+ });
676
+ chunkQueue.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
677
+ chunkQueue.write('data: [DONE]\n\n');
678
+ }
679
+ } catch (e) {
680
+ logger.error(`发送错误消息时出错: ${e}`);
681
  }
682
+
683
+ throw error; // 重新抛出错误以便上层捕获
684
  }
685
  }
686
 
687
  // 应用初始化
688
  async function initialize() {
689
+ logger.info(`初始化Notion配置...`);
690
+
691
+ // 启动代理服务器
692
+ try {
693
+ await proxyServer.start();
694
+ } catch (error) {
695
+ logger.error(`启动代理服务器失败: ${error.message}`);
696
+ }
697
+
698
+ // 初始化cookie管理器
699
+ let initResult = false;
700
+
701
+ // 检查是否配置了cookie文件
702
+ const cookieFilePath = process.env.COOKIE_FILE;
703
+ if (cookieFilePath) {
704
+ logger.info(`检测到COOKIE_FILE配置: ${cookieFilePath}`);
705
+ initResult = await cookieManager.loadFromFile(cookieFilePath);
706
+
707
+ if (!initResult) {
708
+ logger.error(`从文件加载cookie失败,尝试使用环境变量中的NOTION_COOKIE`);
709
  }
710
  }
711
+
712
+ // 如果文件加载失败或未配置文件,尝试从环境变量加载
713
+ if (!initResult) {
714
+ const cookiesString = process.env.NOTION_COOKIE;
715
+ if (!cookiesString) {
716
+ logger.error(`错误: 未设置NOTION_COOKIE环境变量或COOKIE_FILE路径,应用无法正常工作`);
717
+ logger.error(`请在.env文件中设置有效的NOTION_COOKIE值或COOKIE_FILE路径`);
718
+ INITIALIZED_SUCCESSFULLY = false;
719
+ return;
720
+ }
721
+
722
+ logger.info(`正在从环境变量初始化cookie管理器...`);
723
+ initResult = await cookieManager.initialize(cookiesString);
724
+
725
+ if (!initResult) {
726
+ logger.error(`初始化cookie管理器失败,应用无法正常工作`);
727
+ INITIALIZED_SUCCESSFULLY = false;
728
+ return;
729
+ }
730
+ }
731
+
732
+ // 获取第一个可用的cookie数据
733
+ currentCookieData = cookieManager.getNext();
734
+ if (!currentCookieData) {
735
+ logger.error(`没有可用的cookie,应用无法正常工作`);
736
  INITIALIZED_SUCCESSFULLY = false;
737
+ return;
738
+ }
739
+
740
+ logger.success(`成功初始化cookie管理器,共有 ${cookieManager.getValidCount()} 个有效cookie`);
741
+ logger.info(`当前使用的cookie对应的用户ID: ${currentCookieData.userId}`);
742
+ logger.info(`当前使用的cookie对应的空间ID: ${currentCookieData.spaceId}`);
743
+
744
+ if (process.env.USE_NATIVE_PROXY_POOL === 'true') {
745
+ logger.info(`正在初始化本地代理池...`);
746
+ await proxyPool.initialize();
747
  }
748
+
749
+ INITIALIZED_SUCCESSFULLY = true;
750
  }
751
 
752
  // 导出函数