yuoop commited on
Commit
087cef2
·
verified ·
1 Parent(s): cdaad0d

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +984 -1247
index.js CHANGED
@@ -1,1103 +1,955 @@
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
- IS_TEMP_CONVERSATION: process.env.IS_TEMP_CONVERSATION == undefined ? false : process.env.IS_TEMP_CONVERSATION == 'true',
27
- IS_TEMP_GROK2: process.env.IS_TEMP_GROK2 == undefined ? true : process.env.IS_TEMP_GROK2 == 'true',
28
- GROK2_CONCURRENCY_LEVEL: process.env.GROK2_CONCURRENCY_LEVEL || 4,
29
- IS_CUSTOM_SSO: process.env.IS_CUSTOM_SSO == undefined ? false : process.env.IS_CUSTOM_SSO == 'true',
30
- BASE_URL: "https://grok.com",
31
- API_KEY: process.env.API_KEY || "sk-123456",
32
- SIGNATURE_COOKIE: null,
33
- TEMP_COOKIE: null,
34
- PICGO_KEY: process.env.PICGO_KEY || null, //想要流式生图的话需要填入这个PICGO图床的key
35
- TUMY_KEY: process.env.TUMY_KEY || null //想要流式生图的话需要填入这个TUMY图床的key 两个图床二选一,默认使用PICGO
36
- },
37
- SERVER: {
38
- PORT: process.env.PORT || 3000,
39
- BODY_LIMIT: '5mb'
40
- },
41
- RETRY: {
42
- MAX_ATTEMPTS: 2//重试次数
43
- },
44
- SHOW_THINKING: process.env.SHOW_THINKING == undefined ? true : process.env.SHOW_THINKING == 'true',
45
- IS_THINKING: false,
46
- IS_IMG_GEN: false,
47
- IS_IMG_GEN2: false,
48
- TEMP_COOKIE_INDEX: 0,//临时cookie的下标
49
- ISSHOW_SEARCH_RESULTS: process.env.ISSHOW_SEARCH_RESULTS == undefined ? true : process.env.ISSHOW_SEARCH_RESULTS == 'true',//是否显示搜索结果
50
- CHROME_PATH: process.env.CHROME_PATH || null
51
  };
52
  puppeteer.use(StealthPlugin())
53
 
54
  // 请求头配置
55
  const DEFAULT_HEADERS = {
56
- 'accept': '*/*',
57
- 'accept-language': 'zh-CN,zh;q=0.9',
58
- 'accept-encoding': 'gzip, deflate, br, zstd',
59
- 'content-type': 'text/plain;charset=UTF-8',
60
- 'Connection': 'keep-alive',
61
- 'origin': 'https://grok.com',
62
- 'priority': 'u=1, i',
63
- 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
64
- 'sec-ch-ua-mobile': '?0',
65
- 'sec-ch-ua-platform': '"Windows"',
66
- 'sec-fetch-dest': 'empty',
67
- 'sec-fetch-mode': 'cors',
68
- 'sec-fetch-site': 'same-origin',
69
- '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',
70
- 'baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
71
  };
72
 
73
 
74
  async function initialization() {
75
- if (CONFIG.CHROME_PATH == null) {
76
- try {
77
- CONFIG.CHROME_PATH = puppeteer.executablePath();
78
- } catch (error) {
79
- CONFIG.CHROME_PATH = "/usr/bin/chromium";
80
- }
81
- }
82
- Logger.info(`CHROME_PATH: ${CONFIG.CHROME_PATH}`, 'Server');
83
- if (CONFIG.API.IS_CUSTOM_SSO) {
84
- if (CONFIG.API.IS_TEMP_GROK2) {
85
- await tempCookieManager.ensureCookies();
86
- }
87
- return;
88
- }
89
- const ssoArray = process.env.SSO.split(',');
90
- const concurrencyLimit = 1;
91
- for (let i = 0; i < ssoArray.length; i += concurrencyLimit) {
92
- const batch = ssoArray.slice(i, i + concurrencyLimit);
93
- const batchPromises = batch.map(sso =>
94
- tokenManager.addToken(`sso-rw=${sso};sso=${sso}`)
95
- );
96
-
97
- await Promise.all(batchPromises);
98
- Logger.info(`已加载令牌: ${i} 个`, 'Server');
99
- await new Promise(resolve => setTimeout(resolve, 1000));
100
- }
101
- Logger.info(`令牌加载完成: ${JSON.stringify(tokenManager.getAllTokens(), null, 2)}`, 'Server');
102
- Logger.info(`共加载: ${tokenManager.getAllTokens().length}个令牌`, 'Server');
103
- if (CONFIG.API.IS_TEMP_GROK2) {
104
- await tempCookieManager.ensureCookies();
105
- CONFIG.API.TEMP_COOKIE = tempCookieManager.cookies[tempCookieManager.currentIndex];
106
- }
107
- Logger.info("初始化完成", 'Server');
108
  }
109
 
