yxmiler commited on
Commit
1fa635a
·
verified ·
1 Parent(s): d4f7fa5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -181
app.py CHANGED
@@ -26,8 +26,8 @@ class Logger:
26
  )
27
 
28
  logger.add(
29
- sys.stderr,
30
- level=level,
31
  format=format,
32
  colorize=colorize,
33
  backtrace=True,
@@ -43,33 +43,33 @@ class Logger:
43
  full_path = caller_frame.f_code.co_filename
44
  function = caller_frame.f_code.co_name
45
  lineno = caller_frame.f_lineno
46
-
47
  filename = os.path.basename(full_path)
48
-
49
  return {
50
  'filename': filename,
51
  'function': function,
52
  'lineno': lineno
53
  }
54
  finally:
55
- del frame
56
 
57
  def info(self, message, source="API"):
58
  caller_info = self._get_caller_info()
59
  self.logger.bind(**caller_info).info(f"[{source}] {message}")
60
-
61
  def error(self, message, source="API"):
62
  caller_info = self._get_caller_info()
63
-
64
  if isinstance(message, Exception):
65
  self.logger.bind(**caller_info).exception(f"[{source}] {str(message)}")
66
  else:
67
  self.logger.bind(**caller_info).error(f"[{source}] {message}")
68
-
69
  def warning(self, message, source="API"):
70
  caller_info = self._get_caller_info()
71
  self.logger.bind(**caller_info).warning(f"[{source}] {message}")
72
-
73
  def debug(self, message, source="API"):
74
  caller_info = self._get_caller_info()
75
  self.logger.bind(**caller_info).debug(f"[{source}] {message}")
@@ -124,7 +124,10 @@ DEFAULT_HEADERS = {
124
  'Connection': 'keep-alive',
125
  'Origin': 'https://grok.com',
126
  'Priority': 'u=1, i',
 
 
127
  'Sec-Ch-Ua-Mobile': '?0',
 
128
  'Sec-Fetch-Dest': 'empty',
129
  'Sec-Fetch-Mode': 'cors',
130
  'Sec-Fetch-Site': 'same-origin',
@@ -165,9 +168,9 @@ class AuthTokenManager:
165
  self.token_model_map[model] = []
166
  if sso not in self.token_status_map:
167
  self.token_status_map[sso] = {}
168
-
169
  existing_token_entry = next((entry for entry in self.token_model_map[model] if entry["token"] == token), None)
170
-
171
  if not existing_token_entry:
172
  self.token_model_map[model].append({
173
  "token": token,
@@ -175,14 +178,14 @@ class AuthTokenManager:
175
  "AddedTime": int(time.time() * 1000),
176
  "StartCallTime": None
177
  })
178
-
179
  if model not in self.token_status_map[sso]:
180
  self.token_status_map[sso][model] = {
181
  "isValid": True,
182
  "invalidatedTime": None,
183
  "totalRequestCount": 0
184
  }
185
-
186
  def set_token(self, token):
187
  models = list(self.model_config.keys())
188
  self.token_model_map = {model: [{
@@ -191,73 +194,73 @@ class AuthTokenManager:
191
  "AddedTime": int(time.time() * 1000),
192
  "StartCallTime": None
193
  }] for model in models}
194
-
195
  sso = token.split("sso=")[1].split(";")[0]
196
  self.token_status_map[sso] = {model: {
197
  "isValid": True,
198
  "invalidatedTime": None,
199
  "totalRequestCount": 0
200
  } for model in models}
201
-
202
  def delete_token(self, token):
203
  try:
204
  sso = token.split("sso=")[1].split(";")[0]
205
  for model in self.token_model_map:
206
  self.token_model_map[model] = [entry for entry in self.token_model_map[model] if entry["token"] != token]
207
-
208
  if sso in self.token_status_map:
209
  del self.token_status_map[sso]
210
-
211
  logger.info(f"令牌已成功移除: {token}", "TokenManager")
212
  return True
213
  except Exception as error:
214
  logger.error(f"令牌删除失败: {str(error)}")
215
  return False
216
-
217
  def get_next_token_for_model(self, model_id):
218
  normalized_model = self.normalize_model_name(model_id)
219
-
220
  if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
221
  return None
222
-
223
  token_entry = self.token_model_map[normalized_model][0]
224
-
225
  if token_entry:
226
  if token_entry["StartCallTime"] is None:
227
  token_entry["StartCallTime"] = int(time.time() * 1000)
228
-
229
  if not self.token_reset_switch:
230
  self.start_token_reset_process()
231
  self.token_reset_switch = True
232
-
233
  token_entry["RequestCount"] += 1
234
-
235
  if token_entry["RequestCount"] > self.model_config[normalized_model]["RequestFrequency"]:
236
  self.remove_token_from_model(normalized_model, token_entry["token"])
237
  next_token_entry = self.token_model_map[normalized_model][0] if self.token_model_map[normalized_model] else None
238
  return next_token_entry["token"] if next_token_entry else None
239
-
240
  sso = token_entry["token"].split("sso=")[1].split(";")[0]
241
  if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:
242
  if token_entry["RequestCount"] == self.model_config[normalized_model]["RequestFrequency"]:
243
  self.token_status_map[sso][normalized_model]["isValid"] = False
244
  self.token_status_map[sso][normalized_model]["invalidatedTime"] = int(time.time() * 1000)
245
  self.token_status_map[sso][normalized_model]["totalRequestCount"] += 1
246
-
247
  return token_entry["token"]
248
-
249
  return None
250
-
251
  def remove_token_from_model(self, model_id, token):
252
  normalized_model = self.normalize_model_name(model_id)
253
-
254
  if normalized_model not in self.token_model_map:
255
  logger.error(f"模型 {normalized_model} 不存在", "TokenManager")
256
  return False
257
-
258
  model_tokens = self.token_model_map[normalized_model]
259
  token_index = next((i for i, entry in enumerate(model_tokens) if entry["token"] == token), -1)
260
-
261
  if token_index != -1:
262
  removed_token_entry = model_tokens.pop(token_index)
263
  self.expired_tokens.add((
@@ -265,86 +268,86 @@ class AuthTokenManager:
265
  normalized_model,
266
  int(time.time() * 1000)
267
  ))
268
-
269
  if not self.token_reset_switch:
270
  self.start_token_reset_process()
271
  self.token_reset_switch = True
272
-
273
  logger.info(f"模型{model_id}的令牌已失效,已成功移除令牌: {token}", "TokenManager")
274
  return True
275
-
276
  logger.error(f"在模型 {normalized_model} 中未找到 token: {token}", "TokenManager")
277
  return False
278
-
279
  def get_expired_tokens(self):
280
  return list(self.expired_tokens)
281
-
282
  def normalize_model_name(self, model):
283
  if model.startswith('grok-') and 'deepsearch' not in model and 'reasoning' not in model:
284
  return '-'.join(model.split('-')[:2])
285
  return model
286
-
287
  def get_token_count_for_model(self, model_id):
288
  normalized_model = self.normalize_model_name(model_id)
289
  return len(self.token_model_map.get(normalized_model, []))
290
-
291
  def get_remaining_token_request_capacity(self):
292
  remaining_capacity_map = {}
293
-
294
  for model in self.model_config.keys():
295
  model_tokens = self.token_model_map.get(model, [])
296
  model_request_frequency = self.model_config[model]["RequestFrequency"]
297
-
298
  total_used_requests = sum(token_entry.get("RequestCount", 0) for token_entry in model_tokens)
299
-
300
  remaining_capacity = (len(model_tokens) * model_request_frequency) - total_used_requests
301
  remaining_capacity_map[model] = max(0, remaining_capacity)
302
-
303
  return remaining_capacity_map
304
-
305
  def get_token_array_for_model(self, model_id):
306
  normalized_model = self.normalize_model_name(model_id)
307
  return self.token_model_map.get(normalized_model, [])
308
-
309
  def start_token_reset_process(self):
310
  def reset_expired_tokens():
311
  now = int(time.time() * 1000)
312
-
313
  tokens_to_remove = set()
314
  for token_info in self.expired_tokens:
315
  token, model, expired_time = token_info
316
  expiration_time = self.model_config[model]["ExpirationTime"]
317
-
318
  if now - expired_time >= expiration_time:
319
  if not any(entry["token"] == token for entry in self.token_model_map.get(model, [])):
320
  if model not in self.token_model_map:
321
  self.token_model_map[model] = []
322
-
323
  self.token_model_map[model].append({
324
  "token": token,
325
  "RequestCount": 0,
326
  "AddedTime": now,
327
  "StartCallTime": None
328
  })
329
-
330
  sso = token.split("sso=")[1].split(";")[0]
331
  if sso in self.token_status_map and model in self.token_status_map[sso]:
332
  self.token_status_map[sso][model]["isValid"] = True
333
  self.token_status_map[sso][model]["invalidatedTime"] = None
334
  self.token_status_map[sso][model]["totalRequestCount"] = 0
335
-
336
  tokens_to_remove.add(token_info)
337
-
338
  self.expired_tokens -= tokens_to_remove
339
-
340
  for model in self.model_config.keys():
341
  if model not in self.token_model_map:
342
  continue
343
-
344
  for token_entry in self.token_model_map[model]:
345
  if not token_entry.get("StartCallTime"):
346
  continue
347
-
348
  expiration_time = self.model_config[model]["ExpirationTime"]
349
  if now - token_entry["StartCallTime"] >= expiration_time:
350
  sso = token_entry["token"].split("sso=")[1].split(";")[0]
@@ -352,67 +355,67 @@ class AuthTokenManager:
352
  self.token_status_map[sso][model]["isValid"] = True
353
  self.token_status_map[sso][model]["invalidatedTime"] = None
354
  self.token_status_map[sso][model]["totalRequestCount"] = 0
355
-
356
  token_entry["RequestCount"] = 0
357
  token_entry["StartCallTime"] = None
358
-
359
  import threading
360
  # 启动一个线程执行定时任务,每小时执行一次
361
  def run_timer():
362
  while True:
363
  reset_expired_tokens()
364
  time.sleep(3600)
365
-
366
  timer_thread = threading.Thread(target=run_timer)
367
  timer_thread.daemon = True
368
  timer_thread.start()
369
-
370
  def get_all_tokens(self):
371
  all_tokens = set()
372
  for model_tokens in self.token_model_map.values():
373
  for entry in model_tokens:
374
  all_tokens.add(entry["token"])
375
  return list(all_tokens)
376
-
377
  def get_token_status_map(self):
378
  return self.token_status_map
379
-
380
  class Utils:
381
  @staticmethod
382
  def organize_search_results(search_results):
383
  if not search_results or 'results' not in search_results:
384
  return ''
385
-
386
  results = search_results['results']
387
  formatted_results = []
388
-
389
  for index, result in enumerate(results):
390
  title = result.get('title', '未知标题')
391
  url = result.get('url', '#')
392
  preview = result.get('preview', '无预览内容')
393
-
394
  formatted_result = f"\r\n<details><summary>资料[{index}]: {title}</summary>\r\n{preview}\r\n\n[Link]({url})\r\n</details>"
395
  formatted_results.append(formatted_result)
396
-
397
  return '\n\n'.join(formatted_results)
398
-
399
  @staticmethod
400
  def create_auth_headers(model):
401
  return token_manager.get_next_token_for_model(model)
402
-
403
  @staticmethod
404
  def get_proxy_options():
405
  proxy = CONFIG["API"]["PROXY"]
406
  proxy_options = {}
407
-
408
  if proxy:
409
  logger.info(f"使用代理: {proxy}", "Server")
410
  proxy_options["proxies"] = {"https": proxy, "http": proxy}
411
-
412
  if proxy.startswith("socks5://"):
413
  proxy_options["proxies"] = {"https": proxy, "http": proxy}
414
  proxy_options["proxy_type"] = "socks5"
415
-
416
  return proxy_options
417
 
418
  class GrokApiClient:
@@ -420,12 +423,12 @@ class GrokApiClient:
420
  if model_id not in CONFIG["MODELS"]:
421
  raise ValueError(f"不支持的模型: {model_id}")
422
  self.model_id = CONFIG["MODELS"][model_id]
423
-
424
  def process_message_content(self, content):
425
  if isinstance(content, str):
426
  return content
427
  return None
428
-
429
  def get_image_type(self, base64_string):
430
  mime_type = 'image/jpeg'
431
  if 'data:image' in base64_string:
@@ -433,26 +436,26 @@ class GrokApiClient:
433
  matches = re.search(r'data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,', base64_string)
434
  if matches:
435
  mime_type = matches.group(1)
436
-
437
  extension = mime_type.split('/')[1]
438
  file_name = f"image.{extension}"
439
-
440
  return {
441
  "mimeType": mime_type,
442
  "fileName": file_name
443
  }
444
-
445
  def upload_base64_image(self, base64_data, url):
446
  try:
447
  if 'data:image' in base64_data:
448
  image_buffer = base64_data.split(',')[1]
449
  else:
450
  image_buffer = base64_data
451
-
452
  image_info = self.get_image_type(base64_data)
453
  mime_type = image_info["mimeType"]
454
  file_name = image_info["fileName"]
455
-
456
  upload_data = {
457
  "rpc": "uploadFile",
458
  "req": {
@@ -461,9 +464,9 @@ class GrokApiClient:
461
  "content": image_buffer
462
  }
463
  }
464
-
465
  logger.info("发送图片请求", "Server")
466
-
467
  proxy_options = Utils.get_proxy_options()
468
  response = curl_requests.post(
469
  url,
@@ -472,48 +475,48 @@ class GrokApiClient:
472
  "Cookie": CONFIG["API"]["SIGNATURE_COOKIE"]
473
  },
474
  json=upload_data,
475
- impersonate="chrome120",
476
  **proxy_options
477
  )
478
-
479
  if response.status_code != 200:
480
  logger.error(f"上传图片失败,状态码:{response.status_code}", "Server")
481
  return ''
482
-
483
  result = response.json()
484
  logger.info(f"上传图片成功: {result}", "Server")
485
  return result.get("fileMetadataId", "")
486
-
487
  except Exception as error:
488
  logger.error(str(error), "Server")
489
  return ''
490
-
491
  def prepare_chat_request(self, request):
492
- if ((request["model"] == 'grok-2-imageGen' or request["model"] == 'grok-3-imageGen') and
493
- not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"] and
494
  request.get("stream", False)):
495
  raise ValueError("该模型流式输出需要配置PICGO或者TUMY图床密钥!")
496
-
497
  todo_messages = request["messages"]
498
  if request["model"] in ['grok-2-imageGen', 'grok-3-imageGen', 'grok-3-deepsearch']:
499
  last_message = todo_messages[-1]
500
  if last_message["role"] != 'user':
501
  raise ValueError('此模型最后一条消息必须是用户消息!')
502
  todo_messages = [last_message]
503
-
504
  file_attachments = []
505
  messages = ''
506
  last_role = None
507
  last_content = ''
508
  search = request["model"] in ['grok-2-search', 'grok-3-search']
509
-
510
  # 移除<think>标签及其内容和base64图片
511
  def remove_think_tags(text):
512
  import re
513
  text = re.sub(r'<think>[\s\S]*?<\/think>', '', text).strip()
514
  text = re.sub(r'!\[image\]\(data:.*?base64,.*?\)', '[图片]', text)
515
  return text
516
-
517
  def process_content(content):
518
  if isinstance(content, list):
519
  text_content = ''
@@ -529,11 +532,11 @@ class GrokApiClient:
529
  elif content["type"] == 'text':
530
  return remove_think_tags(content["text"])
531
  return remove_think_tags(self.process_message_content(content))
532
-
533
  for current in todo_messages:
534
  role = 'assistant' if current["role"] == 'assistant' else 'user'
535
  is_last_message = current == todo_messages[-1]
536
-
537
  if is_last_message and "content" in current:
538
  if isinstance(current["content"], list):
539
  for item in current["content"]:
@@ -551,10 +554,10 @@ class GrokApiClient:
551
  )
