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

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +482 -659
index.js CHANGED
@@ -1,487 +1,205 @@
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,9 +214,7 @@ class MessageProcessor {
496
  object: 'chat.completion.chunk',
497
  choices: [{
498
  index: 0,
499
- delta: {
500
- content: message
501
- }
502
  }]
503
  };
504
  }
@@ -517,341 +233,448 @@ class MessageProcessor {
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,12 +686,12 @@ app.post('/hf/v1/chat/completions', async (req, res) => {
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
  });
 
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
  IS_IMG_GEN: false,
44
+ ISSHOW_SEARCH_RESULTS: process.env.ISSHOW_SEARCH_RESULTS === 'true',
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
  object: 'chat.completion.chunk',
215
  choices: [{
216
  index: 0,
217
+ delta: { content: message }
 
 
218
  }]
219
  };
220
  }
 
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
 
286
+ removeThinkTags(text) {
287
+ text = text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
288
+ text = text.replace(/!\[image\]\(data:.*?base64,.*?\)/g, '[图片]');
289
+ return text;
290
+ };
291
+
292
+ async transformMessages(messages) {
293
+ if (messages[0].role === 'assistant') {
294
+ throw new Error('ai不能是第一个消息');
295
+ }
296
+
297
+ const processedMessages = [];
298
+ let currentMessage = null;
299
+
300
+ for (const msg of messages) {
301
+ const normalizedMsg = msg.role === 'system' ? { ...msg, role: 'user' } : msg;
302
+
303
+ if (!currentMessage || currentMessage.role !== normalizedMsg.role) {
304
+ if (currentMessage) {
305
+ const processedContent = await this.processMessageContent(
306
+ currentMessage,
307
+ processedMessages.length >= messages.length - 2
308
+ );
309
+ if (processedContent) {
310
+ processedMessages.push(processedContent);
311
+ }
312
+ }
313
+ currentMessage = normalizedMsg;
314
  } else {
315
+ currentMessage.content = typeof currentMessage.content === 'string' && typeof normalizedMsg.content === 'string'
316
+ ? `${currentMessage.content}\n${normalizedMsg.content}`
317
+ : normalizedMsg.content;
318
  }
319
+ }
320
+
321
+ // 处理最后一个消息
322
+ if (currentMessage) {
323
+ const processedContent = await this.processMessageContent(
324
+ currentMessage,
325
+ true
326
+ );
327
+ if (processedContent) {
328
+ processedMessages.push(processedContent);
329
  }
330
+ }
331
+
332
+ return processedMessages;
333
+ }
334
+
335
+ async processMessageContent(msg, isLastTwoMessages) {
336
+ const { role, content } = msg;
337
+ let message = '';
338
+ let fileAttachments = [];
339
+
340
+ if (typeof content === 'string') {
341
+ message = this.removeThinkTags(content);
342
+ } else if (Array.isArray(content) || typeof content === 'object') {
343
+ const { text, imageAttachments } = await this.processComplexContent(content, isLastTwoMessages);
344
+ message = this.removeThinkTags(text);
345
+ fileAttachments = imageAttachments;
346
+ }
347
+
348
+ return {
349
+ message,
350
+ sender: role === 'assistant' ? 2 : 1,
351
+ ...(role === 'user' && { fileAttachments })
352
+ };
353
+ }
354
+
355
+ async processComplexContent(content, isLastTwoMessages) {
356
+ let text = '';
357
+ let imageAttachments = [];
358
+
359
+ const processItem = async (item) => {
360
+ if (item.type === 'text') {
361
+ text += item.text;
362
+ } else if (item.type === 'image_url' && item.image_url.url.includes('data:image')) {
363
+ if (isLastTwoMessages) {
364
+ const uploadResult = await this.uploadImage(item.image_url.url);
365
+ if (Array.isArray(uploadResult)) {
366
+ imageAttachments.push(...uploadResult);
367
+ }
368
+ } else {
369
+ text += '[图片]';
370
+ }
371
  }
372
+ };
373
+
374
+ if (Array.isArray(content)) {
375
+ await Promise.all(content.map(processItem));
376
+ } else {
377
+ await processItem(content);
378
+ }
379
+
380
+ return { text, imageAttachments };
381
+ }
382
+
383
+ async prepareChatRequest(request) {
384
+ const responses = await this.transformMessages(request.messages);
385
+ const conversationId = await ConversationManager.generateNewId();
386
+
387
+ return {
388
+ responses,
389
+ systemPromptName: "",
390
+ grokModelOptionId: this.modelId,
391
+ conversationId,
392
+ returnSearchResults: this.modelType.isReasoning,
393
+ returnCitations: this.modelType.isReasoning,
394
+ promptMetadata: {
395
+ promptSource: "NATURAL",
396
+ action: "INPUT"
397
+ },
398
+ imageGenerationCount: 1,
399
+ requestFeatures: {
400
+ eagerTweets: false,
401
+ serverHistory: false
402
+ },
403
+ enableCustomization: true,
404
+ enableSideBySide: false,
405
+ toolOverrides: {
406
+ imageGen: request.model === 'grok-3-imageGen',
407
+ },
408
+ isDeepsearch: this.modelType.isDeepSearch,
409
+ isReasoning: this.modelType.isReasoning
410
+ };
411
  }
