isididiidid commited on
Commit
a37d4e8
·
verified ·
1 Parent(s): 2e4eaae

Rename app.js to app.py

Browse files
Files changed (2) hide show
  1. app.js +0 -636
  2. app.py +477 -0
app.js DELETED
@@ -1,636 +0,0 @@
1
- const express = require('express');
2
- const axios = require('axios');
3
- const { v4: uuidv4 } = require('uuid');
4
- const dotenv = require('dotenv');
5
-
6
- dotenv.config();
7
-
8
- const app = express();
9
- app.use(express.json());
10
-
11
- // 常量定义
12
- const TARGET_URL = "https://grok.com/rest/app-chat/conversations/new";
13
- const MODELS = ["grok-2", "grok-3", "grok-3-thinking"];
14
- const PORT = process.env.PORT || 7860;
15
-
16
- // 用户代理列表
17
- const USER_AGENTS = [
18
- // Windows - Chrome
19
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
20
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
21
- // Windows - Firefox
22
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
23
- // Windows - Edge
24
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.2420.81",
25
- // Windows - Opera
26
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 OPR/109.0.0.0",
27
- // macOS - Chrome
28
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
29
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
30
- // macOS - Safari
31
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0.1 Safari/605.1.15",
32
- // macOS - Firefox
33
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:132.0) Gecko/20100101 Firefox/132.0",
34
- // macOS - Opera
35
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 OPR/109.0.0.0",
36
- // Linux - Chrome
37
- "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
38
- // Linux - Firefox
39
- "Mozilla/5.0 (X11; Linux i686; rv:124.0) Gecko/20100101 Firefox/124.0",
40
- ];
41
-
42
- // 全局变量
43
- let COOKIE_NUM = 0;
44
- let COOKIE_LIST = [];
45
- let LAST_COOKIE_INDEX = {};
46
- let TEMPORARY_MODE = false;
47
-
48
- // 验证Cookie是否有效
49
- async function validateCookie(session) {
50
- try {
51
- const testUrl = "https://grok.com/rest/user/me";
52
- const response = await axios.get(testUrl, {
53
- headers: session.headers,
54
- timeout: 5000
55
- });
56
- return response.status === 200;
57
- } catch (error) {
58
- console.log(`Cookie验证失败: ${error.message}`);
59
- return false;
60
- }
61
- }
62
-
63
- // 配置解析
64
- async function resolveConfig() {
65
- COOKIE_LIST = [];
66
- let cookieIndex = 1;
67
- let validCookies = 0;
68
- let invalidCookies = 0;
69
-
70
- while (true) {
71
- const cookieEnvName = `GROK_COOKIE_${cookieIndex}`;
72
- const cookieString = process.env[cookieEnvName];
73
-
74
- if (!cookieString) break;
75
-
76
- const session = {
77
- headers: {
78
- 'user-agent': USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)],
79
- 'cookie': cookieString
80
- }
81
- };
82
-
83
- // 验证cookie是否有效
84
- if (await validateCookie(session)) {
85
- COOKIE_LIST.push(session);
86
- validCookies++;
87
- console.log(`Cookie ${cookieIndex} 验证通过`);
88
- } else {
89
- invalidCookies++;
90
- console.log(`警告: Cookie ${cookieIndex} 无效,已跳过`);
91
- }
92
-
93
- cookieIndex++;
94
- }
95
-
96
- COOKIE_NUM = COOKIE_LIST.length;
97
- if (COOKIE_NUM === 0) {
98
- throw new Error("未提供有效的Grok cookies,请通过环境变量设置 (GROK_COOKIE_1, GROK_COOKIE_2, ...)");
99
- }
100
-
101
- const temporaryModeStr = (process.env.GROK_TEMPORARY_MODE || 'false').toLowerCase();
102
- TEMPORARY_MODE = temporaryModeStr === 'true' || temporaryModeStr === '1';
103
-
104
- LAST_COOKIE_INDEX = {};
105
- MODELS.forEach(model => {
106
- LAST_COOKIE_INDEX[model] = 0;
107
- });
108
-
109
- console.log(`已从环境变量加载 ${COOKIE_NUM} 个有效Grok cookies,跳过 ${invalidCookies} 个无效cookies。`);
110
- console.log(`临时模式: ${TEMPORARY_MODE}`);
111
- }
112
-
113
- // 定期验证所有Cookie
114
- async function periodicCookieValidation() {
115
- const validCookies = [];
116
-
117
- console.log("开始定期Cookie验证...");
118
- for (let i = 0; i < COOKIE_LIST.length; i++) {
119
- const session = COOKIE_LIST[i];
120
- if (await validateCookie(session)) {
121
- validCookies.push(session);
122
- console.log(`Cookie ${i+1} 仍然有效`);
123
- } else {
124
- console.log(`警告: Cookie ${i+1} 已失效,将被移除`);
125
- }
126
- }
127
-
128
- const originalCount = COOKIE_NUM;
129
- COOKIE_LIST = validCookies;
130
- COOKIE_NUM = validCookies.length;
131
-
132
- console.log(`Cookie验证完成: ${COOKIE_NUM}/${originalCount} 有效`);
133
- if (COOKIE_NUM === 0) {
134
- console.log("严重警告: 所有Cookie均已失效!");
135
- }
136
-
137
- // 更新模型的最后使用索引,确保它们不会超出范围
138
- MODELS.forEach(model => {
139
- if (COOKIE_NUM > 0) {
140
- LAST_COOKIE_INDEX[model] = LAST_COOKIE_INDEX[model] % COOKIE_NUM;
141
- }
142
- });
143
-
144
- // 10分钟后再次验证
145
- setTimeout(periodicCookieValidation, 10 * 60 * 1000);
146
- }
147
-
148
- // 获取下一个账户
149
- function getNextAccount(model) {
150
- if (COOKIE_NUM === 0) {
151
- throw new Error("没有可用的有效Cookie");
152
- }
153
-
154
- const current = (LAST_COOKIE_INDEX[model] + 1) % COOKIE_NUM;
155
- LAST_COOKIE_INDEX[model] = current;
156
- console.log(`Using account ${current+1}/${COOKIE_NUM} for ${model}`);
157
- return COOKIE_LIST[current];
158
- }
159
-
160
- // 处理消息格式化
161
- function formatMessage(messages) {
162
- let result = '';
163
- const { roleMap, prefix, processedMessages } = extractRole(messages);
164
-
165
- for (const message of processedMessages) {
166
- const role = message.role;
167
- const displayRole = prefix ? '\b' + roleMap[role] : roleMap[role];
168
- const content = message.content.replace(/\\n/g, '\n');
169
-
170
- if (/^<\|removeRole\|>\n/.test(content)) {
171
- result += content.replace(/^<\|removeRole\|>\n/, '') + '\n';
172
- } else {
173
- result += `${displayRole}: ${content}\n`;
174
- }
175
- }
176
-
177
- return result;
178
- }
179
-
180
- // 提取角色信息
181
- function extractRole(messages) {
182
- let roleMap = { "user": "Human", "assistant": "Assistant", "system": "System" };
183
- let prefix = false;
184
- const firstMessage = messages[0].content;
185
-
186
- const pattern = /<roleInfo>\s*user:\s*([^\n]*)\s*assistant:\s*([^\n]*)\s*system:\s*([^\n]*)\s*prefix:\s*([^\n]*)\s*<\/roleInfo>\n/;
187
- const match = pattern.exec(firstMessage);
188
-
189
- if (match) {
190
- roleMap = {
191
- "user": match[1],
192
- "assistant": match[2],
193
- "system": match[3]
194
- };
195
- prefix = match[4] === '1';
196
-
197
- messages[0].content = firstMessage.replace(pattern, '');
198
- console.log(`Extracted role map:`);
199
- console.log(`User: ${roleMap.user}, ${roleMap.assistant}, System: ${roleMap.system}`);
200
- console.log(`Using prefix: ${prefix}`);
201
- }
202
-
203
- return { roleMap, prefix, processedMessages: messages };
204
- }
205
-
206
- // 处理特殊指令
207
- function magic(messages) {
208
- let firstMessage = messages[0].content;
209
- let disableSearch = false;
210
- let forceConcise = false;
211
-
212
- if (/<\|disableSearch\|>/.test(firstMessage)) {
213
- disableSearch = true;
214
- console.log("Disable search");
215
- firstMessage = firstMessage.replace(/<\|disableSearch\|>/g, '');
216
- }
217
-
218
- if (/<\|forceConcise\|>/.test(firstMessage)) {
219
- forceConcise = true;
220
- console.log("Force concise");
221
- firstMessage = firstMessage.replace(/<\|forceConcise\|>/g, '');
222
- }
223
-
224
- messages[0].content = firstMessage;
225
- return { disableSearch, forceConcise, messages };
226
- }
227
-
228
- // 路由定义
229
- app.get('/', (req, res) => {
230
- res.set('Content-Type', 'text/plain').status(200).send('Grok Proxy is running');
231
- });
232
-
233
- app.get('/health', (req, res) => {
234
- res.set('Content-Type', 'text/plain').status(200).send('OK');
235
- });
236
-
237
- app.get('/cookie-status', (req, res) => {
238
- // 授权检查
239
- const authKey = req.query.auth_key;
240
- if (!authKey || authKey !== process.env.STATUS_AUTH_KEY) {
241
- return res.status(401).json({ error: "未授权" });
242
- }
243
-
244
- // 准备验证所有Cookie
245
- const cookieStatus = COOKIE_LIST.map((session, i) => ({
246
- index: i + 1,
247
- valid: true // 这是一个实时检查的简化版本
248
- }));
249
-
250
- res.json({
251
- total_cookies: COOKIE_NUM,
252
- valid_cookies: COOKIE_NUM, // 假设所有列出的cookie都有效
253
- cookies: cookieStatus,
254
- temporary_mode: TEMPORARY_MODE
255
- });
256
- });
257
-
258
- app.get('/v1/models', (req, res) => {
259
- const modelList = MODELS.map(model => ({
260
- id: model,
261
- object: "model",
262
- created: Math.floor(Date.now() / 1000),
263
- owned_by: "Elbert",
264
- name: model
265
- }));
266
-
267
- res.json({
268
- object: "list",
269
- data: modelList
270
- });
271
- });
272
-
273
- app.post('/v1/chat/completions', async (req, res) => {
274
- // 检查是否有有效的Cookie
275
- if (COOKIE_NUM === 0) {
276
- return res.status(503).json({ error: "没有可用的有效Cookie" });
277
- }
278
-
279
- console.log("Received request");
280
- console.log(req.body);
281
-
282
- const stream = req.body.stream || false;
283
- const messages = req.body.messages;
284
- const model = req.body.model;
285
-
286
- if (!MODELS.includes(model)) {
287
- return res.status(500).json({ error: "Model not available" });
288
- }
289
-
290
- if (!messages) {
291
- return res.status(400).json({ error: "Messages is required" });
292
- }
293
-
294
- const { disableSearch, forceConcise, messages: processedMessages } = magic(messages);
295
- const formattedMessage = formatMessage(processedMessages);
296
- const isReasoning = model.length > 6;
297
- const baseModel = model.substring(0, 6);
298
-
299
- if (stream) {
300
- return await sendMessageStream(req, res, formattedMessage, baseModel, disableSearch, forceConcise, isReasoning);
301
- } else {
302
- return await sendMessageNonStream(req, res, formattedMessage, baseModel, disableSearch, forceConcise, isReasoning);
303
- }
304
- });
305
-
306
- // 流式发送消息
307
- async function sendMessageStream(req, res, message, model, disableSearch, forceConcise, isReasoning) {
308
- const headers = {
309
- "authority": "grok.com",
310
- "method": "POST",
311
- "path": "/rest/app-chat/conversations/new",
312
- "scheme": "https",
313
- "accept": "*/*",
314
- "accept-encoding": "gzip, deflate, br, zstd",
315
- "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
316
- "cache-control": "no-cache",
317
- "content-type": "application/json",
318
- "origin": "https://grok.com",
319
- "pragma": "no-cache",
320
- "priority": "u=1, i",
321
- "referer": "https://grok.com/",
322
- "sec-ch-ua": '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
323
- "sec-ch-ua-mobile": "?0",
324
- "sec-ch-ua-platform": '"Windows"',
325
- "sec-fetch-dest": "empty",
326
- "sec-fetch-mode": "cors",
327
- "sec-fetch-site": "same-origin",
328
- };
329
-
330
- const payload = {
331
- "temporary": TEMPORARY_MODE,
332
- "modelName": "grok-3",
333
- "message": message,
334
- "fileAttachments": [],
335
- "imageAttachments": [],
336
- "disableSearch": disableSearch,
337
- "enableImageGeneration": false,
338
- "returnImageBytes": false,
339
- "returnRawGrokInXaiRequest": false,
340
- "enableImageStreaming": true,
341
- "imageGenerationCount": 2,
342
- "forceConcise": forceConcise,
343
- "toolOverrides": {},
344
- "enableSideBySide": true,
345
- "isPreset": false,
346
- "sendFinalMetadata": true,
347
- "customInstructions": "",
348
- "deepsearchPreset": "",
349
- "isReasoning": isReasoning,
350
- };
351
-
352
- try {
353
- const session = getNextAccount(model);
354
-
355
- res.setHeader('Content-Type', 'text/event-stream');
356
- res.setHeader('Cache-Control', 'no-cache');
357
- res.setHeader('Connection', 'keep-alive');
358
-
359
- const response = await axios.post(TARGET_URL, payload, {
360
- headers: {...headers, ...session.headers},
361
- responseType: 'stream'
362
- });
363
-
364
- console.log("---------- Response ----------");
365
- let cnt = 2;
366
- let thinking = 2;
367
-
368
- response.data.on('data', chunk => {
369
- const lines = chunk.toString().trim().split('\n');
370
- for (const line of lines) {
371
- if (!line) continue;
372
-
373
- if (cnt !== 0) {
374
- cnt--;
375
- } else {
376
- try {
377
- const data = JSON.parse(line);
378
- const token = data.result.response.token;
379
- let content = "";
380
-
381
- if (isReasoning) {
382
- if (thinking === 2) {
383
- thinking = 1;
384
- content = `<Thinking>\n${token}`;
385
- process.stdout.write(content);
386
- } else if (thinking && !data.result.response.isThinking) {
387
- thinking = 0;
388
- content = `\n</Thinking>\n${token}`;
389
- process.stdout.write(content);
390
- } else {
391
- content = token;
392
- process.stdout.write(content);
393
- }
394
- } else {
395
- content = token;
396
- process.stdout.write(content);
397
- }
398
-
399
- const openaiChunk = {
400
- id: "chatcmpl-" + uuidv4(),
401
- object: "chat.completion.chunk",
402
- created: Math.floor(Date.now() / 1000),
403
- model: model,
404
- choices: [
405
- {
406
- index: 0,
407
- delta: { content },
408
- finish_reason: null
409
- }
410
- ]
411
- };
412
-
413
- res.write(`data: ${JSON.stringify(openaiChunk)}\n\n`);
414
-
415
- if (data.result.response.isSoftStop) {
416
- const finalChunk = {
417
- id: "chatcmpl-" + uuidv4(),
418
- object: "chat.completion.chunk",
419
- created: Math.floor(Date.now() / 1000),
420
- model: model,
421
- choices: [
422
- {
423
- index: 0,
424
- delta: { content },
425
- finish_reason: "completed"
426
- }
427
- ]
428
- };
429
- res.write(`data: ${JSON.stringify(finalChunk)}\n\n`);
430
- break;
431
- }
432
- } catch (error) {
433
- console.error("Error parsing chunk:", error);
434
- }
435
- }
436
- }
437
- });
438
-
439
- response.data.on('end', () => {
440
- console.log("\n---------- Response End ----------");
441
- res.write(`data: [DONE]\n\n`);
442
- res.end();
443
- });
444
-
445
- response.data.on('error', error => {
446
- console.error("Stream error:", error);
447
- res.write(`data: {"error": "${error.message}"}\n\n`);
448
- res.end();
449
- });
450
-
451
- } catch (error) {
452
- console.error(`Failed to send message: ${error.message}`);
453
-
454
- // 如果是Cookie失效造成的错误
455
- if (error.response && (error.response.status === 401 || error.response.status === 403)) {
456
- console.log("可能Cookie已失效,将触发立即验证");
457
- setTimeout(periodicCookieValidation, 0);
458
- }
459
-
460
- if (!res.headersSent) {
461
- if (error.message === "没有可用的有效Cookie") {
462
- return res.status(503).json({ error: error.message });
463
- }
464
- return res.status(500).json({ error: "Failed to send message" });
465
- }
466
- }
467
- }
468
-
469
- // 非流式发送消息
470
- async function sendMessageNonStream(req, res, message, model, disableSearch, forceConcise, isReasoning) {
471
- const headers = {
472
- "authority": "grok.com",
473
- "method": "POST",
474
- "path": "/rest/app-chat/conversations/new",
475
- "scheme": "https",
476
- "accept": "*/*",
477
- "accept-encoding": "gzip, deflate, br, zstd",
478
- "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
479
- "cache-control": "no-cache",
480
- "content-type": "application/json",
481
- "origin": "https://grok.com",
482
- "pragma": "no-cache",
483
- "priority": "u=1, i",
484
- "referer": "https://grok.com/",
485
- "sec-ch-ua": '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
486
- "sec-ch-ua-mobile": "?0",
487
- "sec-ch-ua-platform": '"Windows"',
488
- "sec-fetch-dest": "empty",
489
- "sec-fetch-mode": "cors",
490
- "sec-fetch-site": "same-origin",
491
- };
492
-
493
- const payload = {
494
- "temporary": TEMPORARY_MODE,
495
- "modelName": "grok-3",
496
- "message": message,
497
- "fileAttachments": [],
498
- "imageAttachments": [],
499
- "disableSearch": disableSearch,
500
- "enableImageGeneration": false,
501
- "returnImageBytes": false,
502
- "returnRawGrokInXaiRequest": false,
503
- "enableImageStreaming": true,
504
- "imageGenerationCount": 2,
505
- "forceConcise": forceConcise,
506
- "toolOverrides": {},
507
- "enableSideBySide": true,
508
- "isPreset": false,
509
- "sendFinalMetadata": true,
510
- "customInstructions": "",
511
- "deepsearchPreset": "",
512
- "isReasoning": isReasoning,
513
- };
514
-
515
- try {
516
- const session = getNextAccount(model);
517
- const chunks = [];
518
- let thinking = 2;
519
- let buffer = '';
520
-
521
- const response = await axios.post(TARGET_URL, payload, {
522
- headers: {...headers, ...session.headers},
523
- responseType: 'stream'
524
- });
525
-
526
- console.log("---------- Response ----------");
527
- let cnt = 2;
528
-
529
- return new Promise((resolve, reject) => {
530
- response.data.on('data', chunk => {
531
- chunks.push(chunk);
532
- const lines = chunk.toString().trim().split('\n');
533
-
534
- for (const line of lines) {
535
- if (!line) continue;
536
-
537
- if (cnt !== 0) {
538
- cnt--;
539
- } else {
540
- try {
541
- const data = JSON.parse(line);
542
- const token = data.result.response.token;
543
- let content = "";
544
-
545
- if (isReasoning) {
546
- if (thinking === 2) {
547
- thinking = 1;
548
- content = `<Thinking>\n${token}`;
549
- process.stdout.write(content);
550
- buffer += content;
551
- } else if (thinking && !data.result.response.isThinking) {
552
- thinking = 0;
553
- content = `\n</Thinking>\n${token}`;
554
- process.stdout.write(content);
555
- buffer += content;
556
- } else {
557
- content = token;
558
- process.stdout.write(content);
559
- buffer += content;
560
- }
561
- } else {
562
- content = token;
563
- process.stdout.write(content);
564
- buffer += content;
565
- }
566
-
567
- if (data.result.response.isSoftStop) {
568
- break;
569
- }
570
- } catch (error) {
571
- console.error("Error parsing chunk:", error);
572
- }
573
- }
574
- }
575
- });
576
-
577
- response.data.on('end', () => {
578
- console.log("\n---------- Response End ----------");
579
- const openaiResponse = {
580
- id: "chatcmpl-" + uuidv4(),
581
- object: "chat.completion",
582
- created: Math.floor(Date.now() / 1000),
583
- model: model,
584
- choices: [
585
- {
586
- index: 0,
587
- message: { role: "assistant", content: buffer },
588
- finish_reason: "completed"
589
- }
590
- ]
591
- };
592
- res.json(openaiResponse);
593
- resolve();
594
- });
595
-
596
- response.data.on('error', error => {
597
- console.error("Stream error:", error);
598
- reject(error);
599
- });
600
- });
601
-
602
- } catch (error) {
603
- console.error(`Failed to send message: ${error.message}`);
604
-
605
- // 如果是Cookie失效造成的错误
606
- if (error.response && (error.response.status === 401 || error.response.status === 403)) {
607
- console.log("可能Cookie已失效,将触发立即验证");
608
- setTimeout(periodicCookieValidation, 0);
609
- }
610
-
611
- if (error.message === "没有可用的有效Cookie") {
612
- return res.status(503).json({ error: error.message });
613
- }
614
- return res.status(500).json({ error: "Failed to send message" });
615
- }
616
- }
617
-
618
- // 初始化并启动服务器
619
- async function startServer() {
620
- try {
621
- await resolveConfig();
622
-
623
- // 启动定期Cookie验证(10分钟后首次验证)
624
- setTimeout(periodicCookieValidation, 10 * 60 * 1000);
625
-
626
- app.listen(PORT, () => {
627
- console.log(`===== Application Startup at ${new Date().toISOString()} =====`);
628
- console.log(`Server is running on port ${PORT}`);
629
- });
630
- } catch (error) {
631
- console.error(`启动失败: ${error.message}`);
632
- process.exit(1);
633
- }
634
- }
635
-
636
- startServer();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,477 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, Response
2
+ import cloudscraper # 替换requests库,专门用于绕过Cloudflare保护
3
+ import io
4
+ import json
5
+ import re
6
+ import uuid
7
+ import random
8
+ import time
9
+ import os
10
+
11
+ app = Flask(__name__)
12
+
13
+ TARGET_URL = "https://grok.com/rest/app-chat/conversations/new"
14
+ MODELS = ["grok-2", "grok-3", "grok-3-thinking"]
15
+ COOKIE_NUM = 0
16
+ COOKIE_LIST = []
17
+ LAST_COOKIE_INDEX = {}
18
+ TEMPORARY_MODE = False
19
+
20
+ USER_AGENTS = [
21
+ # Windows - Chrome
22
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
23
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
24
+ # Windows - Firefox
25
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
26
+ # Windows - Edge
27
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.2420.81",
28
+ # Windows - Opera
29
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 OPR/109.0.0.0",
30
+ # macOS - Chrome
31
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
32
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
33
+ # macOS - Safari
34
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0.1 Safari/605.1.15",
35
+ # macOS - Firefox
36
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:132.0) Gecko/20100101 Firefox/132.0",
37
+ # macOS - Opera
38
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 OPR/109.0.0.0",
39
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.4; rv:124.0) Gecko/20100101 Firefox/124.0",
40
+ # Linux - Chrome
41
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
42
+ # Linux - Firefox
43
+ "Mozilla/5.0 (X11; Linux i686; rv:124.0) Gecko/20100101 Firefox/124.0",
44
+ ]
45
+
46
+ def create_cf_scraper(cookie_string):
47
+ """创建一个配置好的cloudscraper实例"""
48
+ browser = random.choice(['chrome', 'firefox', 'edge'])
49
+ platform = random.choice(['windows', 'darwin', 'linux'])
50
+
51
+ # 创建cloudscraper会话
52
+ scraper = cloudscraper.create_scraper(
53
+ browser={
54
+ 'browser': browser,
55
+ 'platform': platform,
56
+ 'desktop': True
57
+ },
58
+ delay=random.uniform(5, 10), # 等待Cloudflare检查
59
+ interpreter='js2py', # 使用js2py解释JavaScript挑战
60
+ )
61
+
62
+ # 设置自定义用户代理
63
+ selected_ua = random.choice(USER_AGENTS)
64
+
65
+ # 设置基本头信息
66
+ scraper.headers.update({
67
+ "user-agent": selected_ua,
68
+ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
69
+ "accept-language": "en-US,en;q=0.5",
70
+ "accept-encoding": "gzip, deflate, br",
71
+ "dnt": "1",
72
+ "sec-fetch-dest": "document",
73
+ "sec-fetch-mode": "navigate",
74
+ "sec-fetch-site": "none",
75
+ "sec-fetch-user": "?1",
76
+ "upgrade-insecure-requests": "1",
77
+ "cookie": cookie_string
78
+ })
79
+
80
+ return scraper
81
+
82
+ def resolve_config():
83
+ global COOKIE_NUM, COOKIE_LIST, LAST_COOKIE_INDEX, TEMPORARY_MODE
84
+ COOKIE_LIST = []
85
+ cookie_index = 1
86
+
87
+ while True:
88
+ cookie_env_name = f"GROK_COOKIE_{cookie_index}"
89
+ cookie_string = os.environ.get(cookie_env_name)
90
+ if cookie_string:
91
+ try:
92
+ print(f"创建Cookie {cookie_index} 的CloudScraper实例...")
93
+ scraper = create_cf_scraper(cookie_string)
94
+ COOKIE_LIST.append(scraper)
95
+ cookie_index += 1
96
+ except Exception as e:
97
+ print(f"为Cookie {cookie_index} 创建CloudScraper失败: {e}")
98
+ cookie_index += 1
99
+ else:
100
+ break
101
+
102
+ COOKIE_NUM = len(COOKIE_LIST)
103
+ if COOKIE_NUM == 0:
104
+ raise ValueError("未提供Grok cookies,请通过环境变量设置 (GROK_COOKIE_1, GROK_COOKIE_2, ...)")
105
+
106
+ temporary_mode_str = os.environ.get("GROK_TEMPORARY_MODE", "false").lower()
107
+ TEMPORARY_MODE = temporary_mode_str == "true" or temporary_mode_str == "1"
108
+
109
+ LAST_COOKIE_INDEX = {model: 0 for model in MODELS}
110
+
111
+ print(f"已从环境变量加载 {COOKIE_NUM} 个Grok cookies。")
112
+ print(f"临时模式: {TEMPORARY_MODE}")
113
+
114
+
115
+ @app.route("/", methods=["GET"])
116
+ def root():
117
+ return "Grok Proxy is running (Cloudflare Protected)", 200, {'Content-Type': 'text/plain'}
118
+
119
+
120
+ @app.route("/health", methods=["GET"])
121
+ def health_check():
122
+ return "OK", 200, {'Content-Type': 'text/plain'}
123
+
124
+
125
+ @app.route("/v1/models", methods=["GET"])
126
+ def get_models():
127
+ model_list = []
128
+ for model in MODELS:
129
+ model_list.append(
130
+ {
131
+ "id": model,
132
+ "object": "model",
133
+ "created": int(time.time()),
134
+ "owned_by": "Elbert",
135
+ "name": model,
136
+ }
137
+ )
138
+ return jsonify({"object": "list", "data": model_list})
139
+
140
+
141
+ @app.route("/v1/chat/completions", methods=["POST"])
142
+ def chat_completions():
143
+ print("Received request")
144
+ openai_request = request.get_json()
145
+ print(openai_request)
146
+ stream = openai_request.get("stream", False)
147
+ messages = openai_request.get("messages")
148
+ model = openai_request.get("model")
149
+ if model not in MODELS:
150
+ return jsonify({"error": "Model not available"}), 500
151
+ if messages is None:
152
+ return jsonify({"error": "Messages is required"}), 400
153
+ disable_search, force_concise, messages = magic(messages)
154
+ message = format_message(messages)
155
+ is_reasoning = len(model) > 6
156
+ model = model[0:6]
157
+ return (
158
+ send_message(message, model, disable_search, force_concise, is_reasoning)
159
+ if stream
160
+ else send_message_non_stream(
161
+ message, model, disable_search, force_concise, is_reasoning)
162
+ )
163
+
164
+
165
+ def get_next_account(model):
166
+ current = (LAST_COOKIE_INDEX[model] + 1) % COOKIE_NUM
167
+ LAST_COOKIE_INDEX[model] = current
168
+ print(f"Using account {current+1}/{COOKIE_NUM} for {model}")
169
+ return COOKIE_LIST[current]
170
+
171
+
172
+ def send_message(message, model, disable_search, force_concise, is_reasoning):
173
+ headers = {
174
+ "authority": "grok.com",
175
+ "accept": "*/*",
176
+ "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
177
+ "cache-control": "no-cache",
178
+ "content-type": "application/json",
179
+ "origin": "https://grok.com",
180
+ "pragma": "no-cache",
181
+ "referer": "https://grok.com/",
182
+ "sec-ch-ua": '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
183
+ "sec-ch-ua-mobile": "?0",
184
+ "sec-ch-ua-platform": '"Windows"',
185
+ "sec-fetch-dest": "empty",
186
+ "sec-fetch-mode": "cors",
187
+ "sec-fetch-site": "same-origin",
188
+ }
189
+ payload = {
190
+ "temporary": TEMPORARY_MODE,
191
+ "modelName": "grok-3",
192
+ "message": message,
193
+ "fileAttachments": [],
194
+ "imageAttachments": [],
195
+ "disableSearch": disable_search,
196
+ "enableImageGeneration": False,
197
+ "returnImageBytes": False,
198
+ "returnRawGrokInXaiRequest": False,
199
+ "enableImageStreaming": True,
200
+ "imageGenerationCount": 2,
201
+ "forceConcise": force_concise,
202
+ "toolOverrides": {},
203
+ "enableSideBySide": True,
204
+ "isPreset": False,
205
+ "sendFinalMetadata": True,
206
+ "customInstructions": "",
207
+ "deepsearchPreset": "",
208
+ "isReasoning": is_reasoning,
209
+ }
210
+
211
+ try:
212
+ scraper = get_next_account(model)
213
+
214
+ # 预热Cloudflare
215
+ print("预热Cloudflare权限...")
216
+ scraper.get("https://grok.com/", timeout=15)
217
+ time.sleep(random.uniform(1.0, 2.0)) # 随机等待以模拟人类
218
+
219
+ print("发送消息请求...")
220
+ response = scraper.post(TARGET_URL, headers=headers, json=payload, stream=True)
221
+ response.raise_for_status()
222
+
223
+ def generate():
224
+ try:
225
+ print("---------- Response ----------")
226
+ cnt = 2
227
+ thinking = 2
228
+ for line in response.iter_lines():
229
+ if line:
230
+ if cnt != 0:
231
+ cnt -= 1
232
+ else:
233
+ decoded_line = line.decode("utf-8")
234
+ data = json.loads(decoded_line)
235
+ token = data["result"]["response"]["token"]
236
+ content = ""
237
+ if is_reasoning:
238
+ if thinking == 2:
239
+ thinking = 1
240
+ content = f"<Thinking>\n{token}"
241
+ print(f"{content}", end="")
242
+ elif thinking & (
243
+ not data["result"]["response"]["isThinking"]
244
+ ):
245
+ thinking = 0
246
+ content = f"\n</Thinking>\n{token}"
247
+ print(f"{content}", end="")
248
+ else:
249
+ content = token
250
+ print(content, end="")
251
+ else:
252
+ content = token
253
+ print(content, end="")
254
+ openai_chunk = {
255
+ "id": "chatcmpl-" + str(uuid.uuid4()),
256
+ "object": "chat.completion.chunk",
257
+ "created": int(time.time()),
258
+ "model": model,
259
+ "choices": [
260
+ {
261
+ "index": 0,
262
+ "delta": {"content": content},
263
+ "finish_reason": None,
264
+ }
265
+ ],
266
+ }
267
+ yield f"data: {json.dumps(openai_chunk)}\n\n"
268
+ if data["result"]["response"]["isSoftStop"]:
269
+ openai_chunk = {
270
+ "id": "chatcmpl-" + str(uuid.uuid4()),
271
+ "object": "chat.completion.chunk",
272
+ "created": int(time.time()),
273
+ "model": model,
274
+ "choices": [
275
+ {
276
+ "index": 0,
277
+ "delta": {"content": content},
278
+ "finish_reason": "completed",
279
+ }
280
+ ],
281
+ }
282
+ yield f"data: {json.dumps(openai_chunk)}\n\n"
283
+ break
284
+ print("\n---------- Response End ----------")
285
+ yield f"data: [DONE]\n\n"
286
+ except Exception as e:
287
+ print(f"Failed to send message: {e}")
288
+ yield f'data: {{"error": "{e}"}}\n\n'
289
+
290
+ return Response(generate(), content_type="text/event-stream")
291
+ except Exception as e:
292
+ print(f"Failed to send message: {e}")
293
+ return jsonify({"error": f"Failed to send message: {e}"}), 500
294
+
295
+
296
+ def send_message_non_stream(
297
+ message, model, disable_search, force_concise, is_reasoning
298
+ ):
299
+ headers = {
300
+ "authority": "grok.com",
301
+ "accept": "*/*",
302
+ "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
303
+ "cache-control": "no-cache",
304
+ "content-type": "application/json",
305
+ "origin": "https://grok.com",
306
+ "pragma": "no-cache",
307
+ "referer": "https://grok.com/",
308
+ "sec-ch-ua": '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
309
+ "sec-ch-ua-mobile": "?0",
310
+ "sec-ch-ua-platform": '"Windows"',
311
+ "sec-fetch-dest": "empty",
312
+ "sec-fetch-mode": "cors",
313
+ "sec-fetch-site": "same-origin",
314
+ }
315
+ payload = {
316
+ "temporary": TEMPORARY_MODE,
317
+ "modelName": "grok-3",
318
+ "message": message,
319
+ "fileAttachments": [],
320
+ "imageAttachments": [],
321
+ "disableSearch": disable_search,
322
+ "enableImageGeneration": False,
323
+ "returnImageBytes": False,
324
+ "returnRawGrokInXaiRequest": False,
325
+ "enableImageStreaming": True,
326
+ "imageGenerationCount": 2,
327
+ "forceConcise": force_concise,
328
+ "toolOverrides": {},
329
+ "enableSideBySide": True,
330
+ "isPreset": False,
331
+ "sendFinalMetadata": True,
332
+ "customInstructions": "",
333
+ "deepsearchPreset": "",
334
+ "isReasoning": is_reasoning,
335
+ }
336
+
337
+ thinking = 2
338
+ try:
339
+ scraper = get_next_account(model)
340
+
341
+ # 预热Cloudflare
342
+ print("预热Cloudflare权限...")
343
+ scraper.get("https://grok.com/", timeout=15)
344
+ time.sleep(random.uniform(1.0, 2.0)) # 随机等待以模拟人类
345
+
346
+ print("发送非流式消息请求...")
347
+ response = scraper.post(TARGET_URL, headers=headers, json=payload, stream=True)
348
+ response.raise_for_status()
349
+ cnt = 2
350
+ try:
351
+ print("---------- Response ----------")
352
+ buffer = io.StringIO()
353
+ for line in response.iter_lines():
354
+ if line:
355
+ if cnt != 0:
356
+ cnt -= 1
357
+ else:
358
+ decoded_line = line.decode("utf-8")
359
+ data = json.loads(decoded_line)
360
+ token = data["result"]["response"]["token"]
361
+ content = ""
362
+ if is_reasoning:
363
+ if thinking == 2:
364
+ thinking = 1
365
+ content = f"<Thinking>\n{token}"
366
+ print(f"{content}", end="")
367
+ buffer.write(content)
368
+ elif thinking & (
369
+ not data["result"]["response"]["isThinking"]
370
+ ):
371
+ thinking = 0
372
+ content = f"\n</Thinking>\n{token}"
373
+ print(f"{content}", end="")
374
+ buffer.write(content)
375
+ else:
376
+ content = token
377
+ print(content, end="")
378
+ buffer.write(content)
379
+ else:
380
+ content = token
381
+ print(content, end="")
382
+ buffer.write(content)
383
+ if data["result"]["response"]["isSoftStop"]:
384
+ break
385
+ print("\n---------- Response End ----------")
386
+ openai_response = {
387
+ "id": "chatcmpl-" + str(uuid.uuid4()),
388
+ "object": "chat.completion",
389
+ "created": int(time.time()),
390
+ "model": model,
391
+ "choices": [
392
+ {
393
+ "index": 0,
394
+ "message": {"role": "assistant", "content": buffer.getvalue()},
395
+ "finish_reason": "completed",
396
+ }
397
+ ],
398
+ }
399
+ return jsonify(openai_response)
400
+ except Exception as e:
401
+ print(f"Failed to process response: {e}")
402
+ return jsonify({"error": f"Failed to process response: {e}"}), 500
403
+ except Exception as e:
404
+ print(f"Failed to send message: {e}")
405
+ return jsonify({"error": f"Failed to send message: {e}"}), 500
406
+
407
+
408
+ def format_message(messages):
409
+ buffer = io.StringIO()
410
+ role_map, prefix, messages = extract_role(messages)
411
+ for message in messages:
412
+ role = message.get("role")
413
+ role = "\b" + role_map[role] if prefix else role_map[role]
414
+ content = message.get("content").replace("\\n", "\n")
415
+ pattern = re.compile(r"<\|removeRole\|>\n")
416
+ if pattern.match(content):
417
+ content = pattern.sub("", content)
418
+ buffer.write(f"{content}\n")
419
+ else:
420
+ buffer.write(f"{role}: {content}\n")
421
+ return buffer.getvalue()
422
+
423
+
424
+ def extract_role(messages):
425
+ role_map = {"user": "Human", "assistant": "Assistant", "system": "System"}
426
+ prefix = False
427
+ first_message = messages[0]["content"]
428
+ pattern = re.compile(
429
+ r"""
430
+ <roleInfo>\s*
431
+ user:\s*(?P<user>[^\n]*)\s*
432
+ assistant:\s*(?P<assistant>[^\n]*)\s*
433
+ system:\s*(?P<system>[^\n]*)\s*
434
+ prefix:\s*(?P<prefix>[^\n]*)\s*
435
+ </roleInfo>\n
436
+ """,
437
+ re.VERBOSE,
438
+ )
439
+ match = pattern.search(first_message)
440
+ if match:
441
+ role_map = {
442
+ "user": match.group("user"),
443
+ "assistant": match.group("assistant"),
444
+ "system": match.group("system"),
445
+ }
446
+ prefix = match.group("prefix") == "1"
447
+ messages[0]["content"] = pattern.sub("", first_message)
448
+ print(f"Extracted role map:")
449
+ print(
450
+ f"User: {role_map['user']}, {role_map['assistant']}, System: {role_map['system']}"
451
+ )
452
+ print(f"Using prefix: {prefix}")
453
+ return (role_map, prefix, messages)
454
+
455
+
456
+ def magic(messages):
457
+ first_message = messages[0]["content"]
458
+ disable_search = False
459
+ if re.search(r"<\|disableSearch\|>", first_message):
460
+ disable_search = True
461
+ print("Disable search")
462
+ first_message = re.sub(r"<\|disableSearch\|>", "", first_message)
463
+ force_concise = False
464
+ if re.search(r"<\|forceConcise\|>", first_message):
465
+ force_concise = True
466
+ print("Force concise")
467
+ first_message = re.sub(r"<\|forceConcise\|>", "", first_message)
468
+ messages[0]["content"] = first_message
469
+ return (disable_search, force_concise, messages)
470
+
471
+
472
+ # 初始化配置
473
+ resolve_config()
474
+
475
+ if __name__ == "__main__":
476
+ app.run(host="0.0.0.0", port=7860)
477
+