usbtoo commited on
Commit
f87708d
·
verified ·
1 Parent(s): 418daf7

Create app.py

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