552
  if processed_image:
553
  file_attachments.append(processed_image)
554
-
555
 
556
  text_content = process_content(current.get("content", ""))
557
-
558
  if text_content or (is_last_message and file_attachments):
559
  if role == last_role and text_content:
560
  last_content += '\n' + text_content
@@ -563,7 +566,7 @@ class GrokApiClient:
563
  messages += f"{role.upper()}: {text_content or '[图片]'}\n"
564
  last_content = text_content
565
  last_role = role
566
-
567
  return {
568
  "temporary": CONFIG["API"].get("IS_TEMP_CONVERSATION", False),
569
  "modelName": self.model_id,
@@ -601,7 +604,7 @@ class MessageProcessor:
601
  "created": int(time.time()),
602
  "model": model
603
  }
604
-
605
  if is_stream:
606
  return {
607
  **base_response,
@@ -613,7 +616,7 @@ class MessageProcessor:
613
  }
614
  }]
615
  }
616
-
617
  return {
618
  **base_response,
619
  "object": "chat.completion",
@@ -630,12 +633,12 @@ class MessageProcessor:
630
 
631
  def process_model_response(response, model):
632
  result = {"token": None, "imageUrl": None}
633
-
634
  if CONFIG["IS_IMG_GEN"]:
635
  if response.get("cachedImageGenerationResponse") and not CONFIG["IS_IMG_GEN2"]:
636
  result["imageUrl"] = response["cachedImageGenerationResponse"]["imageUrl"]
637
  return result
638
-
639
  if model == 'grok-2':
640
  result["token"] = response.get("token")
641
  elif model in ['grok-2-search', 'grok-3-search']:
@@ -655,11 +658,11 @@ def process_model_response(response, model):
655
  result["token"] = "</think>" + response.get("token", "")
656
  CONFIG["IS_THINKING"] = False
657
  elif (response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "assistant") or response.get("messageTag") == "final":
658
- result["token"] = response.get("token")
659
  elif model == 'grok-3-reasoning':
660
  if response.get("isThinking") and not CONFIG["SHOW_THINKING"]:
661
  return result
662
-
663
  if response.get("isThinking") and not CONFIG["IS_THINKING"]:
664
  result["token"] = "<think>" + response.get("token", "")
665
  CONFIG["IS_THINKING"] = True
@@ -668,14 +671,14 @@ def process_model_response(response, model):
668
  CONFIG["IS_THINKING"] = False
669
  else:
670
  result["token"] = response.get("token")
671
-
672
  return result
673
 
674
  def handle_image_response(image_url):
675
  max_retries = 2
676
  retry_count = 0
677
  image_base64_response = None
678
-
679
  while retry_count < max_retries:
680
  try:
681
  proxy_options = Utils.get_proxy_options()
@@ -688,52 +691,52 @@ def handle_image_response(image_url):
688
  impersonate="chrome120",
689
  **proxy_options
690
  )