110
  class AuthTokenManager {
111
- constructor() {
112
- this.tokenModelMap = {};
113
- this.expiredTokens = new Set();
114
- this.tokenStatusMap = {};
115
-
116
- // 定义模型请求频率限制和过期时间
117
- this.modelConfig = {
118
- "grok-2": {
119
- RequestFrequency: 30,
120
- ExpirationTime: 1 * 60 * 60 * 1000 // 1小时
121
- },
122
- "grok-3": {
123
- RequestFrequency: 20,
124
- ExpirationTime: 2 * 60 * 60 * 1000 // 2小时
125
- },
126
- "grok-3-deepsearch": {
127
- RequestFrequency: 10,
128
- ExpirationTime: 24 * 60 * 60 * 1000 // 24小时
129
- },
130
- "grok-3-reasoning": {
131
- RequestFrequency: 10,
132
- ExpirationTime: 24 * 60 * 60 * 1000 // 24小时
133
- }
134
- };
135
- this.tokenResetSwitch = false;
136
- this.tokenResetTimer = null;
137
- }
138
- async fetchGrokStats(token, modelName) {
139
- let requestKind = 'DEFAULT';
140
- if (modelName == 'grok-2' || modelName == 'grok-3') {
141
- requestKind = 'DEFAULT';
142
- } else if (modelName == 'grok-3-deepsearch') {
143
- requestKind = 'DEEPSEARCH';
144
- } else if (modelName == 'grok-3-reasoning') {
145
- requestKind = 'REASONING';
146
- }
147
- const response = await fetch('https://grok.com/rest/rate-limits', {
148
- method: 'POST',
149
- headers: {
150
- 'content-type': 'application/json',
151
- 'Cookie': token,
152
- },
153
- body: JSON.stringify({
154
- "requestKind": requestKind,
155
- "modelName": modelName == 'grok-2' ? 'grok-latest' : "grok-3"
156
- })
157
- });
158
-
159
- if (response.status != 200) {
160
- return 0;
161
- }
162
- const data = await response.json();
163
- return data.remainingQueries;
164
- }
165
- async addToken(token) {
166
- const sso = token.split("sso=")[1].split(";")[0];
167
-
168
- for (const model of Object.keys(this.modelConfig)) {
169
- if (!this.tokenModelMap[model]) {
170
- this.tokenModelMap[model] = [];
171
- }
172
- if (!this.tokenStatusMap[sso]) {
173
- this.tokenStatusMap[sso] = {};
174
- }
175
- const existingTokenEntry = this.tokenModelMap[model].find(entry => entry.token === token);
176
-
177
- if (!existingTokenEntry) {
178
- try {
179
- const remainingQueries = await this.fetchGrokStats(token, model);
180
-
181
- const modelRequestFrequency = this.modelConfig[model].RequestFrequency;
182
- const usedRequestCount = modelRequestFrequency - remainingQueries;
183
-
184
- if (usedRequestCount === modelRequestFrequency) {
185
- this.expiredTokens.add({
186
- token: token,
187
- model: model,
188
- expiredTime: Date.now()
189
- });
190
-
191
- if (!this.tokenStatusMap[sso][model]) {
192
- this.tokenStatusMap[sso][model] = {
193
- isValid: false,
194
- invalidatedTime: Date.now(),
195
- totalRequestCount: Math.max(0, usedRequestCount)
196
- };
197
- }
198
-
199
- if (!this.tokenResetSwitch) {
200
- this.startTokenResetProcess();
201
- this.tokenResetSwitch = true;
202
- }
203
- } else {
204
- this.tokenModelMap[model].push({
205
- token: token,
206
- RequestCount: Math.max(0, usedRequestCount),
207
- AddedTime: Date.now(),
208
- StartCallTime: null
209
- });
210
-
211
- if (!this.tokenStatusMap[sso][model]) {
212
- this.tokenStatusMap[sso][model] = {
213
- isValid: true,
214
- invalidatedTime: null,
215
- totalRequestCount: Math.max(0, usedRequestCount)
216
- };
217
- }
218
- }
219
- } catch (error) {
220
- this.tokenModelMap[model].push({
221
- token: token,
222
- RequestCount: 0,
223
- AddedTime: Date.now(),
224
- StartCallTime: null
225
- });
226
-
227
- if (!this.tokenStatusMap[sso][model]) {
228
- this.tokenStatusMap[sso][model] = {
229
- isValid: true,
230
- invalidatedTime: null,
231
- totalRequestCount: 0
232
- };
233
- }
234
-
235
- Logger.error(`获取模型 ${model} 的统计信息失败: ${error}`, 'TokenManager');
236
- }
237
- await Utils.delay(200);
238
- }
239
- }
240
- }
241
-
242
- setToken(token) {
243
- const models = Object.keys(this.modelConfig);
244
- this.tokenModelMap = models.reduce((map, model) => {
245
- map[model] = [{
246
- token,
247
- RequestCount: 0,
248
- AddedTime: Date.now(),
249
- StartCallTime: null
250
- }];
251
- return map;
252
- }, {});
253
- const sso = token.split("sso=")[1].split(";")[0];
254
- this.tokenStatusMap[sso] = models.reduce((statusMap, model) => {
255
- statusMap[model] = {
256
- isValid: true,
257
- invalidatedTime: null,
258
- totalRequestCount: 0
259
- };
260
- return statusMap;
261
- }, {});
262
- }
263
-
264
- async deleteToken(token) {
265
- try {
266
- const sso = token.split("sso=")[1].split(";")[0];
267
- await Promise.all([
268
- new Promise((resolve) => {
269
- this.tokenModelMap = Object.fromEntries(
270
- Object.entries(this.tokenModelMap).map(([model, entries]) => [
271
- model,
272
- entries.filter(entry => entry.token !== token)
273
- ])
274
- );
275
- resolve();
276
- }),
277
-
278
- new Promise((resolve) => {
279
- delete this.tokenStatusMap[sso];
280
- resolve();
281
- }),
282
- ]);
283
- Logger.info(`令牌已成功移除: ${token}`, 'TokenManager');
284
- return true;
285
- } catch (error) {
286
- Logger.error('令牌删除失败:', error);
287
- return false;
288
- }
289
- }
290
- getNextTokenForModel(modelId) {
291
- const normalizedModel = this.normalizeModelName(modelId);
292
-
293
- if (!this.tokenModelMap[normalizedModel] || this.tokenModelMap[normalizedModel].length === 0) {
294
- return null;
295
- }
296
- const tokenEntry = this.tokenModelMap[normalizedModel][0];
297
-
298
- if (tokenEntry) {
299
- if (tokenEntry.StartCallTime === null || tokenEntry.StartCallTime === undefined) {
300
- tokenEntry.StartCallTime = Date.now();
301
- }
302
- if (!this.tokenResetSwitch) {
303
- this.startTokenResetProcess();
304
- this.tokenResetSwitch = true;
305
- }
306
- tokenEntry.RequestCount++;
307
-
308
- if (tokenEntry.RequestCount > this.modelConfig[normalizedModel].RequestFrequency) {
309
- this.removeTokenFromModel(normalizedModel, tokenEntry.token);
310
- const nextTokenEntry = this.tokenModelMap[normalizedModel][0];
311
- return nextTokenEntry ? nextTokenEntry.token : null;
312
- }
313
- const sso = tokenEntry.token.split("sso=")[1].split(";")[0];
314
- if (this.tokenStatusMap[sso] && this.tokenStatusMap[sso][normalizedModel]) {
315
- if (tokenEntry.RequestCount === this.modelConfig[normalizedModel].RequestFrequency) {
316
- this.tokenStatusMap[sso][normalizedModel].isValid = false;
317
- this.tokenStatusMap[sso][normalizedModel].invalidatedTime = Date.now();
318
- }
319
- this.tokenStatusMap[sso][normalizedModel].totalRequestCount++;
320
- }
321
- return tokenEntry.token;
322
- }
323
-
324
- return null;
325
- }
326
-
327
- removeTokenFromModel(modelId, token) {
328
- const normalizedModel = this.normalizeModelName(modelId);
329
-
330
- if (!this.tokenModelMap[normalizedModel]) {
331
- Logger.error(`模型 ${normalizedModel} 不存在`, 'TokenManager');
332
- return false;
333
- }
334
-
335
- const modelTokens = this.tokenModelMap[normalizedModel];
336
- const tokenIndex = modelTokens.findIndex(entry => entry.token === token);
337
-
338
- if (tokenIndex !== -1) {
339
- const removedTokenEntry = modelTokens.splice(tokenIndex, 1)[0];
340
- this.expiredTokens.add({
341
- token: removedTokenEntry.token,
342
- model: normalizedModel,
343
- expiredTime: Date.now()
344
- });
345
-
346
- if (!this.tokenResetSwitch) {
347
- this.startTokenResetProcess();
348
- this.tokenResetSwitch = true;
349
- }
350
- Logger.info(`模型${modelId}的令牌已失效,已成功移除令牌: ${token}`, 'TokenManager');
351
- return true;
352
- }
353
-
354
- Logger.error(`在模型 ${normalizedModel} 中未找到 token: ${token}`, 'TokenManager');
355
- return false;
356
- }
357
-
358
- getExpiredTokens() {
359
- return Array.from(this.expiredTokens);
360
- }
361
-
362
- normalizeModelName(model) {
363
- if (model.startsWith('grok-') && !model.includes('deepsearch') && !model.includes('reasoning')) {
364
- return model.split('-').slice(0, 2).join('-');
365
- }
366
- return model;
367
- }
368
-
369
- getTokenCountForModel(modelId) {
370
- const normalizedModel = this.normalizeModelName(modelId);
371
- return this.tokenModelMap[normalizedModel]?.length || 0;
372
- }
373
-
374
- getRemainingTokenRequestCapacity() {
375
- const remainingCapacityMap = {};
376
-
377
- Object.keys(this.modelConfig).forEach(model => {
378
- const modelTokens = this.tokenModelMap[model] || [];
379
-
380
- const modelRequestFrequency = this.modelConfig[model].RequestFrequency;
381
-
382
- const totalUsedRequests = modelTokens.reduce((sum, tokenEntry) => {
383
- return sum + (tokenEntry.RequestCount || 0);
384
- }, 0);
385
-
386
- // 计算剩余可用请求数量
387
- const remainingCapacity = (modelTokens.length * modelRequestFrequency) - totalUsedRequests;
388
- remainingCapacityMap[model] = Math.max(0, remainingCapacity);
389
- });
390
-
391
- return remainingCapacityMap;
392
- }
393
-
394
- getTokenArrayForModel(modelId) {
395
- const normalizedModel = this.normalizeModelName(modelId);
396
- return this.tokenModelMap[normalizedModel] || [];
397
- }
398
-
399
- startTokenResetProcess() {
400
- if (this.tokenResetTimer) {
401
- clearInterval(this.tokenResetTimer);
402
- }
403
-
404
- this.tokenResetTimer = setInterval(() => {
405
- const now = Date.now();
406
-
407
- this.expiredTokens.forEach(expiredTokenInfo => {
408
- const { token, model, expiredTime } = expiredTokenInfo;
409
- const expirationTime = this.modelConfig[model].ExpirationTime;
410
- if (now - expiredTime >= expirationTime) {
411
- if (!this.tokenModelMap[model].some(entry => entry.token === token)) {
412
- this.tokenModelMap[model].push({
413
- token: token,
414
- RequestCount: 0,
415
- AddedTime: now,
416
- StartCallTime: null
417
- });
418
- }
419
- const sso = token.split("sso=")[1].split(";")[0];
420
-
421
- if (this.tokenStatusMap[sso] && this.tokenStatusMap[sso][model]) {
422
- this.tokenStatusMap[sso][model].isValid = true;
423
- this.tokenStatusMap[sso][model].invalidatedTime = null;
424
- this.tokenStatusMap[sso][model].totalRequestCount = 0;
425
- }
426
-
427
- this.expiredTokens.delete(expiredTokenInfo);
428
- }
429
- });
430
-
431
- Object.keys(this.modelConfig).forEach(model => {
432
- if (!this.tokenModelMap[model]) return;
433
-
434
- const processedTokens = this.tokenModelMap[model].map(tokenEntry => {
435
- if (!tokenEntry.StartCallTime) return tokenEntry;
436
-
437
- const expirationTime = this.modelConfig[model].ExpirationTime;
438
- if (now - tokenEntry.StartCallTime >= expirationTime) {
439
- const sso = tokenEntry.token.split("sso=")[1].split(";")[0];
440
- if (this.tokenStatusMap[sso] && this.tokenStatusMap[sso][model]) {
441
- this.tokenStatusMap[sso][model].isValid = true;
442
- this.tokenStatusMap[sso][model].invalidatedTime = null;
443
- this.tokenStatusMap[sso][model].totalRequestCount = 0;
444
- }
445
-
446
- return {
447
- ...tokenEntry,
448
- RequestCount: 0,
449
- StartCallTime: null
450
- };
451
- }
452
-
453
- return tokenEntry;
454
- });
455
-
456
- this.tokenModelMap[model] = processedTokens;
457
- });
458
- }, 1 * 60 * 60 * 1000);
459
- }
460
-
461
- getAllTokens() {
462
- const allTokens = new Set();
463
- Object.values(this.tokenModelMap).forEach(modelTokens => {
464
- modelTokens.forEach(entry => allTokens.add(entry.token));
465
- });
466
- return Array.from(allTokens);
467
- }
468
-
469
- getTokenStatusMap() {
470
- return this.tokenStatusMap;
471
- }
472
  }
473
 
474
 
475
  class Utils {
476
- static delay(time) {
477
- return new Promise(function (resolve) {
478
- setTimeout(resolve, time)
479
- });
480
- }
481
- static async organizeSearchResults(searchResults) {
482
- // 确保传入的是有效的搜索结果对象
483
- if (!searchResults || !searchResults.results) {
484
- return '';
485
- }
486
-
487
- const results = searchResults.results;
488
- const formattedResults = results.map((result, index) => {
489
- // 处理可能为空的字段
490
- const title = result.title || '未知标题';
491
- const url = result.url || '#';
492
- const preview = result.preview || '无预览内容';
493
-
494
- return `\r\n<details><summary>资料[${index}]: ${title}</summary>\r\n${preview}\r\n\n[Link](${url})\r\n</details>`;
495
- });
496
- return formattedResults.join('\n\n');
497
- }
498
- static async createAuthHeaders(model) {
499
- return await tokenManager.getNextTokenForModel(model);
500
- }
501
- }
502
- class GrokTempCookieManager {
503
- constructor() {
504
- this.cookies = [];
505
- this.currentIndex = 0;
506
- this.isRefreshing = false;
507
- this.initialCookieCount = CONFIG.API.GROK2_CONCURRENCY_LEVEL;
508
- this.extractCount = 0;
509
- }
510
-
511
- async ensureCookies() {
512
- // 如果 cookies 数量不足,则重新获取
513
- if (this.cookies.length < this.initialCookieCount) {
514
- await this.refreshCookies();
515
- }
516
- }
517
- async extractGrokHeaders(browser) {
518
- Logger.info("开始提取头信息", 'Server');
519
- try {
520
- const page = await browser.newPage();
521
- await page.goto('https://grok.com/', { waitUntil: 'domcontentloaded' });
522
- let waitTime = 0;
523
- const targetHeaders = ['x-anonuserid', 'x-challenge', 'x-signature'];
524
-
525
- while (true) {
526
- const cookies = await page.cookies();
527
- const extractedHeaders = cookies
528
- .filter(cookie => targetHeaders.includes(cookie.name.toLowerCase()))
529
- .map(cookie => `${cookie.name}=${cookie.value}`);
530
-
531
- if (targetHeaders.every(header =>
532
- extractedHeaders.some(cookie => cookie && cookie.startsWith(header + '='))
533
- )) {
534
- await browser.close();
535
- Logger.info('提取的头信息:', JSON.stringify(extractedHeaders, null, 2), 'Server');
536
- this.cookies.push(extractedHeaders.join(';'));
537
- this.extractCount++;
538
- return true;
539
- }
540
-
541
- await Utils.delay(500);
542
- waitTime += 500;
543
- if (waitTime >= 10000) {
544
- await browser.close();
545
- return null;
546
- }
547
- }
548
- } catch (error) {
549
- Logger.error('获取头信息出错:', error, 'Server');
550
- return null;
551
- }
552
- }
553
- async initializeTempCookies(count = 1) {
554
- Logger.info(`开始初始化 ${count} 个临时账号认证信息`, 'Server');
555
- const browserOptions = {
556
- headless: true,
557
- args: [
558
- '--no-sandbox',
559
- '--disable-setuid-sandbox',
560
- '--disable-dev-shm-usage',
561
- '--disable-gpu'
562
- ],
563
- executablePath: CONFIG.CHROME_PATH
564
- };
565
-
566
- const browsers = await Promise.all(
567
- Array.from({ length: count }, () => puppeteer.launch(browserOptions))
568
- );
569
-
570
- const cookiePromises = browsers.map(browser => this.extractGrokHeaders(browser));
571
- return Promise.all(cookiePromises);
572
- }
573
- async refreshCookies() {
574
- if (this.isRefreshing) return;
575
- this.isRefreshing = true;
576
- this.extractCount = 0;
577
- try {
578
- // 获取新的 cookies
579
- let retryCount = 0;
580
- let remainingCount = this.initialCookieCount - this.cookies.length;
581
-
582
- while (retryCount < CONFIG.RETRY.MAX_ATTEMPTS) {
583
- await this.initializeTempCookies(remainingCount);
584
- if (this.extractCount != remainingCount) {
585
- if (this.extractCount == 0) {
586
- Logger.error(`无法获取足够的有效 TempCookies,可能网络存在问题,当前数量:${this.cookies.length}`);
587
- } else if (this.extractCount < remainingCount) {
588
- remainingCount -= this.extractCount;
589
- this.extractCount = 0;
590
- retryCount++;
591
- await Utils.delay(1000 * retryCount);
592
- } else {
593
- break;
594
- }
595
- } else {
596
- break;
597
- }
598
- }
599
- if (this.currentIndex >= this.cookies.length) {
600
- this.currentIndex = 0;
601
- }
602
-
603
- if (this.cookies.length < this.initialCookieCount) {
604
- if (this.cookies.length !== 0) {
605
- // 如果已经获取到一些 TempCookies,则只提示警告错误
606
- Logger.error(`无法获取足够的有效 TempCookies,可能网络存在问题,当前数量:${this.cookies.length}`);
607
- } else {
608
- // 如果未获取到任何 TempCookies,则抛出错误
609
- throw new Error(`无法获取足够的有效 TempCookies,可能网络存在问题,当前数量:${this.cookies.length}`);
610
- }
611
- }
612
- } catch (error) {
613
- Logger.error('刷新 cookies 失败:', error);
614
- } finally {
615
- Logger.info(`已提取${this.cookies.length}个TempCookies`, 'Server');
616
- Logger.info(`提取的TempCookies为${JSON.stringify(this.cookies, null, 2)}`, 'Server');
617
- this.isRefreshing = false;
618
- }
619
- }
620
  }
