Jones0 commited on
Commit
8f82829
·
verified ·
1 Parent(s): f3d3ef3

Create app.py

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