691
-
692
  if image_base64_response.status_code == 200:
693
  break
694
-
695
  retry_count += 1
696
  if retry_count == max_retries:
697
  raise Exception(f"上游服务请求失败! status: {image_base64_response.status_code}")
698
-
699
  time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
700
-
701
  except Exception as error:
702
  logger.error(str(error), "Server")
703
  retry_count += 1
704
  if retry_count == max_retries:
705
  raise
706
-
707
  time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
708
-
709
  image_buffer = image_base64_response.content
710
-
711
  if not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"]:
712
  base64_image = base64.b64encode(image_buffer).decode('utf-8')
713
  image_content_type = image_base64_response.headers.get('content-type', 'image/jpeg')
714
  return f"![image](data:{image_content_type};base64,{base64_image})"
715
-
716
  logger.info("开始上传图床", "Server")
717
-
718
  if CONFIG["API"]["PICGO_KEY"]:
719
  files = {'source': ('image.jpg', image_buffer, 'image/jpeg')}
720
  headers = {
721
  "X-API-Key": CONFIG["API"]["PICGO_KEY"]
722
  }
723
-
724
  response_url = requests.post(
725
  "https://www.picgo.net/api/1/upload",
726
  files=files,
727
  headers=headers
728
  )
729
-
730
  if response_url.status_code != 200:
731
  return "生图失败,请查看PICGO图床密钥是否设置正确"
732
  else:
733
  logger.info("生图成功", "Server")
734
  result = response_url.json()
735
  return f"![image]({result['image']['url']})"
736
-
737
 
738
  elif CONFIG["API"]["TUMY_KEY"]:
739
  files = {'file': ('image.jpg', image_buffer, 'image/jpeg')}
@@ -741,13 +744,13 @@ def handle_image_response(image_url):
741
  "Accept": "application/json",
742
  'Authorization': f"Bearer {CONFIG['API']['TUMY_KEY']}"
743
  }
744
-
745
  response_url = requests.post(
746
  "https://tu.my/api/v1/upload",
747
  files=files,
748
  headers=headers
749
  )
750
-
751
  if response_url.status_code != 200:
752
  return "生图失败,请查看TUMY图床密钥是否设置正确"
753
  else:
@@ -774,27 +777,27 @@ def handle_non_stream_response(response, model):
774
  if not chunk:
775
  continue
776
  try:
777
- line_json = json.loads(chunk.decode("utf-8").strip())
778
  if line_json.get("error"):
779
  logger.error(json.dumps(line_json, indent=2), "Server")
