dfa32412 commited on
Commit
370f19f
·
verified ·
1 Parent(s): 8c7b6d8

Upload 2 files

Browse files
Files changed (2) hide show
  1. app (1).py +1321 -0
  2. get_xid.py +143 -0
app (1).py ADDED
@@ -0,0 +1,1321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import uuid
4
+ import time
5
+ import base64
6
+ import sys
7
+ import inspect
8
+ import secrets
9
+ from loguru import logger
10
+ from dotenv import load_dotenv
11
+
12
+ import requests
13
+ from flask import Flask, request, Response, jsonify, stream_with_context, render_template, redirect, session
14
+ from curl_cffi import requests as curl_requests
15
+ from werkzeug.middleware.proxy_fix import ProxyFix
16
+ import get_xid
17
+ import threading
18
+
19
+
20
+ current_dir = os.path.dirname(os.path.abspath(__file__))
21
+ env_path = os.path.join(current_dir, '.env')
22
+
23
+ load_dotenv(env_path)
24
+
25
+
26
+ class Logger:
27
+ def __init__(self, level="INFO", colorize=True, format=None):
28
+ logger.remove()
29
+
30
+ if format is None:
31
+ format = (
32
+ "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
33
+ "<level>{level: <8}</level> | "
34
+ "<cyan>{extra[filename]}</cyan>:<cyan>{extra[function]}</cyan>:<cyan>{extra[lineno]}</cyan> | "
35
+ "<level>{message}</level>"
36
+ )
37
+
38
+ logger.add(
39
+ sys.stderr,
40
+ level=level,
41
+ format=format,
42
+ colorize=colorize,
43
+ backtrace=True,
44
+ diagnose=True
45
+ )
46
+
47
+ self.logger = logger
48
+
49
+ def _get_caller_info(self):
50
+ frame = inspect.currentframe()
51
+ try:
52
+ caller_frame = frame.f_back.f_back
53
+ full_path = caller_frame.f_code.co_filename
54
+ function = caller_frame.f_code.co_name
55
+ lineno = caller_frame.f_lineno
56
+
57
+ filename = os.path.basename(full_path)
58
+
59
+ return {
60
+ 'filename': filename,
61
+ 'function': function,
62
+ 'lineno': lineno
63
+ }
64
+ finally:
65
+ del frame
66
+
67
+ def info(self, message, source="API"):
68
+ caller_info = self._get_caller_info()
69
+ self.logger.bind(**caller_info).info(f"[{source}] {message}")
70
+
71
+ def error(self, message, source="API"):
72
+ caller_info = self._get_caller_info()
73
+
74
+ if isinstance(message, Exception):
75
+ self.logger.bind(**caller_info).exception(f"[{source}] {str(message)}")
76
+ else:
77
+ self.logger.bind(**caller_info).error(f"[{source}] {message}")
78
+
79
+ def warning(self, message, source="API"):
80
+ caller_info = self._get_caller_info()
81
+ self.logger.bind(**caller_info).warning(f"[{source}] {message}")
82
+
83
+ def debug(self, message, source="API"):
84
+ caller_info = self._get_caller_info()
85
+ self.logger.bind(**caller_info).debug(f"[{source}] {message}")
86
+
87
+ async def request_logger(self, request):
88
+ caller_info = self._get_caller_info()
89
+ self.logger.bind(**caller_info).info(f"请求: {request.method} {request.path}", "Request")
90
+
91
+
92
+ logger = Logger(level="INFO")
93
+
94
+ CONFIG = {
95
+ "XIDS": None,
96
+ "MODELS": {
97
+ 'grok-2': 'grok-latest',
98
+ 'grok-2-imageGen': 'grok-latest',
99
+ 'grok-2-search': 'grok-latest',
100
+ "grok-3": "grok-3",
101
+ "grok-3-search": "grok-3",
102
+ "grok-3-imageGen": "grok-3",
103
+ "grok-3-deepsearch": "grok-3",
104
+ "grok-3-reasoning": "grok-3"
105
+ },
106
+ "API": {
107
+ "IS_TEMP_CONVERSATION": os.getenv("IS_TEMP_CONVERSATION", "true").lower() == "true",
108
+ "IS_CUSTOM_SSO": os.getenv("IS_CUSTOM_SSO", "false").lower() == "true",
109
+ "BASE_URL": "https://grok.com",
110
+ "API_KEY": os.getenv("API_KEY", "sk-123456"),
111
+ "SIGNATURE_COOKIE": None,
112
+ "PICGO_KEY": os.getenv("PICGO_KEY") or None,
113
+ "TUMY_KEY": os.getenv("TUMY_KEY") or None,
114
+ "RETRY_TIME": 1000,
115
+ "PROXY": os.getenv("PROXY") or None
116
+ },
117
+ "ADMIN": {
118
+ "MANAGER_SWITCH": os.getenv("MANAGER_SWITCH") or None,
119
+ "PASSWORD": os.getenv("ADMINPASSWORD") or None
120
+ },
121
+ "SERVER": {
122
+ "COOKIE": None,
123
+ "CF_CLEARANCE": os.getenv("CF_CLEARANCE") or None,
124
+ "PORT": int(os.getenv("PORT", 5200))
125
+ },
126
+ "RETRY": {
127
+ "RETRYSWITCH": False,
128
+ "MAX_ATTEMPTS": 2
129
+ },
130
+ "SHOW_THINKING": os.getenv("SHOW_THINKING") == "true",
131
+ "IS_THINKING": False,
132
+ "IS_IMG_GEN": False,
133
+ "IS_IMG_GEN2": False,
134
+ "ISSHOW_SEARCH_RESULTS": os.getenv("ISSHOW_SEARCH_RESULTS", "true").lower() == "true"
135
+ }
136
+
137
+ DEFAULT_HEADERS = {
138
+ 'Accept': '*/*',
139
+ 'Accept-Language': 'zh-CN,zh;q=0.9',
140
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
141
+ 'Content-Type': 'text/plain;charset=UTF-8',
142
+ 'Connection': 'keep-alive',
143
+ 'Origin': 'https://grok.com',
144
+ 'Priority': 'u=1, i',
145
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0',
146
+ 'Sec-Ch-Ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
147
+ 'Sec-Ch-Ua-Mobile': '?0',
148
+ 'Sec-Ch-Ua-Platform': '"macOS"',
149
+ 'Sec-Fetch-Dest': 'empty',
150
+ 'Sec-Fetch-Mode': 'cors',
151
+ 'Sec-Fetch-Site': 'same-origin',
152
+ 'x-statsig-id': 'ZTpUeXBlRXJyb3I6IENhbm5vdCByZWFkIHByb3BlcnRpZXMgb2YgdW5kZWZpbmVkIChyZWFkaW5nICdjaGlsZE5vZGVzJyk=',
153
+ 'x-xai-request-id': '95a798e5-f084-44e2-a1e7-f061e10a783b',
154
+ 'Baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
155
+ }
156
+
157
+
158
+ class AuthTokenManager:
159
+ def __init__(self):
160
+ self.token_model_map = {}
161
+ self.expired_tokens = set()
162
+ self.token_status_map = {}
163
+
164
+ self.model_config = {
165
+ "grok-2": {
166
+ "RequestFrequency": 30,
167
+ "ExpirationTime": 1 * 60 * 60 * 1000 # 1小时
168
+ },
169
+ "grok-3": {
170
+ "RequestFrequency": 20,
171
+ "ExpirationTime": 2 * 60 * 60 * 1000 # 2小时
172
+ },
173
+ "grok-3-deepsearch": {
174
+ "RequestFrequency": 10,
175
+ "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时
176
+ },
177
+ "grok-3-reasoning": {
178
+ "RequestFrequency": 10,
179
+ "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时
180
+ }
181
+ }
182
+ self.token_reset_switch = False
183
+ self.token_reset_timer = None
184
+
185
+ def add_token(self, token):
186
+ sso = token.split("sso=")[1].split(";")[0]
187
+ for model in self.model_config.keys():
188
+ if model not in self.token_model_map:
189
+ self.token_model_map[model] = []
190
+ if sso not in self.token_status_map:
191
+ self.token_status_map[sso] = {}
192
+
193
+ existing_token_entry = next((entry for entry in self.token_model_map[model] if entry["token"] == token),
194
+ None)
195
+
196
+ if not existing_token_entry:
197
+ self.token_model_map[model].append({
198
+ "token": token,
199
+ "RequestCount": 0,
200
+ "AddedTime": int(time.time() * 1000),
201
+ "StartCallTime": None
202
+ })
203
+
204
+ if model not in self.token_status_map[sso]:
205
+ self.token_status_map[sso][model] = {
206
+ "isValid": True,
207
+ "invalidatedTime": None,
208
+ "totalRequestCount": 0
209
+ }
210
+
211
+ def set_token(self, token):
212
+ models = list(self.model_config.keys())
213
+ self.token_model_map = {model: [{
214
+ "token": token,
215
+ "RequestCount": 0,
216
+ "AddedTime": int(time.time() * 1000),
217
+ "StartCallTime": None
218
+ }] for model in models}
219
+
220
+ sso = token.split("sso=")[1].split(";")[0]
221
+ self.token_status_map[sso] = {model: {
222
+ "isValid": True,
223
+ "invalidatedTime": None,
224
+ "totalRequestCount": 0
225
+ } for model in models}
226
+
227
+ def delete_token(self, token):
228
+ try:
229
+ sso = token.split("sso=")[1].split(";")[0]
230
+ for model in self.token_model_map:
231
+ self.token_model_map[model] = [entry for entry in self.token_model_map[model] if
232
+ entry["token"] != token]
233
+
234
+ if sso in self.token_status_map:
235
+ del self.token_status_map[sso]
236
+
237
+ logger.info(f"令牌已成功移除: {token}", "TokenManager")
238
+ return True
239
+ except Exception as error:
240
+ logger.error(f"令牌删除失败: {str(error)}")
241
+ return False
242
+
243
+ def reduce_token_request_count(self, model_id, count):
244
+ try:
245
+ normalized_model = self.normalize_model_name(model_id)
246
+
247
+ if normalized_model not in self.token_model_map:
248
+ logger.error(f"模型 {normalized_model} 不存在", "TokenManager")
249
+ return False
250
+
251
+ if not self.token_model_map[normalized_model]:
252
+ logger.error(f"模型 {normalized_model} 没有可用的token", "TokenManager")
253
+ return False
254
+
255
+ token_entry = self.token_model_map[normalized_model][0]
256
+
257
+ # 确保RequestCount不会小于0
258
+ new_count = max(0, token_entry["RequestCount"] - count)
259
+ reduction = token_entry["RequestCount"] - new_count
260
+
261
+ token_entry["RequestCount"] = new_count
262
+
263
+ # 更新token状态
264
+ if token_entry["token"]:
265
+ sso = token_entry["token"].split("sso=")[1].split(";")[0]
266
+ if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:
267
+ self.token_status_map[sso][normalized_model]["totalRequestCount"] = max(
268
+ 0,
269
+ self.token_status_map[sso][normalized_model]["totalRequestCount"] - reduction
270
+ )
271
+ return True
272
+
273
+ except Exception as error:
274
+ logger.error(f"重置校对token请求次数时发生错误: {str(error)}", "TokenManager")
275
+ return False
276
+
277
+ def get_next_token_for_model(self, model_id, is_return=False):
278
+ normalized_model = self.normalize_model_name(model_id)
279
+
280
+ if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
281
+ return None
282
+
283
+ token_entry = self.token_model_map[normalized_model][0]
284
+ if is_return:
285
+ return token_entry["token"]
286
+
287
+ if token_entry:
288
+ if token_entry["StartCallTime"] is None:
289
+ token_entry["StartCallTime"] = int(time.time() * 1000)
290
+
291
+ if not self.token_reset_switch:
292
+ self.start_token_reset_process()
293
+ self.token_reset_switch = True
294
+
295
+ token_entry["RequestCount"] += 1
296
+
297
+ if token_entry["RequestCount"] > self.model_config[normalized_model]["RequestFrequency"]:
298
+ self.remove_token_from_model(normalized_model, token_entry["token"])
299
+ next_token_entry = self.token_model_map[normalized_model][0] if self.token_model_map[
300
+ normalized_model] else None
301
+ return next_token_entry["token"] if next_token_entry else None
302
+
303
+ sso = token_entry["token"].split("sso=")[1].split(";")[0]
304
+ if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:
305
+ if token_entry["RequestCount"] == self.model_config[normalized_model]["RequestFrequency"]:
306
+ self.token_status_map[sso][normalized_model]["isValid"] = False
307
+ self.token_status_map[sso][normalized_model]["invalidatedTime"] = int(time.time() * 1000)
308
+ self.token_status_map[sso][normalized_model]["totalRequestCount"] += 1
309
+
310
+ return token_entry["token"]
311
+
312
+ return None
313
+
314
+ def remove_token_from_model(self, model_id, token):
315
+ normalized_model = self.normalize_model_name(model_id)
316
+
317
+ if normalized_model not in self.token_model_map:
318
+ logger.error(f"模型 {normalized_model} 不存在", "TokenManager")
319
+ return False
320
+
321
+ model_tokens = self.token_model_map[normalized_model]
322
+ token_index = next((i for i, entry in enumerate(model_tokens) if entry["token"] == token), -1)
323
+
324
+ if token_index != -1:
325
+ removed_token_entry = model_tokens.pop(token_index)
326
+ self.expired_tokens.add((
327
+ removed_token_entry["token"],
328
+ normalized_model,
329
+ int(time.time() * 1000)
330
+ ))
331
+
332
+ if not self.token_reset_switch:
333
+ self.start_token_reset_process()
334
+ self.token_reset_switch = True
335
+
336
+ logger.info(f"模型{model_id}的令牌已失效,已成功移除令牌: {token}", "TokenManager")
337
+ return True
338
+
339
+ logger.error(f"在模型 {normalized_model} 中未找到 token: {token}", "TokenManager")
340
+ return False
341
+
342
+ def get_expired_tokens(self):
343
+ return list(self.expired_tokens)
344
+
345
+ def normalize_model_name(self, model):
346
+ if model.startswith('grok-') and 'deepsearch' not in model and 'reasoning' not in model:
347
+ return '-'.join(model.split('-')[:2])
348
+ return model
349
+
350
+ def get_token_count_for_model(self, model_id):
351
+ normalized_model = self.normalize_model_name(model_id)
352
+ return len(self.token_model_map.get(normalized_model, []))
353
+
354
+ def get_remaining_token_request_capacity(self):
355
+ remaining_capacity_map = {}
356
+
357
+ for model in self.model_config.keys():
358
+ model_tokens = self.token_model_map.get(model, [])
359
+ model_request_frequency = self.model_config[model]["RequestFrequency"]
360
+
361
+ total_used_requests = sum(token_entry.get("RequestCount", 0) for token_entry in model_tokens)
362
+
363
+ remaining_capacity = (len(model_tokens) * model_request_frequency) - total_used_requests
364
+ remaining_capacity_map[model] = max(0, remaining_capacity)
365
+
366
+ return remaining_capacity_map
367
+
368
+ def get_token_array_for_model(self, model_id):
369
+ normalized_model = self.normalize_model_name(model_id)
370
+ return self.token_model_map.get(normalized_model, [])
371
+
372
+ def start_token_reset_process(self):
373
+ def reset_expired_tokens():
374
+ now = int(time.time() * 1000)
375
+
376
+ tokens_to_remove = set()
377
+ for token_info in self.expired_tokens:
378
+ token, model, expired_time = token_info
379
+ expiration_time = self.model_config[model]["ExpirationTime"]
380
+
381
+ if now - expired_time >= expiration_time:
382
+ if not any(entry["token"] == token for entry in self.token_model_map.get(model, [])):
383
+ if model not in self.token_model_map:
384
+ self.token_model_map[model] = []
385
+
386
+ self.token_model_map[model].append({
387
+ "token": token,
388
+ "RequestCount": 0,
389
+ "AddedTime": now,
390
+ "StartCallTime": None
391
+ })
392
+
393
+ sso = token.split("sso=")[1].split(";")[0]
394
+ if sso in self.token_status_map and model in self.token_status_map[sso]:
395
+ self.token_status_map[sso][model]["isValid"] = True
396
+ self.token_status_map[sso][model]["invalidatedTime"] = None
397
+ self.token_status_map[sso][model]["totalRequestCount"] = 0
398
+
399
+ tokens_to_remove.add(token_info)
400
+
401
+ self.expired_tokens -= tokens_to_remove
402
+
403
+ for model in self.model_config.keys():
404
+ if model not in self.token_model_map:
405
+ continue
406
+
407
+ for token_entry in self.token_model_map[model]:
408
+ if not token_entry.get("StartCallTime"):
409
+ continue
410
+
411
+ expiration_time = self.model_config[model]["ExpirationTime"]
412
+ if now - token_entry["StartCallTime"] >= expiration_time:
413
+ sso = token_entry["token"].split("sso=")[1].split(";")[0]
414
+ if sso in self.token_status_map and model in self.token_status_map[sso]:
415
+ self.token_status_map[sso][model]["isValid"] = True
416
+ self.token_status_map[sso][model]["invalidatedTime"] = None
417
+ self.token_status_map[sso][model]["totalRequestCount"] = 0
418
+
419
+ token_entry["RequestCount"] = 0
420
+ token_entry["StartCallTime"] = None
421
+
422
+ import threading
423
+ # 启动一个线程执行定时任务,每小时执行一次
424
+ def run_timer():
425
+ while True:
426
+ reset_expired_tokens()
427
+ time.sleep(3600)
428
+
429
+ timer_thread = threading.Thread(target=run_timer)
430
+ timer_thread.daemon = True
431
+ timer_thread.start()
432
+
433
+ def get_all_tokens(self):
434
+ all_tokens = set()
435
+ for model_tokens in self.token_model_map.values():
436
+ for entry in model_tokens:
437
+ all_tokens.add(entry["token"])
438
+ return list(all_tokens)
439
+
440
+ def get_current_token(self, model_id):
441
+ normalized_model = self.normalize_model_name(model_id)
442
+
443
+ if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
444
+ return None
445
+
446
+ token_entry = self.token_model_map[normalized_model][0]
447
+ return token_entry["token"]
448
+
449
+ def get_token_status_map(self):
450
+ return self.token_status_map
451
+
452
+
453
+ class Utils:
454
+ @staticmethod
455
+ def organize_search_results(search_results):
456
+ if not search_results or 'results' not in search_results:
457
+ return ''
458
+
459
+ results = search_results['results']
460
+ formatted_results = []
461
+
462
+ for index, result in enumerate(results):
463
+ title = result.get('title', '未知标题')
464
+ url = result.get('url', '#')
465
+ preview = result.get('preview', '无预览内容')
466
+
467
+ formatted_result = f"\r\n<details><summary>资料[{index}]: {title}</summary>\r\n{preview}\r\n\n[Link]({url})\r\n</details>"
468
+ formatted_results.append(formatted_result)
469
+
470
+ return '\n\n'.join(formatted_results)
471
+
472
+ @staticmethod
473
+ def create_auth_headers(model, is_return=False):
474
+ return token_manager.get_next_token_for_model(model, is_return)
475
+
476
+ @staticmethod
477
+ def get_proxy_options():
478
+ proxy = CONFIG["API"]["PROXY"]
479
+ proxy_options = {}
480
+
481
+ if proxy:
482
+ logger.info(f"使用代理: {proxy}", "Server")
483
+
484
+ if proxy.startswith("socks5://"):
485
+ proxy_options["proxy"] = proxy
486
+
487
+ if '@' in proxy:
488
+ auth_part = proxy.split('@')[0].split('://')[1]
489
+ if ':' in auth_part:
490
+ username, password = auth_part.split(':')
491
+ proxy_options["proxy_auth"] = (username, password)
492
+ else:
493
+ proxy_options["proxies"] = {"https": proxy, "http": proxy}
494
+ return proxy_options
495
+
496
+
497
+ class GrokApiClient:
498
+ def __init__(self, model_id):
499
+ if model_id not in CONFIG["MODELS"]:
500
+ raise ValueError(f"不支持的模型: {model_id}")
501
+ self.model_id = CONFIG["MODELS"][model_id]
502
+
503
+ def process_message_content(self, content):
504
+ if isinstance(content, str):
505
+ return content
506
+ return None
507
+
508
+ def get_image_type(self, base64_string):
509
+ mime_type = 'image/jpeg'
510
+ if 'data:image' in base64_string:
511
+ import re
512
+ matches = re.search(r'data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,', base64_string)
513
+ if matches:
514
+ mime_type = matches.group(1)
515
+
516
+ extension = mime_type.split('/')[1]
517
+ file_name = f"image.{extension}"
518
+
519
+ return {
520
+ "mimeType": mime_type,
521
+ "fileName": file_name
522
+ }
523
+
524
+ def upload_base64_file(self, message, model):
525
+ try:
526
+ message_base64 = base64.b64encode(message.encode('utf-8')).decode('utf-8')
527
+ upload_data = {
528
+ "fileName": "message.txt",
529
+ "fileMimeType": "text/plain",
530
+ "content": message_base64
531
+ }
532
+
533
+ logger.info("发送文字文件请求", "Server")
534
+ cookie = f"{Utils.create_auth_headers(model, True)};{CONFIG['SERVER']['CF_CLEARANCE']}"
535
+ proxy_options = Utils.get_proxy_options()
536
+ response = curl_requests.post(
537
+ "https://grok.com/rest/app-chat/upload-file",
538
+ headers={
539
+ **DEFAULT_HEADERS,
540
+ "Cookie": cookie
541
+ },
542
+ json=upload_data,
543
+ impersonate="chrome133a",
544
+ verify=False,
545
+ **proxy_options
546
+ )
547
+
548
+ if response.status_code != 200:
549
+ logger.error(f"上传文件失败,状态码:{response.status_code}", "Server")
550
+ raise Exception(f"上传文件失败,状态码:{response.status_code}")
551
+
552
+ result = response.json()
553
+ logger.info(f"上传文件成功: {result}", "Server")
554
+ return result.get("fileMetadataId", "")
555
+
556
+ except Exception as error:
557
+ logger.error(str(error), "Server")
558
+ raise Exception(f"上传文件失败,状态码:{response.status_code}")
559
+
560
+ def upload_base64_image(self, base64_data, url):
561
+ try:
562
+ if 'data:image' in base64_data:
563
+ image_buffer = base64_data.split(',')[1]
564
+ else:
565
+ image_buffer = base64_data
566
+
567
+ image_info = self.get_image_type(base64_data)
568
+ mime_type = image_info["mimeType"]
569
+ file_name = image_info["fileName"]
570
+
571
+ upload_data = {
572
+ "rpc": "uploadFile",
573
+ "req": {
574
+ "fileName": file_name,
575
+ "fileMimeType": mime_type,
576
+ "content": image_buffer
577
+ }
578
+ }
579
+
580
+ logger.info("发送图片请求", "Server")
581
+
582
+ proxy_options = Utils.get_proxy_options()
583
+ response = curl_requests.post(
584
+ url,
585
+ headers={
586
+ **DEFAULT_HEADERS,
587
+ "Cookie": CONFIG["SERVER"]['COOKIE']
588
+ },
589
+ json=upload_data,
590
+ impersonate="chrome133a",
591
+ verify=False,
592
+ **proxy_options
593
+ )
594
+
595
+ if response.status_code != 200:
596
+ logger.error(f"上传图片失败,状态码:{response.status_code}", "Server")
597
+ return ''
598
+
599
+ result = response.json()
600
+ logger.info(f"上传图片成功: {result}", "Server")
601
+ return result.get("fileMetadataId", "")
602
+
603
+ except Exception as error:
604
+ logger.error(str(error), "Server")
605
+ return ''
606
+
607
+ def prepare_chat_request(self, request):
608
+ if ((request["model"] == 'grok-2-imageGen' or request["model"] == 'grok-3-imageGen') and
609
+ not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"] and
610
+ request.get("stream", False)):
611
+ raise ValueError("该模型流式输出需要配置PICGO或者TUMY图床密钥!")
612
+
613
+ todo_messages = request["messages"]
614
+ if request["model"] in ['grok-2-imageGen', 'grok-3-imageGen', 'grok-3-deepsearch']:
615
+ last_message = todo_messages[-1]
616
+ if last_message["role"] != 'user':
617
+ raise ValueError('此模型最后一条消息必须是用户消息!')
618
+ todo_messages = [last_message]
619
+ file_attachments = []
620
+ messages = ''
621
+ last_role = None
622
+ last_content = ''
623
+ message_length = 0
624
+ convert_to_file = False
625
+ last_message_content = ''
626
+ search = request["model"] in ['grok-2-search', 'grok-3-search']
627
+
628
+ # 移除<think>标签及其内容和base64图片
629
+ def remove_think_tags(text):
630
+ import re
631
+ text = re.sub(r'<think>[\s\S]*?<\/think>', '', text).strip()
632
+ text = re.sub(r'!\[image\]\(data:.*?base64,.*?\)', '[图片]', text)
633
+ return text
634
+
635
+ def process_content(content):
636
+ if isinstance(content, list):
637
+ text_content = ''
638
+ for item in content:
639
+ if item["type"] == 'image_url':
640
+ text_content += ("[图片]" if not text_content else '\n[图片]')
641
+ elif item["type"] == 'text':
642
+ text_content += (
643
+ remove_think_tags(item["text"]) if not text_content else '\n' + remove_think_tags(
644
+ item["text"]))
645
+ return text_content
646
+ elif isinstance(content, dict) and content is not None:
647
+ if content["type"] == 'image_url':
648
+ return "[图片]"
649
+ elif content["type"] == 'text':
650
+ return remove_think_tags(content["text"])
651
+ return remove_think_tags(self.process_message_content(content))
652
+
653
+ for current in todo_messages:
654
+ role = 'assistant' if current["role"] == 'assistant' else 'user'
655
+ is_last_message = current == todo_messages[-1]
656
+
657
+ if is_last_message and "content" in current:
658
+ if isinstance(current["content"], list):
659
+ for item in current["content"]:
660
+ if item["type"] == 'image_url':
661
+ processed_image = self.upload_base64_image(
662
+ item["image_url"]["url"],
663
+ f"{CONFIG['API']['BASE_URL']}/api/rpc"
664
+ )
665
+ if processed_image:
666
+ file_attachments.append(processed_image)
667
+ elif isinstance(current["content"], dict) and current["content"].get("type") == 'image_url':
668
+ processed_image = self.upload_base64_image(
669
+ current["content"]["image_url"]["url"],
670
+ f"{CONFIG['API']['BASE_URL']}/api/rpc"
671
+ )
672
+ if processed_image:
673
+ file_attachments.append(processed_image)
674
+
675
+ text_content = process_content(current.get("content", ""))
676
+ if is_last_message and convert_to_file:
677
+ last_message_content = f"{role.upper()}: {text_content or '[图片]'}\n"
678
+ continue
679
+ if text_content or (is_last_message and file_attachments):
680
+ if role == last_role and text_content:
681
+ last_content += '\n' + text_content
682
+ messages = messages[:messages.rindex(f"{role.upper()}: ")] + f"{role.upper()}: {last_content}\n"
683
+ else:
684
+ messages += f"{role.upper()}: {text_content or '[图片]'}\n"
685
+ last_content = text_content
686
+ last_role = role
687
+ message_length += len(messages)
688
+ if message_length >= 40000:
689
+ convert_to_file = True
690
+
691
+ if convert_to_file:
692
+ file_id = self.upload_base64_file(messages, request["model"])
693
+ if file_id:
694
+ file_attachments.insert(0, file_id)
695
+ messages = last_message_content.strip()
696
+ if messages.strip() == '':
697
+ if convert_to_file:
698
+ messages = '基于txt文件内容进行回复:'
699
+ else:
700
+ raise ValueError('消息内容为空!')
701
+ return {
702
+ "temporary": CONFIG["API"].get("IS_TEMP_CONVERSATION", False),
703
+ "modelName": self.model_id,
704
+ "message": messages.strip(),
705
+ "fileAttachments": file_attachments[:4],
706
+ "imageAttachments": [],
707
+ "disableSearch": False,
708
+ "enableImageGeneration": True,
709
+ "returnImageBytes": False,
710
+ "returnRawGrokInXaiRequest": False,
711
+ "enableImageStreaming": False,
712
+ "imageGenerationCount": 1,
713
+ "forceConcise": False,
714
+ "toolOverrides": {
715
+ "imageGen": request["model"] in ['grok-2-imageGen', 'grok-3-imageGen'],
716
+ "webSearch": search,
717
+ "xSearch": search,
718
+ "xMediaSearch": search,
719
+ "trendsSearch": search,
720
+ "xPostAnalyze": search
721
+ },
722
+ "enableSideBySide": True,
723
+ "isPreset": False,
724
+ "sendFinalMetadata": True,
725
+ "customInstructions": "",
726
+ "deepsearchPreset": "default" if request["model"] == 'grok-3-deepsearch' else "",
727
+ "isReasoning": request["model"] == 'grok-3-reasoning'
728
+ }
729
+
730
+
731
+ class MessageProcessor:
732
+ @staticmethod
733
+ def create_chat_response(message, model, is_stream=False):
734
+ base_response = {
735
+ "id": f"chatcmpl-{uuid.uuid4()}",
736
+ "created": int(time.time()),
737
+ "model": model
738
+ }
739
+
740
+ if is_stream:
741
+ return {
742
+ **base_response,
743
+ "object": "chat.completion.chunk",
744
+ "choices": [{
745
+ "index": 0,
746
+ "delta": {
747
+ "content": message
748
+ }
749
+ }]
750
+ }
751
+
752
+ return {
753
+ **base_response,
754
+ "object": "chat.completion",
755
+ "choices": [{
756
+ "index": 0,
757
+ "message": {
758
+ "role": "assistant",
759
+ "content": message
760
+ },
761
+ "finish_reason": "stop"
762
+ }],
763
+ "usage": None
764
+ }
765
+
766
+
767
+ def process_model_response(response, model):
768
+ result = {"token": None, "imageUrl": None}
769
+
770
+ if CONFIG["IS_IMG_GEN"]:
771
+ if response.get("cachedImageGenerationResponse") and not CONFIG["IS_IMG_GEN2"]:
772
+ result["imageUrl"] = response["cachedImageGenerationResponse"]["imageUrl"]
773
+ return result
774
+
775
+ if model == 'grok-2':
776
+ result["token"] = response.get("token")
777
+ elif model in ['grok-2-search', 'grok-3-search']:
778
+ if response.get("webSearchResults") and CONFIG["ISSHOW_SEARCH_RESULTS"]:
779
+ result["token"] = f"\r\n<think>{Utils.organize_search_results(response['webSearchResults'])}</think>\r\n"
780
+ else:
781
+ result["token"] = response.get("token")
782
+ elif model == 'grok-3':
783
+ result["token"] = response.get("token")
784
+ elif model == 'grok-3-deepsearch':
785
+ if response.get("messageStepId") and not CONFIG["SHOW_THINKING"]:
786
+ return result
787
+ if response.get("messageStepId") and not CONFIG["IS_THINKING"]:
788
+ result["token"] = "<think>" + response.get("token", "")
789
+ CONFIG["IS_THINKING"] = True
790
+ elif not response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "final":
791
+ result["token"] = "</think>" + response.get("token", "")
792
+ CONFIG["IS_THINKING"] = False
793
+ elif (response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get(
794
+ "messageTag") == "assistant") or response.get("messageTag") == "final":
795
+ result["token"] = response.get("token")
796
+ elif model == 'grok-3-reasoning':
797
+ if response.get("isThinking") and not CONFIG["SHOW_THINKING"]:
798
+ return result
799
+
800
+ if response.get("isThinking") and not CONFIG["IS_THINKING"]:
801
+ result["token"] = "<think>" + response.get("token", "")
802
+ CONFIG["IS_THINKING"] = True
803
+ elif not response.get("isThinking") and CONFIG["IS_THINKING"]:
804
+ result["token"] = "</think>" + response.get("token", "")
805
+ CONFIG["IS_THINKING"] = False
806
+ else:
807
+ result["token"] = response.get("token")
808
+
809
+ return result
810
+
811
+
812
+ def handle_image_response(image_url):
813
+ max_retries = 2
814
+ retry_count = 0
815
+ image_base64_response = None
816
+
817
+ while retry_count < max_retries:
818
+ try:
819
+ proxy_options = Utils.get_proxy_options()
820
+ image_base64_response = curl_requests.get(
821
+ f"https://assets.grok.com/{image_url}",
822
+ headers={
823
+ **DEFAULT_HEADERS,
824
+ "Cookie": CONFIG["SERVER"]['COOKIE']
825
+ },
826
+ impersonate="chrome133a",
827
+ **proxy_options
828
+ )
829
+
830
+ if image_base64_response.status_code == 200:
831
+ break
832
+
833
+ retry_count += 1
834
+ if retry_count == max_retries:
835
+ raise Exception(f"上游服务请求失败! status: {image_base64_response.status_code}")
836
+
837
+ time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
838
+
839
+ except Exception as error:
840
+ logger.error(str(error), "Server")
841
+ retry_count += 1
842
+ if retry_count == max_retries:
843
+ raise
844
+
845
+ time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
846
+
847
+ image_buffer = image_base64_response.content
848
+
849
+ if not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"]:
850
+ base64_image = base64.b64encode(image_buffer).decode('utf-8')
851
+ image_content_type = image_base64_response.headers.get('content-type', 'image/jpeg')
852
+ return f"![image](data:{image_content_type};base64,{base64_image})"
853
+
854
+ logger.info("开始上传图床", "Server")
855
+
856
+ if CONFIG["API"]["PICGO_KEY"]:
857
+ files = {'source': ('image.jpg', image_buffer, 'image/jpeg')}
858
+ headers = {
859
+ "X-API-Key": CONFIG["API"]["PICGO_KEY"]
860
+ }
861
+
862
+ response_url = requests.post(
863
+ "https://www.picgo.net/api/1/upload",
864
+ files=files,
865
+ headers=headers
866
+ )
867
+
868
+ if response_url.status_code != 200:
869
+ return "生图失败,请查看PICGO图床密钥是否设置正确"
870
+ else:
871
+ logger.info("生图成功", "Server")
872
+ result = response_url.json()
873
+ return f"![image]({result['image']['url']})"
874
+
875
+
876
+ elif CONFIG["API"]["TUMY_KEY"]:
877
+ files = {'file': ('image.jpg', image_buffer, 'image/jpeg')}
878
+ headers = {
879
+ "Accept": "application/json",
880
+ 'Authorization': f"Bearer {CONFIG['API']['TUMY_KEY']}"
881
+ }
882
+
883
+ response_url = requests.post(
884
+ "https://tu.my/api/v1/upload",
885
+ files=files,
886
+ headers=headers
887
+ )
888
+
889
+ if response_url.status_code != 200:
890
+ return "生图失败,请查看TUMY图床密钥是否设置正确"
891
+ else:
892
+ try:
893
+ result = response_url.json()
894
+ logger.info("生图成功", "Server")
895
+ return f"![image]({result['data']['links']['url']})"
896
+ except Exception as error:
897
+ logger.error(str(error), "Server")
898
+ return "生图失败,请查看TUMY图床密钥是否设置正确"
899
+
900
+
901
+ def handle_non_stream_response(response, model):
902
+ try:
903
+ logger.info("开始处理非流式响应", "Server")
904
+
905
+ stream = response.iter_lines()
906
+ full_response = ""
907
+
908
+ CONFIG["IS_THINKING"] = False
909
+ CONFIG["IS_IMG_GEN"] = False
910
+ CONFIG["IS_IMG_GEN2"] = False
911
+
912
+ for chunk in stream:
913
+ if not chunk:
914
+ continue
915
+ try:
916
+ line_json = json.loads(chunk.decode("utf-8").strip())
917
+ if line_json.get("error"):
918
+ logger.error(json.dumps(line_json, indent=2), "Server")
919
+ return json.dumps({"error": "RateLimitError"}) + "\n\n"
920
+
921
+ response_data = line_json.get("result", {}).get("response")
922
+ if not response_data:
923
+ continue
924
+
925
+ if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
926
+ CONFIG["IS_IMG_GEN"] = True
927
+
928
+ result = process_model_response(response_data, model)
929
+
930
+ if result["token"]:
931
+ full_response += result["token"]
932
+
933
+ if result["imageUrl"]:
934
+ CONFIG["IS_IMG_GEN2"] = True
935
+ return handle_image_response(result["imageUrl"])
936
+
937
+ except json.JSONDecodeError:
938
+ continue
939
+ except Exception as e:
940
+ logger.error(f"处理流式响应行时出错: {str(e)}", "Server")
941
+ continue
942
+
943
+ return full_response
944
+ except Exception as error:
945
+ logger.error(str(error), "Server")
946
+ raise
947
+
948
+
949
+ def handle_stream_response(response, model):
950
+ def generate():
951
+ logger.info("开始处理流式响应", "Server")
952
+
953
+ stream = response.iter_lines()
954
+ CONFIG["IS_THINKING"] = False
955
+ CONFIG["IS_IMG_GEN"] = False
956
+ CONFIG["IS_IMG_GEN2"] = False
957
+
958
+ for chunk in stream:
959
+ if not chunk:
960
+ continue
961
+ try:
962
+ line_json = json.loads(chunk.decode("utf-8").strip())
963
+ if line_json.get("error"):
964
+ logger.error(json.dumps(line_json, indent=2), "Server")
965
+ yield json.dumps({"error": "RateLimitError"}) + "\n\n"
966
+ return
967
+
968
+ response_data = line_json.get("result", {}).get("response")
969
+ if not response_data:
970
+ continue
971
+
972
+ if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
973
+ CONFIG["IS_IMG_GEN"] = True
974
+
975
+ result = process_model_response(response_data, model)
976
+
977
+ if result["token"]:
978
+ yield f"data: {json.dumps(MessageProcessor.create_chat_response(result['token'], model, True))}\n\n"
979
+
980
+ if result["imageUrl"]:
981
+ CONFIG["IS_IMG_GEN2"] = True
982
+ image_data = handle_image_response(result["imageUrl"])
983
+ yield f"data: {json.dumps(MessageProcessor.create_chat_response(image_data, model, True))}\n\n"
984
+
985
+ except json.JSONDecodeError:
986
+ continue
987
+ except Exception as e:
988
+ logger.error(f"处理流式响应行时出错: {str(e)}", "Server")
989
+ continue
990
+
991
+ yield "data: [DONE]\n\n"
992
+
993
+ return generate()
994
+
995
+
996
+ def initialization():
997
+ sso_array = os.getenv("SSO", "").split(',')
998
+ logger.info("开始加载令牌", "Server")
999
+ for sso in sso_array:
1000
+ if sso:
1001
+ token_manager.add_token(f"sso-rw={sso};sso={sso}")
1002
+
1003
+ logger.info(f"成功加载令牌: {json.dumps(token_manager.get_all_tokens(), indent=2)}", "Server")
1004
+ logger.info(f"令牌加载完成,共加载: {len(token_manager.get_all_tokens())}个令牌", "Server")
1005
+
1006
+ if CONFIG["API"]["PROXY"]:
1007
+ logger.info(f"代理已设置: {CONFIG['API']['PROXY']}", "Server")
1008
+
1009
+
1010
+ logger.info("初始化完成", "Server")
1011
+
1012
+
1013
+ app = Flask(__name__)
1014
+ app.wsgi_app = ProxyFix(app.wsgi_app)
1015
+ app.secret_key = os.getenv('FLASK_SECRET_KEY') or secrets.token_hex(16)
1016
+ app.json.sort_keys = False
1017
+
1018
+
1019
+ @app.route('/manager/login', methods=['GET', 'POST'])
1020
+ def manager_login():
1021
+ if CONFIG["ADMIN"]["MANAGER_SWITCH"]:
1022
+ if request.method == 'POST':
1023
+ password = request.form.get('password')
1024
+ if password == CONFIG["ADMIN"]["PASSWORD"]:
1025
+ session['is_logged_in'] = True
1026
+ return redirect('/manager')
1027
+ return render_template('login.html', error=True)
1028
+ return render_template('login.html', error=False)
1029
+ else:
1030
+ return redirect('/')
1031
+
1032
+
1033
+ def check_auth():
1034
+ return session.get('is_logged_in', False)
1035
+
1036
+
1037
+ @app.route('/manager')
1038
+ def manager():
1039
+ if not check_auth():
1040
+ return redirect('/manager/login')
1041
+ return render_template('manager.html')
1042
+
1043
+
1044
+ @app.route('/manager/api/xids',methods=['POST'])
1045
+ def set_xid():
1046
+ auth = request.headers.get('Authorization')
1047
+ if auth is not None:
1048
+ auth = auth.replace('Bearer ', '')
1049
+ if auth != CONFIG["ADMIN"]["PASSWORD"]:
1050
+ return jsonify({"error": "Unauthorized"}), 401
1051
+
1052
+ CONFIG["XIDS"] = request.json
1053
+ logger.info(f"get XIDS: {CONFIG['XIDS']}", "Server")
1054
+ return jsonify({"success": True})
1055
+
1056
+ @app.route('/manager/api/get')
1057
+ def get_manager_tokens():
1058
+ if not check_auth():
1059
+ return jsonify({"error": "Unauthorized"}), 401
1060
+ return jsonify(token_manager.get_token_status_map())
1061
+
1062
+
1063
+ @app.route('/manager/api/add', methods=['POST'])
1064
+ def add_manager_token():
1065
+ if not check_auth():
1066
+ return jsonify({"error": "Unauthorized"}), 401
1067
+ try:
1068
+ sso = request.json.get('sso')
1069
+ if not sso:
1070
+ return jsonify({"error": "SSO token is required"}), 400
1071
+ token_manager.add_token(f"sso-rw={sso};sso={sso}")
1072
+ return jsonify({"success": True})
1073
+ except Exception as e:
1074
+ return jsonify({"error": str(e)}), 500
1075
+
1076
+
1077
+ @app.route('/manager/api/delete', methods=['POST'])
1078
+ def delete_manager_token():
1079
+ if not check_auth():
1080
+ return jsonify({"error": "Unauthorized"}), 401
1081
+ try:
1082
+ sso = request.json.get('sso')
1083
+ if not sso:
1084
+ return jsonify({"error": "SSO token is required"}), 400
1085
+ token_manager.delete_token(f"sso-rw={sso};sso={sso}")
1086
+ return jsonify({"success": True})
1087
+ except Exception as e:
1088
+ return jsonify({"error": str(e)}), 500
1089
+
1090
+
1091
+ @app.route('/manager/api/cf_clearance', methods=['POST'])
1092
+ def setCf_Manager_clearance():
1093
+ if not check_auth():
1094
+ return jsonify({"error": "Unauthorized"}), 401
1095
+ try:
1096
+ cf_clearance = request.json.get('cf_clearance')
1097
+ if not cf_clearance:
1098
+ return jsonify({"error": "cf_clearance is required"}), 400
1099
+ CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance
1100
+ return jsonify({"success": True})
1101
+ except Exception as e:
1102
+ return jsonify({"error": str(e)}), 500
1103
+
1104
+
1105
+ @app.route('/get/tokens', methods=['GET'])
1106
+ def get_tokens():
1107
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
1108
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1109
+ return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 403
1110
+ elif auth_token != CONFIG["API"]["API_KEY"]:
1111
+ return jsonify({"error": 'Unauthorized'}), 401
1112
+ return jsonify(token_manager.get_token_status_map())
1113
+
1114
+
1115
+ @app.route('/add/token', methods=['POST'])
1116
+ def add_token():
1117
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
1118
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1119
+ return jsonify({"error": '自定义的SSO令牌模式无法添加sso令牌'}), 403
1120
+ elif auth_token != CONFIG["API"]["API_KEY"]:
1121
+ return jsonify({"error": 'Unauthorized'}), 401
1122
+
1123
+ try:
1124
+ sso = request.json.get('sso')
1125
+ token_manager.add_token(f"sso-rw={sso};sso={sso}")
1126
+ return jsonify(token_manager.get_token_status_map().get(sso, {})), 200
1127
+ except Exception as error:
1128
+ logger.error(str(error), "Server")
1129
+ return jsonify({"error": '添加sso令牌失败'}), 500
1130
+
1131
+
1132
+ @app.route('/set/cf_clearance', methods=['POST'])
1133
+ def setCf_clearance():
1134
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
1135
+ if auth_token != CONFIG["API"]["API_KEY"]:
1136
+ return jsonify({"error": 'Unauthorized'}), 401
1137
+ try:
1138
+ cf_clearance = request.json.get('cf_clearance')
1139
+ CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance
1140
+ return jsonify({"message": '设置cf_clearance成功'}), 200
1141
+ except Exception as error:
1142
+ logger.error(str(error), "Server")
1143
+ return jsonify({"error": '设置cf_clearance失败'}), 500
1144
+
1145
+
1146
+ @app.route('/delete/token', methods=['POST'])
1147
+ def delete_token():
1148
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
1149
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1150
+ return jsonify({"error": '自定义的SSO令牌模式无法删除sso令牌'}), 403
1151
+ elif auth_token != CONFIG["API"]["API_KEY"]:
1152
+ return jsonify({"error": 'Unauthorized'}), 401
1153
+
1154
+ try:
1155
+ sso = request.json.get('sso')
1156
+ token_manager.delete_token(f"sso-rw={sso};sso={sso}")
1157
+ return jsonify({"message": '删除sso令牌成功'}), 200
1158
+ except Exception as error:
1159
+ logger.error(str(error), "Server")
1160
+ return jsonify({"error": '删除sso令牌失败'}), 500
1161
+
1162
+
1163
+ @app.route('/v1/models', methods=['GET'])
1164
+ def get_models():
1165
+ return jsonify({
1166
+ "object": "list",
1167
+ "data": [
1168
+ {
1169
+ "id": model,
1170
+ "object": "model",
1171
+ "created": int(time.time()),
1172
+ "owned_by": "grok"
1173
+ }
1174
+ for model in CONFIG["MODELS"].keys()
1175
+ ]
1176
+ })
1177
+
1178
+
1179
+ @app.route('/v1/chat/completions', methods=['POST'])
1180
+ def chat_completions():
1181
+ response_status_code = 500
1182
+ try:
1183
+ auth_token = request.headers.get('Authorization',
1184
+ '').replace('Bearer ', '')
1185
+ if auth_token:
1186
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1187
+ result = f"sso={auth_token};sso-rw={auth_token}"
1188
+ token_manager.set_token(result)
1189
+ elif auth_token != CONFIG["API"]["API_KEY"]:
1190
+ return jsonify({"error": 'Unauthorized'}), 401
1191
+ else:
1192
+ return jsonify({"error": 'API_KEY缺失'}), 401
1193
+
1194
+ data = request.json
1195
+ model = data.get("model")
1196
+ stream = data.get("stream", False)
1197
+
1198
+ retry_count = 0
1199
+ grok_client = GrokApiClient(model)
1200
+ request_payload = grok_client.prepare_chat_request(data)
1201
+
1202
+ while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:
1203
+ retry_count += 1
1204
+ CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(model)
1205
+
1206
+ if not CONFIG["API"]["SIGNATURE_COOKIE"]:
1207
+ raise ValueError('该模型无可用令牌')
1208
+
1209
+ logger.info(
1210
+ f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}", "Server")
1211
+ logger.info(
1212
+ f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}",
1213
+ "Server")
1214
+
1215
+ if CONFIG['SERVER']['CF_CLEARANCE']:
1216
+ CONFIG["SERVER"]['COOKIE'] = f"{CONFIG['API']['SIGNATURE_COOKIE']};{CONFIG['SERVER']['CF_CLEARANCE']}"
1217
+ else:
1218
+ CONFIG["SERVER"]['COOKIE'] = CONFIG['API']['SIGNATURE_COOKIE']
1219
+ logger.info("有请求到来", "Server")
1220
+ if CONFIG['XIDS'] is not None:
1221
+ DEFAULT_HEADERS['x-xai-request-id'] = CONFIG['XIDS']['x-xai-request-id']
1222
+ DEFAULT_HEADERS['x-statsig-id'] = CONFIG['XIDS']['x-statsig-id']
1223
+ DEFAULT_HEADERS['User-Agent'] = CONFIG['XIDS']['user-agent']
1224
+
1225
+ try:
1226
+ proxy_options = Utils.get_proxy_options()
1227
+ response = curl_requests.post(
1228
+ f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",
1229
+ headers={
1230
+ **DEFAULT_HEADERS,
1231
+ "Cookie": CONFIG["SERVER"]['COOKIE']
1232
+ },
1233
+ json=request_payload,
1234
+ impersonate="chrome133a",
1235
+ verify=False,
1236
+ stream=True,
1237
+ **proxy_options)
1238
+ logger.info(CONFIG["SERVER"]['COOKIE'], "Server")
1239
+ if response.status_code == 200:
1240
+ response_status_code = 200
1241
+ logger.info("请求成功", "Server")
1242
+ logger.info(f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
1243
+ "Server")
1244
+
1245
+ try:
1246
+ if stream:
1247
+ return Response(stream_with_context(
1248
+ handle_stream_response(response, model)), content_type='text/event-stream')
1249
+ else:
1250
+ content = handle_non_stream_response(response, model)
1251
+ return jsonify(
1252
+ MessageProcessor.create_chat_response(content, model))
1253
+
1254
+ except Exception as error:
1255
+ logger.error(str(error), "Server")
1256
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1257
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1258
+ token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
1259
+ if token_manager.get_token_count_for_model(model) == 0:
1260
+ raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1261
+ elif response.status_code == 403:
1262
+ response_status_code = 403
1263
+ token_manager.reduce_token_request_count(model, 1) # 重置去除当前因为错误未成功请求的次数,确保不会因为错误未成功请求的次数导致次数上限
1264
+ if token_manager.get_token_count_for_model(model) == 0:
1265
+ raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1266
+ raise ValueError(f"IP暂时被封无法破盾,请稍后重试或者更换ip")
1267
+ elif response.status_code == 429:
1268
+ response_status_code = 429
1269
+ token_manager.reduce_token_request_count(model, 1)
1270
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1271
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1272
+
1273
+ token_manager.remove_token_from_model(
1274
+ model, CONFIG["API"]["SIGNATURE_COOKIE"])
1275
+ if token_manager.get_token_count_for_model(model) == 0:
1276
+ raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1277
+
1278
+ else:
1279
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1280
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1281
+
1282
+ logger.error(f"令牌异常错误状态!status: {response.status_code}", "Server")
1283
+ token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
1284
+ logger.info(
1285
+ f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
1286
+ "Server")
1287
+
1288
+ except Exception as e:
1289
+ logger.error(f"请求处理异常: {str(e)}", "Server")
1290
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1291
+ raise
1292
+ continue
1293
+ if response_status_code == 403:
1294
+ raise ValueError('IP暂时被封无法破盾,请稍后重试或者更换ip')
1295
+ elif response_status_code == 500:
1296
+ raise ValueError('当前模型所有令牌暂无可用,请稍后重试')
1297
+
1298
+ except Exception as error:
1299
+ logger.error(str(error), "ChatAPI")
1300
+ return jsonify(
1301
+ {"error": {
1302
+ "message": str(error),
1303
+ "type": "server_error"
1304
+ }}), response_status_code
1305
+
1306
+
1307
+ @app.route('/', defaults={'path': ''})
1308
+ @app.route('/<path:path>')
1309
+ def catch_all(path):
1310
+ return 'api运行正常', 200
1311
+
1312
+
1313
+ if __name__ == '__main__':
1314
+ token_manager = AuthTokenManager()
1315
+ initialization()
1316
+
1317
+ app.run(
1318
+ host='0.0.0.0',
1319
+ port=CONFIG["SERVER"]["PORT"],
1320
+ debug=False
1321
+ )
get_xid.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from playwright.sync_api import sync_playwright
2
+
3
+ # 用于存储捕获到的请求头信息
4
+ all_request_headers_info = []
5
+
6
+
7
+ def handle_request(request):
8
+ """
9
+ 捕获每个请求的URL, 方法和头部信息
10
+ """
11
+ # print(f"Intercepted request to: {request.url}") # 调试时可以取消注释
12
+ all_request_headers_info.append({
13
+ "url": request.url,
14
+ "method": request.method,
15
+ "headers": request.headers # request.headers 是一个字典
16
+ })
17
+
18
+
19
+ def main():
20
+ with sync_playwright() as p:
21
+ # 启动浏览器,可以是 chromium, firefox, or webkit
22
+ # headless=False 可以看到浏览器操作,True则为无头模式
23
+ browser = p.chromium.launch(headless=True,
24
+ args=[
25
+ '--no-sandbox',
26
+ '--disable-setuid-sandbox',
27
+ '--disable-dev-shm-usage' # 有时也需要这个,但 --shm-size 更好
28
+ ])
29
+
30
+ # 创建一个新的浏览器上下文
31
+ # 可以在这里设置 user_agent, viewport, etc.
32
+ context = browser.new_context(
33
+ user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0",
34
+ )
35
+
36
+ # 在上下文中创建一个新页面
37
+ page = context.new_page()
38
+
39
+ # 注册请求拦截器,这必须在导航之前完成
40
+ # 'request' 事件会在每个HTTP请求发起时触发
41
+ page.on("request", handle_request)
42
+
43
+ print(f"Navigating to https://grok.com/ ...")
44
+ try:
45
+ # 访问目标网站,设置一个合理的超时时间(例如60秒)
46
+ page.goto("https://grok.com/", timeout=60000)
47
+ page.wait_for_timeout(5000)
48
+ print("Page loaded. Waiting for 10 seconds for dynamic content or further requests...")
49
+
50
+ # 检查是否仍然被 Cloudflare 阻止 (例如,查找特定的标题或元素)
51
+ title = page.title()
52
+ print(f"Page title: {title}")
53
+
54
+ if "请稍候…" in page.content() or "Just a moment..." in page.content() or "Cloudflare" in title or "Checking your browser" in title:
55
+ print("Still on a Cloudflare challenge page. Waiting longer or trying interaction...")
56
+ # 你可能需要在这里添加更长的等待或模拟用户交互
57
+ # 例如,等待特定的元素出现,表明挑战已通过
58
+ try:
59
+ page.wait_for_selector("body:not(:has-text('请稍候…'))", timeout=60000)
60
+ print("Cloudflare challenge likely passed.")
61
+ title = page.title()
62
+ print(f"New page title: {title}")
63
+ page.screenshot(path="cf_passed.png")
64
+ except Exception as e:
65
+ print(f"Failed to pass Cloudflare challenge after extended wait: {e}")
66
+ page.screenshot(path="cf_failed.png")
67
+ else:
68
+ print("Successfully navigated to the page.")
69
+ page.screenshot(path="cf_success.png")
70
+
71
+ page.evaluate("""
72
+ function(){
73
+ const element = document.getElementById('turnstile-widget');
74
+ if (element) {
75
+ element.style.display = 'none';
76
+ }
77
+ }
78
+ """)
79
+ page.wait_for_timeout(10000)
80
+
81
+ try:
82
+ textarea_locator = page.get_by_label("Ask Grok anything")
83
+ textarea_locator.fill("你好")
84
+ print("Successfully entered '你好' into the textarea.")
85
+ except Exception as e:
86
+ print(f"Could not find or fill the textarea with aria-label 'Ask Grok anything'. Error: {e}")
87
+ browser.close()
88
+ return
89
+
90
+ # 2. 查找 aria-label 为“提交”的 button 并点击
91
+ # 使用 get_by_role('button', name='...') 是 Playwright 推荐的方式来查找具有特定可访问名称的按钮
92
+ try:
93
+ submit_button_locator = page.get_by_role("button", name="Submit")
94
+ submit_button_locator.click()
95
+ print("Successfully clicked the 'Submit' button.")
96
+ except Exception as e:
97
+ print(f"Could not find or click the button with aria-label 'Submit'. Error: {e}")
98
+ browser.close()
99
+ return
100
+
101
+ # 等待10秒
102
+ # Playwright 的 page.wait_for_timeout() 是首选,因为它与Playwright的事件循环集成
103
+ # page.wait_for_timeout(10000)
104
+ # 或者使用 time.sleep(10) 也可以,但在Playwright脚本中前者更佳
105
+
106
+ print("\n--- Cookies ---")
107
+ # 获取当前上下文中的所有cookies
108
+ cookies = context.cookies()
109
+ if cookies:
110
+ for cookie in cookies:
111
+ print(
112
+ f"Name: {cookie['name']}, Value: {cookie['value']}, Domain: {cookie['domain']}, Path: {cookie['path']}")
113
+ else:
114
+ print("No cookies found.")
115
+
116
+ print("\n--- Request Headers (collected during the session) ---")
117
+ if all_request_headers_info:
118
+ # 打印捕获到的每个请求的头部信息
119
+ # 注意:这里会包含所有资源的请求(HTML, CSS, JS, XHR, 图片等)
120
+ for i, req_info in enumerate(all_request_headers_info):
121
+ if req_info['url'] == 'https://grok.com/rest/app-chat/conversations/new':
122
+ datas = {
123
+ 'x-xai-request-id': req_info['headers']['x-xai-request-id'],
124
+ 'x-statsig-id': req_info['headers']['x-statsig-id'],
125
+ 'user-agent': req_info['headers']['user-agent'],
126
+ }
127
+ print(datas)
128
+ return datas
129
+ else:
130
+ print("No requests were intercepted (this is unlikely if the page loaded).")
131
+
132
+ except Exception as e:
133
+ print(f"An error occurred: {e}")
134
+ finally:
135
+ # 确保浏览器关闭
136
+ print("\nClosing browser...")
137
+ page.close()
138
+ browser.close()
139
+ return None
140
+
141
+
142
+ if __name__ == "__main__":
143
+ main()