keungliang commited on
Commit
7ac534e
·
verified ·
1 Parent(s): a62d2bc

Upload 2 files

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