780
  return json.dumps({"error": "RateLimitError"}) + "\n\n"
781
-
782
  response_data = line_json.get("result", {}).get("response")
783
  if not response_data:
784
  continue
785
-
786
  if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
787
  CONFIG["IS_IMG_GEN"] = True
788
-
789
  result = process_model_response(response_data, model)
790
-
791
  if result["token"]:
792
  full_response += result["token"]
793
-
794
  if result["imageUrl"]:
795
  CONFIG["IS_IMG_GEN2"] = True
796
  return handle_image_response(result["imageUrl"])
797
-
798
  except json.JSONDecodeError:
799
  continue
800
  except Exception as e:
@@ -818,35 +821,35 @@ def handle_stream_response(response, model):
818
  if not chunk:
819
  continue
820
  try:
821
- line_json = json.loads(chunk.decode("utf-8").strip())
822
  if line_json.get("error"):
823
  logger.error(json.dumps(line_json, indent=2), "Server")
824
  yield json.dumps({"error": "RateLimitError"}) + "\n\n"
825
  return
826
-
827
  response_data = line_json.get("result", {}).get("response")
828
  if not response_data:
829
  continue
830
-
831
  if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
832
  CONFIG["IS_IMG_GEN"] = True
833
-
834
  result = process_model_response(response_data, model)
835
-
836
  if result["token"]:
837
  yield f"data: {json.dumps(MessageProcessor.create_chat_response(result['token'], model, True))}\n\n"
838
-
839
  if result["imageUrl"]:
840
  CONFIG["IS_IMG_GEN2"] = True
841
  image_data = handle_image_response(result["imageUrl"])
842
  yield f"data: {json.dumps(MessageProcessor.create_chat_response(image_data, model, True))}\n\n"
843
-
844
  except json.JSONDecodeError:
845
  continue
846
  except Exception as e:
847
  logger.error(f"处理流式响应行时出错: {str(e)}", "Server")
848
  continue
849
-
850
  yield "data: [DONE]\n\n"
851
  return generate()
852
 
@@ -854,15 +857,15 @@ def initialization():
854
  sso_array = os.environ.get("SSO", "").split(',')
855
  logger.info("开始加载令牌", "Server")
856
  for sso in sso_array:
857
- if sso:
858
  token_manager.add_token(f"sso-rw={sso};sso={sso}")
859
-
860
  logger.info(f"成功加载令牌: {json.dumps(token_manager.get_all_tokens(), indent=2)}", "Server")
861
  logger.info(f"令牌加载完成,共加载: {len(token_manager.get_all_tokens())}个令牌", "Server")
862
-
863
  if CONFIG["API"]["PROXY"]:
864
  logger.info(f"代理已设置: {CONFIG['API']['PROXY']}", "Server")
865
-
866
  logger.info("初始化完成", "Server")
867
 
868
 
@@ -881,7 +884,7 @@ def get_tokens():
881
  return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 403
882
  elif auth_token != CONFIG["API"]["API_KEY"]:
883
  return jsonify({"error": 'Unauthorized'}), 401
884
-
885
  return jsonify(token_manager.get_token_status_map())
886
 
887
  @app.route('/add/token', methods=['POST'])
@@ -891,7 +894,7 @@ def add_token():
891
  return jsonify({"error": '自定义的SSO令牌模式无法添加sso令牌'}), 403
892
  elif auth_token != CONFIG["API"]["API_KEY"]:
893
  return jsonify({"error": 'Unauthorized'}), 401
894
-
895
  try:
896
  sso = request.json.get('sso')
897
  token_manager.add_token(f"sso-rw={sso};sso={sso}")
@@ -907,7 +910,7 @@ def delete_token():
907
  return jsonify({"error": '自定义的SSO令牌模式无法删除sso令牌'}), 403
908
  elif auth_token != CONFIG["API"]["API_KEY"]:
909
  return jsonify({"error": 'Unauthorized'}), 401
910
-
911
  try:
912
  sso = request.json.get('sso')
913
  token_manager.delete_token(f"sso-rw={sso};sso={sso}")
@@ -926,7 +929,7 @@ def get_models():
926
  "object": "model",
927
  "created": int(time.time()),
928
  "owned_by": "grok"
929
- }
930
  for model in CONFIG["MODELS"].keys()
931
  ]
932
  })
@@ -934,7 +937,8 @@ def get_models():
934
  @app.route('/v1/chat/completions', methods=['POST'])
935
  def chat_completions():
936
  try:
937
- auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
 
938
  if auth_token:
939
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
940
  result = f"sso={auth_token};sso-rw={auth_token}"
@@ -943,94 +947,106 @@ def chat_completions():
943
  return jsonify({"error": 'Unauthorized'}), 401
944
  else:
945
  return jsonify({"error": 'API_KEY缺失'}), 401
946
-
947
  data = request.json
948
  model = data.get("model")
949
  stream = data.get("stream", False)
950
-
951
  retry_count = 0
952
  grok_client = GrokApiClient(model)
953
  request_payload = grok_client.prepare_chat_request(data)
954
-
955
  while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:
956
  retry_count += 1
957
- CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(model)
958
-
 
959
  if not CONFIG["API"]["SIGNATURE_COOKIE"]:
960
  raise ValueError('该模型无可用令牌')
961
-
962
- logger.info(f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}", "Server")
963
- logger.info(f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}", "Server")
964
-
 
 
 
 
965
  try:
966
  proxy_options = Utils.get_proxy_options()
967
  response = curl_requests.post(
968
  f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",
969
  headers={
970
- **DEFAULT_HEADERS,
971
- "Cookie": CONFIG["API"]["SIGNATURE_COOKIE"]
972
  },
973
  data=json.dumps(request_payload),
974
- impersonate="chrome120",
975
  stream=True,
976
- **proxy_options
977
- )
978
-
979
  if response.status_code == 200:
980
  logger.info("请求成功", "Server")
981
- logger.info(f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}", "Server")
982
-
 
 
983
  try:
984
  if stream:
985
- return Response(
986
- stream_with_context(handle_stream_response(response, model)),
987
- content_type='text/event-stream'
988
- )
989
  else:
990
- content = handle_non_stream_response(response, model)
991
- return jsonify(MessageProcessor.create_chat_response(content, model))
992
-
 
 
 
993
  except Exception as error:
994
  logger.error(str(error), "Server")
995
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
996
  raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
997
-
998
- token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
 
999
  if token_manager.get_token_count_for_model(model) == 0:
1000
  raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1001
-
1002
  elif response.status_code == 429:
1003
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
1004
  raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1005
-
1006
- token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
 
1007
  if token_manager.get_token_count_for_model(model) == 0:
1008
  raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1009
-
1010
  else:
1011
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
1012
  raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1013
-
1014
- logger.error(f"令牌异常错误状态!status: {response.status_code}", "Server")
1015
- token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])
1016
- logger.info(f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}", "Server")
1017
-
 
 
 
 
1018
  except Exception as e:
1019
  logger.error(f"请求处理异常: {str(e)}", "Server")
1020
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
1021
  raise
1022
  continue
1023
-
1024
  raise ValueError('当前模型所有令牌都已耗尽')
1025
-
1026
  except Exception as error:
