chb2024 commited on
Commit
bb489f8
·
verified ·
1 Parent(s): 960ae3f

Create index.js

Browse files
Files changed (1) hide show
  1. index.js +708 -0
index.js ADDED
@@ -0,0 +1,708 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import fetch from 'node-fetch';
3
+ import FormData from 'form-data';
4
+ import dotenv from 'dotenv';
5
+ import cors from 'cors';
6
+ import puppeteer from 'puppeteer';
7
+ import { v4 as uuidv4 } from 'uuid';
8
+
9
+ dotenv.config();
10
+ // 配置常量
11
+ const CONFIG = {
12
+ MODELS: {
13
+ 'grok-latest': 'grok-latest',
14
+ 'grok-latest-image': 'grok-latest',
15
+ 'grok-latest-search': 'grok-latest'
16
+ },
17
+ API: {
18
+ BASE_URL: "https://grok.com",
19
+ API_KEY: process.env.API_KEY || "sk-123456",
20
+ SSO_TOKEN: null,//登录时才有的认证cookie,这里暂时用不到,之后可能需要
21
+ SIGNATURE_COOKIE: null,
22
+ PICGO_KEY: process.env.PICGO_KEY || null //想要生图的话需要填入这个PICGO图床的key
23
+ },
24
+ SERVER: {
25
+ PORT: process.env.PORT || 3000,
26
+ BODY_LIMIT: '5mb'
27
+ },
28
+ RETRY: {
29
+ MAX_ATTEMPTS: 1,//重试次数
30
+ DELAY_BASE: 1000 // 基础延迟时间(毫秒)
31
+ },
32
+ ISSHOW_SEARCH_RESULTS: process.env.ISSHOW_SEARCH_RESULTS === 'true',//是否显示搜索结果,默认关闭
33
+ CHROME_PATH: process.env.CHROME_PATH || "/usr/bin/chromium"//chrome路径
34
+ };
35
+
36
+ // 请求头配置
37
+ const DEFAULT_HEADERS = {
38
+ 'accept': '*/*',
39
+ 'accept-language': 'zh-CN,zh;q=0.9',
40
+ 'accept-encoding': 'gzip, deflate, br, zstd',
41
+ 'content-type': 'text/plain;charset=UTF-8',
42
+ 'Connection': 'keep-alive',
43
+ 'origin': 'https://grok.com',
44
+ 'priority': 'u=1, i',
45
+ 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
46
+ 'sec-ch-ua-mobile': '?0',
47
+ 'sec-ch-ua-platform': '"Windows"',
48
+ 'sec-fetch-dest': 'empty',
49
+ 'sec-fetch-mode': 'cors',
50
+ 'sec-fetch-site': 'same-origin',
51
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
52
+ 'baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
53
+ };
54
+
55
+ class Utils {
56
+ static async extractGrokHeaders() {
57
+ console.log("开始提取头信息");
58
+ try {
59
+ // 启动浏览器
60
+ const browser = await puppeteer.launch({
61
+ headless: true,
62
+ args: [
63
+ '--no-sandbox',
64
+ '--disable-setuid-sandbox',
65
+ '--disable-dev-shm-usage',
66
+ '--disable-gpu'
67
+ ],
68
+ executablePath: CONFIG.CHROME_PATH
69
+ });
70
+
71
+ const page = await browser.newPage();
72
+ await page.goto('https://grok.com/', { waitUntil: 'networkidle0' });
73
+
74
+ // 获取所有 Cookies
75
+ const cookies = await page.cookies();
76
+ const targetHeaders = ['x-anonuserid', 'x-challenge', 'x-signature'];
77
+ const extractedHeaders = {};
78
+ // 遍历 Cookies
79
+ for (const cookie of cookies) {
80
+ // 检查是否为目标头信息
81
+ if (targetHeaders.includes(cookie.name.toLowerCase())) {
82
+ extractedHeaders[cookie.name.toLowerCase()] = cookie.value;
83
+ }
84
+ }
85
+ // 关闭浏览器
86
+ await browser.close();
87
+ // 打印并返回提取的头信息
88
+ console.log('提取的头信息:', JSON.stringify(extractedHeaders, null, 2));
89
+ return extractedHeaders;
90
+
91
+ } catch (error) {
92
+ console.error('获取头信息出错:', error);
93
+ return null;
94
+ }
95
+ }
96
+ static async get_signature() {
97
+ if (CONFIG.API.SIGNATURE_COOKIE) {
98
+ return CONFIG.API.SIGNATURE_COOKIE;
99
+ }
100
+ console.log("刷新认证信息");
101
+ let retryCount = 0;
102
+ while (retryCount < CONFIG.RETRY.MAX_ATTEMPTS) {
103
+ let headers = await Utils.extractGrokHeaders();
104
+ if (headers) {
105
+ console.log("获取认证信息成功");
106
+ CONFIG.API.SIGNATURE_COOKIE = { cookie: `x-anonuserid=${headers["x-anonuserid"]}; x-challenge=${headers["x-challenge"]}; x-signature=${headers["x-signature"]}` };
107
+ return CONFIG.API.SIGNATURE_COOKIE;
108
+ }
109
+ retryCount++;
110
+ if (retryCount >= CONFIG.RETRY.MAX_ATTEMPTS) {
111
+ throw new Error(`获取认证信息失败!`);
112
+ }
113
+ await new Promise(resolve => setTimeout(resolve, CONFIG.RETRY.DELAY_BASE * retryCount));
114
+ }
115
+ }
116
+ static async handleError(error, res) {
117
+ // 如果是500错误且提供了原始请求函数,尝试重新获取签名并重试
118
+ if (String(error).includes("status: 500")) {
119
+ try {
120
+ await Utils.get_signature();
121
+ if (CONFIG.API.SIGNATURE_COOKIE) {
122
+ return res.status(500).json({
123
+ error: {
124
+ message: `${retryError.message}已重新刷新认证信息,请重新对话`,
125
+ type: 'server_error',
126
+ param: null,
127
+ code: error.code || null
128
+ }
129
+ });
130
+ } else {
131
+ return res.status(500).json({
132
+ error: {
133
+ message: "认证信息获取失败",
134
+ type: 'server_error',
135
+ param: null,
136
+ code: null
137
+ }
138
+ });
139
+ }
140
+ } catch (retryError) {
141
+ console.error('重试失败:', retryError);
142
+ return res.status(500).json({
143
+ error: {
144
+ message: `${retryError.message},认证信息获取失败`,
145
+ type: 'server_error',
146
+ param: null,
147
+ code: retryError.code || null
148
+ }
149
+ });
150
+ }
151
+ }
152
+
153
+ // 其他错误直接返回
154
+ res.status(500).json({
155
+ error: {
156
+ message: error.message,
157
+ type: 'server_error',
158
+ param: null,
159
+ code: error.code || null
160
+ }
161
+ });
162
+ }
163
+ static async organizeSearchResults(searchResults) {
164
+ // 确保传入的是有效的搜索结果对象
165
+ if (!searchResults || !searchResults.results) {
166
+ return '';
167
+ }
168
+
169
+ const results = searchResults.results;
170
+ const formattedResults = results.map((result, index) => {
171
+ // 处理可能为空的字段
172
+ const title = result.title || '未知标题';
173
+ const url = result.url || '#';
174
+ const preview = result.preview || '无预览内容';
175
+
176
+ return `\r\n<details><summary>资料[${index}]: ${title}</summary>\r\n${preview}\r\n\n[Link](${url})\r\n</details>`;
177
+ });
178
+ return formattedResults.join('\n\n');
179
+ }
180
+ }
181
+
182
+
183
+ class GrokApiClient {
184
+ constructor(modelId) {
185
+ if (!CONFIG.MODELS[modelId]) {
186
+ throw new Error(`不支持的模型: ${modelId}`);
187
+ }
188
+ this.modelId = CONFIG.MODELS[modelId];
189
+ }
190
+
191
+ processMessageContent(content) {
192
+ if (typeof content === 'string') return content;
193
+ return null;
194
+ }
195
+ // 获取图片类型
196
+ getImageType(base64String) {
197
+ let mimeType = 'image/jpeg';
198
+ if (base64String.includes('data:image')) {
199
+ const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
200
+ if (matches) {
201
+ mimeType = matches[1];
202
+ }
203
+ }
204
+ const extension = mimeType.split('/')[1];
205
+ const fileName = `image.${extension}`;
206
+
207
+ return {
208
+ mimeType: mimeType,
209
+ fileName: fileName
210
+ };
211
+ }
212
+
213
+ async uploadBase64Image(base64Data, url) {
214
+ try {
215
+ // 处理 base64 数据
216
+ let imageBuffer;
217
+ if (base64Data.includes('data:image')) {
218
+ imageBuffer = base64Data.split(',')[1];
219
+ } else {
220
+ imageBuffer = base64Data
221
+ }
222
+ const { mimeType, fileName } = this.getImageType(base64Data);
223
+ let uploadData = {
224
+ rpc: "uploadFile",
225
+ req: {
226
+ fileName: fileName,
227
+ fileMimeType: mimeType,
228
+ content: imageBuffer
229
+ }
230
+ };
231
+ console.log("发送图片请求");
232
+ // 发送请求
233
+ const response = await fetch(url, {
234
+ method: 'POST',
235
+ headers: {
236
+ ...CONFIG.DEFAULT_HEADERS,
237
+ ...CONFIG.API.SIGNATURE_COOKIE
238
+ },
239
+ body: JSON.stringify(uploadData)
240
+ });
241
+
242
+ if (!response.ok) {
243
+ console.error(`上传图片失败,状态码:${response.status},原因:${response.error}`);
244
+ return '';
245
+ }
246
+
247
+ const result = await response.json();
248
+ console.log('上传图片成功:', result);
249
+ return result.fileMetadataId;
250
+
251
+ } catch (error) {
252
+ console.error('上传图片失败:', error);
253
+ return '';
254
+ }
255
+ }
256
+
257
+ async prepareChatRequest(request) {
258
+
259
+ if (request.model === 'grok-latest-image' && !CONFIG.API.PICGO_KEY) {
260
+ throw new Error(`该模型需要配置PICGO图床密钥!`);
261
+ }
262
+ var todoMessages = request.messages;
263
+ if (request.model === 'grok-latest-image' || request.model === 'grok-latest-search') {
264
+ todoMessages = Array.isArray(todoMessages) ? [todoMessages[todoMessages.length - 1]] : [todoMessages];;
265
+ }
266
+ let fileAttachments = [];
267
+ let messages = '';
268
+ let lastRole = null;
269
+ let lastContent = '';
270
+ let search = false;
271
+
272
+ const processImageUrl = async (content) => {
273
+ if (content.type === 'image_url' && content.image_url.url.includes('data:image')) {
274
+ const imageResponse = await this.uploadBase64Image(
275
+ content.image_url.url,
276
+ `${CONFIG.API.BASE_URL}/api/rpc`
277
+ );
278
+ return imageResponse;
279
+ }
280
+ return null;
281
+ };
282
+
283
+ for (const current of todoMessages) {
284
+ const role = current.role === 'assistant' ? 'assistant' : 'user';
285
+ let textContent = '';
286
+ // 处理消息内容
287
+ if (Array.isArray(current.content)) {
288
+ // 处理数组内的所有内容
289
+ for (const item of current.content) {
290
+ if (item.type === 'image_url') {
291
+ // 如果是图片且是最后一条消息,则处理图片
292
+ if (current === todoMessages[todoMessages.length - 1]) {
293
+ const processedImage = await processImageUrl(item);
294
+ if (processedImage) fileAttachments.push(processedImage);
295
+ }
296
+ textContent += (textContent ? '\n' : '') + "[图片]";//图片占位符
297
+ } else if (item.type === 'text') {
298
+ textContent += (textContent ? '\n' : '') + item.text;
299
+ }
300
+ }
301
+ } else if (typeof current.content === 'object' && current.content !== null) {
302
+ // 处理单个对象内容
303
+ if (current.content.type === 'image_url') {
304
+ // 如果是图片且是最后一条消息,则处理图片
305
+ if (current === todoMessages[todoMessages.length - 1]) {
306
+ const processedImage = await processImageUrl(current.content);
307
+ if (processedImage) fileAttachments.push(processedImage);
308
+ }
309
+ textContent += (textContent ? '\n' : '') + "[图片]";//图片占位符
310
+ } else if (current.content.type === 'text') {
311
+ textContent = current.content.text;
312
+ }
313
+ } else {
314
+ // 处理普通文本内容
315
+ textContent = this.processMessageContent(current.content);
316
+ }
317
+ // 添加文本内容到消息字符串
318
+ if (textContent) {
319
+ if (role === lastRole) {
320
+ // 如果角色相同,合并消息内容
321
+ lastContent += '\n' + textContent;
322
+ messages = messages.substring(0, messages.lastIndexOf(`${role.toUpperCase()}: `)) +
323
+ `${role.toUpperCase()}: ${lastContent}\n`;
324
+ } else {
325
+ // 如果角色不同,添加新的消息
326
+ messages += `${role.toUpperCase()}: ${textContent}\n`;
327
+ lastContent = textContent;
328
+ lastRole = role;
329
+ }
330
+ } else if (current === todoMessages[todoMessages.length - 1] && fileAttachments.length > 0) {
331
+ // 如果是最后一条消息且有图片附件,添加空消息占位
332
+ messages += `${role.toUpperCase()}: [图片]\n`;
333
+ }
334
+ }
335
+
336
+ if (fileAttachments.length > 4) {
337
+ fileAttachments = fileAttachments.slice(0, 4); // 最多上传4张
338
+ }
339
+
340
+ messages = messages.trim();
341
+
342
+ if (request.model === 'grok-latest-search') {
343
+ search = true;
344
+ }
345
+ return {
346
+ message: messages,
347
+ modelName: this.modelId,
348
+ disableSearch: false,
349
+ imageAttachments: [],
350
+ returnImageBytes: false,
351
+ returnRawGrokInXaiRequest: false,
352
+ fileAttachments: fileAttachments,
353
+ enableImageStreaming: false,
354
+ imageGenerationCount: 1,
355
+ toolOverrides: {
356
+ imageGen: request.model === 'grok-latest-image',
357
+ webSearch: search,
358
+ xSearch: search,
359
+ xMediaSearch: search,
360
+ trendsSearch: search,
361
+ xPostAnalyze: search
362
+ }
363
+ };
364
+ }
365
+ }
366
+
367
+ class MessageProcessor {
368
+ static createChatResponse(message, model, isStream = false) {
369
+ const baseResponse = {
370
+ id: `chatcmpl-${uuidv4()}`,
371
+ created: Math.floor(Date.now() / 1000),
372
+ model: model
373
+ };
374
+
375
+ if (isStream) {
376
+ return {
377
+ ...baseResponse,
378
+ object: 'chat.completion.chunk',
379
+ choices: [{
380
+ index: 0,
381
+ delta: {
382
+ content: message
383
+ }
384
+ }]
385
+ };
386
+ }
387
+
388
+ return {
389
+ ...baseResponse,
390
+ object: 'chat.completion',
391
+ choices: [{
392
+ index: 0,
393
+ message: {
394
+ role: 'assistant',
395
+ content: message
396
+ },
397
+ finish_reason: 'stop'
398
+ }],
399
+ usage: null
400
+ };
401
+ }
402
+ }
403
+
404
+ // 中间件配置
405
+ const app = express();
406
+ app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
407
+ app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
408
+ app.use(cors({
409
+ origin: '*',
410
+ methods: ['GET', 'POST', 'OPTIONS'],
411
+ allowedHeaders: ['Content-Type', 'Authorization']
412
+ }));
413
+ // API路由
414
+ app.get('/hf/v1/models', (req, res) => {
415
+ res.json({
416
+ object: "list",
417
+ data: Object.keys(CONFIG.MODELS).map((model, index) => ({
418
+ id: model,
419
+ object: "model",
420
+ created: Math.floor(Date.now() / 1000),
421
+ owned_by: "xai",
422
+ }))
423
+ });
424
+ });
425
+
426
+ app.post('/hf/v1/chat/completions', async (req, res) => {
427
+ const authToken = req.headers.authorization?.replace('Bearer ', '');
428
+ if (authToken !== CONFIG.API.API_KEY) {
429
+ return res.status(401).json({ error: 'Unauthorized' });
430
+ }
431
+ const makeRequest = async () => {
432
+ if (!CONFIG.API.SIGNATURE_COOKIE) {
433
+ await Utils.get_signature();
434
+ }
435
+ const grokClient = new GrokApiClient(req.body.model);
436
+ const requestPayload = await grokClient.prepareChatRequest(req.body);
437
+ //创建新对话
438
+ const newMessageReq = await fetch(`${CONFIG.API.BASE_URL}/api/rpc`, {
439
+ method: 'POST',
440
+ headers: {
441
+ ...DEFAULT_HEADERS,
442
+ ...CONFIG.API.SIGNATURE_COOKIE
443
+ },
444
+ body: JSON.stringify({
445
+ rpc: "createConversation",
446
+ req: {
447
+ temporary: false
448
+ }
449
+ })
450
+ });
451
+ if (!newMessageReq.ok) {
452
+ throw new Error(`上游服务请求失败! status: ${newMessageReq.status}`);
453
+ }
454
+
455
+ // 获取响应文本
456
+ const responseText = await newMessageReq.json();
457
+ const conversationId = responseText.conversationId;
458
+ console.log("会话ID:conversationId", conversationId);
459
+ if (!conversationId) {
460
+ throw new Error(`创建会话失败! status: ${newMessageReq.status}`);
461
+ }
462
+ //发送对话
463
+ const response = await fetch(`${CONFIG.API.BASE_URL}/api/conversations/${conversationId}/responses`, {
464
+ method: 'POST',
465
+ headers: {
466
+ "accept": "text/event-stream",
467
+ "baggage": "sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c",
468
+ "content-type": "text/plain;charset=UTF-8",
469
+ "Connection": "keep-alive",
470
+ ...CONFIG.API.SIGNATURE_COOKIE
471
+ },
472
+ body: JSON.stringify(requestPayload)
473
+ });
474
+
475
+ if (!response.ok) {
476
+ throw new Error(`上游服务请求失败! status: ${response.status}`);
477
+ }
478
+ if (req.body.stream) {
479
+ await handleStreamResponse(response, req.body.model, res);
480
+ } else {
481
+ await handleNormalResponse(response, req.body.model, res);
482
+ }
483
+ }
484
+ try {
485
+ await makeRequest();
486
+ } catch (error) {
487
+ CONFIG.API.SIGNATURE_COOKIE = null;
488
+ await Utils.handleError(error, res);
489
+ }
490
+ });
491
+
492
+ async function handleStreamResponse(response, model, res) {
493
+ res.setHeader('Content-Type', 'text/event-stream');
494
+ res.setHeader('Cache-Control', 'no-cache');
495
+ res.setHeader('Connection', 'keep-alive');
496
+
497
+ try {
498
+ const stream = response.body;
499
+ let buffer = '';
500
+
501
+ stream.on('data', async (chunk) => {
502
+ buffer += chunk.toString();
503
+ const lines = buffer.split('\n');
504
+ buffer = lines.pop() || '';
505
+
506
+ for (const line of lines) {
507
+ if (!line.trim()) continue;
508
+ const trimmedLine = line.trim();
509
+ if (trimmedLine.startsWith('data: ')) {
510
+ const data = trimmedLine.substring(6);
511
+ if(data === "[DONE]"){
512
+ console.log("流结束");
513
+ res.write('data: [DONE]\n\n');
514
+ return res.end();
515
+ };
516
+ try {
517
+ if(!data.trim())continue;
518
+ const linejosn = JSON.parse(data);
519
+ if (linejosn?.error?.name === "RateLimitError") {
520
+ var responseData = MessageProcessor.createChatResponse(`${linejosn.error.name},请重新对话`, model, true);
521
+ CONFIG.API.SIGNATURE_COOKIE = null;
522
+ console.log("认证信息已删除");
523
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
524
+ res.write('data: [DONE]\n\n');
525
+ return res.end();
526
+ }
527
+ switch (model) {
528
+ case 'grok-latest-image':
529
+ if (linejosn.response === "modelResponse" && linejosn?.modelResponse?.generatedImageUrls) {
530
+ const dataImage = await handleImageResponse(linejosn.modelResponse.generatedImageUrls);
531
+ const responseData = MessageProcessor.createChatResponse(dataImage, model, true);
532
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
533
+ }
534
+ break;
535
+ case 'grok-latest':
536
+ if (linejosn.response === "token") {
537
+ const token = linejosn.token;
538
+ if (token && token.length > 0) {
539
+ const responseData = MessageProcessor.createChatResponse(token, model, true);
540
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
541
+ }
542
+ }
543
+ break;
544
+ case 'grok-latest-search':
545
+ if (linejosn.response === "token") {
546
+ const token = linejosn.token;
547
+ if (token && token.length > 0) {
548
+ const responseData = MessageProcessor.createChatResponse(token, model, true);
549
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
550
+ }
551
+ }
552
+ if (linejosn.response === "webSearchResults" && CONFIG.ISSHOW_SEARCH_RESULTS) {
553
+ const searchResults = await Utils.organizeSearchResults(linejosn.webSearchResults);
554
+ const responseData = MessageProcessor.createChatResponse(`<think>\r\n${searchResults}\r\n</think>\r\n`, model, true);
555
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
556
+ }
557
+ break;
558
+ }
559
+ } catch (error) {
560
+ console.error('JSON解析错误:', error);
561
+ }
562
+ }
563
+ }
564
+ });
565
+ stream.on('error', (error) => {
566
+ console.error('流处理错误:', error);
567
+ res.write('data: [DONE]\n\n');
568
+ res.end();
569
+ });
570
+
571
+ } catch (error) {
572
+ console.error('处理响应错误:', error);
573
+ res.write('data: [DONE]\n\n');
574
+ res.end();
575
+ }
576
+ }
577
+
578
+ async function handleNormalResponse(response, model, res) {
579
+ let fullResponse = '';
580
+ let imageUrl = '';
581
+
582
+ try {
583
+ const responseText = await response.text();
584
+ const lines = responseText.split('\n');
585
+
586
+ for (const line of lines) {
587
+ if (!line.trim()) continue;
588
+ const data = line.slice(6);
589
+ if (data === '[DONE]') continue;
590
+ let linejosn = JSON.parse(data);
591
+ if (linejosn?.error?.name === "RateLimitError") {
592
+ fullResponse = `${linejosn.error.name},请重新对话`;
593
+ CONFIG.API.SIGNATURE_COOKIE = null;
594
+ return res.json(MessageProcessor.createChatResponse(fullResponse, model));
595
+ }
596
+ switch (model) {
597
+ case 'grok-latest-image':
598
+ if (linejosn.response === "modelResponse" && linejosn?.modelResponse?.generatedImageUrls) {
599
+ imageUrl = linejosn.modelResponse.generatedImageUrls;
600
+ }
601
+ break;
602
+ case 'grok-latest':
603
+ if (linejosn.response === "token") {
604
+ const token = linejosn.token;
605
+ if (token && token.length > 0) {
606
+ fullResponse += token;
607
+ }
608
+ }
609
+ break;
610
+ case 'grok-latest-search':
611
+ if (linejosn.response === "token") {
612
+ const token = linejosn.token;
613
+ if (token && token.length > 0) {
614
+ fullResponse += token;
615
+ }
616
+ }
617
+ if (linejosn.response === "webSearchResults" && CONFIG.ISSHOW_SEARCH_RESULTS) {
618
+ fullResponse += `\r\n<think>${await Utils.organizeSearchResults(linejosn.webSearchResults)}</think>\r\n`;
619
+ }
620
+ break;
621
+ }
622
+ }
623
+ if (imageUrl) {
624
+ const dataImage = await handleImageResponse(imageUrl);
625
+ const responseData = MessageProcessor.createChatResponse(dataImage, model);
626
+ res.json(responseData);
627
+ } else {
628
+ const responseData = MessageProcessor.createChatResponse(fullResponse, model);
629
+ res.json(responseData);
630
+ }
631
+ } catch (error) {
632
+ Utils.handleError(error, res);
633
+ }
634
+ }
635
+ async function handleImageResponse(imageUrl) {
636
+ //对服务器发送图片请求
637
+ const MAX_RETRIES = 3;
638
+ let retryCount = 0;
639
+ let imageBase64Response;
640
+
641
+ while (retryCount < MAX_RETRIES) {
642
+ try {
643
+ //发送图片请求获取图片
644
+ imageBase64Response = await fetch(`https://assets.grok.com/${imageUrl}`, {
645
+ method: 'GET',
646
+ headers: {
647
+ ...DEFAULT_HEADERS,
648
+ ...CONFIG.API.SIGNATURE_COOKIE
649
+ }
650
+ });
651
+
652
+ if (imageBase64Response.ok) {
653
+ break; // 如果请求成功,跳出重试循环
654
+ }
655
+
656
+ retryCount++;
657
+ if (retryCount === MAX_RETRIES) {
658
+ throw new Error(`上游服务请求失败! status: ${imageBase64Response.status}`);
659
+ }
660
+
661
+ // 等待一段时间后重试(可以使用指数退避)
662
+ await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
663
+
664
+ } catch (error) {
665
+ retryCount++;
666
+ if (retryCount === MAX_RETRIES) {
667
+ throw error;
668
+ }
669
+ // 等待一段时间后重试
670
+ await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
671
+ }
672
+ }
673
+
674
+ const arrayBuffer = await imageBase64Response.arrayBuffer();
675
+ const imageBuffer = Buffer.from(arrayBuffer);
676
+ const formData = new FormData();
677
+
678
+ formData.append('source', imageBuffer, {
679
+ filename: 'new.jpg',
680
+ contentType: 'image/jpeg'
681
+ });
682
+ const formDataHeaders = formData.getHeaders();
683
+ const responseURL = await fetch("https://www.picgo.net/api/1/upload", {
684
+ method: "POST",
685
+ headers: {
686
+ ...formDataHeaders,
687
+ "Content-Type": "multipart/form-data",
688
+ "X-API-Key": CONFIG.API.PICGO_KEY
689
+ },
690
+ body: formData
691
+ });
692
+ if (!responseURL.ok) {
693
+ return "生图失败,请查看图床密钥是否设置正确"
694
+ } else {
695
+ const result = await responseURL.json();
696
+ return `![image](${result.image.url})`
697
+ }
698
+ }
699
+
700
+ // 404处理
701
+ app.use((req, res) => {
702
+ res.status(200).send('api运行正常');
703
+ });
704
+
705
+ // 启动服务器
706
+ app.listen(CONFIG.SERVER.PORT, () => {
707
+ console.log(`服务器已启动,监听端口: ${CONFIG.SERVER.PORT}`);
708
+ });