cvener/GrokPythonService
0
1import os2import json3import uuid4import time5import base646import sys7import inspect8import secrets9from loguru import logger10from pathlib import Path11 12import requests13from flask import Flask, request, Response, jsonify, stream_with_context, render_template, redirect, session14from curl_cffi import requests as curl_requests15from werkzeug.middleware.proxy_fix import ProxyFix16 17class Logger:18 def __init__(self, level="INFO", colorize=True, format=None):19 logger.remove()20 21 if format is None:22 format = (23 "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "24 "<level>{level: <8}</level> | "25 "<cyan>{extra[filename]}</cyan>:<cyan>{extra[function]}</cyan>:<cyan>{extra[lineno]}</cyan> | "26 "<level>{message}</level>"27 )28 29 logger.add(30 sys.stderr,31 level=level,32 format=format,33 colorize=colorize,34 backtrace=True,35 diagnose=True36 )37 38 self.logger = logger39 40 def _get_caller_info(self):41 frame = inspect.currentframe()42 try:43 caller_frame = frame.f_back.f_back44 full_path = caller_frame.f_code.co_filename45 function = caller_frame.f_code.co_name46 lineno = caller_frame.f_lineno47 48 filename = os.path.basename(full_path)49 50 return {51 'filename': filename,52 'function': function,53 'lineno': lineno54 }55 finally:56 del frame57 58 def info(self, message, source="API"):59 caller_info = self._get_caller_info()60 self.logger.bind(**caller_info).info(f"[{source}] {message}")61 62 def error(self, message, source="API"):63 caller_info = self._get_caller_info()64 65 if isinstance(message, Exception):66 self.logger.bind(**caller_info).exception(f"[{source}] {str(message)}")67 else:68 self.logger.bind(**caller_info).error(f"[{source}] {message}")69 70 def warning(self, message, source="API"):71 caller_info = self._get_caller_info()72 self.logger.bind(**caller_info).warning(f"[{source}] {message}")73 74 def debug(self, message, source="API"):75 caller_info = self._get_caller_info()76 self.logger.bind(**caller_info).debug(f"[{source}] {message}")77 78 async def request_logger(self, request):79 caller_info = self._get_caller_info()80 self.logger.bind(**caller_info).info(f"请求: {request.method} {request.path}", "Request")81 82logger = Logger(level="INFO")83 84CONFIG = {85 "MODELS": {86 'grok-2': 'grok-latest',87 'grok-2-imageGen': 'grok-latest',88 'grok-2-search': 'grok-latest',89 "grok-3": "grok-3",90 "grok-3-search": "grok-3",91 "grok-3-imageGen": "grok-3",92 "grok-3-deepsearch": "grok-3",93 "grok-3-deepersearch": "grok-3",94 "grok-3-reasoning": "grok-3"95 },96 "API": {97 "IS_TEMP_CONVERSATION": os.environ.get("IS_TEMP_CONVERSATION", "true").lower() == "true",98 "IS_CUSTOM_SSO": os.environ.get("IS_CUSTOM_SSO", "false").lower() == "true",99 "BASE_URL": "https://grok.com",100 "API_KEY": os.environ.get("API_KEY", "sk-123456"),101 "SIGNATURE_COOKIE": None,102 "PICGO_KEY": os.environ.get("PICGO_KEY") or None,103 "TUMY_KEY": os.environ.get("TUMY_KEY") or None,104 "RETRY_TIME": 1000,105 "PROXY": os.environ.get("PROXY") or None106 },107 "ADMIN": {108 "MANAGER_SWITCH": os.environ.get("MANAGER_SWITCH") or None,109 "PASSWORD": os.environ.get("ADMINPASSWORD") or None 110 },111 "SERVER": {112 "COOKIE": None,113 "CF_CLEARANCE":os.environ.get("CF_CLEARANCE") or None,114 "PORT": int(os.environ.get("PORT", 5200))115 },116 "RETRY": {117 "RETRYSWITCH": False,118 "MAX_ATTEMPTS": 2119 },120 "SHOW_THINKING": os.environ.get("SHOW_THINKING") == "true",121 "IS_THINKING": False,122 "IS_IMG_GEN": False,123 "IS_IMG_GEN2": False,124 "ISSHOW_SEARCH_RESULTS": os.environ.get("ISSHOW_SEARCH_RESULTS", "true").lower() == "true"125}126 127 128DEFAULT_HEADERS = {129 'Accept': '*/*',130 'Accept-Language': 'zh-CN,zh;q=0.9',131 'Accept-Encoding': 'gzip, deflate, br, zstd',132 'Content-Type': 'text/plain;charset=UTF-8',133 'Connection': 'keep-alive',134 'Origin': 'https://grok.com',135 'Priority': 'u=1, i',136 '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',137 'Sec-Ch-Ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',138 'Sec-Ch-Ua-Mobile': '?0',139 'Sec-Ch-Ua-Platform': '"macOS"',140 'Sec-Fetch-Dest': 'empty',141 'Sec-Fetch-Mode': 'cors',142 'Sec-Fetch-Site': 'same-origin',143 'Baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c',144 'x-statsig-id': 'ZTpUeXBlRXJyb3I6IENhbm5vdCByZWFkIHByb3BlcnRpZXMgb2YgbnVsbCAocmVhZGluZyAndGl0bGVbMV0nKQ=='# 暂用固定x-statsig-id,失效再看145}146 147class AuthTokenManager:148 def __init__(self):149 self.token_model_map = {}150 self.expired_tokens = set()151 self.token_status_map = {}152 153 self.model_config = {154 "grok-2": {155 "RequestFrequency": 30,156 "ExpirationTime": 1 * 60 * 60 * 1000 # 1小时157 },158 "grok-3": {159 "RequestFrequency": 20,160 "ExpirationTime": 2 * 60 * 60 * 1000 # 2小时161 },162 "grok-3-deepsearch": {163 "RequestFrequency": 10,164 "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时165 },166 "grok-3-deepersearch": {167 "RequestFrequency": 3,168 "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时169 },170 "grok-3-reasoning": {171 "RequestFrequency": 10,172 "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时173 }174 }175 self.token_reset_switch = False176 self.token_reset_timer = None177 self.load_token_status() # 加载令牌状态178 def save_token_status(self):179 try: 180 with open(CONFIG["TOKEN_STATUS_FILE"], 'w', encoding='utf-8') as f:181 json.dump(self.token_status_map, f, indent=2, ensure_ascii=False)182 logger.info("令牌状态已保存到配置文件", "TokenManager")183 except Exception as error:184 logger.error(f"保存令牌状态失败: {str(error)}", "TokenManager")185 186 def load_token_status(self):187 try:188 token_status_file = Path(CONFIG["TOKEN_STATUS_FILE"])189 if token_status_file.exists():190 with open(token_status_file, 'r', encoding='utf-8') as f:191 self.token_status_map = json.load(f)192 logger.info("已从配置文件加载令牌状态", "TokenManager")193 except Exception as error:194 logger.error(f"加载令牌状态失败: {str(error)}", "TokenManager")195 def add_token(self, token,isinitialization=False):196 sso = token.split("sso=")[1].split(";")[0]197 for model in self.model_config.keys():198 if model not in self.token_model_map:199 self.token_model_map[model] = []200 if sso not in self.token_status_map:201 self.token_status_map[sso] = {}202 203 existing_token_entry = next((entry for entry in self.token_model_map[model] if entry["token"] == token), None)204 205 if not existing_token_entry:206 self.token_model_map[model].append({207 "token": token,208 "RequestCount": 0,209 "AddedTime": int(time.time() * 1000),210 "StartCallTime": None211 })212 213 if model not in self.token_status_map[sso]:214 self.token_status_map[sso][model] = {215 "isValid": True,216 "invalidatedTime": None,217 "totalRequestCount": 0218 }219 if not isinitialization:220 self.save_token_status()221 222 def set_token(self, token):223 models = list(self.model_config.keys())224 self.token_model_map = {model: [{225 "token": token,226 "RequestCount": 0,227 "AddedTime": int(time.time() * 1000),228 "StartCallTime": None229 }] for model in models}230 231 sso = token.split("sso=")[1].split(";")[0]232 self.token_status_map[sso] = {model: {233 "isValid": True,234 "invalidatedTime": None,235 "totalRequestCount": 0236 } for model in models}237 238 def delete_token(self, token):239 try:240 sso = token.split("sso=")[1].split(";")[0]241 for model in self.token_model_map:242 self.token_model_map[model] = [entry for entry in self.token_model_map[model] if entry["token"] != token]243 244 if sso in self.token_status_map:245 del self.token_status_map[sso]246 247 self.save_token_status()248 249 logger.info(f"令牌已成功移除: {token}", "TokenManager")250 return True251 except Exception as error:252 logger.error(f"令牌删除失败: {str(error)}")253 return False254 def reduce_token_request_count(self, model_id, count):255 try:256 normalized_model = self.normalize_model_name(model_id)257 258 if normalized_model not in self.token_model_map:259 logger.error(f"模型 {normalized_model} 不存在", "TokenManager")260 return False261 262 if not self.token_model_map[normalized_model]:263 logger.error(f"模型 {normalized_model} 没有可用的token", "TokenManager")264 return False265 266 token_entry = self.token_model_map[normalized_model][0]267 268 # 确保RequestCount不会小于0269 new_count = max(0, token_entry["RequestCount"] - count)270 reduction = token_entry["RequestCount"] - new_count271 272 token_entry["RequestCount"] = new_count273 274 # 更新token状态275 if token_entry["token"]:276 sso = token_entry["token"].split("sso=")[1].split(";")[0]277 if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:278 self.token_status_map[sso][normalized_model]["totalRequestCount"] = max(279 0, 280 self.token_status_map[sso][normalized_model]["totalRequestCount"] - reduction281 )282 return True283 284 except Exception as error:285 logger.error(f"重置校对token请求次数时发生错误: {str(error)}", "TokenManager")286 return False287 def get_next_token_for_model(self, model_id, is_return=False):288 normalized_model = self.normalize_model_name(model_id)289 290 if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:291 return None292 293 token_entry = self.token_model_map[normalized_model][0]294 if is_return:295 return token_entry["token"]296 297 if token_entry:298 if token_entry["StartCallTime"] is None:299 token_entry["StartCallTime"] = int(time.time() * 1000)300 301 if not self.token_reset_switch:302 self.start_token_reset_process()303 self.token_reset_switch = True304 305 token_entry["RequestCount"] += 1306 307 if token_entry["RequestCount"] > self.model_config[normalized_model]["RequestFrequency"]:308 self.remove_token_from_model(normalized_model, token_entry["token"])309 next_token_entry = self.token_model_map[normalized_model][0] if self.token_model_map[normalized_model] else None310 return next_token_entry["token"] if next_token_entry else None311 312 sso = token_entry["token"].split("sso=")[1].split(";")[0]313 if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:314 if token_entry["RequestCount"] == self.model_config[normalized_model]["RequestFrequency"]:315 self.token_status_map[sso][normalized_model]["isValid"] = False316 self.token_status_map[sso][normalized_model]["invalidatedTime"] = int(time.time() * 1000)317 self.token_status_map[sso][normalized_model]["totalRequestCount"] += 1318 319 self.save_token_status()320 321 return token_entry["token"]322 323 return None324 325 def remove_token_from_model(self, model_id, token):326 normalized_model = self.normalize_model_name(model_id)327 328 if normalized_model not in self.token_model_map:329 logger.error(f"模型 {normalized_model} 不存在", "TokenManager")330 return False331 332 model_tokens = self.token_model_map[normalized_model]333 token_index = next((i for i, entry in enumerate(model_tokens) if entry["token"] == token), -1)334 335 if token_index != -1:336 removed_token_entry = model_tokens.pop(token_index)337 self.expired_tokens.add((338 removed_token_entry["token"],339 normalized_model,340 int(time.time() * 1000)341 ))342 343 if not self.token_reset_switch:344 self.start_token_reset_process()345 self.token_reset_switch = True346 347 logger.info(f"模型{model_id}的令牌已失效,已成功移除令牌: {token}", "TokenManager")348 return True349 350 logger.error(f"在模型 {normalized_model} 中未找到 token: {token}", "TokenManager")351 return False352 353 def get_expired_tokens(self):354 return list(self.expired_tokens)355 356 def normalize_model_name(self, model):357 if model.startswith('grok-') and 'deepsearch' not in model and 'reasoning' not in model:358 return '-'.join(model.split('-')[:2])359 return model360 361 def get_token_count_for_model(self, model_id):362 normalized_model = self.normalize_model_name(model_id)363 return len(self.token_model_map.get(normalized_model, []))364 365 def get_remaining_token_request_capacity(self):366 remaining_capacity_map = {}367 368 for model in self.model_config.keys():369 model_tokens = self.token_model_map.get(model, [])370 model_request_frequency = self.model_config[model]["RequestFrequency"]371 372 total_used_requests = sum(token_entry.get("RequestCount", 0) for token_entry in model_tokens)373 374 remaining_capacity = (len(model_tokens) * model_request_frequency) - total_used_requests375 remaining_capacity_map[model] = max(0, remaining_capacity)376 377 return remaining_capacity_map378 379 def get_token_array_for_model(self, model_id):380 normalized_model = self.normalize_model_name(model_id)381 return self.token_model_map.get(normalized_model, [])382 383 def start_token_reset_process(self):384 def reset_expired_tokens():385 now = int(time.time() * 1000)386 387 tokens_to_remove = set()388 for token_info in self.expired_tokens:389 token, model, expired_time = token_info390 expiration_time = self.model_config[model]["ExpirationTime"]391 392 if now - expired_time >= expiration_time:393 if not any(entry["token"] == token for entry in self.token_model_map.get(model, [])):394 if model not in self.token_model_map:395 self.token_model_map[model] = []396 397 self.token_model_map[model].append({398 "token": token,399 "RequestCount": 0,400 "AddedTime": now,401 "StartCallTime": None402 })403 404 sso = token.split("sso=")[1].split(";")[0]405 if sso in self.token_status_map and model in self.token_status_map[sso]:406 self.token_status_map[sso][model]["isValid"] = True407 self.token_status_map[sso][model]["invalidatedTime"] = None408 self.token_status_map[sso][model]["totalRequestCount"] = 0409 410 tokens_to_remove.add(token_info)411 412 self.expired_tokens -= tokens_to_remove413 414 for model in self.model_config.keys():415 if model not in self.token_model_map:416 continue417 418 for token_entry in self.token_model_map[model]:419 if not token_entry.get("StartCallTime"):420 continue421 422 expiration_time = self.model_config[model]["ExpirationTime"]423 if now - token_entry["StartCallTime"] >= expiration_time:424 sso = token_entry["token"].split("sso=")[1].split(";")[0]425 if sso in self.token_status_map and model in self.token_status_map[sso]:426 self.token_status_map[sso][model]["isValid"] = True427 self.token_status_map[sso][model]["invalidatedTime"] = None428 self.token_status_map[sso][model]["totalRequestCount"] = 0429 430 token_entry["RequestCount"] = 0431 token_entry["StartCallTime"] = None432 433 import threading434 # 启动一个线程执行定时任务,每小时执行一次435 def run_timer():436 while True:437 reset_expired_tokens()438 time.sleep(3600)439 440 timer_thread = threading.Thread(target=run_timer)441 timer_thread.daemon = True442 timer_thread.start()443 444 def get_all_tokens(self):445 all_tokens = set()446 for model_tokens in self.token_model_map.values():447 for entry in model_tokens:448 all_tokens.add(entry["token"])449 return list(all_tokens)450 def get_current_token(self, model_id):451 normalized_model = self.normalize_model_name(model_id)452 453 if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:454 return None455 456 token_entry = self.token_model_map[normalized_model][0]457 return token_entry["token"]458 459 def get_token_status_map(self):460 return self.token_status_map461 462class Utils:463 @staticmethod464 def organize_search_results(search_results):465 if not search_results or 'results' not in search_results:466 return ''467 468 results = search_results['results']469 formatted_results = []470 471 for index, result in enumerate(results):472 title = result.get('title', '未知标题')473 url = result.get('url', '#')474 preview = result.get('preview', '无预览内容')475 476 formatted_result = f"\r\n<details><summary>资料[{index}]: {title}</summary>\r\n{preview}\r\n\n[Link]({url})\r\n</details>"477 formatted_results.append(formatted_result)478 479 return '\n\n'.join(formatted_results)480 481 @staticmethod482 def create_auth_headers(model, is_return=False):483 return token_manager.get_next_token_for_model(model, is_return)484 485 @staticmethod486 def get_proxy_options():487 proxy = CONFIG["API"]["PROXY"]488 proxy_options = {}489 490 if proxy:491 logger.info(f"使用代理: {proxy}", "Server")492 493 if proxy.startswith("socks5://"):494 proxy_options["proxy"] = proxy495 496 if '@' in proxy:497 auth_part = proxy.split('@')[0].split('://')[1]498 if ':' in auth_part:499 username, password = auth_part.split(':')500 proxy_options["proxy_auth"] = (username, password)501 else:502 proxy_options["proxies"] = {"https": proxy, "http": proxy} 503 return proxy_options504 505class GrokApiClient:506 def __init__(self, model_id):507 if model_id not in CONFIG["MODELS"]:508 raise ValueError(f"不支持的模型: {model_id}")509 self.model_id = CONFIG["MODELS"][model_id]510 511 def process_message_content(self, content):512 if isinstance(content, str):513 return content514 return None515 516 def get_image_type(self, base64_string):517 mime_type = 'image/jpeg'518 if 'data:image' in base64_string:519 import re520 matches = re.search(r'data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,', base64_string)521 if matches:522 mime_type = matches.group(1)523 524 extension = mime_type.split('/')[1]525 file_name = f"image.{extension}"526 527 return {528 "mimeType": mime_type,529 "fileName": file_name530 }531 def upload_base64_file(self, message, model):532 try:533 message_base64 = base64.b64encode(message.encode('utf-8')).decode('utf-8')534 upload_data = {535 "fileName": "message.txt",536 "fileMimeType": "text/plain",537 "content": message_base64538 }539 540 logger.info("发送文字文件请求", "Server")541 cookie = f"{Utils.create_auth_headers(model, True)};{CONFIG['SERVER']['CF_CLEARANCE']}" 542 proxy_options = Utils.get_proxy_options()543 response = curl_requests.post(544 "https://grok.com/rest/app-chat/upload-file",545 headers={546 **DEFAULT_HEADERS,547 "Cookie":cookie548 },549 json=upload_data,550 impersonate="chrome133a",551 **proxy_options552 )553 554 if response.status_code != 200:555 logger.error(f"上传文件失败,状态码:{response.status_code}", "Server")556 raise Exception(f"上传文件失败,状态码:{response.status_code}")557 558 result = response.json()559 logger.info(f"上传文件成功: {result}", "Server")560 return result.get("fileMetadataId", "")561 562 except Exception as error:563 logger.error(str(error), "Server")564 raise Exception(f"上传文件失败,状态码:{response.status_code}")565 def upload_base64_image(self, base64_data, url):566 try:567 if 'data:image' in base64_data:568 image_buffer = base64_data.split(',')[1]569 else:570 image_buffer = base64_data571 572 image_info = self.get_image_type(base64_data)573 mime_type = image_info["mimeType"]574 file_name = image_info["fileName"]575 576 upload_data = {577 "rpc": "uploadFile",578 "req": {579 "fileName": file_name,580 "fileMimeType": mime_type,581 "content": image_buffer582 }583 }584 585 logger.info("发送图片请求", "Server")586 587 proxy_options = Utils.get_proxy_options()588 response = curl_requests.post(589 url,590 headers={591 **DEFAULT_HEADERS,592 "Cookie":CONFIG["SERVER"]['COOKIE']593 },594 json=upload_data,595 impersonate="chrome133a",596 **proxy_options597 )598 599 if response.status_code != 200:600 logger.error(f"上传图片失败,状态码:{response.status_code}", "Server")601 return ''602 603 result = response.json()604 logger.info(f"上传图片成功: {result}", "Server")605 return result.get("fileMetadataId", "")606 607 except Exception as error:608 logger.error(str(error), "Server")609 return ''610 # def convert_system_messages(self, messages):611 # try:612 # system_prompt = []613 # i = 0614 # while i < len(messages):615 # if messages[i].get('role') != 'system':616 # break617 618 # system_prompt.append(self.process_message_content(messages[i].get('content')))619 # i += 1620 621 # messages = messages[i:]622 # system_prompt = '\n'.join(system_prompt)623 624 # if not messages:625 # raise ValueError("没有找到用户或者AI消息")626 # return {"system_prompt":system_prompt,"messages":messages}627 # except Exception as error:628 # logger.error(str(error), "Server")629 # raise ValueError(error)630 def prepare_chat_request(self, request):631 if ((request["model"] == 'grok-2-imageGen' or request["model"] == 'grok-3-imageGen') and632 not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"] and633 request.get("stream", False)):634 raise ValueError("该模型流式输出需要配置PICGO或者TUMY图床密钥!")635 636 # system_message, todo_messages = self.convert_system_messages(request["messages"]).values()637 todo_messages = request["messages"]638 if request["model"] in ['grok-2-imageGen', 'grok-3-imageGen', 'grok-3-deepsearch']:639 last_message = todo_messages[-1]640 if last_message["role"] != 'user':641 raise ValueError('此模型最后一条消息必须是用户消息!')642 todo_messages = [last_message]643 file_attachments = []644 messages = ''645 last_role = None646 last_content = ''647 message_length = 0648 convert_to_file = False649 last_message_content = ''650 search = request["model"] in ['grok-2-search', 'grok-3-search']651 deepsearchPreset = ''652 if request["model"] == 'grok-3-deepsearch':653 deepsearchPreset = 'default'654 elif request["model"] == 'grok-3-deepersearch':655 deepsearchPreset = 'deeper'656 657 # 移除<think>标签及其内容和base64图片658 def remove_think_tags(text):659 import re660 text = re.sub(r'<think>[\s\S]*?<\/think>', '', text).strip()661 text = re.sub(r'!\[image\]\(data:.*?base64,.*?\)', '[图片]', text)662 return text663 664 def process_content(content):665 if isinstance(content, list):666 text_content = ''667 for item in content:668 if item["type"] == 'image_url':669 text_content += ("[图片]" if not text_content else '\n[图片]')670 elif item["type"] == 'text':671 text_content += (remove_think_tags(item["text"]) if not text_content else '\n' + remove_think_tags(item["text"]))672 return text_content673 elif isinstance(content, dict) and content is not None:674 if content["type"] == 'image_url':675 return "[图片]"676 elif content["type"] == 'text':677 return remove_think_tags(content["text"])678 return remove_think_tags(self.process_message_content(content))679 for current in todo_messages:680 role = 'assistant' if current["role"] == 'assistant' else 'user'681 is_last_message = current == todo_messages[-1]682 683 if is_last_message and "content" in current:684 if isinstance(current["content"], list):685 for item in current["content"]:686 if item["type"] == 'image_url':687 processed_image = self.upload_base64_image(688 item["image_url"]["url"],689 f"{CONFIG['API']['BASE_URL']}/api/rpc"690 )691 if processed_image:692 file_attachments.append(processed_image)693 elif isinstance(current["content"], dict) and current["content"].get("type") == 'image_url':694 processed_image = self.upload_base64_image(695 current["content"]["image_url"]["url"],696 f"{CONFIG['API']['BASE_URL']}/api/rpc"697 )698 if processed_image:699 file_attachments.append(processed_image)700 701 702 text_content = process_content(current.get("content", ""))703 if is_last_message and convert_to_file:704 last_message_content = f"{role.upper()}: {text_content or '[图片]'}\n"705 continue706 if text_content or (is_last_message and file_attachments):707 if role == last_role and text_content:708 last_content += '\n' + text_content709 messages = messages[:messages.rindex(f"{role.upper()}: ")] + f"{role.upper()}: {last_content}\n"710 else:711 messages += f"{role.upper()}: {text_content or '[图片]'}\n"712 last_content = text_content713 last_role = role714 message_length += len(messages)715 if message_length >= 40000:716 convert_to_file = True717 718 if convert_to_file:719 file_id = self.upload_base64_file(messages, request["model"])720 if file_id:721 file_attachments.insert(0, file_id)722 messages = last_message_content.strip()723 if messages.strip() == '':724 if convert_to_file:725 messages = '基于txt文件内容进行回复:'726 else:727 raise ValueError('消息内容为空!')728 return {729 "temporary": CONFIG["API"].get("IS_TEMP_CONVERSATION", False),730 "modelName": self.model_id,731 "message": messages.strip(),732 "fileAttachments": file_attachments[:4],733 "imageAttachments": [],734 "disableSearch": False,735 "enableImageGeneration": True,736 "returnImageBytes": False,737 "returnRawGrokInXaiRequest": False,738 "enableImageStreaming": False,739 "imageGenerationCount": 1,740 "forceConcise": False,741 "toolOverrides": {742 "imageGen": request["model"] in ['grok-2-imageGen', 'grok-3-imageGen'],743 "webSearch": search,744 "xSearch": search,745 "xMediaSearch": search,746 "trendsSearch": search,747 "xPostAnalyze": search748 },749 "enableSideBySide": True,750 "sendFinalMetadata": True,751 "customPersonality": "",752 "deepsearchPreset": deepsearchPreset,753 "isReasoning": request["model"] == 'grok-3-reasoning',754 "disableTextFollowUps": True755 }756 757class MessageProcessor:758 @staticmethod759 def create_chat_response(message, model, is_stream=False):760 base_response = {761 "id": f"chatcmpl-{uuid.uuid4()}",762 "created": int(time.time()),763 "model": model764 }765 766 if is_stream:767 return {768 **base_response,769 "object": "chat.completion.chunk",770 "choices": [{771 "index": 0,772 "delta": {773 "content": message774 }775 }]776 }777 778 return {779 **base_response,780 "object": "chat.completion",781 "choices": [{782 "index": 0,783 "message": {784 "role": "assistant",785 "content": message786 },787 "finish_reason": "stop"788 }],789 "usage": None790 }791 792def process_model_response(response, model):793 result = {"token": None, "imageUrl": None}794 795 if CONFIG["IS_IMG_GEN"]:796 if response.get("cachedImageGenerationResponse") and not CONFIG["IS_IMG_GEN2"]:797 result["imageUrl"] = response["cachedImageGenerationResponse"]["imageUrl"]798 return result799 800 if model == 'grok-2':801 result["token"] = response.get("token")802 elif model in ['grok-2-search', 'grok-3-search']:803 if response.get("webSearchResults") and CONFIG["ISSHOW_SEARCH_RESULTS"]:804 result["token"] = f"\r\n<think>{Utils.organize_search_results(response['webSearchResults'])}</think>\r\n"805 else:806 result["token"] = response.get("token")807 elif model == 'grok-3':808 result["token"] = response.get("token")809 elif model in ['grok-3-deepsearch', 'grok-3-deepersearch']:810 if response.get("messageStepId") and not CONFIG["SHOW_THINKING"]:811 return result812 if response.get("messageStepId") and not CONFIG["IS_THINKING"]:813 result["token"] = "<think>" + response.get("token", "")814 CONFIG["IS_THINKING"] = True815 elif not response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "final":816 result["token"] = "</think>" + response.get("token", "")817 CONFIG["IS_THINKING"] = False818 elif (response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "assistant") or response.get("messageTag") == "final":819 result["token"] = response.get("token","")820 elif (CONFIG["IS_THINKING"] and response.get("token","").get("action","") == "webSearch"):821 result["token"] = response.get("token","").get("action_input","").get("query","") 822 elif (CONFIG["IS_THINKING"] and response.get("webSearchResults")):823 result["token"] = Utils.organize_search_results(response['webSearchResults'])824 elif model == 'grok-3-reasoning':825 if response.get("isThinking") and not CONFIG["SHOW_THINKING"]:826 return result827 828 if response.get("isThinking") and not CONFIG["IS_THINKING"]:829 result["token"] = "<think>" + response.get("token", "")830 CONFIG["IS_THINKING"] = True831 elif not response.get("isThinking") and CONFIG["IS_THINKING"]:832 result["token"] = "</think>" + response.get("token", "")833 CONFIG["IS_THINKING"] = False834 else:835 result["token"] = response.get("token")836 837 return result838 839def handle_image_response(image_url):840 max_retries = 2841 retry_count = 0842 image_base64_response = None843 844 while retry_count < max_retries:845 try:846 proxy_options = Utils.get_proxy_options()847 image_base64_response = curl_requests.get(848 f"https://assets.grok.com/{image_url}",849 headers={850 **DEFAULT_HEADERS,851 "Cookie":CONFIG["SERVER"]['COOKIE']852 },853 impersonate="chrome133a",854 **proxy_options855 )856 857 if image_base64_response.status_code == 200:858 break859 860 retry_count += 1861 if retry_count == max_retries:862 raise Exception(f"上游服务请求失败! status: {image_base64_response.status_code}")863 864 time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)865 866 except Exception as error:867 logger.error(str(error), "Server")868 retry_count += 1869 if retry_count == max_retries:870 raise871 872 time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)873 874 image_buffer = image_base64_response.content875 876 if not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"]:877 base64_image = base64.b64encode(image_buffer).decode('utf-8')878 image_content_type = image_base64_response.headers.get('content-type', 'image/jpeg')879 return f""880 881 logger.info("开始上传图床", "Server")882 883 if CONFIG["API"]["PICGO_KEY"]:884 files = {'source': ('image.jpg', image_buffer, 'image/jpeg')}885 headers = {886 "X-API-Key": CONFIG["API"]["PICGO_KEY"]887 }888 889 response_url = requests.post(890 "https://www.picgo.net/api/1/upload",891 files=files,892 headers=headers893 )894 895 if response_url.status_code != 200:896 return "生图失败,请查看PICGO图床密钥是否设置正确"897 else:898 logger.info("生图成功", "Server")899 result = response_url.json()900 return f""901 902 903 elif CONFIG["API"]["TUMY_KEY"]:904 files = {'file': ('image.jpg', image_buffer, 'image/jpeg')}905 headers = {906 "Accept": "application/json",907 'Authorization': f"Bearer {CONFIG['API']['TUMY_KEY']}"908 }909 910 response_url = requests.post(911 "https://tu.my/api/v1/upload",912 files=files,913 headers=headers914 )915 916 if response_url.status_code != 200:917 return "生图失败,请查看TUMY图床密钥是否设置正确"918 else:919 try:920 result = response_url.json()921 logger.info("生图成功", "Server")922 return f""923 except Exception as error:924 logger.error(str(error), "Server")925 return "生图失败,请查看TUMY图床密钥是否设置正确"926 927def handle_non_stream_response(response, model):928 try:929 logger.info("开始处理非流式响应", "Server")930 931 stream = response.iter_lines()932 full_response = ""933 934 CONFIG["IS_THINKING"] = False935 CONFIG["IS_IMG_GEN"] = False936 CONFIG["IS_IMG_GEN2"] = False937 938 for chunk in stream:939 if not chunk:940 continue941 try:942 line_json = json.loads(chunk.decode("utf-8").strip())943 if line_json.get("error"):944 logger.error(json.dumps(line_json, indent=2), "Server")945 return json.dumps({"error": "RateLimitError"}) + "\n\n"946 947 response_data = line_json.get("result", {}).get("response")948 if not response_data:949 continue950 951 if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):952 CONFIG["IS_IMG_GEN"] = True953 954 result = process_model_response(response_data, model)955 956 if result["token"]:957 full_response += result["token"]958 959 if result["imageUrl"]:960 CONFIG["IS_IMG_GEN2"] = True961 return handle_image_response(result["imageUrl"])962 963 except json.JSONDecodeError:964 continue965 except Exception as e:966 logger.error(f"处理流式响应行时出错: {str(e)}", "Server")967 continue968 969 return full_response970 except Exception as error:971 logger.error(str(error), "Server")972 raise973def handle_stream_response(response, model):974 def generate():975 logger.info("开始处理流式响应", "Server")976 977 stream = response.iter_lines()978 CONFIG["IS_THINKING"] = False979 CONFIG["IS_IMG_GEN"] = False980 CONFIG["IS_IMG_GEN2"] = False981 982 for chunk in stream:983 if not chunk:984 continue985 try:986 line_json = json.loads(chunk.decode("utf-8").strip())987 print(line_json)988 if line_json.get("error"):989 logger.error(json.dumps(line_json, indent=2), "Server")990 yield json.dumps({"error": "RateLimitError"}) + "\n\n"991 return992 993 response_data = line_json.get("result", {}).get("response")994 if not response_data:995 continue996 997 if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):998 CONFIG["IS_IMG_GEN"] = True999 1000 result = process_model_response(response_data, model)1001 1002 if result["token"]:1003 yield f"data: {json.dumps(MessageProcessor.create_chat_response(result['token'], model, True))}\n\n"1004 1005 if result["imageUrl"]:1006 CONFIG["IS_IMG_GEN2"] = True1007 image_data = handle_image_response(result["imageUrl"])1008 yield f"data: {json.dumps(MessageProcessor.create_chat_response(image_data, model, True))}\n\n"1009 1010 except json.JSONDecodeError:1011 continue1012 except Exception as e:1013 logger.error(f"处理流式响应行时出错: {str(e)}", "Server")1014 continue1015 1016 yield "data: [DONE]\n\n"1017 return generate()1018 1019def initialization():1020 sso_array = os.environ.get("SSO", "").split(',')1021 logger.info("开始加载令牌", "Server")1022 token_manager.load_token_status()1023 for sso in sso_array:1024 if sso:1025 token_manager.add_token(f"sso-rw={sso};sso={sso}",True)1026 token_manager.save_token_status()1027 1028 logger.info(f"成功加载令牌: {json.dumps(token_manager.get_all_tokens(), indent=2)}", "Server")1029 logger.info(f"令牌加载完成,共加载: {len(token_manager.get_all_tokens())}个令牌", "Server")1030 1031 if CONFIG["API"]["PROXY"]:1032 logger.info(f"代理已设置: {CONFIG['API']['PROXY']}", "Server")1033 1034logger.info("初始化完成", "Server")1035 1036 1037app = Flask(__name__)1038app.wsgi_app = ProxyFix(app.wsgi_app)1039app.secret_key = os.environ.get('FLASK_SECRET_KEY') or secrets.token_hex(16)1040app.json.sort_keys = False1041 1042@app.route('/manager/login', methods=['GET', 'POST'])1043def manager_login():1044 if CONFIG["ADMIN"]["MANAGER_SWITCH"]:1045 if request.method == 'POST':1046 password = request.form.get('password')1047 if password == CONFIG["ADMIN"]["PASSWORD"]:1048 session['is_logged_in'] = True1049 return redirect('/manager')1050 return render_template('login.html', error=True)1051 return render_template('login.html', error=False)1052 else:1053 return redirect('/')1054 1055def check_auth():1056 return session.get('is_logged_in', False)1057 1058@app.route('/manager')1059def manager():1060 if not check_auth():1061 return redirect('/manager/login')1062 return render_template('manager.html')1063 1064@app.route('/manager/api/get')1065def get_manager_tokens():1066 if not check_auth():1067 return jsonify({"error": "Unauthorized"}), 4011068 return jsonify(token_manager.get_token_status_map())1069 1070@app.route('/manager/api/add', methods=['POST'])1071def add_manager_token():1072 if not check_auth():1073 return jsonify({"error": "Unauthorized"}), 4011074 try:1075 sso = request.json.get('sso')1076 if not sso:1077 return jsonify({"error": "SSO token is required"}), 4001078 token_manager.add_token(f"sso-rw={sso};sso={sso}")1079 return jsonify({"success": True})1080 except Exception as e:1081 return jsonify({"error": str(e)}), 5001082 1083@app.route('/manager/api/delete', methods=['POST'])1084def delete_manager_token():1085 if not check_auth():1086 return jsonify({"error": "Unauthorized"}), 4011087 try:1088 sso = request.json.get('sso')1089 if not sso:1090 return jsonify({"error": "SSO token is required"}), 4001091 token_manager.delete_token(f"sso-rw={sso};sso={sso}")1092 return jsonify({"success": True})1093 except Exception as e:1094 return jsonify({"error": str(e)}), 5001095 1096@app.route('/manager/api/cf_clearance', methods=['POST']) 1097def setCf_Manager_clearance():1098 if not check_auth():1099 return jsonify({"error": "Unauthorized"}), 4011100 try:1101 cf_clearance = request.json.get('cf_clearance')1102 if not cf_clearance:1103 return jsonify({"error": "cf_clearance is required"}), 4001104 CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance1105 return jsonify({"success": True})1106 except Exception as e:1107 return jsonify({"error": str(e)}), 5001108 1109 1110@app.route('/get/tokens', methods=['GET'])1111def get_tokens():1112 auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')1113 if CONFIG["API"]["IS_CUSTOM_SSO"]:1114 return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 4031115 elif auth_token != CONFIG["API"]["API_KEY"]:1116 return jsonify({"error": 'Unauthorized'}), 4011117 return jsonify(token_manager.get_token_status_map())1118 1119@app.route('/add/token', methods=['POST'])1120def add_token():1121 auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')1122 if CONFIG["API"]["IS_CUSTOM_SSO"]:1123 return jsonify({"error": '自定义的SSO令牌模式无法添加sso令牌'}), 4031124 elif auth_token != CONFIG["API"]["API_KEY"]:1125 return jsonify({"error": 'Unauthorized'}), 4011126 1127 try:1128 sso = request.json.get('sso')1129 token_manager.add_token(f"sso-rw={sso};sso={sso}")1130 return jsonify(token_manager.get_token_status_map().get(sso, {})), 2001131 except Exception as error:1132 logger.error(str(error), "Server")1133 return jsonify({"error": '添加sso令牌失败'}), 5001134 1135@app.route('/set/cf_clearance', methods=['POST'])1136def setCf_clearance():1137 auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')1138 if auth_token != CONFIG["API"]["API_KEY"]:1139 return jsonify({"error": 'Unauthorized'}), 4011140 try:1141 cf_clearance = request.json.get('cf_clearance')1142 CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance1143 return jsonify({"message": '设置cf_clearance成功'}), 2001144 except Exception as error:1145 logger.error(str(error), "Server")1146 return jsonify({"error": '设置cf_clearance失败'}), 5001147 1148@app.route('/delete/token', methods=['POST'])1149def delete_token():1150 auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')1151 if CONFIG["API"]["IS_CUSTOM_SSO"]:1152 return jsonify({"error": '自定义的SSO令牌模式无法删除sso令牌'}), 4031153 elif auth_token != CONFIG["API"]["API_KEY"]:1154 return jsonify({"error": 'Unauthorized'}), 4011155 1156 try:1157 sso = request.json.get('sso')1158 token_manager.delete_token(f"sso-rw={sso};sso={sso}")1159 return jsonify({"message": '删除sso令牌成功'}), 2001160 except Exception as error:1161 logger.error(str(error), "Server")1162 return jsonify({"error": '删除sso令牌失败'}), 5001163 1164@app.route('/v1/models', methods=['GET'])1165def get_models():1166 return jsonify({1167 "object": "list",1168 "data": [1169 {1170 "id": model,1171 "object": "model",1172 "created": int(time.time()),1173 "owned_by": "grok"1174 }1175 for model in CONFIG["MODELS"].keys()1176 ]1177 })1178 1179@app.route('/v1/chat/completions', methods=['POST'])1180def chat_completions():1181 response_status_code = 5001182 try:1183 auth_token = request.headers.get('Authorization',1184 '').replace('Bearer ', '')1185 if auth_token:1186 if CONFIG["API"]["IS_CUSTOM_SSO"]:1187 result = f"sso={auth_token};sso-rw={auth_token}"1188 token_manager.set_token(result)1189 elif auth_token != CONFIG["API"]["API_KEY"]:1190 return jsonify({"error": 'Unauthorized'}), 4011191 else:1192 return jsonify({"error": 'API_KEY缺失'}), 4011193 1194 data = request.json1195 model = data.get("model")1196 stream = data.get("stream", False)1197 1198 retry_count = 01199 grok_client = GrokApiClient(model)1200 request_payload = grok_client.prepare_chat_request(data)