1027
  logger.error(str(error), "ChatAPI")
1028
- return jsonify({
1029
- "error": {
1030
  "message": str(error),
1031
  "type": "server_error"
1032
- }
1033
- }), 500
1034
 
1035
  @app.route('/', defaults={'path': ''})
1036
  @app.route('/<path:path>')
@@ -1040,7 +1056,7 @@ def catch_all(path):
1040
  if __name__ == '__main__':
1041
  token_manager = AuthTokenManager()
1042
  initialization()
1043
-
1044
  app.run(
1045
  host='0.0.0.0',
1046
  port=CONFIG["SERVER"]["PORT"],
 
26
  )
27
 
28
  logger.add(
29
+ sys.stderr,
30
+ level=level,
31
  format=format,
32
  colorize=colorize,
33
  backtrace=True,
 
43
  full_path = caller_frame.f_code.co_filename
44
  function = caller_frame.f_code.co_name
45
  lineno = caller_frame.f_lineno
46
+
47
  filename = os.path.basename(full_path)
48
+
49
  return {
50
  'filename': filename,
51
  'function': function,
52
  'lineno': lineno
53
  }
54
  finally:
55
+ del frame
56
 
57
  def info(self, message, source="API"):
58
  caller_info = self._get_caller_info()
59
  self.logger.bind(**caller_info).info(f"[{source}] {message}")
60
+
61
  def error(self, message, source="API"):
62
  caller_info = self._get_caller_info()
63
+
64
  if isinstance(message, Exception):
65
  self.logger.bind(**caller_info).exception(f"[{source}] {str(message)}")
66
  else:
67
  self.logger.bind(**caller_info).error(f"[{source}] {message}")
68
+
69
  def warning(self, message, source="API"):
70
  caller_info = self._get_caller_info()
71
  self.logger.bind(**caller_info).warning(f"[{source}] {message}")
72
+
73
  def debug(self, message, source="API"):
74
  caller_info = self._get_caller_info()
75
  self.logger.bind(**caller_info).debug(f"[{source}] {message}")
 
124
  'Connection': 'keep-alive',
125
  'Origin': 'https://grok.com',
126
  'Priority': 'u=1, i',
127
+ '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',
128
+ 'Sec-Ch-Ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
129
  'Sec-Ch-Ua-Mobile': '?0',
130
+ 'Sec-Ch-Ua-Platform': '"macOS"',
131
  'Sec-Fetch-Dest': 'empty',
132
  'Sec-Fetch-Mode': 'cors',
133
  'Sec-Fetch-Site': 'same-origin',
 
168
  self.token_model_map[model] = []
169
  if sso not in self.token_status_map:
170
  self.token_status_map[sso] = {}
171
+
172
  existing_token_entry = next((entry for entry in self.token_model_map[model] if entry["token"] == token), None)
173
+
174
  if not existing_token_entry:
175
  self.token_model_map[model].append({
176
  "token": token,
 
178
  "AddedTime": int(time.time() * 1000),
179
  "StartCallTime": None
180
  })
181
+
182
  if model not in self.token_status_map[sso]:
183
  self.token_status_map[sso][model] = {
184
  "isValid": True,
185
  "invalidatedTime": None,
186
  "totalRequestCount": 0
187
  }
188
+
189
  def set_token(self, token):
190
  models = list(self.model_config.keys())
191
  self.token_model_map = {model: [{
 
194
  "AddedTime": int(time.time() * 1000),
195
  "StartCallTime": None
196
  }] for model in models}
197
+
198
  sso = token.split("sso=")[1].split(";")[0]
199
  self.token_status_map[sso] = {model: {
200
  "isValid": True,
201
  "invalidatedTime": None,
202
  "totalRequestCount": 0
203
  } for model in models}
204
+
205
  def delete_token(self, token):
206
  try:
207
  sso = token.split("sso=")[1].split(";")[0]
208
  for model in self.token_model_map:
209
  self.token_model_map[model] = [entry for entry in self.token_model_map[model] if entry["token"] != token]
210
+
211
  if sso in self.token_status_map:
212
  del self.token_status_map[sso]
213
+
214
  logger.info(f"令牌已成功移除: {token}", "TokenManager")
215
  return True
216
  except Exception as error:
217
  logger.error(f"令牌删除失败: {str(error)}")
218
  return False
219
+
220
  def get_next_token_for_model(self, model_id):
221
  normalized_model = self.normalize_model_name(model_id)
222
+
223
  if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
224
  return None
225
+
226
  token_entry = self.token_model_map[normalized_model][0]
227
+
228
  if token_entry:
229
  if token_entry["StartCallTime"] is None:
230
  token_entry["StartCallTime"] = int(time.time() * 1000)
231
+
232
  if not self.token_reset_switch:
233
  self.start_token_reset_process()
234
  self.token_reset_switch = True
235
+
236
  token_entry["RequestCount"] += 1
237
+
238
  if token_entry["RequestCount"] > self.model_config[normalized_model]["RequestFrequency"]:
239
  self.remove_token_from_model(normalized_model, token_entry["token"])
240
  next_token_entry = self.token_model_map[normalized_model][0] if self.token_model_map[normalized_model] else None
241
  return next_token_entry["token"] if next_token_entry else None
242
+
243
  sso = token_entry["token"].split("sso=")[1].split(";")[0]
244
  if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:
245
  if token_entry["RequestCount"] == self.model_config[normalized_model]["RequestFrequency"]:
246
  self.token_status_map[sso][normalized_model]["isValid"] = False
247
  self.token_status_map[sso][normalized_model]["invalidatedTime"] = int(time.time() * 1000)
248
  self.token_status_map[sso][normalized_model]["totalRequestCount"] += 1
249
+
250
  return token_entry["token"]
251
+
252
  return None
253
+
254
  def remove_token_from_model(self, model_id, token):
255
  normalized_model = self.normalize_model_name(model_id)
256
+
257
  if normalized_model not in self.token_model_map:
258
  logger.error(f"模型 {normalized_model} 不存在", "TokenManager")
259
  return False
260
+
261
  model_tokens = self.token_model_map[normalized_model]
262
  token_index = next((i for i, entry in enumerate(model_tokens) if entry["token"] == token), -1)
263
+
264
  if token_index != -1:
265
  removed_token_entry = model_tokens.pop(token_index)
266
  self.expired_tokens.add((
 
268
  normalized_model,
269
  int(time.time() * 1000)
270
  ))
271
+
272
  if not self.token_reset_switch:
273
  self.start_token_reset_process()
274
  self.token_reset_switch = True
275
+
276
  logger.info(f"模型{model_id}的令牌已失效,已成功移除令牌: {token}", "TokenManager")
277
  return True
278
+
279
  logger.error(f"在模型 {normalized_model} 中未找到 token: {token}", "TokenManager")
280
  return False
281
+
282
  def get_expired_tokens(self):
283
  return list(self.expired_tokens)
284
+
285
  def normalize_model_name(self, model):
286
  if model.startswith('grok-') and 'deepsearch' not in model and 'reasoning' not in model:
287
  return '-'.join(model.split('-')[:2])
288
  return model
289
+
290
  def get_token_count_for_model(self, model_id):
291
  normalized_model = self.normalize_model_name(model_id)
292
  return len(self.token_model_map.get(normalized_model, []))
293
+
294
  def get_remaining_token_request_capacity(self):
295
  remaining_capacity_map = {}
296
+
297
  for model in self.model_config.keys():
298
  model_tokens = self.token_model_map.get(model, [])
299
  model_request_frequency = self.model_config[model]["RequestFrequency"]
300
+
301
  total_used_requests = sum(token_entry.get("RequestCount", 0) for token_entry in model_tokens)
302
+
303
  remaining_capacity = (len(model_tokens) * model_request_frequency) - total_used_requests
304
  remaining_capacity_map[model] = max(0, remaining_capacity)
305
+
306
  return remaining_capacity_map
307
+
308
  def get_token_array_for_model(self, model_id):
309
  normalized_model = self.normalize_model_name(model_id)
310
  return self.token_model_map.get(normalized_model, [])
311
+
312
  def start_token_reset_process(self):
313
  def reset_expired_tokens():
314
  now = int(time.time() * 1000)
315
+
316
  tokens_to_remove = set()
317
  for token_info in self.expired_tokens:
318
  token, model, expired_time = token_info
319
  expiration_time = self.model_config[model]["ExpirationTime"]
320
+
321
  if now - expired_time >= expiration_time:
322
  if not any(entry["token"] == token for entry in self.token_model_map.get(model, [])):
323
  if model not in self.token_model_map:
324
  self.token_model_map[model] = []
325
+
326
  self.token_model_map[model].append({
327
  "token": token,
328
  "RequestCount": 0,
329
  "AddedTime": now,
330
  "StartCallTime": None
331
  })
332
+
333
  sso = token.split("sso=")[1].split(";")[0]
334
  if sso in self.token_status_map and model in self.token_status_map[sso]:
335
  self.token_status_map[sso][model]["isValid"] = True
336
  self.token_status_map[sso][model]["invalidatedTime"] = None
337
  self.token_status_map[sso][model]["totalRequestCount"] = 0
338
+
339
  tokens_to_remove.add(token_info)
340
+
341
  self.expired_tokens -= tokens_to_remove
342
+
343
  for model in self.model_config.keys():
344
  if model not in self.token_model_map:
345
  continue
346
+
347
  for token_entry in self.token_model_map[model]:
348
  if not token_entry.get("StartCallTime"):
349
  continue
350
+
351
  expiration_time = self.model_config[model]["ExpirationTime"]
352
  if now - token_entry["StartCallTime"] >= expiration_time:
353
  sso = token_entry["token"].split("sso=")[1].split(";")[0]
 
355
  self.token_status_map[sso][model]["isValid"] = True
356
  self.token_status_map[sso][model]["invalidatedTime"] = None
357
  self.token_status_map[sso][model]["totalRequestCount"] = 0
358
+
359
  token_entry["RequestCount"] = 0
360
  token_entry["StartCallTime"] = None
361
+
362
  import threading
363
  # 启动一个线程执行定时任务,每小时执行一次
364
  def run_timer():
365
  while True:
366
  reset_expired_tokens()
367
  time.sleep(3600)
368
+
369
  timer_thread = threading.Thread(target=run_timer)
370
  timer_thread.daemon = True
371
  timer_thread.start()
372
+
373
  def get_all_tokens(self):
374
  all_tokens = set()
375
  for model_tokens in self.token_model_map.values():
376
  for entry in model_tokens:
377
  all_tokens.add(entry["token"])
378
  return list(all_tokens)
379
+
380
  def get_token_status_map(self):
381
  return self.token_status_map
382
+
383
  class Utils:
384
  @staticmethod
385
  def organize_search_results(search_results):
386
  if not search_results or 'results' not in search_results:
387
  return ''
388
+
389
  results = search_results['results']
390
  formatted_results = []
391
+
392
  for index, result in enumerate(results):
393
  title = result.get('title', '未知标题')
394
  url = result.get('url', '#')
395
  preview = result.get('preview', '无预览内容')
396
+
397
  formatted_result = f"\r\n<details><summary>资料[{index}]: {title}</summary>\r\n{preview}\r\n\n[Link]({url})\r\n</details>"
398
  formatted_results.append(formatted_result)
399
+
400
  return '\n\n'.join(formatted_results)
401
+
402
  @staticmethod
403
  def create_auth_headers(model):
404
  return token_manager.get_next_token_for_model(model)
405
+
406
  @staticmethod
407
  def get_proxy_options():
408
  proxy = CONFIG["API"]["PROXY"]
409
  proxy_options = {}
410
+
411
  if proxy:
412
  logger.info(f"使用代理: {proxy}", "Server")
413
  proxy_options["proxies"] = {"https": proxy, "http": proxy}
414
+
415
  if proxy.startswith("socks5://"):
416
  proxy_options["proxies"] = {"https": proxy, "http": proxy}
417
  proxy_options["proxy_type"] = "socks5"
418
+
419
  return proxy_options
420
 
421
  class GrokApiClient:
 
423
  if model_id not in CONFIG["MODELS"]:
424
  raise ValueError(f"不支持的模型: {model_id}")
425
  self.model_id = CONFIG["MODELS"][model_id]
426
+
427
  def process_message_content(self, content):
428
  if isinstance(content, str):
429
  return content
430
  return None
431
+
432
  def get_image_type(self, base64_string):
433
  mime_type = 'image/jpeg'
434
  if 'data:image' in base64_string:
 
436
  matches = re.search(r'data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,', base64_string)
437
  if matches:
438
  mime_type = matches.group(1)
439
+
440
  extension = mime_type.split('/')[1]
441
  file_name = f"image.{extension}"
442
+
443
  return {
444
  "mimeType": mime_type,
445
  "fileName": file_name
446
  }
447
+
448
  def upload_base64_image(self, base64_data, url):
449
  try:
450
  if 'data:image' in base64_data:
451
  image_buffer = base64_data.split(',')[1]
452
  else:
453
  image_buffer = base64_data
454
+
455
  image_info = self.get_image_type(base64_data)
456
  mime_type = image_info["mimeType"]
457
  file_name = image_info["fileName"]
458
+
459
  upload_data = {
460
  "rpc": "uploadFile",
461
  "req": {
 
464
  "content": image_buffer
465
  }
466
  }
467
+
468
  logger.info("发送图片请求", "Server")
469
+
470
  proxy_options = Utils.get_proxy_options()
471
  response = curl_requests.post(
472
  url,
 
475
  "Cookie": CONFIG["API"]["SIGNATURE_COOKIE"]
476
  },
477
  json=upload_data,
478
+ impersonate="chrome133a",
479
  **proxy_options
480
  )