412
  }
413
 
414
+ // 响应处理类
415
+ class ResponseHandler {
416
+ static async handleStreamResponse(response, model, res) {
417
+ res.setHeader('Content-Type', 'text/event-stream');
418
+ res.setHeader('Cache-Control', 'no-cache');
419
+ res.setHeader('Connection', 'keep-alive');
420
+
421
+ const reader = response.body;
422
  let buffer = '';
423
+ CONFIG.IS_IMG_GEN = false;
424
+ CONFIG.IS_THINKING = false;
425
+
426
+ try {
427
+ for await (const chunk of reader) {
428
+ const lines = (buffer + chunk.toString()).split('\n');
 
429
  buffer = lines.pop() || '';
430
 
431
  for (const line of lines) {
432
  if (!line.trim()) continue;
433
+ await this.processStreamLine(JSON.parse(line), model, res);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  }
435
+ }
436
 
437
+ res.write('data: [DONE]\n\n');
438
+ res.end();
439
+ } catch (error) {
440
+ Logger.error('Stream response error:', error, 'ChatAPI');
441
+ throw error;
442
+ }
443
+ }
 
 
 
 
 
 
 
 
 
 
 
444
 
445
+ static async processStreamLine(jsonData, model, res) {
446
+ if (jsonData.result?.doImgGen) {
447
+ CONFIG.IS_IMG_GEN = true;
448
+ return;
449
+ }
450
+
451
+ if (CONFIG.IS_IMG_GEN && jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
452
+ await this.handleImageGeneration(jsonData, model, res);
453
+ return;
454
+ }
455
+
456
+ if (!CONFIG.IS_IMG_GEN && jsonData.result?.message) {
457
+ await this.handleTextMessage(jsonData, model, res);
458
+ }
459
+ }
460
+
461
+ static async handleImageGeneration(jsonData, model, res) {
462
+ const imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
463
+ const imageResponse = await fetch(imageUrl, {
464
+ method: 'GET',
465
+ headers: Utils.createAuthHeaders()
466
  });
467
+
468
+ if (!imageResponse.ok) {
469
+ throw new Error(`Image request failed: ${imageResponse.status}`);
470
+ }
471
+
472
+ const imageBuffer = await imageResponse.arrayBuffer();
473
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
474
+ const imageContentType = imageResponse.headers.get('content-type');
475
+ const message = `![image](data:${imageContentType};base64,${base64Image})`;
476
+
477
+ const responseData = MessageProcessor.createChatResponse(message, model, true);
478
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
479
  }
 
480
 
481
+ static async handleTextMessage(jsonData, model, res) {
482
+ let message = jsonData.result.message;
483
+
484
+ switch (model) {
485
+ case "grok-3-reasoning":
486
+ if(!CONFIG.ISSHOW_SEARCH_RESULTS && jsonData.result?.isThinking)return;
487
+ if (!CONFIG.IS_THINKING && jsonData.result?.isThinking) {
488
+ message = "<think>" + message;
489
+ CONFIG.IS_THINKING = true;
490
+ } else if (CONFIG.IS_THINKING && !jsonData.result?.isThinking) {
491
+ message = "</think>" + message;
492
+ CONFIG.IS_THINKING = false;
493
+ }
494
+ break;
495
+ case "grok-3-deepsearch":
496
+ if (jsonData.result?.messageTag !== "final") return;
497
+ break;
498
+ }
499
+
500
+ const responseData = MessageProcessor.createChatResponse(message, model, true);
501
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
502
+ }
503
 
504
+ static async handleNormalResponse(response, model, res) {
505
+ const reader = response.body;
506
+ let buffer = '';
507
+ let fullResponse = '';
508
+ let imageUrl = null;
509
  try {
510
+ for await (const chunk of reader) {
511
+ const lines = (buffer + chunk.toString()).split('\n');
512
+ buffer = lines.pop() || '';
 
 
 
 
513
 
514
+ for (const line of lines) {
515
+ if (!line.trim()) continue;
516
+ const result = await this.processNormalLine(JSON.parse(line), model, CONFIG.IS_THINKING);
517
+ fullResponse += result.text || '';
518
+ imageUrl = result.imageUrl || imageUrl;
519
+ CONFIG.IS_THINKING = result.isThinking;
520
+ }
521
  }
 
522
 
523
+ if (imageUrl) {
524
+ await this.sendImageResponse(imageUrl, model, res);
525
+ } else {
526
+ if (fullResponse.includes("You've reached your limit of 15 Grok")) {
527
+ throw new Error('You have reached your limit of 15 Grok');
528
+ }
529
+ const responseData = MessageProcessor.createChatResponse(fullResponse, model);
530
+ res.json(responseData);
531
  }
532
+ } catch (error) {
533
+ Logger.error('Normal response error:', error, 'ChatAPI');
534
+ throw error;
535
  }
536
  }
537
 
538
+ static async processNormalLine(jsonData, model, isThinking) {
539
+ let result = { text: '', imageUrl: null, isThinking };
540
+
541
+ if (jsonData.result?.message) {
542
+ switch (model) {
543
+ case "grok-3-reasoning":
544
+ result = this.processReasoningMessage(jsonData, isThinking);
545
+ break;
546
+ case "grok-3-deepsearch":
547
+ if (jsonData.result?.messageTag === "final") {
548
+ result.text = jsonData.result.message;
549
+ }
550
+ break;
551
+ default:
552
+ result.text = jsonData.result.message;
553
+ }
554
+ }
555
+
556
+ if (jsonData.result?.event?.imageAttachmentUpdate?.progress === 100) {
557
+ result.imageUrl = jsonData.result.event.imageAttachmentUpdate.imageUrl;
558
+ }
559
 
560
+ return result;
561
+ }
562
 
563
+ static processReasoningMessage(jsonData, isThinking) {
564
+ let result = { text: '', isThinking };
565
+ if(!CONFIG.ISSHOW_SEARCH_RESULTS && jsonData.result?.isThinking)return result;
566
+ if (jsonData.result?.isThinking && !isThinking) {
567
+ result.text = "<think>" + jsonData.result.message;
568
+ result.isThinking = true;
569
+ } else if (isThinking && !jsonData.result?.isThinking) {
570
+ result.text = "</think>" + jsonData.result.message;
571
+ result.isThinking = false;
572
+ } else {
573
+ result.text = jsonData.result.message;
574
+ }
575
+
576
+ return result;
577
  }
578
 
579
+ static async sendImageResponse(imageUrl, model, res) {
580
+ const response = await fetch(imageUrl, {
581
+ method: 'GET',
582
+ headers: Utils.createAuthHeaders()
583
+ });
584
 
585
+ if (!response.ok) {
586
+ throw new Error(`Image request failed: ${response.status}`);
587
+ }
588
+
589
+ const imageBuffer = await response.arrayBuffer();
590
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
591
+ const imageContentType = response.headers.get('content-type');
592
+
593
+ const responseData = MessageProcessor.createChatResponse(
594
+ `![image](data:${imageContentType};base64,${base64Image})`,
595
+ model
596
+ );
597
+ res.json(responseData);
 
 
 
 
 
 
 
598
  }
599
  }
