bluewinliang commited on
Commit
5c6e20b
·
verified ·
1 Parent(s): 0f8f720

Upload 3 files

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