481
+
482
  if response.status_code != 200:
483
  logger.error(f"上传图片失败,状态码:{response.status_code}", "Server")
484
  return ''
485
+
486
  result = response.json()
487
  logger.info(f"上传图片成功: {result}", "Server")
488
  return result.get("fileMetadataId", "")
489
+
490
  except Exception as error:
491
  logger.error(str(error), "Server")
492
  return ''
493
+
494
  def prepare_chat_request(self, request):
495
+ if ((request["model"] == 'grok-2-imageGen' or request["model"] == 'grok-3-imageGen') and
496
+ not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"] and
497
  request.get("stream", False)):
498
  raise ValueError("该模型流式输出需要配置PICGO或者TUMY图床密钥!")
499
+
500
  todo_messages = request["messages"]
501
  if request["model"] in ['grok-2-imageGen', 'grok-3-imageGen', 'grok-3-deepsearch']:
502
  last_message = todo_messages[-1]
503
  if last_message["role"] != 'user':
504
  raise ValueError('此模型最后一条消息必须是用户消息!')
505
  todo_messages = [last_message]
506
+
507
  file_attachments = []
508
  messages = ''
509
  last_role = None
510
  last_content = ''
511
  search = request["model"] in ['grok-2-search', 'grok-3-search']
512
+
513
  # 移除<think>标签及其内容和base64图片
