Spaces:
Running
Running
Rename app.js to app.py
Browse files
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 |
+
|