dfa32412 commited on
Commit
0e7fef8
·
verified ·
1 Parent(s): 9931235

Update app.py

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