514
  def remove_think_tags(text):
515
  import re
516
  text = re.sub(r'<think>[\s\S]*?<\/think>', '', text).strip()
517
  text = re.sub(r'!\[image\]\(data:.*?base64,.*?\)', '[图片]', text)
518
  return text
519
+
520
  def process_content(content):
521
  if isinstance(content, list):
522
  text_content = ''
 
532
  elif content["type"] == 'text':
533
  return remove_think_tags(content["text"])
534
  return remove_think_tags(self.process_message_content(content))
535
+
536
  for current in todo_messages:
537
  role = 'assistant' if current["role"] == 'assistant' else 'user'
538
  is_last_message = current == todo_messages[-1]
539
+
540
  if is_last_message and "content" in current:
541
  if isinstance(current["content"], list):
542
  for item in current["content"]:
 
554
  )
555
  if processed_image:
556
  file_attachments.append(processed_image)
557
+
558
 
559
  text_content = process_content(current.get("content", ""))
560
+
561
  if text_content or (is_last_message and file_attachments):
562
  if role == last_role and text_content:
563
  last_content += '\n' + text_content
 
566
  messages += f"{role.upper()}: {text_content or '[图片]'}\n"
567
  last_content = text_content
568
  last_role = role
569
+
570
  return {
571
  "temporary": CONFIG["API"].get("IS_TEMP_CONVERSATION", False),
572
  "modelName": self.model_id,
 
604
  "created": int(time.time()),
605
  "model": model
606
  }
607
+
608
  if is_stream:
609
  return {
610
  **base_response,
 
616
  }
617
  }]
618
  }
619
+
620
  return {
621
  **base_response,
622
  "object": "chat.completion",
 
633
 
634
  def process_model_response(response, model):
635
  result = {"token": None, "imageUrl": None}
636
+
637
  if CONFIG["IS_IMG_GEN"]:
638
  if response.get("cachedImageGenerationResponse") and not CONFIG["IS_IMG_GEN2"]:
639
  result["imageUrl"] = response["cachedImageGenerationResponse"]["imageUrl"]
640
  return result
641
+
642
  if model == 'grok-2':
643
  result["token"] = response.get("token")
644
  elif model in ['grok-2-search', 'grok-3-search']:
 
658
  result["token"] = "</think>" + response.get("token", "")
659
  CONFIG["IS_THINKING"] = False
660
  elif (response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "assistant") or response.get("messageTag") == "final":
661
+ result["token"] = response.get("token")
662
  elif model == 'grok-3-reasoning':
663
  if response.get("isThinking") and not CONFIG["SHOW_THINKING"]:
664
  return result
665
+
666
  if response.get("isThinking") and not CONFIG["IS_THINKING"]:
667
  result["token"] = "<think>" + response.get("token", "")
668
  CONFIG["IS_THINKING"] = True
 
671
  CONFIG["IS_THINKING"] = False
672
  else:
673
  result["token"] = response.get("token")
674
+
675
  return result
676
 
677
  def handle_image_response(image_url):
678
  max_retries = 2
679
  retry_count = 0
680
  image_base64_response = None
681
+
682
  while retry_count < max_retries:
683
  try:
684
  proxy_options = Utils.get_proxy_options()
 
691
  impersonate="chrome120",
692
  **proxy_options
693
  )
694
+
695
  if image_base64_response.status_code == 200:
696
  break
697
+
698
  retry_count += 1
699
  if retry_count == max_retries:
700
  raise Exception(f"上游服务请求失败! status: {image_base64_response.status_code}")
701
+
702
  time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
703
+
704
  except Exception as error:
705
  logger.error(str(error), "Server")
706
  retry_count += 1
707
  if retry_count == max_retries:
708
  raise
709
+
710
  time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
711
+
712
  image_buffer = image_base64_response.content
713
+
714
  if not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"]:
715
  base64_image = base64.b64encode(image_buffer).decode('utf-8')
716
  image_content_type = image_base64_response.headers.get('content-type', 'image/jpeg')
717
  return f"![image](data:{image_content_type};base64,{base64_image})"
718
+
719
  logger.info("开始上传图床", "Server")
720
+
721
  if CONFIG["API"]["PICGO_KEY"]:
722
  files = {'source': ('image.jpg', image_buffer, 'image/jpeg')}
723
  headers = {
724
  "X-API-Key": CONFIG["API"]["PICGO_KEY"]
725
  }
726
+
727
  response_url = requests.post(
728
  "https://www.picgo.net/api/1/upload",
729
  files=files,
730
  headers=headers
731
  )
732
+
733
  if response_url.status_code != 200:
734
  return "生图失败,请查看PICGO图床密钥是否设置正确"
735
  else:
736
  logger.info("生图成功", "Server")
737
  result = response_url.json()
738
  return f"![image]({result['image']['url']})"
739
+
740
 
741
  elif CONFIG["API"]["TUMY_KEY"]:
742
  files = {'file': ('image.jpg', image_buffer, 'image/jpeg')}
 
744
  "Accept": "application/json",
745
  'Authorization': f"Bearer {CONFIG['API']['TUMY_KEY']}"
746
  }
747
+
748
  response_url = requests.post(
749
  "https://tu.my/api/v1/upload",
750
  files=files,
751
  headers=headers
752
  )
753
+
754
  if response_url.status_code != 200:
755
  return "生图失败,请查看TUMY图床密钥是否设置正确"
756
  else:
 
777
  if not chunk:
778
  continue
779
  try:
780
+ line_json = json.loads(chunk.decode("utf-8").strip())
781
  if line_json.get("error"):
782
  logger.error(json.dumps(line_json, indent=2), "Server")
783
  return json.dumps({"error": "RateLimitError"}) + "\n\n"
784
+
785
  response_data = line_json.get("result", {}).get("response")
786
  if not response_data:
787
  continue
788
+
789
  if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
790
  CONFIG["IS_IMG_GEN"] = True
791
+
792
  result = process_model_response(response_data, model)
793
+
794
  if result["token"]:
795
  full_response += result["token"]
796
+
797
  if result["imageUrl"]:
798
  CONFIG["IS_IMG_GEN2"] = True
799
  return handle_image_response(result["imageUrl"])
800
+
801
  except json.JSONDecodeError:
802
  continue
803
  except Exception as e:
 
821
  if not chunk:
822
  continue
823
  try:
824
+ line_json = json.loads(chunk.decode("utf-8").strip())
825
  if line_json.get("error"):
