isididiidid commited on
Commit
91dc22e
·
verified ·
1 Parent(s): eeb0582

Update src/lightweight-client-express.js

Browse files
Files changed (1) hide show
  1. src/lightweight-client-express.js +278 -7
src/lightweight-client-express.js CHANGED
@@ -7,11 +7,9 @@ import chalk from 'chalk';
7
  import {
8
  ChatMessage, ChatCompletionRequest, Choice, ChoiceDelta, ChatCompletionChunk
9
  } from './models.js';
10
- import {
11
- initialize,
12
- streamNotionResponse
13
- } from './lightweight-client.js';
14
  import { cookieManager } from './CookieManager.js';
 
 
15
 
16
  // 获取当前文件的目录路径
17
  const __filename = fileURLToPath(import.meta.url);
@@ -54,7 +52,6 @@ async function validateProxy() {
54
  }
55
 
56
  try {
57
- const { default: fetch } = await import('node-fetch');
58
  const { HttpsProxyAgent } = await import('https-proxy-agent');
59
 
60
  const testResponse = await fetch('https://httpbin.org/ip', {
@@ -78,6 +75,282 @@ async function validateProxy() {
78
  }
79
  }
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  // 构建Notion请求的函数
82
  function buildNotionRequest(requestData) {
83
  const cookieData = cookieManager.getNext();
@@ -487,5 +760,3 @@ initialize().then(async (initResult) => {
487
  logger.warning(`服务已在 0.0.0.0:${PORT} 上启动(初始化失败模式)`);
488
  });
489
  });
490
-
491
-
 
7
  import {
8
  ChatMessage, ChatCompletionRequest, Choice, ChoiceDelta, ChatCompletionChunk
9
  } from './models.js';
 
 
 
 
10
  import { cookieManager } from './CookieManager.js';
11
+ import { PassThrough } from 'stream';
12
+ import fetch from 'node-fetch';
13
 
14
  // 获取当前文件的目录路径
15
  const __filename = fileURLToPath(import.meta.url);
 
52
  }
53
 