600
 
601
+ // Express 应用配置
 
 
 
602
  const app = express();
603
  app.use(Logger.requestLogger);
604
+ app.use(cors(CONFIG.SERVER.CORS_OPTIONS));
605
  app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
606
  app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
 
 
 
 
 
607
 
608
+ // API 路由
609
  app.get('/hf/v1/models', (req, res) => {
610
  res.json({
611
  object: "list",
612
+ data: Object.keys(CONFIG.MODELS).map(model => ({
613
  id: model,
614
  object: "model",
615
  created: Math.floor(Date.now() / 1000),
616
+ owned_by: "xgrok",
617
  }))
618
  });
619
  });
 
 
620
  app.post('/hf/v1/chat/completions', async (req, res) => {
621
  try {
622
  const authToken = req.headers.authorization?.replace('Bearer ', '');
623
  if (authToken !== CONFIG.API.API_KEY) {
624
  return res.status(401).json({ error: 'Unauthorized' });
625
  }
 
 
 
 
 
 
 
 
 
 
626
 
627
+ while (tokenManager.getTokenCount() > 0) {
628
+ const grokClient = new TwitterGrokApiClient(req.body.model);
629
+ const requestPayload = await grokClient.prepareChatRequest(req.body);
630
+ Logger.info(`当前令牌索引: ${CONFIG.SIGNATURE_INDEX}`, 'Server');
631
+ const response = await fetch(CONFIG.API.ENDPOINTS.CHAT, {
 
 
 
632
  method: 'POST',
633
+ headers: Utils.createAuthHeaders(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  body: JSON.stringify(requestPayload)
635
  });
636
 
637
  if (response.ok) {
638
  Logger.info(`请求成功`, 'Server');
639
+ CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
640
  Logger.info(`当前剩余可用令牌数: ${tokenManager.getTokenCount()}`, 'Server');
641
  try {
642
+ await (req.body.stream
643
+ ? ResponseHandler.handleStreamResponse(response, req.body.model, res)
644
+ : ResponseHandler.handleNormalResponse(response, req.body.model, res));
645
+ return; // 成功后直接返回
646
  } catch (error) {
647
+ tokenManager.removeTokenByIndex(CONFIG.SIGNATURE_INDEX);
648
+ if (!tokenManager.isRecoveryProcess) {
649
+ tokenManager.startTokenRecoveryProcess();
 
 
 
 
 
 
 
 
 
650
  }
651
+ Logger.warn(`当前令牌失效,已移除令牌,剩余令牌数: ${tokenManager.getTokenCount()}`, 'Server');
652
+ // 更新签名索引
653
+ CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
654
  }
655
  } else {
656
+ // 处理429错误
657
  if (response.status === 429) {
658
+ tokenManager.removeTokenByIndex(CONFIG.SIGNATURE_INDEX);
659
+ if (!tokenManager.isRecoveryProcess) {
660
+ tokenManager.startTokenRecoveryProcess();
 
 
 
 
 
 
 
 
 
661
  }
662
+ Logger.warn(`当前令牌失效,已移除令牌,剩余令牌数: ${tokenManager.getTokenCount()}`, 'Server');
663
+ // 更新签名索引
664
+ CONFIG.SIGNATURE_INDEX = (CONFIG.SIGNATURE_INDEX + 1) % tokenManager.getTokenCount();
665
+ Logger.warn(`请求被限流,剩余重试次数: ${tokenManager.getTokenCount()}`, 'ChatAPI');
666
  } else {
667
  // 非429错误直接抛出
668
+ throw new Error(`上游服务请求失败! status: ${response.status}`);
 
 
 
 
 
 
 
669
  }
670
  }
671
  }
672
+
673
+ // 如果重试次数用完仍然是429
674
+ throw new Error('所有令牌都已耗尽,请求被限流');
675
+
676
  } catch (error) {
677
+ Logger.error('Chat Completions Request Error', error, 'ChatAPI');
678
  res.status(500).json({
679
  error: {
680
  message: error.message,
 
686
  }
687
  });
688
 
689
+ // 404 处理
690
  app.use((req, res) => {
691
+ res.status(404).json({ error: '请求路径不存在' });
692
  });
693
 
694
+ // 启动服务器
695
  app.listen(CONFIG.SERVER.PORT, () => {
696
+ Logger.info(`服务器运行在端口 ${CONFIG.SERVER.PORT}`, 'Server');
697
  });