letterm commited on
Commit
1704fef
·
verified ·
1 Parent(s): a293648

Update index.js

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