54
  try {
 
55
  const { HttpsProxyAgent } = await import('https-proxy-agent');
56
 
57
  const testResponse = await fetch('https://httpbin.org/ip', {
 
75
  }
76
  }
77
 
78
+ // 初始化函数
79
+ async function initialize() {
80
+ logger.info('开始系统初始化...');
81
+
82
+ try {
83
+ const envCookie = process.env.NOTION_COOKIE;
84
+ const envCookieFile = process.env.COOKIE_FILE;
85
+
86
+ if (envCookie) {
87
+ logger.info('发现环境变量中的NOTION_COOKIE,正在初始化...');
88
+ const success = await cookieManager.initialize(envCookie);
89
+ if (success) {
90
+ logger.success('Cookie初始化成功');
91
+ return true;
92
+ } else {
93
+ logger.error('Cookie初始化失败');
94
+ return false;
95
+ }
96
+ } else if (envCookieFile) {
97
+ logger.info(`发现环境变量中的COOKIE_FILE: ${envCookieFile},正在加载...`);
98
+ const success = await cookieManager.loadFromFile(envCookieFile);
99
+ if (success) {
100
+ logger.success('从文件加载Cookie成功');
101
+ return true;
102
+ } else {
103
+ logger.error('从文件加载Cookie失败');
104
+ return false;
105
+ }
106
+ } else {
107
+ logger.warning('未找到NOTION_COOKIE或COOKIE_FILE环境变量');
108
+ return false;
109
+ }
110
+ } catch (error) {
111
+ logger.error(`初始化过程出错: ${error.message}`);
112
+ return false;
113
+ }
114
+ }
115
+
116
+ // 流式响应函数
117
+ async function streamNotionResponse(notionRequestBody) {
118
+ const stream = new PassThrough();
119
+ let streamClosed = false;
120
+
121
+ const originalEnd = stream.end;
122
+ stream.end = function(...args) {
123
+ if (streamClosed) return;
124
+ streamClosed = true;
125
+ return originalEnd.apply(this, args);
126
+ };
127
+
128
+ stream.write(':\n\n');
129
+
130
+ const timeoutId = setTimeout(() => {
131
+ if (streamClosed) return;
132
+
133
+ logger.warning(`请求超时,30秒内未收到响应`);
134
+ try {
135
+ const endChunk = new ChatCompletionChunk({
136
+ choices: [
137
+ new Choice({
138
+ delta: new ChoiceDelta({ content: "请求超时,未收到Notion响应。" }),
139
+ finish_reason: "timeout"
140
+ })
141
+ ]
142
+ });
143
+ stream.write(`data: ${JSON.stringify(endChunk)}\n\n`);
144
+ stream.write('data: [DONE]\n\n');
145
+ stream.end();
146
+ } catch (error) {
147
+ logger.error(`发送超时消息时出错: ${error}`);
148
+ if (!streamClosed) stream.end();
149
+ }
150
+ }, 30000);
151
+
152
+ fetchNotionResponse(stream, notionRequestBody, timeoutId).catch((error) => {
153
+ if (streamClosed) return;
154
+
155
+ logger.error(`流处理出错: ${error}`);
156
+ clearTimeout(timeoutId);
157
+
158
+ try {
159
+ const errorChunk = new ChatCompletionChunk({
160
+ choices: [
161
+ new Choice({
162
+ delta: new ChoiceDelta({ content: `处理请求时出错: ${error.message}` }),
163
+ finish_reason: "error"
164
+ })
165
+ ]
166
+ });
167
+ stream.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
168
+ stream.write('data: [DONE]\n\n');
169
+ } catch (e) {
170
+ logger.error(`发送错误消息时出错: ${e}`);
171
+ } finally {
172
+ if (!streamClosed) stream.end();
173
+ }
174
+ });
175
+
176
+ return stream;
177
+ }
178
+
179
+ // 实际请求Notion API的函数
180
+ async function fetchNotionResponse(chunkQueue, notionRequestBody, timeoutId) {
181
+ let responseReceived = false;
182
+
183
+ const isStreamClosed = () => {
184
+ return chunkQueue.destroyed || (typeof chunkQueue.closed === 'boolean' && chunkQueue.closed);
185
+ };
186
+
187
+ const safeWrite = (data) => {
188
+ if (!isStreamClosed()) {
189
+ try {
190
+ return chunkQueue.write(data);
191
+ } catch (error) {
192
+ logger.error(`流写入错误: ${error.message}`);
193
+ return false;
194
+ }
195
+ }
196
+ return false;
197
+ };
198
+
199
+ try {
200
+ const cookieData = cookieManager.getNext();
201
+ if (!cookieData) {
202
+ throw new Error('没有可用的cookie');
203
+ }
204
+
205
+ const headers = {
206
+ 'Content-Type': 'application/json',
207
+ 'accept': 'application/x-ndjson',
208
+ 'accept-language': 'en-US,en;q=0.9',
209
+ 'notion-audit-log-platform': 'web',
210
+ 'notion-client-version': '23.13.0.3686',
211
+ 'origin': 'https://www.notion.so',
212
+ 'referer': 'https://www.notion.so/chat',
213
+ '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',
214
+ 'x-notion-active-user-header': cookieData.userId,
215
+ 'x-notion-space-id': cookieData.spaceId,
216
+ 'Cookie': cookieData.cookie
217
+ };
218
+
219
+ const fetchOptions = {
220
+ method: 'POST',
221
+ headers: headers,
222
+ body: JSON.stringify(notionRequestBody),
223
+ };
224
+
225
+ // 添加代理配置
226
+ if (PROXY_URL) {
227
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
228
+ fetchOptions.agent = new HttpsProxyAgent(PROXY_URL);
229
+ logger.info(`使用固定代理连接Notion API`);
230
+ }
231
+
232
+ const response = await fetch('https://www.notion.so/api/v3/runInferenceTranscript', fetchOptions);
233
+
234
+ if (response.status === 401) {
235
+ logger.error(`收到401未授权错误,cookie可能已失效`);
236
+ cookieManager.markAsInvalid(cookieData.userId);
237
+ throw new Error('Cookie已失效');
238
+ }
239
+
240
+ if (!response.ok) {
241
+ throw new Error(`HTTP error! status: ${response.status}`);
242
+ }
243
+
244
+ if (!response.body) {
245
+ throw new Error("Response body is null");
246
+ }
247
+
248
+ const reader = response.body;
249
+ let buffer = '';
250
+
251
+ reader.on('data', (chunk) => {
252
+ if (isStreamClosed()) {
253
+ try {
254
+ reader.destroy();
255
+ } catch (error) {
256
+ logger.error(`销毁reader时出错: ${error.message}`);
257
+ }
258
+ return;
259
+ }
260
+
261
+ try {
262
+ if (!responseReceived) {
263
+ responseReceived = true;
264
+ logger.info(`已连接Notion API`);
265
+ clearTimeout(timeoutId);
266
+ }
267
+
268
+ const text = chunk.toString('utf8');
269
+ buffer += text;
270
+
271
+ const lines = buffer.split('\n');
272
+ buffer = lines.pop() || '';
273
+
274
+ for (const line of lines) {
275
+ if (!line.trim()) continue;
276
+
277
+ try {
278
+ const jsonData = JSON.parse(line);
279
+
280
+ if (jsonData?.type === "markdown-chat" && typeof jsonData?.value === "string") {
281
+ const content = jsonData.value;
282
+ if (!content) continue;
283
+
284
+ const chunk = new ChatCompletionChunk({
285
+ choices: [
286
+ new Choice({
287
+ delta: new ChoiceDelta({ content }),
288
+ finish_reason: null
289
+ })
290
+ ]
291
+ });
292
+
293
+ const dataStr = `data: ${JSON.stringify(chunk)}\n\n`;
294
+ if (!safeWrite(dataStr)) {
295
+ try {
296
+ reader.destroy();
297
+ } catch (error) {
298
+ logger.error(`写入失败后销毁reader时出错: ${error.message}`);
299
+ }
300
+ return;
301
+ }
302
+ }
303
+ } catch (parseError) {
304
+ logger.error(`解析JSON失败: ${parseError.message}`);
305
+ }
306
+ }
307
+ } catch (error) {
308
+ logger.error(`处理数据块时出错: ${error.message}`);
309
+ }
310
+ });
311
+
312
+ reader.on('end', () => {
313
+ logger.info('Notion API响应流结束');
314
+ clearTimeout(timeoutId);
315
+
316
+ try {
317
+ const endChunk = new ChatCompletionChunk({
318
+ choices: [
319
+ new Choice({
320
+ delta: new ChoiceDelta({ content: "" }),
321
+ finish_reason: "stop"
322
+ })
323
+ ]
324
+ });
325
+ safeWrite(`data: ${JSON.stringify(endChunk)}\n\n`);
326
+ safeWrite('data: [DONE]\n\n');
327
+ } catch (error) {
328
+ logger.error(`发送结束标记时出错: ${error.message}`);
329
+ }
330
+
331
+ if (!isStreamClosed()) {
332
+ chunkQueue.end();
333
+ }
334
+ });
335
+
336
+ reader.on('error', (error) => {
337
+ logger.error(`读取响应流时出错: ${error.message}`);
338
+ clearTimeout(timeoutId);
339
+ if (!isStreamClosed()) {
340
+ chunkQueue.end();
341
+ }
342
+ });
343
+
344
+ } catch (error) {
345
+ logger.error(`请求Notion API失败: ${error.message}`);
346
+ clearTimeout(timeoutId);
347
+ if (!isStreamClosed()) {
348
+ chunkQueue.end();
349
+ }
350
+ throw error;
351
+ }
352
+ }
353
+
354
  // 构建Notion请求的函数
355
  function buildNotionRequest(requestData) {
356
  const cookieData = cookieManager.getNext();
 
760
  logger.warning(`服务已在 0.0.0.0:${PORT} 上启动(初始化失败模式)`);
761
  });
762
  });