621
 
622
  class GrokApiClient {
623
- constructor(modelId) {
624
- if (!CONFIG.MODELS[modelId]) {
625
- throw new Error(`不支持的模型: ${modelId}`);
626
- }
627
- this.modelId = CONFIG.MODELS[modelId];
628
- }
629
-
630
- processMessageContent(content) {
631
- if (typeof content === 'string') return content;
632
- return null;
633
- }
634
- // 获取图片类型
635
- getImageType(base64String) {
636
- let mimeType = 'image/jpeg';
637
- if (base64String.includes('data:image')) {
638
- const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
639
- if (matches) {
640
- mimeType = matches[1];
641
- }
642
- }
643
- const extension = mimeType.split('/')[1];
644
- const fileName = `image.${extension}`;
645
-
646
- return {
647
- mimeType: mimeType,
648
- fileName: fileName
649
- };
650
- }
651
-
652
- async uploadBase64Image(base64Data, url) {
653
- try {
654
- // 处理 base64 数据
655
- let imageBuffer;
656
- if (base64Data.includes('data:image')) {
657
- imageBuffer = base64Data.split(',')[1];
658
- } else {
659
- imageBuffer = base64Data
660
- }
661
- const { mimeType, fileName } = this.getImageType(base64Data);
662
- let uploadData = {
663
- rpc: "uploadFile",
664
- req: {
665
- fileName: fileName,
666
- fileMimeType: mimeType,
667
- content: imageBuffer
668
- }
669
- };
670
- Logger.info("发送图片请求", 'Server');
671
- // 发送请求
672
- const response = await fetch(url, {
673
- method: 'POST',
674
- headers: {
675
- ...CONFIG.DEFAULT_HEADERS,
676
- "cookie": CONFIG.API.SIGNATURE_COOKIE
677
- },
678
- body: JSON.stringify(uploadData)
679
- });
680
-
681
- if (!response.ok) {
682
- Logger.error(`上传图片失败,状态码:${response.status},原因:${response.error}`, 'Server');
683
- return '';
684
- }
685
-
686
- const result = await response.json();
687
- Logger.info('上传图片成功:', result, 'Server');
688
- return result.fileMetadataId;
689
-
690
- } catch (error) {
691
- Logger.error(error, 'Server');
692
- return '';
693
- }
694
- }
695
-
696
- async prepareChatRequest(request) {
697
- if ((request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen') && !CONFIG.API.PICGO_KEY && !CONFIG.API.TUMY_KEY && request.stream) {
698
- throw new Error(`该模型流式输出需要配置PICGO或者TUMY图床密钥!`);
699
- }
700
-
701
- // 处理画图模型的消息限制
702
- let todoMessages = request.messages;
703
- if (request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen') {
704
- const lastMessage = todoMessages[todoMessages.length - 1];
705
- if (lastMessage.role !== 'user') {
706
- throw new Error('画图模型的最后一条消息必须是用户消息!');
707
- }
708
- todoMessages = [lastMessage];
709
- }
710
-
711
- const fileAttachments = [];
712
- let messages = '';
713
- let lastRole = null;
714
- let lastContent = '';
715
- const search = request.model === 'grok-2-search' || request.model === 'grok-3-search';
716
-
717
- // 移除<think>标签及其内容和base64图片
718
- const removeThinkTags = (text) => {
719
- text = text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
720
- text = text.replace(/!\[image\]\(data:.*?base64,.*?\)/g, '[图片]');
721
- return text;
722
- };
723
-
724
- const processImageUrl = async (content) => {
725
- if (content.type === 'image_url' && content.image_url.url.includes('data:image')) {
726
- const imageResponse = await this.uploadBase64Image(
727
- content.image_url.url,
728
- `${CONFIG.API.BASE_URL}/api/rpc`
729
- );
730
- return imageResponse;
731
- }
732
- return null;
733
- };
734
-
735
- const processContent = async (content) => {
736
- if (Array.isArray(content)) {
737
- let textContent = '';
738
- for (const item of content) {
739
- if (item.type === 'image_url') {
740
- textContent += (textContent ? '\n' : '') + "[图片]";
741
- } else if (item.type === 'text') {
742
- textContent += (textContent ? '\n' : '') + removeThinkTags(item.text);
743
- }
744
- }
745
- return textContent;
746
- } else if (typeof content === 'object' && content !== null) {
747
- if (content.type === 'image_url') {
748
- return "[图片]";
749
- } else if (content.type === 'text') {
750
- return removeThinkTags(content.text);
751
- }
752
- }
753
- return removeThinkTags(this.processMessageContent(content));
754
- };
755
-
756
- for (const current of todoMessages) {
757
- const role = current.role === 'assistant' ? 'assistant' : 'user';
758
- const isLastMessage = current === todoMessages[todoMessages.length - 1];
759
-
760
- // 处理图片附件
761
- if (isLastMessage && current.content) {
762
- if (Array.isArray(current.content)) {
763
- for (const item of current.content) {
764
- if (item.type === 'image_url') {
765
- const processedImage = await processImageUrl(item);
766
- if (processedImage) fileAttachments.push(processedImage);
767
- }
768
- }
769
- } else if (current.content.type === 'image_url') {
770
- const processedImage = await processImageUrl(current.content);
771
- if (processedImage) fileAttachments.push(processedImage);
772
- }
773
- }
774
-
775
- // 处理文本内容
776
- const textContent = await processContent(current.content);
777
-
778
- if (textContent || (isLastMessage && fileAttachments.length > 0)) {
779
- if (role === lastRole && textContent) {
780
- lastContent += '\n' + textContent;
781
- messages = messages.substring(0, messages.lastIndexOf(`${role.toUpperCase()}: `)) +
782
- `${role.toUpperCase()}: ${lastContent}\n`;
783
- } else {
784
- messages += `${role.toUpperCase()}: ${textContent || '[图片]'}\n`;
785
- lastContent = textContent;
786
- lastRole = role;
787
- }
788
- }
789
- }
790
-
791
- return {
792
- temporary: CONFIG.API.IS_TEMP_CONVERSATION,
793
- modelName: this.modelId,
794
- message: messages.trim(),
795
- fileAttachments: fileAttachments.slice(0, 4),
796
- imageAttachments: [],
797
- disableSearch: false,
798
- enableImageGeneration: true,
799
- returnImageBytes: false,
800
- returnRawGrokInXaiRequest: false,
801
- enableImageStreaming: false,
802
- imageGenerationCount: 1,
803
- forceConcise: false,
804
- toolOverrides: {
805
- imageGen: request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen',
806
- webSearch: search,
807
- xSearch: search,
808
- xMediaSearch: search,
809
- trendsSearch: search,
810
- xPostAnalyze: search
811
- },
812
- enableSideBySide: true,
813
- isPreset: false,
814
- sendFinalMetadata: true,
815
- customInstructions: "",
816
- deepsearchPreset: request.model === 'grok-3-deepsearch' ? "default" : "",
817
- isReasoning: request.model === 'grok-3-reasoning'
818
- };
819
- }
 
 
820
  }
821
 
822
  class MessageProcessor {
823
- static createChatResponse(message, model, isStream = false) {
824
- const baseResponse = {
825
- id: `chatcmpl-${uuidv4()}`,
826
- created: Math.floor(Date.now() / 1000),
827
- model: model
828
- };
829
-
830
- if (isStream) {
831
- return {
832
- ...baseResponse,
833
- object: 'chat.completion.chunk',
834
- choices: [{
835
- index: 0,
836
- delta: {
837
- content: message
838
- }
839
- }]
840
- };
841
- }
842
-
843
- return {
844
- ...baseResponse,
845
- object: 'chat.completion',
846
- choices: [{
847
- index: 0,
848
- message: {
849
- role: 'assistant',
850
- content: message
851
- },
852
- finish_reason: 'stop'
853
- }],
854
- usage: null
855
- };
856
- }
857
  }
858
  async function processModelResponse(response, model) {
859
- let result = { token: null, imageUrl: null }
860
- if (CONFIG.IS_IMG_GEN) {
861
- if (response?.cachedImageGenerationResponse && !CONFIG.IS_IMG_GEN2) {
862
- result.imageUrl = response.cachedImageGenerationResponse.imageUrl;
863
- }
864
- return result;
865
- }
866
-
867
- //非生图模型的处理
868
- switch (model) {
869
- case 'grok-2':
870
- result.token = response?.token;
871
- return result;
872
- case 'grok-2-search':
873
- case 'grok-3-search':
874
- if (response?.webSearchResults && CONFIG.ISSHOW_SEARCH_RESULTS) {
875
- result.token = `\r\n<think>${await Utils.organizeSearchResults(response.webSearchResults)}</think>\r\n`;
876
- } else {
877
- result.token = response?.token;
878
- }
879
- return result;
880
- case 'grok-3':
881
- result.token = response?.token;
882
- return result;
883
- case 'grok-3-deepsearch':
884
- if (response?.messageTag === "final") {
885
- result.token = response?.token;
886
- }
887
- return result;
888
- case 'grok-3-reasoning':
889
- if (response?.isThinking && !CONFIG.SHOW_THINKING) return result;
890
-
891
- if (response?.isThinking && !CONFIG.IS_THINKING) {
892
- result.token = "<think>" + response?.token;
893
- CONFIG.IS_THINKING = true;
894
- } else if (!response.isThinking && CONFIG.IS_THINKING) {
895
- result.token = "</think>" + response?.token;
896
- CONFIG.IS_THINKING = false;
897
- } else {
898
- result.token = response?.token;
899
- }
900
- return result;
901
- }
902
- return result;
 
 
 
 
 
 
903
  }
904
 
905
  async function handleResponse(response, model, res, isStream) {
906
- try {
907
- const stream = response.body;
908
- let buffer = '';
909
- let fullResponse = '';
910
- const dataPromises = [];
911
- if (isStream) {
912
- res.setHeader('Content-Type', 'text/event-stream');
913
- res.setHeader('Cache-Control', 'no-cache');
914
- res.setHeader('Connection', 'keep-alive');
915
- }
916
- CONFIG.IS_THINKING = false;
917
- CONFIG.IS_IMG_GEN = false;
918
- CONFIG.IS_IMG_GEN2 = false;
919
- Logger.info("开始处理流式响应", 'Server');
920
-
921
- return new Promise((resolve, reject) => {
922
- stream.on('data', async (chunk) => {
923
- buffer += chunk.toString();
924
- const lines = buffer.split('\n');
925
- buffer = lines.pop() || '';
926
-
927
- for (const line of lines) {
928
- if (!line.trim()) continue;
929
- try {
930
- const linejosn = JSON.parse(line.trim());
931
- if (linejosn?.error) {
932
- Logger.error(JSON.stringify(linejosn, null, 2), 'Server');
933
- if (linejosn.error?.name === "RateLimitError") {
934
- CONFIG.API.TEMP_COOKIE = null;
935
- }
936
- stream.destroy();
937
- reject(new Error("RateLimitError"));
938
- return;
939
- }
940
- let response = linejosn?.result?.response;
941
- if (!response) continue;
942
- if (response?.doImgGen || response?.imageAttachmentInfo) {
943
- CONFIG.IS_IMG_GEN = true;
944
- }
945
- const processPromise = (async () => {
946
- const result = await processModelResponse(response, model);
947
-
948
- if (result.token) {
949
- if (isStream) {
950
- res.write(`data: ${JSON.stringify(MessageProcessor.createChatResponse(result.token, model, true))}\n\n`);
951
- } else {
952
- fullResponse += result.token;
953
- }
954
- }
955
- if (result.imageUrl) {
956
- CONFIG.IS_IMG_GEN2 = true;
957
- const dataImage = await handleImageResponse(result.imageUrl);
958
- if (isStream) {
959
- res.write(`data: ${JSON.stringify(MessageProcessor.createChatResponse(dataImage, model, true))}\n\n`);
960
- } else {
961
- res.json(MessageProcessor.createChatResponse(dataImage, model));
962
- }
963
- }
964
- })();
965
- dataPromises.push(processPromise);
966
- } catch (error) {
967
- Logger.error(error, 'Server');
968
- continue;
969
- }
970
- }
971
- });
972
-
973
- stream.on('end', async () => {
974
- try {
975
- await Promise.all(dataPromises);
976
- if (isStream) {
977
- res.write('data: [DONE]\n\n');
978
- res.end();
979
- } else {
980
- if (!CONFIG.IS_IMG_GEN2) {
981
- res.json(MessageProcessor.createChatResponse(fullResponse, model));
982
- }
983
- }
984
- resolve();
985
- } catch (error) {
986
- Logger.error(error, 'Server');
987
- reject(error);
988
- }
989
- });
990
-
991
- stream.on('error', (error) => {
992
- Logger.error(error, 'Server');
993
- reject(error);
994
- });
995
- });
996
- } catch (error) {
997
- Logger.error(error, 'Server');
998
- throw new Error(error);
999
- }
1000
  }
1001
 
1002
  async function handleImageResponse(imageUrl) {
1003
- const MAX_RETRIES = 2;
1004
- let retryCount = 0;
1005
- let imageBase64Response;
1006
-
1007
- while (retryCount < MAX_RETRIES) {
1008
- try {
1009
- imageBase64Response = await fetch(`https://assets.grok.com/${imageUrl}`, {
1010
- method: 'GET',
1011
- headers: {
1012
- ...DEFAULT_HEADERS,
1013
- "cookie": CONFIG.API.SIGNATURE_COOKIE
1014
- }
1015
- });
1016
-
1017
- if (imageBase64Response.ok) break;
1018
- retryCount++;
1019
- if (retryCount === MAX_RETRIES) {
1020
- throw new Error(`上游服务请求失败! status: ${imageBase64Response.status}`);
1021
- }
1022
- await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
1023
-
1024
- } catch (error) {
1025
- Logger.error(error, 'Server');
1026
- retryCount++;
1027
- if (retryCount === MAX_RETRIES) {
1028
- throw error;
1029
- }
1030
- await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
1031
- }
1032
- }
1033
-
1034
-
1035
- const arrayBuffer = await imageBase64Response.arrayBuffer();
1036
- const imageBuffer = Buffer.from(arrayBuffer);
1037
-
1038
- if (!CONFIG.API.PICGO_KEY && !CONFIG.API.TUMY_KEY) {
1039
- const base64Image = imageBuffer.toString('base64');
1040
- const imageContentType = imageBase64Response.headers.get('content-type');
1041
- return `![image](data:${imageContentType};base64,${base64Image})`
1042
- }
1043
-
1044
- Logger.info("开始上传图床", 'Server');
1045
- const formData = new FormData();
1046
- if (CONFIG.API.PICGO_KEY) {
1047
- formData.append('source', imageBuffer, {
1048
- filename: `image-${Date.now()}.jpg`,
1049
- contentType: 'image/jpeg'
1050
- });
1051
- const formDataHeaders = formData.getHeaders();
1052
- const responseURL = await fetch("https://www.picgo.net/api/1/upload", {
1053
- method: "POST",
1054
- headers: {
1055
- ...formDataHeaders,
1056
- "Content-Type": "multipart/form-data",
1057
- "X-API-Key": CONFIG.API.PICGO_KEY
1058
- },
1059
- body: formData
1060
- });
1061
- if (!responseURL.ok) {
1062
- return "生图失败,请查看PICGO图床密钥是否设置正确"
1063
- } else {
1064
- Logger.info("生图成功", 'Server');
1065
- const result = await responseURL.json();
1066
- return `![image](${result.image.url})`
1067
- }
1068
- } else if (CONFIG.API.TUMY_KEY) {
1069
- const formData = new FormData();
1070
- formData.append('file', imageBuffer, {
1071
- filename: `image-${Date.now()}.jpg`,
1072
- contentType: 'image/jpeg'
1073
- });
1074
- const formDataHeaders = formData.getHeaders();
1075
- const responseURL = await fetch("https://tu.my/api/v1/upload", {
1076
- method: "POST",
1077
- headers: {
1078
- ...formDataHeaders,
1079
- "Accept": "application/json",
1080
- 'Authorization': `Bearer ${CONFIG.API.TUMY_KEY}`
1081
- },
1082
- body: formData
1083
- });
1084
- if (!responseURL.ok) {
1085
- return "生图失败,请查看TUMY图床密钥是否设置正确"
1086
- } else {
1087
- try {
1088
- const result = await responseURL.json();
1089
- Logger.info("生图成功", 'Server');
1090
- return `![image](${result.data.links.url})`
1091
- } catch (error) {
1092
- Logger.error(error, 'Server');
1093
- return "生图失败,请查看TUMY图床密钥是否设置正确"
1094
- }
1095
- }
1096
- }
1097
  }
1098
 
1099
  const tokenManager = new AuthTokenManager();
1100
- const tempCookieManager = new GrokTempCookieManager();
1101
  await initialization();
1102
 
1103
  // 中间件配置
@@ -1106,214 +958,99 @@ app.use(Logger.requestLogger);
1106
  app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
1107
  app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
1108
  app.use(cors({
1109
- origin: '*',
1110
- methods: ['GET', 'POST', 'OPTIONS'],
1111
- allowedHeaders: ['Content-Type', 'Authorization']
1112
  }));
1113
 
1114
-
1115
- app.get('/hf/get/tokens', (req, res) => {
1116
- const authToken = req.headers.authorization?.replace('Bearer ', '');
1117
- if (CONFIG.API.IS_CUSTOM_SSO) {
1118
- return res.status(403).json({ error: '自定义的SSO令牌模式无法获取轮询sso令牌状态' });
1119
- } else if (authToken !== CONFIG.API.API_KEY) {
1120
- return res.status(401).json({ error: 'Unauthorized' });
1121
- }
1122
- res.json(tokenManager.getTokenStatusMap());
1123
- });
1124
- app.post('/hf/add/token', async (req, res) => {
1125
- const authToken = req.headers.authorization?.replace('Bearer ', '');
1126
- if (CONFIG.API.IS_CUSTOM_SSO) {
1127
- return res.status(403).json({ error: '自定义的SSO令牌模式无法添加sso令牌' });
1128
- } else if (authToken !== CONFIG.API.API_KEY) {
1129
- return res.status(401).json({ error: 'Unauthorized' });
1130
- }
1131
- try {
1132
- const sso = req.body.sso;
1133
- await tokenManager.addToken(`sso-rw=${sso};sso=${sso}`);
1134
- res.status(200).json(tokenManager.getTokenStatusMap()[sso]);
1135
- } catch (error) {
1136
- Logger.error(error, 'Server');
1137
- res.status(500).json({ error: '添加sso令牌失败' });
1138
- }
1139
- });
1140
- app.post('/hf/delete/token', async (req, res) => {
1141
- const authToken = req.headers.authorization?.replace('Bearer ', '');
1142
- if (CONFIG.API.IS_CUSTOM_SSO) {
1143
- return res.status(403).json({ error: '自定义的SSO令牌模式无法删除sso令牌' });
1144
- } else if (authToken !== CONFIG.API.API_KEY) {
1145
- return res.status(401).json({ error: 'Unauthorized' });
1146
- }
1147
- try {
1148
- const sso = req.body.sso;
1149
- await tokenManager.deleteToken(`sso-rw=${sso};sso=${sso}`);
1150
- res.status(200).json({ message: '删除sso令牌成功' });
1151
- } catch (error) {
1152
- Logger.error(error, 'Server');
1153
- res.status(500).json({ error: '删除sso令牌失败' });
1154
- }
1155
- });
1156
-
1157
  app.get('/hf/v1/models', (req, res) => {
1158
- res.json({
1159
- object: "list",
1160
- data: Object.keys(tokenManager.tokenModelMap).map((model, index) => ({
1161
- id: model,
1162
- object: "model",
1163
- created: Math.floor(Date.now() / 1000),
1164
- owned_by: "grok",
1165
- }))
1166
- });
1167
  });
1168
-
1169
 
1170
  app.post('/hf/v1/chat/completions', async (req, res) => {
1171
- try {
1172
- const authToken = req.headers.authorization?.replace('Bearer ', '');
1173
- if (CONFIG.API.IS_CUSTOM_SSO) {
1174
- if (authToken) {
1175
- const result = `sso=${authToken};ssp_rw=${authToken}`;
1176
- tokenManager.setToken(result);
1177
- } else {
1178
- return res.status(401).json({ error: '自定义的SSO令牌缺失' });
1179
- }
1180
- } else if (authToken !== CONFIG.API.API_KEY) {
1181
- return res.status(401).json({ error: 'Unauthorized' });
1182
- }
1183
- const { model, stream } = req.body;
1184
- let isTempCookie = model.includes("grok-2") && CONFIG.API.IS_TEMP_GROK2;
1185
- let retryCount = 0;
1186
- const grokClient = new GrokApiClient(model);
1187
- const requestPayload = await grokClient.prepareChatRequest(req.body);
1188
- //Logger.info(`请求体: ${JSON.stringify(requestPayload, null, 2)}`, 'Server');
1189
-
1190
- while (retryCount < CONFIG.RETRY.MAX_ATTEMPTS) {
1191
- retryCount++;
1192
- if (isTempCookie) {
1193
- CONFIG.API.SIGNATURE_COOKIE = CONFIG.API.TEMP_COOKIE;
1194
- Logger.info(`已切换为临时令牌`, 'Server');
1195
- } else {
1196
- CONFIG.API.SIGNATURE_COOKIE = await Utils.createAuthHeaders(model);
1197
- }
1198
- if (!CONFIG.API.SIGNATURE_COOKIE) {
1199
- throw new Error('该模型无可用令牌');
1200
- }
1201
- Logger.info(`当前令牌: ${JSON.stringify(CONFIG.API.SIGNATURE_COOKIE, null, 2)}`, 'Server');
1202
- Logger.info(`当前可用模型的全部可用数量: ${JSON.stringify(tokenManager.getRemainingTokenRequestCapacity(), null, 2)}`, 'Server');
1203
- const response = await fetch(`${CONFIG.API.BASE_URL}/rest/app-chat/conversations/new`, {
1204
- method: 'POST',
1205
- headers: {
1206
- "accept": "text/event-stream",
1207
- "baggage": "sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c",
1208
- "content-type": "text/plain;charset=UTF-8",
1209
- "Connection": "keep-alive",
1210
- "cookie": CONFIG.API.SIGNATURE_COOKIE
1211
- },
1212
- body: JSON.stringify(requestPayload)
1213
- });
1214
 
1215
- if (response.ok) {
1216
- Logger.info(`请求成功`, 'Server');
1217
- Logger.info(`当前${model}剩余可用令牌数: ${tokenManager.getTokenCountForModel(model)}`, 'Server');
1218
- try {
1219
- await handleResponse(response, model, res, stream);
1220
- Logger.info(`请求结束`, 'Server');
1221
- return;
1222
- } catch (error) {
1223
- Logger.error(error, 'Server');
1224
- if (isTempCookie) {
1225
- tempCookieManager.cookies.splice(tempCookieManager.currentIndex, 1);
1226
- if (tempCookieManager.cookies.length != 0) {
1227
- tempCookieManager.currentIndex = tempCookieManager.currentIndex % tempCookieManager.cookies.length;
1228
- CONFIG.API.TEMP_COOKIE = tempCookieManager.cookies[tempCookieManager.currentIndex];
1229
- tempCookieManager.ensureCookies()
1230
- } else {
1231
- try {
1232
- await tempCookieManager.ensureCookies();
1233
- tempCookieManager.currentIndex = tempCookieManager.currentIndex % tempCookieManager.cookies.length;
1234
- CONFIG.API.TEMP_COOKIE = tempCookieManager.cookies[tempCookieManager.currentIndex];
1235
- } catch (error) {
1236
- throw error;
1237
- }
1238
- }
1239
- } else {
1240
- if (CONFIG.API.IS_CUSTOM_SSO) throw new Error(`自定义SSO令牌当前模型${model}的请求次数已失效`);
1241
- tokenManager.removeTokenFromModel(model, CONFIG.API.SIGNATURE_COOKIE.cookie);
1242
- if (tokenManager.getTokenCountForModel(model) === 0) {
1243
- throw new Error(`${model} 次数已达上限,请切换其他模型或者重新对话`);
1244
- }
1245
- }
1246
- }
1247
- } else {
1248
- if (response.status === 429) {
1249
- if (isTempCookie) {
1250
- // 移除当前失效的 cookie
1251
- tempCookieManager.cookies.splice(tempCookieManager.currentIndex, 1);
1252
- if (tempCookieManager.cookies.length != 0) {
1253
- tempCookieManager.currentIndex = tempCookieManager.currentIndex % tempCookieManager.cookies.length;
1254
- CONFIG.API.TEMP_COOKIE = tempCookieManager.cookies[tempCookieManager.currentIndex];
1255
- tempCookieManager.ensureCookies()
1256
- } else {
1257
- try {
1258
- await tempCookieManager.ensureCookies();
1259
- tempCookieManager.currentIndex = tempCookieManager.currentIndex % tempCookieManager.cookies.length;
1260
- CONFIG.API.TEMP_COOKIE = tempCookieManager.cookies[tempCookieManager.currentIndex];
1261
- } catch (error) {
1262
- throw error;
1263
- }
1264
- }
1265
- } else {
1266
- if (CONFIG.API.IS_CUSTOM_SSO) throw new Error(`自定义SSO令牌当前模型${model}的请求次数已失效`);
1267
- tokenManager.removeTokenFromModel(model, CONFIG.API.SIGNATURE_COOKIE.cookie);
1268
- if (tokenManager.getTokenCountForModel(model) === 0) {
1269
- throw new Error(`${model} 次数已达上限,请切换其他模型或者重新对话`);
1270
- }
1271
- }
1272
- } else {
1273
- // 非429错误直接抛出
1274
- if (isTempCookie) {
1275
- // 移除当前失效的 cookie
1276
- tempCookieManager.cookies.splice(tempCookieManager.currentIndex, 1);
1277
- if (tempCookieManager.cookies.length != 0) {
1278
- tempCookieManager.currentIndex = tempCookieManager.currentIndex % tempCookieManager.cookies.length;
1279
- CONFIG.API.TEMP_COOKIE = tempCookieManager.cookies[tempCookieManager.currentIndex];
1280
- tempCookieManager.ensureCookies()
1281
- } else {
1282
- try {
1283
- await tempCookieManager.ensureCookies();
1284
- tempCookieManager.currentIndex = tempCookieManager.currentIndex % tempCookieManager.cookies.length;
1285
- CONFIG.API.TEMP_COOKIE = tempCookieManager.cookies[tempCookieManager.currentIndex];
1286
- } catch (error) {
1287
- throw error;
1288
- }
1289
- }
1290
- } else {
1291
- if (CONFIG.API.IS_CUSTOM_SSO) throw new Error(`自定义SSO令牌当前模型${model}的请求次数已失效`);
1292
- Logger.error(`令牌异常错误状态!status: ${response.status}`, 'Server');
1293
- tokenManager.removeTokenFromModel(model, CONFIG.API.SIGNATURE_COOKIE.cookie);
1294
- Logger.info(`当前${model}剩余可用令牌数: ${tokenManager.getTokenCountForModel(model)}`, 'Server');
1295
- }
1296
  }
1297
- }
1298
  }
1299
- throw new Error('当前模型所有令牌都已耗尽');
1300
- } catch (error) {
1301
- Logger.error(error, 'ChatAPI');
1302
- res.status(500).json({
 
 
 
 
 
 
1303
  error: {
1304
- message: error.message || error,
1305
- type: 'server_error'
1306
  }
1307
  });
1308
  }
1309
- });
1310
 
 
 
 
 
 
 
 
 
 
 
 
1311
 
1312
  app.use((req, res) => {
1313
- res.status(200).send('api运行正常');
1314
  });
1315
 
1316
-
1317
  app.listen(CONFIG.SERVER.PORT, () => {
1318
- Logger.info(`服务器已启动,监听端口: ${CONFIG.SERVER.PORT}`, 'Server');
1319
  });
 
1
  import express from 'express';
2
  import fetch from 'node-fetch';
3
  import FormData from 'form-data';
 
4
  import cors from 'cors';
5
  import puppeteer from 'puppeteer-extra'
6
  import StealthPlugin from 'puppeteer-extra-plugin-stealth'
7
  import { v4 as uuidv4 } from 'uuid';
8
  import Logger from './logger.js';
9
 
 
 
10
  // 配置常量
11
  const CONFIG = {
12
+ MODELS: {
13
+ 'grok-2': 'grok-latest',
14
+ 'grok-2-imageGen': 'grok-latest',
15
+ 'grok-2-search': 'grok-latest',
16
+ "grok-3": "grok-3",
17
+ "grok-3-search": "grok-3",
18
+ "grok-3-imageGen": "grok-3",
19
+ "grok-3-deepsearch": "grok-3",
20
+ "grok-3-reasoning": "grok-3"
21
+ },
22
+ API: {
23
+ IS_TEMP_CONVERSATION: false, // 假设默认不使用临时会话
24
+ GROK2_CONCURRENCY_LEVEL: 4,
25
+ BASE_URL: "https://grok.com",
26
+ PICGO_KEY: process.env.PICGO_KEY || null, //想要流式生图的话需要填入这个PICGO图床的key
27
+ TUMY_KEY: process.env.TUMY_KEY || null //想要流式生图的话需要填入这个TUMY图床的key 两个图床二选一,默认使用PICGO
28
+ },
29
+ SERVER: {
30
+ PORT: process.env.PORT || 3000,
31
+ BODY_LIMIT: '5mb'
32
+ },
33
+ RETRY: {
34
+ MAX_ATTEMPTS: 2//重试次数
35
+ },
36
+ SHOW_THINKING: true,
37
+ IS_THINKING: false,
38
+ IS_IMG_GEN: false,
39
+ IS_IMG_GEN2: false,
40
+ ISSHOW_SEARCH_RESULTS: true,//是否显示搜索结果
41
+ CHROME_PATH: process.env.CHROME_PATH || null
 
 
 
 
 
 
42
  };
43
  puppeteer.use(StealthPlugin())
44
 
45
  // 请求头配置
46
  const DEFAULT_HEADERS = {
47
+ 'accept': '*/*',
48
+ 'accept-language': 'zh-CN,zh;q=0.9',
49
+ 'accept-encoding': 'gzip, deflate, br, zstd',
50
+ 'content-type': 'text/plain;charset=UTF-8',
51
+ 'Connection': 'keep-alive',
52
+ 'origin': 'https://grok.com',
53
+ 'priority': 'u=1, i',
54
+ 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
55
+ 'sec-ch-ua-mobile': '?0',
56
+ 'sec-ch-ua-platform': '"Windows"',
57
+ 'sec-fetch-dest': 'empty',
58
+ 'sec-fetch-mode': 'cors',
59
+ 'sec-fetch-site': 'same-origin',
60
+ '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',
61
+ 'baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
62
  };
63
 
64
 
65
  async function initialization() {
66
+ if (CONFIG.CHROME_PATH == null) {
67
+ try {
68
+ CONFIG.CHROME_PATH = puppeteer.executablePath();
69
+ } catch (error) {
70
+ CONFIG.CHROME_PATH = "/usr/bin/chromium";
71
+ }
72
+ }
73
+ Logger.info(`CHROME_PATH: ${CONFIG.CHROME_PATH}`, 'Server');
74
+ Logger.info("初始化完成", 'Server');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
 
77
  class AuthTokenManager {
78
+ constructor() {
79
+ this.tokenModelMap = {};
80
+ this.expiredTokens = new Set();
81
+ this.tokenStatusMap = {};
82
+
83
+ // 定义模型请求频率限制和过期时间
84
+ this.modelConfig = {
85
+ "grok-2": {
86
+ RequestFrequency: 30,
87
+ ExpirationTime: 1 * 60 * 60 * 1000 // 1小时
88
+ },
89
+ "grok-3": {
90
+ RequestFrequency: 20,
91
+ ExpirationTime: 2 * 60 * 60 * 1000 // 2小时
92
+ },
93
+ "grok-3-deepsearch": {
94
+ RequestFrequency: 10,
95
+ ExpirationTime: 24 * 60 * 60 * 1000 // 24小时
96
+ },
97
+ "grok-3-reasoning": {
98
+ RequestFrequency: 10,
99
+ ExpirationTime: 24 * 60 * 60 * 1000 // 24小时
100
+ }
101
+ };
102
+ this.tokenResetSwitch = false;
103
+ this.tokenResetTimer = null;
104
+ }
105
+ async fetchGrokStats(token, modelName) {
106
+ let requestKind = 'DEFAULT';
107
+ if (modelName == 'grok-2' || modelName == 'grok-3') {
108
+ requestKind = 'DEFAULT';
109
+ } else if (modelName == 'grok-3-deepsearch') {
110
+ requestKind = 'DEEPSEARCH';
111
+ } else if (modelName == 'grok-3-reasoning') {
112
+ requestKind = 'REASONING';
113
+ }
114
+ const response = await fetch('https://grok.com/rest/rate-limits', {
115
+ method: 'POST',
116
+ headers: {
117
+ 'content-type': 'application/json',
118
+ 'Cookie': token,
119
+ },
120
+ body: JSON.stringify({
121
+ "requestKind": requestKind,
122
+ "modelName": modelName == 'grok-2' ? 'grok-latest' : "grok-3"
123
+ })
124
+ });
125
+
126
+ if (response.status != 200) {
127
+ return 0;
128
+ }
129
+ const data = await response.json();
130
+ return data.remainingQueries;
131
+ }
132
+ async addToken(token) {
133
+ const sso = token.split("sso=")[1].split(";")[0];
134
+
135
+ for (const model of Object.keys(this.modelConfig)) {
136
+ if (!this.tokenModelMap[model]) {
137
+ this.tokenModelMap[model] = [];
138
+ }
139
+ if (!this.tokenStatusMap[sso]) {
140
+ this.tokenStatusMap[sso] = {};
141
+ }
142
+ const existingTokenEntry = this.tokenModelMap[model].find(entry => entry.token === token);
143
+
144
+ if (!existingTokenEntry) {
145
+ try {
146
+ const remainingQueries = await this.fetchGrokStats(token, model);
147
+
148
+ const modelRequestFrequency = this.modelConfig[model].RequestFrequency;
149
+ const usedRequestCount = modelRequestFrequency - remainingQueries;
150
+
151
+ if (usedRequestCount === modelRequestFrequency) {
152
+ this.expiredTokens.add({
153
+ token: token,
154
+ model: model,
155
+ expiredTime: Date.now()
156
+ });
157
+
158
+ if (!this.tokenStatusMap[sso][model]) {
159
+ this.tokenStatusMap[sso][model] = {
160
+ isValid: false,
161
+ invalidatedTime: Date.now(),
162
+ totalRequestCount: Math.max(0, usedRequestCount)
163
+ };
164
+ }
165
+
166
+ if (!this.tokenResetSwitch) {
167
+ this.startTokenResetProcess();
168
+ this.tokenResetSwitch = true;
169
+ }
170
+ } else {
171
+ this.tokenModelMap[model].push({
172
+ token: token,
173
+ RequestCount: Math.max(0, usedRequestCount),
174
+ AddedTime: Date.now(),
175
+ StartCallTime: null
176
+ });
177
+
178
+ if (!this.tokenStatusMap[sso][model]) {
179
+ this.tokenStatusMap[sso][model] = {
180
+ isValid: true,
181
+ invalidatedTime: null,
182
+ totalRequestCount: Math.max(0, usedRequestCount)
183
+ };
184
+ }
185
+ }
186
+ } catch (error) {
187
+ this.tokenModelMap[model].push({
188
+ token: token,
189
+ RequestCount: 0,
190
+ AddedTime: Date.now(),
191
+ StartCallTime: null
192
+ });
193
+
194
+ if (!this.tokenStatusMap[sso][model]) {
195
+ this.tokenStatusMap[sso][model] = {
196
+ isValid: true,
197
+ invalidatedTime: null,
198
+ totalRequestCount: 0
199
+ };
200
+ }
201
+
202
+ Logger.error(`获取模型 ${model} 的统计信息失败: ${error}`, 'TokenManager');
203
+ }
204
+ await Utils.delay(200);
205
+ }
206
+ }
207
+ }
208
+
209
+ setToken(token) {
210
+ const models = Object.keys(this.modelConfig);
211
+ this.tokenModelMap = models.reduce((map, model) => {
212
+ map[model] = [{
213
+ token,
214
+ RequestCount: 0,
215
+ AddedTime: Date.now(),
216
+ StartCallTime: null
217
+ }];
218
+ return map;
219
+ }, {});
220
+ const sso = token.split("sso=")[1].split(";")[0];
221
+ this.tokenStatusMap[sso] = models.reduce((statusMap, model) => {
222
+ statusMap[model] = {
223
+ isValid: true,
224
+ invalidatedTime: null,
225
+ totalRequestCount: 0
226
+ };
227
+ return statusMap;
228
+ }, {});
229
+ }
230
+
231
+ async deleteToken(token) {
232
+ try {
233
+ const sso = token.split("sso=")[1].split(";")[0];
234
+ await Promise.all([
235
+ new Promise((resolve) => {
236
+ this.tokenModelMap = Object.fromEntries(
237
+ Object.entries(this.tokenModelMap).map(([model, entries]) => [
238
+ model,
239
+ entries.filter(entry => entry.token !== token)
240
+ ])
241
+ );
242
+ resolve();
243
+ }),
244
+
245
+ new Promise((resolve) => {
246
+ delete this.tokenStatusMap[sso];
247
+ resolve();
248
+ }),
249
+ ]);
250
+ Logger.info(`令牌已成功移除: ${token}`, 'TokenManager');
251
+ return true;
252
+ } catch (error) {
253
+ Logger.error('令牌删除失败:', error);
254
+ return false;
255
+ }
256
+ }
257
+ getNextTokenForModel(modelId) {
258
+ const normalizedModel = this.normalizeModelName(modelId);
259
+
260
+ if (!this.tokenModelMap[normalizedModel] || this.tokenModelMap[normalizedModel].length === 0) {
261
+ return null;
262
+ }
263
+ const tokenEntry = this.tokenModelMap[normalizedModel][0];
264
+
265
+ if (tokenEntry) {
266
+ if (tokenEntry.StartCallTime === null || tokenEntry.StartCallTime === undefined) {
267
+ tokenEntry.StartCallTime = Date.now();
268
+ }
269
+ if (!this.tokenResetSwitch) {
270
+ this.startTokenResetProcess();
271
+ this.tokenResetSwitch = true;
272
+ }
273
+ tokenEntry.RequestCount++;
274
+
275
+ if (tokenEntry.RequestCount > this.modelConfig[normalizedModel].RequestFrequency) {
276
+ this.removeTokenFromModel(normalizedModel, tokenEntry.token);
277
+ const nextTokenEntry = this.tokenModelMap[normalizedModel][0];
278
+ return nextTokenEntry ? nextTokenEntry.token : null;
279
+ }
280
+ const sso = tokenEntry.token.split("sso=")[1].split(";")[0];
281
+ if (this.tokenStatusMap[sso] && this.tokenStatusMap[sso][normalizedModel]) {
282
+ if (tokenEntry.RequestCount === this.modelConfig[normalizedModel].RequestFrequency) {
283
+ this.tokenStatusMap[sso][normalizedModel].isValid = false;
284
+ this.tokenStatusMap[sso][normalizedModel].invalidatedTime = Date.now();
285
+ }
286
+ this.tokenStatusMap[sso][normalizedModel].totalRequestCount++;
287
+ }
288
+ return tokenEntry.token;
289
+ }
290
+
291
+ return null;
292
+ }
293
+
294
+ removeTokenFromModel(modelId, token) {
295
+ const normalizedModel = this.normalizeModelName(modelId);
296
+
297
+ if (!this.tokenModelMap[normalizedModel]) {
298
+ Logger.error(`模型 ${normalizedModel} 不存在`, 'TokenManager');
299
+ return false;
300
+ }
301
+
302
+ const modelTokens = this.tokenModelMap[normalizedModel];
303
+ const tokenIndex = modelTokens.findIndex(entry => entry.token === token);
304
+
305
+ if (tokenIndex !== -1) {
306
+ const removedTokenEntry = modelTokens.splice(tokenIndex, 1)[0];
307
+ this.expiredTokens.add({
308
+ token: removedTokenEntry.token,
309
+ model: normalizedModel,
310
+ expiredTime: Date.now()
311
+ });
312
+
313
+ if (!this.tokenResetSwitch) {
314
+ this.startTokenResetProcess();
315
+ this.tokenResetSwitch = true;
316
+ }
317
+ Logger.info(`模型${modelId}的令牌已失效,已成功移除令牌: ${token}`, 'TokenManager');
318
+ return true;
319
+ }
320
+
321
+ Logger.error(`在模型 ${normalizedModel} 中未找到 token: ${token}`, 'TokenManager');
322
+ return false;
323
+ }
324
+
325
+ getExpiredTokens() {
326
+ return Array.from(this.expiredTokens);
327
+ }
328
+
329
+ normalizeModelName(model) {
330
+ if (model.startsWith('grok-') && !model.includes('deepsearch') && !model.includes('reasoning')) {
331
+ return model.split('-').slice(0, 2).join('-');
332
+ }
333
+ return model;
334
+ }
335
+
336
+ getTokenCountForModel(modelId) {
337
+ const normalizedModel = this.normalizeModelName(modelId);
338
+ return this.tokenModelMap[normalizedModel]?.length || 0;
339
+ }
340
+
341
+ getRemainingTokenRequestCapacity() {
342
+ const remainingCapacityMap = {};
343
+
344
+ Object.keys(this.modelConfig).forEach(model => {
345
+ const modelTokens = this.tokenModelMap[model] || [];
346
+
347
+ const modelRequestFrequency = this.modelConfig[model].RequestFrequency;
348
+
349
+ const totalUsedRequests = modelTokens.reduce((sum, tokenEntry) => {
350
+ return sum + (tokenEntry.RequestCount || 0);
351
+ }, 0);
352
+
353
+ // 计算剩余可用请求数量
354
+ const remainingCapacity = (modelTokens.length * modelRequestFrequency) - totalUsedRequests;
355
+ remainingCapacityMap[model] = Math.max(0, remainingCapacity);
356
+ });
357
+
358
+ return remainingCapacityMap;
359
+ }
360
+
361
+ getTokenArrayForModel(modelId) {
362
+ const normalizedModel = this.normalizeModelName(modelId);
363
+ return this.tokenModelMap[normalizedModel] || [];
364
+ }
365
+
366
+ startTokenResetProcess() {
367
+ if (this.tokenResetTimer) {
368
+ clearInterval(this.tokenResetTimer);
369
+ }
370
+
371
+ this.tokenResetTimer = setInterval(() => {
372
+ const now = Date.now();
373
+
374
+ this.expiredTokens.forEach(expiredTokenInfo => {
375
+ const { token, model, expiredTime } = expiredTokenInfo;
376
+ const expirationTime = this.modelConfig[model].ExpirationTime;
377
+ if (now - expiredTime >= expirationTime) {
378
+ if (!this.tokenModelMap[model].some(entry => entry.token === token)) {
379
+ this.tokenModelMap[model].push({
380
+ token: token,
381
+ RequestCount: 0,
382
+ AddedTime: now,
383
+ StartCallTime: null
384
+ });
385
+ }
386
+ const sso = token.split("sso=")[1].split(";")[0];
387
+
388
+ if (this.tokenStatusMap[sso] && this.tokenStatusMap[sso][model]) {
389
+ this.tokenStatusMap[sso][model].isValid = true;
390
+ this.tokenStatusMap[sso][model].invalidatedTime = null;
391
+ this.tokenStatusMap[sso][model].totalRequestCount = 0;
392
+ }
393
+
394
+ this.expiredTokens.delete(expiredTokenInfo);
395
+ }
396
+ });
397
+
398
+ Object.keys(this.modelConfig).forEach(model => {
399
+ if (!this.tokenModelMap[model]) return;
400
+
401
+ const processedTokens = this.tokenModelMap[model].map(tokenEntry => {
402
+ if (!tokenEntry.StartCallTime) return tokenEntry;
403
+
404
+ const expirationTime = this.modelConfig[model].ExpirationTime;
405
+ if (now - tokenEntry.StartCallTime >= expirationTime) {
406
+ const sso = tokenEntry.token.split("sso=")[1].split(";")[0];
407
+ if (this.tokenStatusMap[sso] && this.tokenStatusMap[sso][model]) {
408
+ this.tokenStatusMap[sso][model].isValid = true;
409
+ this.tokenStatusMap[sso][model].invalidatedTime = null;
410
+ this.tokenStatusMap[sso][model].totalRequestCount = 0;
411
+ }
412
+
413
+ return {
414
+ ...tokenEntry,
415
+ RequestCount: 0,
416
+ StartCallTime: null
417
+ };
418
+ }
419
+
420
+ return tokenEntry;
421
+ });
422
+
423
+ this.tokenModelMap[model] = processedTokens;
424
+ });
425
+ }, 1 * 60 * 60 * 1000);
426
+ }
427
+
428
+ getAllTokens() {
429
+ const allTokens = new Set();
430
+ Object.values(this.tokenModelMap).forEach(modelTokens => {
431
+ modelTokens.forEach(entry => allTokens.add(entry.token));
432
+ });
433
+ return Array.from(allTokens);
434
+ }
435
+
436
+ getTokenStatusMap() {
437
+ return this.tokenStatusMap;
438
+ }
439
  }
440
 
441
 
442
  class Utils {
443
+ static delay(time) {
444
+ return new Promise(function (resolve) {
445
+ setTimeout(resolve, time)
446
+ });
447
+ }
448
+ static async organizeSearchResults(searchResults) {
449
+ // 确保传入的是有效的搜索结果对象
450
+ if (!searchResults || !searchResults.results) {
451
+ return '';
452
+ }
453
+
454
+ const results = searchResults.results;
455
+ const formattedResults = results.map((result, index) => {
456
+ // 处理可能为空的字段
457
+ const title = result.title || '未知标题';
458
+ const url = result.url || '#';
459
+ const preview = result.preview || '无预览内容';
460
+
461
+ return `\r\n<details><summary>资料[${index}]: ${title}</summary>\r\n${preview}\r\n\n[Link](${url})\r\n</details>`;
462
+ });
463
+ return formattedResults.join('\n\n');
464
+ }
465
+ static async createAuthHeaders(model, authorization) {
466
+ return `sso=${authorization};ssp_rw=${authorization}`; // 直接使用authorization
467
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  }
469
 
470
  class GrokApiClient {
471
+ constructor(modelId) {
472
+ if (!CONFIG.MODELS[modelId]) {
473
+ throw new Error(`不支持的模型: ${modelId}`);
474
+ }
475
+ this.modelId = CONFIG.MODELS[modelId];
476
+ }
477
+
478
+ processMessageContent(content) {
479
+ if (typeof content === 'string') return content;
480
+ return null;
481
+ }
482
+ // 获取图片类型
483
+ getImageType(base64String) {
484
+ let mimeType = 'image/jpeg';
485
+ if (base64String.includes('data:image')) {
486
+ const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
487
+ if (matches) {
488
+ mimeType = matches[1];
489
+ }
490
+ }
491
+ const extension = mimeType.split('/')[1];
492
+ const fileName = `image.${extension}`;
493
+
494
+ return {
495
+ mimeType: mimeType,
496
+ fileName: fileName
497
+ };
498
+ }
499
+
500
+ async uploadBase64Image(base64Data, url) {
501
+ try {
502
+ // 处理 base64 数据
503
+ let imageBuffer;
504
+ if (base64Data.includes('data:image')) {
505
+ imageBuffer = base64Data.split(',')[1];
506
+ } else {
507
+ imageBuffer = base64Data
508
+ }
509
+ const { mimeType, fileName } = this.getImageType(base64Data);
510
+ let uploadData = {
511
+ rpc: "uploadFile",
512
+ req: {
513
+ fileName: fileName,
514
+ fileMimeType: mimeType,
515
+ content: imageBuffer
516
+ }
517
+ };
518
+ Logger.info("发送图片请求", 'Server');
519
+ // 发送请求
520
+ const response = await fetch(url, {
521
+ method: 'POST',
522
+ headers: {
523
+ ...CONFIG.DEFAULT_HEADERS,
524
+ "cookie": CONFIG.API.SIGNATURE_COOKIE
525
+ },
526
+ body: JSON.stringify(uploadData)
527
+ });
528
+
529
+ if (!response.ok) {
530
+ Logger.error(`上传图片失败,状态码:${response.status},原因:${response.error}`, 'Server');
531
+ return '';
532
+ }
533
+
534
+ const result = await response.json();
535
+ Logger.info('上传图片成功:', result, 'Server');
536
+ return result.fileMetadataId;
537
+
538
+ } catch (error) {
539
+ Logger.error(error, 'Server');
540
+ return '';
541
+ }
542
+ }
543
+
544
+ async prepareChatRequest(request) {
545
+ if ((request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen') && !CONFIG.API.PICGO_KEY && !CONFIG.API.TUMY_KEY && request.stream) {
546
+ throw new Error(`该模型流式输出需要配置PICGO或者TUMY图床密钥!`);
547
+ }
548
+
549
+ // 处理画图模型的消息限制
550
+ let todoMessages = request.messages;
551
+ if (request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen') {
552
+ const lastMessage = todoMessages[todoMessages.length - 1];
553
+ if (lastMessage.role !== 'user') {
554
+ throw new Error('画图模型的最后一条消息必须是用户消息!');
555
+ }
556
+ todoMessages = [lastMessage];
557
+ }
558
+
559
+ const fileAttachments = [];
560
+ let messages = '';
561
+ let lastRole = null;
562
+ let lastContent = '';
563
+ const search = request.model === 'grok-2-search' || request.model === 'grok-3-search';
564
+
565
+ // 移除<details>
566
+ <summary>💭 <b>思考过程 (点击展开)</b></summary>
567
+ 标签及其内容和base64图片
568
+ const removeThinkTags = (text) => {
569
+ text = text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
570
+ text = text.replace(/!$$image$$$data:.*?base64,.*?$/g, '[图片]');
571
+ return text;
572
+ };
573
+
574
+ const processImageUrl = async (content) => {
575
+ if (content.type === 'image_url' && content.image_url.url.includes('data:image')) {
576
+ const imageResponse = await this.uploadBase64Image(
577
+ content.image_url.url,
578
+ `${CONFIG.API.BASE_URL}/api/rpc`
579
+ );
580
+ return imageResponse;
581
+ }
582
+ return null;
583
+ };
584
+
585
+ const processContent = async (content) => {
586
+ if (Array.isArray(content)) {
587
+ let textContent = '';
588
+ for (const item of content) {
589
+ if (item.type === 'image_url') {
590
+ textContent += (textContent ? '\n' : '') + "[图片]";
591
+ } else if (item.type === 'text') {
592
+ textContent += (textContent ? '\n' : '') + removeThinkTags(item.text);
593
+ }
594
+ }
595
+ return textContent;
596
+ } else if (typeof content === 'object' && content !== null) {
597
+ if (content.type === 'image_url') {
598
+ return "[图片]";
599
+ } else if (content.type === 'text') {
600
+ return removeThinkTags(content.text);
601
+ }
602
+ }
603
+ return removeThinkTags(this.processMessageContent(content));
604
+ };
605
+
606
+ for (const current of todoMessages) {
607
+ const role = current.role === 'assistant' ? 'assistant' : 'user';
608
+ const isLastMessage = current === todoMessages[todoMessages.length - 1];
609
+
610
+ // 处理图片附件
611
+ if (isLastMessage && current.content) {
612
+ if (Array.isArray(current.content)) {
613
+ for (const item of current.content) {
614
+ if (item.type === 'image_url') {
615
+ const processedImage = await processImageUrl(item);
616
+ if (processedImage) fileAttachments.push(processedImage);
617
+ }
618
+ }
619
+ } else if (current.content.type === 'image_url') {
620
+ const processedImage = await processImageUrl(current.content);
621
+ if (processedImage) fileAttachments.push(processedImage);
622
+ }
623
+ }
624
+
625
+ // 处理文本内容
626
+ const textContent = await processContent(current.content);
627
+
628
+ if (textContent || (isLastMessage && fileAttachments.length > 0)) {
629
+ if (role === lastRole && textContent) {
630
+ lastContent += '\n' + textContent;
631
+ messages = messages.substring(0, messages.lastIndexOf(`${role.toUpperCase()}: `)) +
632
+ `${role.toUpperCase()}: ${lastContent}\n`;
633
+ } else {
634
+ messages += `${role.toUpperCase()}: ${textContent || '[图片]'}\n`;
635
+ lastContent = textContent;
636
+ lastRole = role;
637
+ }
638
+ }
639
+ }
640
+
641
+ return {
642
+ temporary: CONFIG.API.IS_TEMP_CONVERSATION,
643
+ modelName: this.modelId,
644
+ message: messages.trim(),
645
+ fileAttachments: fileAttachments.slice(0, 4),
646
+ imageAttachments: [],
647
+ disableSearch: false,
648
+ enableImageGeneration: true,
649
+ returnImageBytes: false,
650
+ returnRawGrokInXaiRequest: false,
651
+ enableImageStreaming: false,
652
+ imageGenerationCount: 1,
653
+ forceConcise: false,
654
+ toolOverrides: {
655
+ imageGen: request.model === 'grok-2-imageGen' || request.model === 'grok-3-imageGen',
656
+ webSearch: search,
657
+ xSearch: search,
658
+ xMediaSearch: search,
659
+ trendsSearch: search,
660
+ xPostAnalyze: search
661
+ },
662
+ enableSideBySide: true,
663
+ isPreset: false,
664
+ sendFinalMetadata: true,
665
+ customInstructions: "",
666
+ deepsearchPreset: request.model === 'grok-3-deepsearch' ? "default" : "",
667
+ isReasoning: request.model === 'grok-3-reasoning'
668
+ };
669
+ }
670
  }
671
 
672
  class MessageProcessor {
673
+ static createChatResponse(message, model, isStream = false) {
674
+ const baseResponse = {
675
+ id: `chatcmpl-${uuidv4()}`,
676
+ created: Math.floor(Date.now() / 1000),
677
+ model: model
678
+ };
679
+
680
+ if (isStream) {
681
+ return {
682
+ ...baseResponse,
683
+ object: 'chat.completion.chunk',
684
+ choices: [{
685
+ index: 0,
686
+ delta: {
687
+ content: message
688
+ }
689
+ }]
690
+ };
691
+ }
692
+
693
+ return {
694
+ ...baseResponse,
695
+ object: 'chat.completion',
696
+ choices: [{
697
+ index: 0,
698
+ message: {
699
+ role: 'assistant',
700
+ content: message
701
+ },
702
+ finish_reason: 'stop'
703
+ }],
704
+ usage: null
705
+ };
706
+ }
707
  }
708
  async function processModelResponse(response, model) {
709
+ let result = { token: null, imageUrl: null }
710
+ if (CONFIG.IS_IMG_GEN) {
711
+ if (response?.cachedImageGenerationResponse && !CONFIG.IS_IMG_GEN2) {
712
+ result.imageUrl = response.cachedImageGenerationResponse.imageUrl;
713
+ }
714
+ return result;
715
+ }
716
+
717
+ //非生图模型的处理
718
+ switch (model) {
719
+ case 'grok-2':
720
+ result.token = response?.token;
721
+ return result;
722
+ case 'grok-2-search':
723
+ case 'grok-3-search':
724
+ if (response?.webSearchResults && CONFIG.ISSHOW_SEARCH_RESULTS) {
725
+ result.token = `\r\n<think>${await Utils.organizeSearchResults(response.webSearchResults)}
726
+ </details>
727
+ \r\n`;
728
+ } else {
729
+ result.token = response?.token;
730
+ }
731
+ return result;
732
+ case 'grok-3':
733
+ result.token = response?.token;
734
+ return result;
735
+ case 'grok-3-deepsearch':
736
+ if (response?.messageTag === "final") {
737
+ result.token = response?.token;
738
+ }
739
+ return result;
740
+ case 'grok-3-reasoning':
741
+ if (response?.isThinking && !CONFIG.SHOW_THINKING) return result;
742
+
743
+ if (response?.isThinking && !CONFIG.IS_THINKING) {
744
+ result.token = "<details>
745
+ <summary>💭 <b>思考过程 (点击展开)</b></summary>
746
+ " + response?.token;
747
+ CONFIG.IS_THINKING = true;
748
+ } else if (!response.isThinking && CONFIG.IS_THINKING) {
749
+ result.token = "
750
+ </details>
751
+ " + response?.token;
752
+ CONFIG.IS_THINKING = false;
753
+ } else {
754
+ result.token = response?.token;
755
+ }
756
+ return result;
757
+ }
758
+ return result;
759
  }
760
 
761
  async function handleResponse(response, model, res, isStream) {
762
+ try {
763
+ const stream = response.body;
764
+ let buffer = '';
765
+ let fullResponse = '';
766
+ const dataPromises = [];
767
+ if (isStream) {
768
+ res.setHeader('Content-Type', 'text/event-stream');
769
+ res.setHeader('Cache-Control', 'no-cache');
770
+ res.setHeader('Connection', 'keep-alive');
771
+ }
772
+ CONFIG.IS_THINKING = false;
773
+ CONFIG.IS_IMG_GEN = false;
774
+ CONFIG.IS_IMG_GEN2 = false;
775
+ Logger.info("开始处理流式响应", 'Server');
776
+
777
+ return new Promise((resolve, reject) => {
778
+ stream.on('data', async (chunk) => {
779
+ buffer += chunk.toString();
780
+ const lines = buffer.split('\n');
781
+ buffer = lines.pop() || '';
782
+
783
+ for (const line of lines) {
784
+ if (!line.trim()) continue;
785
+ try {
786
+ const linejosn = JSON.parse(line.trim());
787
+ if (linejosn?.error) {
788
+ Logger.error(JSON.stringify(linejosn, null, 2), 'Server');
789
+ stream.destroy();
790
+ reject(new Error(linejosn.error.message || "API Error"));
791
+ return;
792
+ }
793
+ let response = linejosn?.result?.response;
794
+ if (!response) continue;
795
+ if (response?.doImgGen || response?.imageAttachmentInfo) {
796
+ CONFIG.IS_IMG_GEN = true;
797
+ }
798
+ const processPromise = (async () => {
799
+ const result = await processModelResponse(response, model);
800
+
801
+ if (result.token) {
802
+ if (isStream) {
803
+ res.write(`data: ${JSON.stringify(MessageProcessor.createChatResponse(result.token, model, true))}\n\n`);
804
+ } else {
805
+ fullResponse += result.token;
806
+ }
807
+ }
808
+ if (result.imageUrl) {
809
+ CONFIG.IS_IMG_GEN2 = true;
810
+ const dataImage = await handleImageResponse(result.imageUrl);
811
+ if (isStream) {
812
+ res.write(`data: ${JSON.stringify(MessageProcessor.createChatResponse(dataImage, model, true))}\n\n`);
813
+ } else {
814
+ res.json(MessageProcessor.createChatResponse(dataImage, model));
815
+ }
816
+ }
817
+ })();
818
+ dataPromises.push(processPromise);
819
+ } catch (error) {
820
+ Logger.error(error, 'Server');
821
+ continue;
822
+ }
823
+ }
824
+ });
825
+
826
+ stream.on('end', async () => {
827
+ try {
828
+ await Promise.all(dataPromises);
829
+ if (isStream) {
830
+ res.write('data: [DONE]\n\n');
831
+ res.end();
832
+ } else {
833
+ if (!CONFIG.IS_IMG_GEN2) {
834
+ res.json(MessageProcessor.createChatResponse(fullResponse, model));
835
+ }
836
+ }
837
+ resolve();
838
+ } catch (error) {
839
+ Logger.error(error, 'Server');
840
+ reject(error);
841
+ }
842
+ });
843
+
844
+ stream.on('error', (error) => {
845
+ Logger.error(error, 'Server');
846
+ reject(error);
847
+ });
848
+ });
849
+ } catch (error) {
850
+ Logger.error(error, 'Server');
851
+ throw new Error(error);
852
+ }
 
 
 
853
  }
854
 
855
  async function handleImageResponse(imageUrl) {
856
+ const MAX_RETRIES = 2;
857
+ let retryCount = 0;
858
+ let imageBase64Response;
859
+
860
+ while (retryCount < MAX_RETRIES) {
861
+ try {
862
+ imageBase64Response = await fetch(`https://assets.grok.com/${imageUrl}`, {
863
+ method: 'GET',
864
+ headers: {
865
+ ...DEFAULT_HEADERS,
866
+ "cookie": CONFIG.API.SIGNATURE_COOKIE
867
+ }
868
+ });
869
+
870
+ if (imageBase64Response.ok) break;
871
+ retryCount++;
872
+ if (retryCount === MAX_RETRIES) {
873
+ throw new Error(`上游服务请求失败! status: ${imageBase64Response.status}`);
874
+ }
875
+ await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
876
+
877
+ } catch (error) {
878
+ Logger.error(error, 'Server');
879
+ retryCount++;
880
+ if (retryCount === MAX_RETRIES) {
881
+ throw error;
882
+ }
883
+ await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
884
+ }
885
+ }
886
+
887
+
888
+ const arrayBuffer = await imageBase64Response.arrayBuffer();
889
+ const imageBuffer = Buffer.from(arrayBuffer);
890
+
891
+ if (!CONFIG.API.PICGO_KEY && !CONFIG.API.TUMY_KEY) {
892
+ const base64Image = imageBuffer.toString('base64');
893
+ const imageContentType = imageBase64Response.headers.get('content-type');
894
+ return `![image](data:${imageContentType};base64,${base64Image})`
895
+ }
896
+
897
+ Logger.info("开始上传图床", 'Server');
898
+ const formData = new FormData();
899
+ if (CONFIG.API.PICGO_KEY) {
900
+ formData.append('source', imageBuffer, {
901
+ filename: image-${Date.now()}.jpg,
902
+ contentType: 'image/jpeg'
903
+ });
904
+ const formDataHeaders = formData.getHeaders();
905
+ const responseURL = await fetch("https://www.picgo.net/api/1/upload", {
906
+ method: "POST",
907
+ headers: {
908
+ ...formDataHeaders,
909
+ "Content-Type": "multipart/form-data",
910
+ "X-API-Key": CONFIG.API.PICGO_KEY
911
+ },
912
+ body: formData
913
+ });
914
+ if (!responseURL.ok) {
915
+ return "生图失败,请查看PICGO图床密钥是否设置正确"
916
+ } else {
917
+ Logger.info("生图成功", 'Server');
918
+ const result = await responseURL.json();
919
+ return ![image](${result.image.url})
920
+ }
921
+ } else if (CONFIG.API.TUMY_KEY) {
922
+ const formData = new FormData();
923
+ formData.append('file', imageBuffer, {
924
+ filename: image-${Date.now()}.jpg,
925
+ contentType: 'image/jpeg'
926
+ });
927
+ const formDataHeaders = formData.getHeaders();
928
+ const responseURL = await fetch("https://tu.my/api/v1/upload", {
929
+ method: "POST",
930
+ headers: {
931
+ ...formDataHeaders,
932
+ "Accept": "application/json",
933
+ 'Authorization': Bearer ${CONFIG.API.TUMY_KEY}
934
+ },
935
+ body: formData
936
+ });
937
+ if (!responseURL.ok) {
938
+ return "生图失败,请查看TUMY图床密钥是否设置正确"
939
+ } else {
940
+ try {
941
+ const result = await responseURL.json();
942
+ Logger.info("生图成功", 'Server');
943
+ return ![image](${result.data.links.url})
944
+ } catch (error) {
945
+ Logger.error(error, 'Server');
946
+ return "生图失败,请查看TUMY图床密钥是否设置正确"
947
+ }
948
+ }
949
+ }
950
  }
951
 
952
  const tokenManager = new AuthTokenManager();
 
953
  await initialization();
954
 
955
  // 中间件配置
 
958
  app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
959
  app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
960
  app.use(cors({
961
+ origin: '*',
962
+ methods: ['GET', 'POST', 'OPTIONS'],
963
+ allowedHeaders: ['Content-Type', 'Authorization']
964
  }));
965
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
966
  app.get('/hf/v1/models', (req, res) => {
967
+ res.json({
968
+ object: "list",
969
+ data: Object.keys(tokenManager.tokenModelMap).map((model, index) => ({
970
+ id: model,
971
+ object: "model",
972
+ created: Math.floor(Date.now() / 1000),
973
+ owned_by: "grok",
974
+ }))
975
+ });
976
  });
 
977
 
978
  app.post('/hf/v1/chat/completions', async (req, res) => {
979
+ try {
980
+ const authorization = req.headers.authorization?.replace('Bearer ', '');
981
+ if (!authorization) {
982
+ return res.status(401).json({ error: 'Authorization header is missing' });
983
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
984
 
985
+ const { model, stream } = req.body;
986
+ const grokClient = new GrokApiClient(model);
987
+ const requestPayload = await grokClient.prepareChatRequest(req.body);
988
+
989
+ CONFIG.API.SIGNATURE_COOKIE = await Utils.createAuthHeaders(model, authorization);
990
+
991
+ Logger.info(`当前令牌: ${JSON.stringify(CONFIG.API.SIGNATURE_COOKIE, null, 2)}`, 'Server');
992
+ Logger.info(`当前可用模型的全部可用数量: ${JSON.stringify(tokenManager.getRemainingTokenRequestCapacity(), null, 2)}`, 'Server');
993
+
994
+ const response = await fetch(`${CONFIG.API.BASE_URL}/rest/app-chat/conversations/new`, {
995
+ method: 'POST',
996
+ headers: {
997
+ "accept": "text/event-stream",
998
+ "baggage": "sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c",
999
+ "content-type": "text/plain;charset=UTF-8",
1000
+ "Connection": "keep-alive",
1001
+ "cookie": CONFIG.API.SIGNATURE_COOKIE
1002
+ },
1003
+ body: JSON.stringify(requestPayload)
1004
+ });
1005
+
1006
+ if (response.ok) {
1007
+ Logger.info(`请求成功`, 'Server');
1008
+ try {
1009
+ await handleResponse(response, model, res, stream);
1010
+ Logger.info(`请求结束`, 'Server');
1011
+ return;
1012
+ } catch (error) {
1013
+ Logger.error(error, 'Server');
1014
+ res.status(500).json({
1015
+ error: {
1016
+ message: error.message || error,
1017
+ type: 'server_error'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1018
  }
1019
+ });
1020
  }
1021
+ } else {
1022
+ Logger.error(`请求失败,状态码: ${response.status}`, 'Server');
1023
+ let errorMessage = `请求失败,状态码: ${response.status}`;
1024
+ try {
1025
+ const errorBody = await response.json();
1026
+ errorMessage += `, 错误信息: ${JSON.stringify(errorBody)}`;
1027
+ } catch (e) {
1028
+ Logger.warn("无法解析错误信息");
1029
+ }
1030
+ res.status(response.status).json({
1031
  error: {
1032
+ message: errorMessage,
1033
+ type: 'api_error'
1034
  }
1035
  });
1036
  }
 
1037
 
1038
+ } catch (error) {
1039
+ Logger.error(error, 'ChatAPI');
1040
+ res.status(500).json({
1041
+ error: {
1042
+ message: error.message || error,
1043
+ type: 'server_error'
1044
+ }
1045
+ });
1046
+ }
1047
+ 展开
1048
+ });
1049
 
1050
  app.use((req, res) => {
1051
+ res.status(200).send('api运行正常');
1052
  });
1053
 
 
1054
  app.listen(CONFIG.SERVER.PORT, () => {
1055
+ Logger.info(服务器已启动,监听端口: ${CONFIG.SERVER.PORT}, 'Server');
1056
  });