826
  logger.error(json.dumps(line_json, indent=2), "Server")
827
  yield json.dumps({"error": "RateLimitError"}) + "\n\n"
828
  return
829
+
830
  response_data = line_json.get("result", {}).get("response")
831
  if not response_data:
832
  continue
833
+
834
  if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
835
  CONFIG["IS_IMG_GEN"] = True
836
+
837
  result = process_model_response(response_data, model)
838
+
839
  if result["token"]:
840
  yield f"data: {json.dumps(MessageProcessor.create_chat_response(result['token'], model, True))}\n\n"
841
+
842
  if result["imageUrl"]:
843
  CONFIG["IS_IMG_GEN2"] = True
844
  image_data = handle_image_response(result["imageUrl"])
845
  yield f"data: {json.dumps(MessageProcessor.create_chat_response(image_data, model, True))}\n\n"
846
+
847
  except json.JSONDecodeError:
848
  continue
849
  except Exception as e:
850
  logger.error(f"处理流式响应行时出错: {str(e)}", "Server")
851
  continue
852
+
853
  yield "data: [DONE]\n\n"
854
  return generate()
855
 
 
857
  sso_array = os.environ.get("SSO", "").split(',')
858
  logger.info("开始加载令牌", "Server")
859
  for sso in sso_array:
860
+ if sso:
861
  token_manager.add_token(f"sso-rw={sso};sso={sso}")
862
+
863
  logger.info(f"成功加载令牌: {json.dumps(token_manager.get_all_tokens(), indent=2)}", "Server")
864
  logger.info(f"令牌加载完成,共加载: {len(token_manager.get_all_tokens())}个令牌", "Server")
865
+
866
  if CONFIG["API"]["PROXY"]:
867
  logger.info(f"代理已设置: {CONFIG['API']['PROXY']}", "Server")
868
+
869
  logger.info("初始化完成", "Server")
870
 
871
 
 
884
  return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 403
885
  elif auth_token != CONFIG["API"]["API_KEY"]:
886
  return jsonify({"error": 'Unauthorized'}), 401
887
+
888
  return jsonify(token_manager.get_token_status_map())
889
 
890
  @app.route('/add/token', methods=['POST'])
 
894
  return jsonify({"error": '自定义的SSO令牌模式无法添加sso令牌'}), 403
895
  elif auth_token != CONFIG["API"]["API_KEY"]:
896
  return jsonify({"error": 'Unauthorized'}), 401
897
+
898
  try:
899
  sso = request.json.get('sso')
900
  token_manager.add_token(f"sso-rw={sso};sso={sso}")
 
910
  return jsonify({"error": '自定义的SSO令牌模式无法删除sso令牌'}), 403
911
  elif auth_token != CONFIG["API"]["API_KEY"]:
912
  return jsonify({"error": 'Unauthorized'}), 401
913
+
914
  try:
915
  sso = request.json.get('sso')
916
  token_manager.delete_token(f"sso-rw={sso};sso={sso}")
 
929
  "object": "model",
930
  "created": int(time.time()),
931
  "owned_by": "grok"
932
+ }
933
  for model in CONFIG["MODELS"].keys()
934
  ]
935
  })
 
937
  @app.route('/v1/chat/completions', methods=['POST'])
938
  def chat_completions():
939
  try:
940
+ auth_token = request.headers.get('Authorization',
941
+ '').replace('Bearer ', '')
942
  if auth_token:
943
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
944
  result = f"sso={auth_token};sso-rw={auth_token}"
 
947
  return jsonify({"error": 'Unauthorized'}), 401
948
  else:
949
  return jsonify({"error": 'API_KEY缺失'}), 401
950
+
951
  data = request.json
952
  model = data.get("model")
953
  stream = data.get("stream", False)
954
+
955
  retry_count = 0
956
  grok_client = GrokApiClient(model)
957
  request_payload = grok_client.prepare_chat_request(data)
958
+
959
  while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:
960
  retry_count += 1
961
+ CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(
962
+ model)
963
+
964
  if not CONFIG["API"]["SIGNATURE_COOKIE"]:
965
  raise ValueError('该模型无可用令牌')
966
+
967
+ logger.info(
968
+ f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}",
969
+ "Server")
970
+ logger.info(
971
+ f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}",
972
+ "Server")
973
+
974
  try:
975
  proxy_options = Utils.get_proxy_options()
976
  response = curl_requests.post(
977
  f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",
978
  headers={
979
+ **DEFAULT_HEADERS, "Cookie":
980
+ CONFIG["API"]["SIGNATURE_COOKIE"]
981
  },
982
  data=json.dumps(request_payload),
983
+ impersonate="chrome133a",
984
  stream=True,
985
+ **proxy_options)
 
 
986
  if response.status_code == 200:
987
  logger.info("请求成功", "Server")
988
+ logger.info(
989
+ f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
990
+ "Server")
991
+
992
  try:
993
  if stream:
994
+ return Response(stream_with_context(
995
+ handle_stream_response(response, model)),
996
+ content_type='text/event-stream')
 
997
  else:
998
+ content = handle_non_stream_response(
999
+ response, model)
1000
+ return jsonify(
1001
+ MessageProcessor.create_chat_response(
1002
+ content, model))
1003
+
1004
  except Exception as error:
1005
  logger.error(str(error), "Server")
1006
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
1007
  raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1008
+
1009
+ token_manager.remove_token_from_model(
1010
+ model, CONFIG["API"]["SIGNATURE_COOKIE"])
1011
  if token_manager.get_token_count_for_model(model) == 0:
1012
  raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1013
+
1014
  elif response.status_code == 429:
1015
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
1016
  raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1017
+
1018
+ token_manager.remove_token_from_model(
1019
+ model, CONFIG["API"]["SIGNATURE_COOKIE"])
1020
  if token_manager.get_token_count_for_model(model) == 0:
1021
  raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1022
+
1023
  else:
1024
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
1025
  raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1026
+
1027
+ logger.error(f"令牌异常错误状态!status: {response.status_code}",
1028
+ "Server")
1029
+ token_manager.remove_token_from_model(
1030
+ model, CONFIG["API"]["SIGNATURE_COOKIE"])
1031
+ logger.info(
1032
+ f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
1033
+ "Server")
1034
+
1035
  except Exception as e:
1036
  logger.error(f"请求处理异常: {str(e)}", "Server")
1037
  if CONFIG["API"]["IS_CUSTOM_SSO"]:
1038
  raise
1039
  continue
1040
+
1041
  raise ValueError('当前模型所有令牌都已耗尽')
1042
+
1043
  except Exception as error:
1044
  logger.error(str(error), "ChatAPI")
1045
+ return jsonify(
1046
+ {"error": {
1047
  "message": str(error),
1048
  "type": "server_error"
1049
+ }}), 500
 
1050
 
1051
  @app.route('/', defaults={'path': ''})
1052
  @app.route('/<path:path>')
 
1056
  if __name__ == '__main__':
1057
  token_manager = AuthTokenManager()
1058
  initialization()
1059
+
1060
  app.run(
1061
  host='0.0.0.0',
1062
  port=CONFIG["SERVER"]["PORT"],