iseehf commited on
Commit
74d95f1
·
verified ·
1 Parent(s): c3be65c

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +32 -13
  2. index.js +720 -403
  3. logger.js +1 -1
  4. package.json +22 -19
Dockerfile CHANGED
@@ -1,13 +1,32 @@
1
- FROM node:lts-alpine
2
-
3
- WORKDIR /app
4
-
5
- COPY package*.json ./
6
-
7
- RUN npm install
8
-
9
- COPY . .
10
- ENV PORT=7860
11
- EXPOSE 7860
12
-
13
- CMD ["npm", "start"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-slim
2
+
3
+ # 安装 Chrome 依赖
4
+ RUN apt-get update && apt-get install -y \
5
+ wget \
6
+ gnupg \
7
+ ca-certificates \
8
+ procps \
9
+ chromium \
10
+ chromium-sandbox
11
+
12
+ # 设置工作目录
13
+ WORKDIR /app
14
+
15
+ # 复制 package.json 和 package-lock.json
16
+ COPY package*.json ./
17
+
18
+ # 安装依赖
19
+ RUN npm install
20
+
21
+ # 复制源代码
22
+ COPY . .
23
+
24
+ # 设置环境变量
25
+ ENV CHROME_PATH=/usr/bin/chromium
26
+ ENV PORT=7860
27
+
28
+ # 暴露端口
29
+ EXPOSE 7860
30
+
31
+ # 启动应用
32
+ CMD ["npm", "start"]
index.js CHANGED
@@ -1,527 +1,848 @@
1
  import express from 'express';
2
- import FormData from 'form-data';
3
- import { v4 as uuidv4 } from 'uuid';
4
  import fetch from 'node-fetch';
 
 
5
  import cors from 'cors';
 
 
 
6
  import Logger from './logger.js';
7
- import dotenv from 'dotenv';
8
 
9
- // 初始化环境变量
10
  dotenv.config();
11
 
12
- // 配置管理
13
  const CONFIG = {
14
- SERVER: {
15
- PORT: process.env.PORT || 25526,
16
- BODY_LIMIT: '5mb',
17
- CORS_OPTIONS: {
18
- origin: '*',
19
- methods: ['GET', 'POST', 'OPTIONS'],
20
- allowedHeaders: ['Content-Type', 'Authorization'],
21
- credentials: true
22
- }
23
  },
24
  API: {
 
25
  API_KEY: process.env.API_KEY || "sk-123456",
26
- AUTH_TOKEN: process.env.AUTH_TOKEN,
27
- CT0: process.env.CT0,
28
- ENDPOINTS: {
29
- CHAT: 'https://grok.x.com/2/grok/add_response.json',
30
- CREATE_CONVERSATION: 'https://x.com/i/api/graphql/vvC5uy7pWWHXS2aDi1FZeA/CreateGrokConversation',
31
- DELETE_CONVERSATION: 'https://x.com/i/api/graphql/TlKHSWVMVeaa-i7dqQqFQA/ConversationItem_DeleteConversationMutation',
32
- UPLOAD_IMAGE: 'https://x.com/i/api/2/grok/attachment.json'
33
- }
34
  },
35
- MODELS: {
36
- "grok-3": "grok-3",
37
- "grok-3-deepsearch": "grok-3",
38
- "grok-3-reasoning": "grok-3",
39
- "grok-3-imageGen": "grok-3",
 
40
  },
 
 
41
  IS_IMG_GEN: false,
42
- IS_THINKING: false
 
 
 
43
  };
44
-
45
- // HTTP 请求头配置
46
  const DEFAULT_HEADERS = {
47
- 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
48
- 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
49
- 'sec-ch-ua-mobile': '?0',
50
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
51
  'accept': '*/*',
 
 
52
  'content-type': 'text/plain;charset=UTF-8',
53
- 'origin': 'https://x.com',
54
- 'sec-fetch-site': 'same-site',
 
 
 
 
 
55
  'sec-fetch-mode': 'cors',
56
- 'accept-encoding': 'gzip, deflate, br, zstd',
57
- 'accept-language': 'zh-CN,zh;q=0.9',
58
- 'priority': 'u=1, i'
59
  };
60
 
61
- // 工具类
62
- class Utils {
63
- static generateRandomString(length, charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
64
- return Array(length).fill(null)
65
- .map(() => charset[Math.floor(Math.random() * charset.length)])
66
- .join('');
67
- }
68
 
69
- static createAuthHeaders() {
70
- return {
71
- ...DEFAULT_HEADERS,
72
- 'x-csrf-token': CONFIG.API.CT0,
73
- 'cookie': `auth_token=${CONFIG.API.AUTH_TOKEN};ct0=${CONFIG.API.CT0}`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  };
75
  }
76
 
77
- static getImageMimeType(base64String) {
78
- const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
79
- return matches ? matches[1] : 'image/jpeg';
 
 
 
 
 
 
80
  }
81
 
82
- static async handleApiResponse(response, errorMessage) {
83
- if (!response.ok) {
84
- throw new Error(`${errorMessage} Status: ${response.status}`);
 
85
  }
86
- return await response.json();
 
 
87
  }
88
- }
89
 
90
- // 会话管理类
91
- class ConversationManager {
92
- static async generateNewId() {
93
- const response = await fetch(CONFIG.API.ENDPOINTS.CREATE_CONVERSATION, {
94
- method: 'POST',
95
- headers: Utils.createAuthHeaders(),
96
- body: JSON.stringify({
97
- variables: {},
98
- queryId: "vvC5uy7pWWHXS2aDi1FZeA"
99
- })
100
- });
101
-
102
- const data = await Utils.handleApiResponse(response, '创建会话失败!');
103
- return data.data.create_grok_conversation.conversation_id;
104
  }
 
 
 
 
 
 
 
 
 
105
 
106
- static async deleteConversation(conversationId) {
107
- if (!conversationId) return;
108
-
109
- await fetch(CONFIG.API.ENDPOINTS.DELETE_CONVERSATION, {
110
- method: 'POST',
111
- headers: Utils.createAuthHeaders(),
112
- body: JSON.stringify({
113
- variables: { conversationId },
114
- queryId: "TlKHSWVMVeaa-i7dqQqFQA"
115
- })
116
- });
117
  }
118
- }
 
 
119
 
120
- // 消息处理类
121
- class MessageProcessor {
122
- static createChatResponse(message, model, isStream = false) {
123
- const baseResponse = {
124
- id: `chatcmpl-${uuidv4()}`,
125
- created: Math.floor(Date.now() / 1000),
126
- model: model
127
- };
128
 
129
- if (isStream) {
130
- return {
131
- ...baseResponse,
132
- object: 'chat.completion.chunk',
133
- choices: [{
134
- index: 0,
135
- delta: { content: message }
136
- }]
137
- };
138
  }
139
-
140
- return {
141
- ...baseResponse,
142
- object: 'chat.completion',
143
- choices: [{
144
- index: 0,
145
- message: {
146
- role: 'assistant',
147
- content: message
148
- },
149
- finish_reason: 'stop'
150
- }],
151
- usage: null
152
- };
153
  }
154
 
155
- static processMessageContent(content) {
156
- if (typeof content === 'string') return content;
157
- if (Array.isArray(content)) {
158
- if (content.some(item => item.type === 'image_url')) return null;
159
- return content
160
- .filter(item => item.type === 'text')
161
- .map(item => item.text)
162
- .join('\n');
163
  }
164
- if (typeof content === 'object') return content.text || null;
165
- return null;
 
 
 
166
  }
167
- }
168
 
169
- // Grok API 客户端类
170
- class TwitterGrokApiClient {
171
- constructor(modelId) {
172
- if (!CONFIG.MODELS[modelId]) {
173
- throw new Error(`不支持的模型: ${modelId}`);
174
- }
175
- this.modelId = CONFIG.MODELS[modelId];
176
- this.modelType = {
177
- isDeepSearch: modelId === 'grok-3-deepsearch',
178
- isReasoning: modelId === 'grok-3-reasoning'
179
- };
 
 
 
 
 
 
180
  }
181
 
182
- async uploadImage(imageData) {
183
- const formData = new FormData();
184
- const imageBuffer = Buffer.from(imageData.split(',')[1], 'base64');
185
- const mimeType = Utils.getImageMimeType(imageData);
186
 
187
- formData.append('photo', imageBuffer, {
188
- filename: 'image.png',
189
- contentType: mimeType
190
- });
191
 
192
- const response = await fetch(CONFIG.API.ENDPOINTS.UPLOAD_IMAGE, {
193
- method: 'POST',
194
- headers: {
195
- ...Utils.createAuthHeaders(),
196
- ...formData.getHeaders()
197
- },
198
- body: formData
199
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
- return await Utils.handleApiResponse(response, '图片上传失败');
 
 
 
202
  }
203
-
204
- async transformMessages(messages) {
205
- const processedMessages = [];
206
- for (let i = 0; i < messages.length; i++) {
207
- const isLastTwoMessages = i >= messages.length - 2;
208
- const content = await this.processMessageContent(messages[i], isLastTwoMessages);
209
- if (content) {
210
- processedMessages.push(content);
 
 
 
 
 
 
 
 
211
  }
212
  }
213
- return processedMessages;
214
  }
215
-
216
- async processMessageContent(msg, isLastTwoMessages) {
217
- const { role, content } = msg;
218
- let message = '';
219
- let fileAttachments = [];
220
-
221
- if (typeof content === 'string') {
222
- message = content;
223
- } else if (Array.isArray(content) || typeof content === 'object') {
224
- const { text, imageAttachments } = await this.processComplexContent(content, isLastTwoMessages);
225
- message = text;
226
- fileAttachments = imageAttachments;
227
  }
228
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  return {
230
- message,
231
- sender: role === 'user' ? 1 : 2,
232
- ...(role === 'user' && { fileAttachments })
233
  };
234
  }
 
235
 
236
- async processComplexContent(content, isLastTwoMessages) {
237
- let text = '';
238
- let imageAttachments = [];
239
-
240
- const processItem = async (item) => {
241
- if (item.type === 'text') {
242
- text += item.text;
243
- } else if (item.type === 'image_url' && item.image_url.url.includes('data:image')) {
244
- if (isLastTwoMessages) {
245
- const uploadResult = await this.uploadImage(item.image_url.url);
246
- if (Array.isArray(uploadResult)) {
247
- imageAttachments.push(...uploadResult);
248
- }
249
- } else {
250
- text += '[图片]';
251
- }
252
- }
253
- };
254
 
255
- if (Array.isArray(content)) {
256
- await Promise.all(content.map(processItem));
257
- } else {
258
- await processItem(content);
259
  }
260
-
261
- return { text, imageAttachments };
262
  }
263
 
264
- async prepareChatRequest(request) {
265
- const responses = await this.transformMessages(request.messages);
266
- const conversationId = await ConversationManager.generateNewId();
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  return {
269
- responses,
270
- systemPromptName: "",
271
- grokModelOptionId: this.modelId,
272
- conversationId,
273
- returnSearchResults: this.modelType.isReasoning,
274
- returnCitations: this.modelType.isReasoning,
275
- promptMetadata: {
276
- promptSource: "NATURAL",
277
- action: "INPUT"
278
- },
279
- imageGenerationCount: 1,
280
- requestFeatures: {
281
- eagerTweets: false,
282
- serverHistory: false
283
- },
284
- enableCustomization: true,
285
- enableSideBySide: false,
286
- toolOverrides: {
287
- imageGen: request.model === 'grok-3-imageGen',
288
- },
289
- isDeepsearch: this.modelType.isDeepSearch,
290
- isReasoning: this.modelType.isReasoning
291
  };
292
  }
293
- }
294
-
295
- // 响应处理类
296
- class ResponseHandler {
297
- static async handleStreamResponse(response, model, res) {
298
- res.setHeader('Content-Type', 'text/event-stream');
299
- res.setHeader('Cache-Control', 'no-cache');
300
- res.setHeader('Connection', 'keep-alive');
301
-
302
- const reader = response.body;
303
- let buffer = '';
304
- CONFIG.IS_IMG_GEN = false;
305
- CONFIG.IS_THINKING = false;
306
 
 
307
  try {
308
- for await (const chunk of reader) {
309
- const lines = (buffer + chunk.toString()).split('\n');
310
- buffer = lines.pop() || '';
311
-
312
- for (const line of lines) {
313
- if (!line.trim()) continue;
314
- await this.processStreamLine(JSON.parse(line), model, res);
 
 
 
 
 
 
 
315
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  }
317
 
318
- res.write('data: [DONE]\n\n');
319
- res.end();
 
 
320
  } catch (error) {
321
- Logger.error('Stream response error:', error, 'ChatAPI');
322
- throw error;
323
  }
324
  }
325
 
326
- static async processStreamLine(jsonData, model, res) {
327
- if (jsonData.result?.doImgGen) {
328
- CONFIG.IS_IMG_GEN = true;
329
- return;
330
  }
331
-
332
- if (CONFIG.IS_IMG_GEN && jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
333
- await this.handleImageGeneration(jsonData, model, res);
334
- return;
 
 
 
 
 
335
  }
336
-
337
- if (!CONFIG.IS_IMG_GEN && jsonData.result?.message) {
338
- await this.handleTextMessage(jsonData, model, res);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  }
 
341
 
342
- static async handleImageGeneration(jsonData, model, res) {
343
- const imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
344
- const imageResponse = await fetch(imageUrl, {
345
- method: 'GET',
346
- headers: Utils.createAuthHeaders()
347
- });
 
348
 
349
- if (!imageResponse.ok) {
350
- throw new Error(`Image request failed: ${imageResponse.status}`);
 
 
 
 
 
 
 
 
 
351
  }
352
 
353
- const imageBuffer = await imageResponse.arrayBuffer();
354
- const base64Image = Buffer.from(imageBuffer).toString('base64');
355
- const imageContentType = imageResponse.headers.get('content-type');
356
- const message = `![image](data:${imageContentType};base64,${base64Image})`;
357
-
358
- const responseData = MessageProcessor.createChatResponse(message, model, true);
359
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
 
 
 
 
 
 
360
  }
361
-
362
- static async handleTextMessage(jsonData, model, res) {
363
- let message = jsonData.result.message;
364
-
365
- switch (model) {
366
- case "grok-3-reasoning":
367
- if (!CONFIG.IS_THINKING && jsonData.result?.isThinking) {
368
- message = "<think>" + message;
369
- CONFIG.IS_THINKING = true;
370
- } else if (CONFIG.IS_THINKING && !jsonData.result?.isThinking) {
371
- message = "</think>" + message;
372
- CONFIG.IS_THINKING = false;
373
- }
374
- break;
375
- case "grok-3-deepsearch":
376
- if (jsonData.result?.messageTag !== "final") return;
377
- break;
378
  }
379
-
380
- const responseData = MessageProcessor.createChatResponse(message, model, true);
381
- res.write(`data: ${JSON.stringify(responseData)}\n\n`);
382
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
- static async handleNormalResponse(response, model, res) {
385
- const reader = response.body;
 
386
  let buffer = '';
387
  let fullResponse = '';
388
- let imageUrl = null;
389
-
390
- try {
391
- for await (const chunk of reader) {
392
- const lines = (buffer + chunk.toString()).split('\n');
 
393
  buffer = lines.pop() || '';
394
 
395
  for (const line of lines) {
396
  if (!line.trim()) continue;
397
- const result = await this.processNormalLine(JSON.parse(line), model, CONFIG.IS_THINKING);
398
- fullResponse += result.text || '';
399
- imageUrl = result.imageUrl || imageUrl;
400
- CONFIG.IS_THINKING = result.isThinking;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  }
402
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
 
404
- if (imageUrl) {
405
- await this.sendImageResponse(imageUrl, model, res);
406
- } else {
407
- const responseData = MessageProcessor.createChatResponse(fullResponse, model);
408
- res.json(responseData);
409
- }
410
- } catch (error) {
411
- Logger.error('Normal response error:', error, 'ChatAPI');
412
- throw error;
413
- }
414
  }
 
415
 
416
- static async processNormalLine(jsonData, model, isThinking) {
417
- let result = { text: '', imageUrl: null, isThinking };
418
-
419
- if (jsonData.result?.message) {
420
- switch (model) {
421
- case "grok-3-reasoning":
422
- result = this.processReasoningMessage(jsonData, isThinking);
423
- break;
424
- case "grok-3-deepsearch":
425
- if (jsonData.result?.messageTag === "final") {
426
- result.text = jsonData.result.message;
427
- }
428
- break;
429
- default:
430
- result.text = jsonData.result.message;
431
- }
432
- }
433
 
434
- if (jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
435
- result.imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
436
- }
 
 
 
 
 
 
437
 
438
- return result;
439
- }
 
 
 
 
440
 
441
- static processReasoningMessage(jsonData, isThinking) {
442
- let result = { text: '', isThinking };
443
-
444
- if (jsonData.result?.isThinking && !isThinking) {
445
- result.text = "<think>" + jsonData.result.message;
446
- result.isThinking = true;
447
- } else if (isThinking && !jsonData.result?.isThinking) {
448
- result.text = "</think>" + jsonData.result.message;
449
- result.isThinking = false;
450
- } else {
451
- result.text = jsonData.result.message;
452
  }
453
-
454
- return result;
455
  }
456
 
457
- static async sendImageResponse(imageUrl, model, res) {
458
- const response = await fetch(imageUrl, {
459
- method: 'GET',
460
- headers: Utils.createAuthHeaders()
461
- });
462
 
463
- if (!response.ok) {
464
- throw new Error(`Image request failed: ${response.status}`);
465
- }
466
 
467
- const imageBuffer = await response.arrayBuffer();
468
- const base64Image = Buffer.from(imageBuffer).toString('base64');
469
- const imageContentType = response.headers.get('content-type');
 
 
470
 
471
- const responseData = MessageProcessor.createChatResponse(
472
- `![image](data:${imageContentType};base64,${base64Image})`,
473
- model
474
- );
475
- res.json(responseData);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  }
477
  }
478
 
479
- // Express 应用配置
 
 
 
480
  const app = express();
481
  app.use(Logger.requestLogger);
482
- app.use(cors(CONFIG.SERVER.CORS_OPTIONS));
483
  app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
484
  app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
 
 
 
 
 
485
 
486
- // API 路由
487
- app.get('/v1/models', (req, res) => {
488
  res.json({
489
  object: "list",
490
- data: Object.keys(CONFIG.MODELS).map(model => ({
491
  id: model,
492
  object: "model",
493
  created: Math.floor(Date.now() / 1000),
494
- owned_by: "xgrok",
495
  }))
496
  });
497
  });
498
 
499
- app.post('/v1/chat/completions', async (req, res) => {
 
500
  try {
501
  const authToken = req.headers.authorization?.replace('Bearer ', '');
502
  if (authToken !== CONFIG.API.API_KEY) {
503
  return res.status(401).json({ error: 'Unauthorized' });
504
  }
 
 
 
 
 
 
 
 
 
 
 
505
 
506
- const grokClient = new TwitterGrokApiClient(req.body.model);
507
- const requestPayload = await grokClient.prepareChatRequest(req.body);
508
-
509
- const response = await fetch(CONFIG.API.ENDPOINTS.CHAT, {
510
- method: 'POST',
511
- headers: Utils.createAuthHeaders(),
512
- body: JSON.stringify(requestPayload)
513
- });
514
-
515
- if (!response.ok) {
516
- throw new Error(`上游服务请求失败! status: ${response.status}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  }
518
-
519
- await (req.body.stream
520
- ? ResponseHandler.handleStreamResponse(response, req.body.model, res)
521
- : ResponseHandler.handleNormalResponse(response, req.body.model, res));
522
-
523
  } catch (error) {
524
- Logger.error('Chat Completions Request Error', error, 'ChatAPI');
525
  res.status(500).json({
526
  error: {
527
  message: error.message,
@@ -530,19 +851,15 @@ app.post('/v1/chat/completions', async (req, res) => {
530
  code: error.code || null
531
  }
532
  });
533
- } finally {
534
- if (req.body.conversationId) {
535
- await ConversationManager.deleteConversation(req.body.conversationId);
536
- }
537
  }
538
  });
539
 
540
- // 404 处理
541
  app.use((req, res) => {
542
- res.status(404).json({ error: '请求路径不存在' });
543
  });
544
 
545
- // 启动服务器
546
  app.listen(CONFIG.SERVER.PORT, () => {
547
- Logger.info(`服务器运行在端口 ${CONFIG.SERVER.PORT}`, 'Server');
548
  });
 
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-extra'
7
+ import StealthPlugin from 'puppeteer-extra-plugin-stealth'
8
+ import { v4 as uuidv4 } from 'uuid';
9
  import Logger from './logger.js';
 
10
 
 
11
  dotenv.config();
12
 
13
+ // 配置常量
14
  const CONFIG = {
15
+ MODELS: {
16
+ 'grok-2': 'grok-latest',
17
+ 'grok-2-imageGen': 'grok-latest',
18
+ 'grok-2-search': 'grok-latest',
19
+ "grok-3": "grok-3",
20
+ "grok-3-imageGen": "grok-3",
21
+ "grok-3-deepsearch": "grok-3",
22
+ "grok-3-reasoning": "grok-3"
 
23
  },
24
  API: {
25
+ BASE_URL: "https://grok.com",
26
  API_KEY: process.env.API_KEY || "sk-123456",
27
+ SIGNATURE_COOKIE: null,
28
+ TEMP_COOKIE: null,
29
+ PICGO_KEY: process.env.PICGO_KEY || null //想要流式生图的话需要填入这个PICGO图床的key
 
 
 
 
 
30
  },
31
+ SERVER: {
32
+ PORT: process.env.PORT || 3000,
33
+ BODY_LIMIT: '5mb'
34
+ },
35
+ RETRY: {
36
+ MAX_ATTEMPTS: 2//重试次数
37
  },
38
+ SHOW_THINKING:process.env.SHOW_THINKING === 'true',//显示思考过程
39
+ IS_THINKING: false,
40
  IS_IMG_GEN: false,
41
+ IS_IMG_GEN2: false,
42
+ SSO_INDEX: 0,//sso的索引
43
+ ISSHOW_SEARCH_RESULTS: process.env.ISSHOW_SEARCH_RESULTS === 'true',//是否显示搜索结果,默认关闭
44
+ CHROME_PATH: process.env.CHROME_PATH || "/usr/bin/chromium"//chrome路径
45
  };
46
+ puppeteer.use(StealthPlugin())
47
+ // 请求头配置
48
  const DEFAULT_HEADERS = {
 
 
 
 
49
  'accept': '*/*',
50
+ 'accept-language': 'zh-CN,zh;q=0.9',
51
+ 'accept-encoding': 'gzip, deflate, br, zstd',
52
  'content-type': 'text/plain;charset=UTF-8',
53
+ 'Connection': 'keep-alive',
54
+ 'origin': 'https://grok.com',
55
+ 'priority': 'u=1, i',
56
+ 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
57
+ 'sec-ch-ua-mobile': '?0',
58
+ 'sec-ch-ua-platform': '"Windows"',
59
+ 'sec-fetch-dest': 'empty',
60
  'sec-fetch-mode': 'cors',
61
+ 'sec-fetch-site': 'same-origin',
62
+ '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',
63
+ 'baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
64
  };
65
 
 
 
 
 
 
 
 
66
 
67
+ async function initialization() {
68
+ const ssoArray = process.env.SSO.split(',');
69
+ const ssorwArray = process.env.SSO_RW.split(',');
70
+ ssoArray.forEach((sso, index) => {
71
+ tokenManager.addToken(`sso-rw=${ssorwArray[index]};sso=${sso}`);
72
+ });
73
+ console.log(JSON.stringify(tokenManager.getActiveTokens(), null, 2));
74
+ await Utils.get_signature()
75
+ Logger.info("初始化完成", 'Server');
76
+ }
77
+
78
+
79
+ class AuthTokenManager {
80
+ constructor() {
81
+ this.activeTokens = [];
82
+ this.expiredTokens = new Map();
83
+ this.tokenModelFrequency = new Map();
84
+ this.modelRateLimit = {
85
+ "grok-3": { RequestFrequency: 20 },
86
+ "grok-3-deepsearch": { RequestFrequency: 5 },
87
+ "grok-3-reasoning": { RequestFrequency: 5 }
88
  };
89
  }
90
 
91
+ addToken(token) {
92
+ if (!this.activeTokens.includes(token)) {
93
+ this.activeTokens.push(token);
94
+ this.tokenModelFrequency.set(token, {
95
+ "grok-3": 0,
96
+ "grok-3-deepsearch": 0,
97
+ "grok-3-reasoning": 0
98
+ });
99
+ }
100
  }
101
 
102
+ getTokenByIndex(index, model) {
103
+ if (!this.modelRateLimit[model]) return;
104
+ if(this.activeTokens.length === 0){
105
+ return null;
106
  }
107
+ const token = this.activeTokens[index];
108
+ this.recordModelRequest(token, model);
109
+ return token;
110
  }
 
111
 
112
+ recordModelRequest(token, model) {
113
+ if (!this.modelRateLimit[model]) return;
114
+ const tokenFrequency = this.tokenModelFrequency.get(token);
115
+ if (tokenFrequency && tokenFrequency[model] !== undefined) {
116
+ tokenFrequency[model]++;
117
+ }
118
+ this.checkAndRemoveTokenIfLimitReached(token);
 
 
 
 
 
 
 
119
  }
120
+ setModelLimit(index, model) {
121
+ if (!this.modelRateLimit[model]) return;
122
+ const tokenFrequency = this.tokenModelFrequency.get(this.activeTokens[index]);
123
+ tokenFrequency[model] = 9999;
124
+ }
125
+ isTokenModelLimitReached(index, model) {
126
+ if (!this.modelRateLimit[model]) return;
127
+ const token = this.activeTokens[index];
128
+ const tokenFrequency = this.tokenModelFrequency.get(token);
129
 
130
+ if (!tokenFrequency) {
131
+ return false;
132
+ }
133
+ return tokenFrequency[model] >= this.modelRateLimit[model].RequestFrequency;
 
 
 
 
 
 
 
134
  }
135
+ checkAndRemoveTokenIfLimitReached(token) {
136
+ const tokenFrequency = this.tokenModelFrequency.get(token);
137
+ if (!tokenFrequency) return;
138
 
139
+ const isLimitReached = Object.keys(tokenFrequency).every(model =>
140
+ tokenFrequency[model] >= this.modelRateLimit[model].RequestFrequency
141
+ );
 
 
 
 
 
142
 
143
+ if (isLimitReached) {
144
+ const tokenIndex = this.activeTokens.indexOf(token);
145
+ if (tokenIndex !== -1) {
146
+ this.removeTokenByIndex(tokenIndex);
147
+ }
 
 
 
 
148
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
 
151
+ removeTokenByIndex(index) {
152
+ if (!this.isRecoveryProcess) {
153
+ this.startTokenRecoveryProcess();
 
 
 
 
 
154
  }
155
+ const token = this.activeTokens[index];
156
+ this.expiredTokens.set(token, Date.now());
157
+ this.activeTokens.splice(index, 1);
158
+ this.tokenModelFrequency.delete(token);
159
+ Logger.info(`令牌${token}已达到上限,已移除`, 'TokenManager');
160
  }
 
161
 
162
+ startTokenRecoveryProcess() {
163
+ setInterval(() => {
164
+ const now = Date.now();
165
+ for (const [token, expiredTime] of this.expiredTokens.entries()) {
166
+ if (now - expiredTime >= 2 * 60 * 60 * 1000) {
167
+ this.tokenModelUsage.set(token, {
168
+ "grok-3": 0,
169
+ "grok-3-imageGen": 0,
170
+ "grok-3-deepsearch": 0,
171
+ "grok-3-reasoning": 0
172
+ });
173
+ this.activeTokens.push(token);
174
+ this.expiredTokens.delete(token);
175
+ Logger.info(`令牌${token}已恢复,已添加到可用令牌列表`, 'TokenManager');
176
+ }
177
+ }
178
+ }, 2 * 60 * 60 * 1000);
179
  }
180
 
181
+ getTokenCount() {
182
+ return this.activeTokens.length || 0;
183
+ }
 
184
 
185
+ getActiveTokens() {
186
+ return [...this.activeTokens];
187
+ }
188
+ }
189
 
190
+ class Utils {
191
+ static async extractGrokHeaders() {
192
+ Logger.info("开始提取头信息", 'Server');
193
+ try {
194
+ // 启动浏览器
195
+ const browser = await puppeteer.launch({
196
+ headless: true,
197
+ args: [
198
+ '--no-sandbox',
199
+ '--disable-setuid-sandbox',
200
+ '--disable-dev-shm-usage',
201
+ '--disable-gpu'
202
+ ],
203
+ executablePath: CONFIG.CHROME_PATH
204
+ });
205
+
206
+ const page = await browser.newPage();
207
+ await page.goto('https://grok.com/', { waitUntil: 'domcontentloaded' });
208
+ await page.evaluate(() => {
209
+ return new Promise(resolve => setTimeout(resolve, 5000))
210
+ })
211
+ // 获取所有 Cookies
212
+ const cookies = await page.cookies();
213
+ const targetHeaders = ['x-anonuserid', 'x-challenge', 'x-signature'];
214
+ const extractedHeaders = {};
215
+ // 遍历 Cookies
216
+ for (const cookie of cookies) {
217
+ // 检查是否为目标头信息
218
+ if (targetHeaders.includes(cookie.name.toLowerCase())) {
219
+ extractedHeaders[cookie.name.toLowerCase()] = cookie.value;
220
+ }
221
+ }
222
+ // 关闭浏览器
223
+ await browser.close();
224
+ // 打印并返回提取的头信息
225
+ Logger.info('提取的头信息:', JSON.stringify(extractedHeaders, null, 2), 'Server');
226
+ return extractedHeaders;
227
 
228
+ } catch (error) {
229
+ Logger.error('获取头信息出错:', error, 'Server');
230
+ return null;
231
+ }
232
  }
233
+ static async get_signature() {
234
+ if (CONFIG.API.TEMP_COOKIE) {
235
+ return CONFIG.API.TEMP_COOKIE;
236
+ }
237
+ Logger.info("刷新认证信息", 'Server');
238
+ let retryCount = 0;
239
+ while (!CONFIG.API.TEMP_COOKIE || retryCount < CONFIG.RETRY.MAX_ATTEMPTS) {
240
+ let headers = await Utils.extractGrokHeaders();
241
+ if (headers) {
242
+ Logger.info("获取认证信息成功", 'Server');
243
+ CONFIG.API.TEMP_COOKIE = { cookie: `x-anonuserid=${headers["x-anonuserid"]}; x-challenge=${headers["x-challenge"]}; x-signature=${headers["x-signature"]}` };
244
+ return;
245
+ }
246
+ retryCount++;
247
+ if (retryCount >= CONFIG.RETRY.MAX_ATTEMPTS) {
248
+ throw new Error(`获取认证信息失败!`);
249
  }
250
  }
 
251
  }
252
+ static async organizeSearchResults(searchResults) {
253
+ // 确保传入的是有效的搜索结果对象
254
+ if (!searchResults || !searchResults.results) {
255
+ return '';
 
 
 
 
 
 
 
 
256
  }
257
 
258
+ const results = searchResults.results;
259
+ const formattedResults = results.map((result, index) => {
260
+ // 处理可能为空的字段
261
+ const title = result.title || '未知标题';
262
+ const url = result.url || '#';
263
+ const preview = result.preview || '无预览内容';
264
+
265
+ return `\r\n<details><summary>资料[${index}]: ${title}</summary>\r\n${preview}\r\n\n[Link](${url})\r\n</details>`;
266
+ });
267
+ return formattedResults.join('\n\n');
268
+ }
269
+ static createAuthHeaders(model) {
270
  return {
271
+ 'cookie': `${tokenManager.getTokenByIndex(CONFIG.SSO_INDEX, model)}`
 
 
272
  };
273
  }
274
+ }
275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
+ class GrokApiClient {
278
+ constructor(modelId) {
279
+ if (!CONFIG.MODELS[modelId]) {
280
+ throw new Error(`不支持的模型: ${modelId}`);
281
  }
282
+ this.modelId = CONFIG.MODELS[modelId];
 
283
  }
284
 
285
+ processMessageContent(content) {
286
+ if (typeof content === 'string') return content;
287
+ return null;
288
+ }
289
+ // 获取图片类型
290
+ getImageType(base64String) {
291
+ let mimeType = 'image/jpeg';
292
+ if (base64String.includes('data:image')) {
293
+ const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
294
+ if (matches) {
295
+ mimeType = matches[1];
296
+ }
297
+ }
298
+ const extension = mimeType.split('/')[1];
299
+ const fileName = `image.${extension}`;
300
 
301
  return {
302
+ mimeType: mimeType,
303
+ fileName: fileName
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  };
305
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
+ async uploadBase64Image(base64Data, url) {
308
  try {
309
+ // 处理 base64 数据
310
+ let imageBuffer;
311
+ if (base64Data.includes('data:image')) {
312
+ imageBuffer = base64Data.split(',')[1];
313
+ } else {
314
+ imageBuffer = base64Data
315
+ }
316
+ const { mimeType, fileName } = this.getImageType(base64Data);
317
+ let uploadData = {
318
+ rpc: "uploadFile",
319
+ req: {
320
+ fileName: fileName,
321
+ fileMimeType: mimeType,
322
+ content: imageBuffer
323
  }
324
+ };
325
+ Logger.info("发送图片请求", 'Server');
326
+ // 发送请求
327
+ const response = await fetch(url, {
328
+ method: 'POST',
329
+ headers: {
330
+ ...CONFIG.DEFAULT_HEADERS,
331
+ ...CONFIG.API.SIGNATURE_COOKIE
332
+ },
333
+ body: JSON.stringify(uploadData)
334
+ });
335
+
336
+ if (!response.ok) {
337
+ Logger.error(`上传图片失败,状态码:${response.status},原因:${response.error}`, 'Server');
338
+ return '';
339
  }
340
 
341
+ const result = await response.json();
342
+ Logger.info('上传图片成功:', result, 'Server');
343
+ return result.fileMetadataId;
344
+
345
  } catch (error) {
346
+ Logger.error(error, 'Server');
347
+ return '';
348
  }
349
  }
350
 
351
+ async prepareChatRequest(request) {
352
+ if ((request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen') && !CONFIG.API.PICGO_KEY && request.stream) {
353
+ throw new Error(`该模型流式输出需要配置PICGO图床密钥!`);
 
354
  }
355
+
356
+ // 处理画图模型的消息限制
357
+ let todoMessages = request.messages;
358
+ if (request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen') {
359
+ const lastMessage = todoMessages[todoMessages.length - 1];
360
+ if (lastMessage.role !== 'user') {
361
+ throw new Error('画图模型的最后一条消息必须是用户消息!');
362
+ }
363
+ todoMessages = [lastMessage];
364
  }
365
+
366
+ const fileAttachments = [];
367
+ let messages = '';
368
+ let lastRole = null;
369
+ let lastContent = '';
370
+ const search = request.model === 'grok-2-search';
371
+
372
+ // 移除<think>标签及其内容和base64图片
373
+ const removeThinkTags = (text) => {
374
+ text = text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
375
+ text = text.replace(/!\[image\]\(data:.*?base64,.*?\)/g, '[图片]');
376
+ return text;
377
+ };
378
+
379
+ const processImageUrl = async (content) => {
380
+ if (content.type === 'image_url' && content.image_url.url.includes('data:image')) {
381
+ const imageResponse = await this.uploadBase64Image(
382
+ content.image_url.url,
383
+ `${CONFIG.API.BASE_URL}/api/rpc`
384
+ );
385
+ return imageResponse;
386
+ }
387
+ return null;
388
+ };
389
+
390
+ const processContent = async (content) => {
391
+ if (Array.isArray(content)) {
392
+ let textContent = '';
393
+ for (const item of content) {
394
+ if (item.type === 'image_url') {
395
+ textContent += (textContent ? '\n' : '') + "[图片]";
396
+ } else if (item.type === 'text') {
397
+ textContent += (textContent ? '\n' : '') + removeThinkTags(item.text);
398
+ }
399
+ }
400
+ return textContent;
401
+ } else if (typeof content === 'object' && content !== null) {
402
+ if (content.type === 'image_url') {
403
+ return "[图片]";
404
+ } else if (content.type === 'text') {
405
+ return removeThinkTags(content.text);
406
+ }
407
+ }
408
+ return removeThinkTags(this.processMessageContent(content));
409
+ };
410
+
411
+ for (const current of todoMessages) {
412
+ const role = current.role === 'assistant' ? 'assistant' : 'user';
413
+ const isLastMessage = current === todoMessages[todoMessages.length - 1];
414
+
415
+ // 处理图片附件
416
+ if (isLastMessage && current.content) {
417
+ if (Array.isArray(current.content)) {
418
+ for (const item of current.content) {
419
+ if (item.type === 'image_url') {
420
+ const processedImage = await processImageUrl(item);
421
+ if (processedImage) fileAttachments.push(processedImage);
422
+ }
423
+ }
424
+ } else if (current.content.type === 'image_url') {
425
+ const processedImage = await processImageUrl(current.content);
426
+ if (processedImage) fileAttachments.push(processedImage);
427
+ }
428
+ }
429
+
430
+ // 处理文本内容
431
+ const textContent = await processContent(current.content);
432
+
433
+ if (textContent || (isLastMessage && fileAttachments.length > 0)) {
434
+ if (role === lastRole && textContent) {
435
+ lastContent += '\n' + textContent;
436
+ messages = messages.substring(0, messages.lastIndexOf(`${role.toUpperCase()}: `)) +
437
+ `${role.toUpperCase()}: ${lastContent}\n`;
438
+ } else {
439
+ messages += `${role.toUpperCase()}: ${textContent || '[图片]'}\n`;
440
+ lastContent = textContent;
441
+ lastRole = role;
442
+ }
443
+ }
444
  }
445
+
446
+ return {
447
+ modelName: this.modelId,
448
+ message: messages.trim(),
449
+ fileAttachments: fileAttachments.slice(0, 4),
450
+ imageAttachments: [],
451
+ disableSearch: false,
452
+ enableImageGeneration: true,
453
+ returnImageBytes: false,
454
+ returnRawGrokInXaiRequest: false,
455
+ enableImageStreaming: false,
456
+ imageGenerationCount: 1,
457
+ forceConcise: false,
458
+ toolOverrides: {
459
+ imageGen: request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen',
460
+ webSearch: search,
461
+ xSearch: search,
462
+ xMediaSearch: search,
463
+ trendsSearch: search,
464
+ xPostAnalyze: search
465
+ },
466
+ enableSideBySide: true,
467
+ isPreset: false,
468
+ sendFinalMetadata: true,
469
+ customInstructions: "",
470
+ deepsearchPreset: request.model === 'grok-3-deepsearch' ? "default" : "",
471
+ isReasoning: request.model === 'grok-3-reasoning'
472
+ };
473
  }
474
+ }
475
 
476
+ class MessageProcessor {
477
+ static createChatResponse(message, model, isStream = false) {
478
+ const baseResponse = {
479
+ id: `chatcmpl-${uuidv4()}`,
480
+ created: Math.floor(Date.now() / 1000),
481
+ model: model
482
+ };
483
 
484
+ if (isStream) {
485
+ return {
486
+ ...baseResponse,
487
+ object: 'chat.completion.chunk',
488
+ choices: [{
489
+ index: 0,
490
+ delta: {
491
+ content: message
492
+ }
493
+ }]
494
+ };
495
  }
496
 
497
+ return {
498
+ ...baseResponse,
499
+ object: 'chat.completion',
500
+ choices: [{
501
+ index: 0,
502
+ message: {
503
+ role: 'assistant',
504
+ content: message
505
+ },
506
+ finish_reason: 'stop'
507
+ }],
508
+ usage: null
509
+ };
510
  }
511
+ }
512
+ async function processModelResponse(linejosn, model) {
513
+ let result = { token: '', imageUrl: null }
514
+ if (CONFIG.IS_IMG_GEN) {
515
+ if (linejosn?.cachedImageGenerationResponse && !CONFIG.IS_IMG_GEN2) {
516
+ result.imageUrl = linejosn.cachedImageGenerationResponse.imageUrl;
 
 
 
 
 
 
 
 
 
 
 
517
  }
518
+ return result;
 
 
519
  }
520
+ var token = linejosn?.token
521
+ if (!token) return result;
522
+ //非生图模型的处理
523
+ switch (model) {
524
+ case 'grok-2':
525
+ result.token = token;
526
+ return result;
527
+ case 'grok-2-search':
528
+ if (linejosn?.webSearchResults && CONFIG.ISSHOW_SEARCH_RESULTS) {
529
+ result.token = `\r\n<think>${await Utils.organizeSearchResults(linejosn.webSearchResults)}</think>\r\n`;
530
+ } else {
531
+ result.token = token;
532
+ }
533
+ return result;
534
+ case 'grok-3':
535
+ result.token = token;
536
+ return result;
537
+ case 'grok-3-deepsearch':
538
+ if (linejosn.messageTag === "final") {
539
+ result.token = token;
540
+ }
541
+ return result;
542
+ case 'grok-3-reasoning':
543
+ if(linejosn?.isThinking && !CONFIG.SHOW_THINKING)return result;
544
+
545
+ if (linejosn?.isThinking && !CONFIG.IS_THINKING) {
546
+ result.token = "<think>" + token;
547
+ CONFIG.IS_THINKING = true;
548
+ } else if (CONFIG.IS_THINKING && !linejosn.isThinking) {
549
+ result.token = "</think>" + token;
550
+ CONFIG.IS_THINKING = false;
551
+ } else {
552
+ result.token = token;
553
+ }
554
+ return result;
555
+ }
556
+ }
557
 
558
+ async function handleResponse(response, model, res, isStream) {
559
+ try {
560
+ const stream = response.body;
561
  let buffer = '';
562
  let fullResponse = '';
563
+ const dataPromises = [];
564
+
565
+ return new Promise((resolve, reject) => {
566
+ stream.on('data', async (chunk) => {
567
+ buffer += chunk.toString();
568
+ const lines = buffer.split('\n');
569
  buffer = lines.pop() || '';
570
 
571
  for (const line of lines) {
572
  if (!line.trim()) continue;
573
+ const trimmedLine = line.trim();
574
+ if (trimmedLine.startsWith('data: ')) {
575
+ const data = trimmedLine.substring(6);
576
+ try {
577
+ if (!data.trim()) continue;
578
+ if(data === "[DONE]") continue;
579
+ const linejosn = JSON.parse(data);
580
+ if (linejosn?.error) {
581
+ stream.destroy();
582
+ reject(new Error("RateLimitError"));
583
+ return;
584
+ }
585
+ if (linejosn?.doImgGen || linejosn?.imageAttachmentInfo) {
586
+ CONFIG.IS_IMG_GEN = true;
587
+ }
588
+ const processPromise = (async () => {
589
+ const result = await processModelResponse(linejosn, model);
590
+ if (result.token) {
591
+ if (isStream) {
592
+ res.write(`data: ${JSON.stringify(MessageProcessor.createChatResponse(result.token, model, true))}\n\n`);
593
+ } else {
594
+ fullResponse += result.token;
595
+ }
596
+ }
597
+ if (result.imageUrl) {
598
+ CONFIG.IS_IMG_GEN2 = true;
599
+ const dataImage = await handleImageResponse(result.imageUrl);
600
+ if (isStream) {
601
+ res.write(`data: ${JSON.stringify(MessageProcessor.createChatResponse(dataImage, model, true))}\n\n`);
602
+ } else {
603
+ res.json(MessageProcessor.createChatResponse(dataImage, model));
604
+ }
605
+ }
606
+ })();
607
+ dataPromises.push(processPromise);
608
+ } catch (error) {
609
+ continue;
610
+ }
611
+ }
612
  }
613
+ });
614
+
615
+ stream.on('end', async () => {
616
+ try {
617
+ await Promise.all(dataPromises);
618
+ if (isStream) {
619
+ res.write('data: [DONE]\n\n');
620
+ res.end();
621
+ } else {
622
+ if (!CONFIG.IS_IMG_GEN2) {
623
+ res.json(MessageProcessor.createChatResponse(fullResponse, model));
624
+ }
625
+ }
626
+ CONFIG.IS_IMG_GEN = false;
627
+ CONFIG.IS_IMG_GEN2 = false;
628
+ resolve();
629
+ } catch (error) {
630
+ reject(error);
631
+ }
632
+ });
633
 
634
+ stream.on('error', (error) => {
635
+ reject(error);
636
+ });
637
+ });
638
+ } catch (error) {
639
+ Logger.error(error, 'Server');
640
+ CONFIG.IS_IMG_GEN = false;
641
+ CONFIG.IS_IMG_GEN2 = false;
642
+ throw error;
 
643
  }
644
+ }
645
 
646
+ async function handleImageResponse(imageUrl) {
647
+ const MAX_RETRIES = 2;
648
+ let retryCount = 0;
649
+ let imageBase64Response;
 
 
 
 
 
 
 
 
 
 
 
 
 
650
 
651
+ while (retryCount < MAX_RETRIES) {
652
+ try {
653
+ imageBase64Response = await fetch(`https://assets.grok.com/${imageUrl}`, {
654
+ method: 'GET',
655
+ headers: {
656
+ ...DEFAULT_HEADERS,
657
+ ...CONFIG.API.SIGNATURE_COOKIE
658
+ }
659
+ });
660
 
661
+ if (imageBase64Response.ok) break;
662
+ retryCount++;
663
+ if (retryCount === MAX_RETRIES) {
664
+ throw new Error(`上游服务请求失败! status: ${imageBase64Response.status}`);
665
+ }
666
+ await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
667
 
668
+ } catch (error) {
669
+ retryCount++;
670
+ if (retryCount === MAX_RETRIES) {
671
+ throw error;
672
+ }
673
+ await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
 
 
 
 
 
674
  }
 
 
675
  }
676
 
 
 
 
 
 
677
 
678
+ const arrayBuffer = await imageBase64Response.arrayBuffer();
679
+ const imageBuffer = Buffer.from(arrayBuffer);
 
680
 
681
+ if(!CONFIG.API.PICGO_KEY){
682
+ const base64Image = imageBuffer.toString('base64');
683
+ const imageContentType = imageBase64Response.headers.get('content-type');
684
+ return `![image](data:${imageContentType};base64,${base64Image})`
685
+ }
686
 
687
+ const formData = new FormData();
688
+
689
+ formData.append('source', imageBuffer, {
690
+ filename: 'new.jpg',
691
+ contentType: 'image/jpeg'
692
+ });
693
+ const formDataHeaders = formData.getHeaders();
694
+ const responseURL = await fetch("https://www.picgo.net/api/1/upload", {
695
+ method: "POST",
696
+ headers: {
697
+ ...formDataHeaders,
698
+ "Content-Type": "multipart/form-data",
699
+ "X-API-Key": CONFIG.API.PICGO_KEY
700
+ },
701
+ body: formData
702
+ });
703
+ if (!responseURL.ok) {
704
+ return "生图失败,请查看图床密钥是否设置正确"
705
+ } else {
706
+ console.log("生图成功");
707
+ const result = await responseURL.json();
708
+ return `![image](${result.image.url})`
709
  }
710
  }
711
 
712
+ const tokenManager = new AuthTokenManager();
713
+ await initialization();
714
+
715
+ // 中间件配置
716
  const app = express();
717
  app.use(Logger.requestLogger);
 
718
  app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
719
  app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
720
+ app.use(cors({
721
+ origin: '*',
722
+ methods: ['GET', 'POST', 'OPTIONS'],
723
+ allowedHeaders: ['Content-Type', 'Authorization']
724
+ }));
725
 
726
+ app.get('/hf/v1/models', (req, res) => {
 
727
  res.json({
728
  object: "list",
729
+ data: Object.keys(CONFIG.MODELS).map((model, index) => ({
730
  id: model,
731
  object: "model",
732
  created: Math.floor(Date.now() / 1000),
733
+ owned_by: "grok",
734
  }))
735
  });
736
  });
737
 
738
+
739
+ app.post('/hf/v1/chat/completions', async (req, res) => {
740
  try {
741
  const authToken = req.headers.authorization?.replace('Bearer ', '');
742
  if (authToken !== CONFIG.API.API_KEY) {
743
  return res.status(401).json({ error: 'Unauthorized' });
744
  }
745
+ let isTempCookie = req.body.model.includes("grok-2");
746
+ let retryCount = 0;
747
+
748
+ while (retryCount < CONFIG.RETRY.MAX_ATTEMPTS) {
749
+ retryCount++;
750
+ const grokClient = new GrokApiClient(req.body.model);
751
+ const requestPayload = await grokClient.prepareChatRequest(req.body);
752
+
753
+ if (!CONFIG.API.TEMP_COOKIE) {
754
+ await Utils.get_signature();
755
+ }
756
 
757
+ if (isTempCookie) {
758
+ CONFIG.API.SIGNATURE_COOKIE = CONFIG.API.TEMP_COOKIE;
759
+ Logger.info(`已切换为临时令牌`, 'Server');
760
+ } else {
761
+ CONFIG.API.SIGNATURE_COOKIE = Utils.createAuthHeaders(req.body.model);
762
+ }
763
+ Logger.info(`当前令牌索引: ${CONFIG.SSO_INDEX}`, 'Server');
764
+ const newMessageReq = await fetch(`${CONFIG.API.BASE_URL}/api/rpc`, {
765
+ method: 'POST',
766
+ headers: {
767
+ ...DEFAULT_HEADERS,
768
+ ...CONFIG.API.SIGNATURE_COOKIE
769
+ },
770
+ body: JSON.stringify({
771
+ rpc: "createConversation",
772
+ req: {
773
+ temporary: false
774
+ }
775
+ })
776
+ });
777
+
778
+ const responseText = await newMessageReq.json();
779
+ const conversationId = responseText.conversationId;
780
+
781
+ const response = await fetch(`${CONFIG.API.BASE_URL}/api/conversations/${conversationId}/responses`, {
782
+ method: 'POST',
783
+ headers: {
784
+ "accept": "text/event-stream",
785
+ "baggage": "sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c",
786
+ "content-type": "text/plain;charset=UTF-8",
787
+ "Connection": "keep-alive",
788
+ ...CONFIG.API.SIGNATURE_COOKIE
789
+ },
790
+ body: JSON.stringify(requestPayload)
791
+ });
792
+
793
+ if (response.ok) {
794
+ Logger.info(`请求成功`, 'Server');
795
+ CONFIG.SSO_INDEX = (CONFIG.SSO_INDEX + 1) % tokenManager.getTokenCount();
796
+ Logger.info(`当前剩余可用令牌数: ${tokenManager.getTokenCount()}`, 'Server');
797
+ try {
798
+ await handleResponse(response, req.body.model, res, req.body.stream);
799
+ return;
800
+ } catch (error) {
801
+ if(isTempCookie){
802
+ await Utils.get_signature();
803
+ }else{
804
+ tokenManager.setModelLimit(CONFIG.SSO_INDEX, req.body.model);
805
+ for (let i = 1; i <= tokenManager.getTokenCount(); i++) {
806
+ CONFIG.SSO_INDEX = (CONFIG.SSO_INDEX + 1) % tokenManager.getTokenCount();
807
+ if (!tokenManager.isTokenModelLimitReached(CONFIG.SSO_INDEX, req.body.model)) {
808
+ break;
809
+ } else if (i >= tokenManager.getTokenCount()) {
810
+ throw new Error(`${req.body.model} 次数已达上限,请切换其他模型或者重新对话`);
811
+ }
812
+ }
813
+ }
814
+ }
815
+ } else {
816
+ if (response.status === 429) {
817
+ if (isTempCookie) {
818
+ await Utils.get_signature();
819
+ } else {
820
+ tokenManager.setModelLimit(CONFIG.SSO_INDEX, req.body.model);
821
+ for (let i = 1; i <= tokenManager.getTokenCount(); i++) {
822
+ CONFIG.SSO_INDEX = (CONFIG.SSO_INDEX + 1) % tokenManager.getTokenCount();
823
+ if (!tokenManager.isTokenModelLimitReached(CONFIG.SSO_INDEX, req.body.model)) {
824
+ break;
825
+ } else if (i >= tokenManager.getTokenCount()) {
826
+ throw new Error(`${req.body.model} 次数已达上限,请切换其他模型或者重新对话`);
827
+ }
828
+ }
829
+ }
830
+ } else {
831
+ // 非429错误直接抛出
832
+ if (isTempCookie) {
833
+ await Utils.get_signature();
834
+ } else {
835
+ Logger.error(`令牌异常错误状态!status: ${response.status}, 已移除当前令牌${CONFIG.SSO_INDEX.cookie}`, 'Server');
836
+ tokenManager.removeTokenByIndex(CONFIG.SSO_INDEX);
837
+ Logger.info(`当前剩余可用令牌数: ${tokenManager.getTokenCount()}`, 'Server');
838
+ CONFIG.SSO_INDEX = (CONFIG.SSO_INDEX + 1) % tokenManager.getTokenCount();
839
+ }
840
+ }
841
+ }
842
  }
843
+ throw new Error('当前模型所有令牌都已耗尽');
 
 
 
 
844
  } catch (error) {
845
+ Logger.error(error, 'ChatAPI');
846
  res.status(500).json({
847
  error: {
848
  message: error.message,
 
851
  code: error.code || null
852
  }
853
  });
 
 
 
 
854
  }
855
  });
856
 
857
+
858
  app.use((req, res) => {
859
+ res.status(200).send('api运行正常');
860
  });
861
 
862
+
863
  app.listen(CONFIG.SERVER.PORT, () => {
864
+ Logger.info(`服务器已启动,监听端口: ${CONFIG.SERVER.PORT}`, 'Server');
865
  });
logger.js CHANGED
@@ -34,7 +34,7 @@ static warn(message, context) {
34
  console.warn(this.formatMessage(LogLevel.WARN, context ? `[${context}] ${message}` : message));
35
  }
36
 
37
- static error(message, error, context) {
38
  const errorMessage = error ? ` - ${error.message}` : '';
39
  console.error(this.formatMessage(LogLevel.ERROR, `${context ? `[${context}] ` : ''}${message}${errorMessage}`));
40
  }
 
34
  console.warn(this.formatMessage(LogLevel.WARN, context ? `[${context}] ${message}` : message));
35
  }
36
 
37
+ static error(message, context, error = null) {
38
  const errorMessage = error ? ` - ${error.message}` : '';
39
  console.error(this.formatMessage(LogLevel.ERROR, `${context ? `[${context}] ` : ''}${message}${errorMessage}`));
40
  }
package.json CHANGED
@@ -1,20 +1,23 @@
1
  {
2
- "name": "xgrokServicee",
3
- "version": "1.0.0",
4
- "main": "index.js",
5
- "type": "module",
6
- "scripts": {
7
- "start": "node index.js"
8
- },
9
- "author": "yxmiler",
10
- "dependencies": {
11
- "express": "^4.18.2",
12
- "cors": "^2.8.5",
13
- "dotenv": "^16.3.1",
14
- "uuid": "^9.0.0",
15
- "chalk": "^5.4.1",
16
- "moment": "^2.30.1",
17
- "node-fetch": "^3.3.2",
18
- "form-data": "^4.0.0"
19
- }
20
- }
 
 
 
 
1
  {
2
+ "name": "grok2api",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node index.js"
8
+ },
9
+ "author": "yxmiler",
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "node-fetch": "^3.3.2",
13
+ "dotenv": "^16.3.1",
14
+ "cors": "^2.8.5",
15
+ "form-data": "^4.0.0",
16
+ "puppeteer": "^22.8.2",
17
+ "puppeteer-extra": "^3.3.6",
18
+ "puppeteer-extra-plugin-stealth": "^2.11.2",
19
+ "moment": "^2.30.1",
20
+ "chalk": "^5.4.1",
21
+ "uuid": "^9.0.0"
22
+ }
23
+ }