apollor/GrokPythonService
0
1import os2import json3import uuid4import time5import base646import sys7import inspect8import secrets9from loguru import logger10from dotenv import load_dotenv11 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 17current_dir = os.path.dirname(os.path.abspath(__file__))18env_path = os.path.join(current_dir, '.env')19 20load_dotenv(env_path)21 22class 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=True41 )42 43 self.logger = logger44 45 def _get_caller_info(self):46 frame = inspect.currentframe()47 try:48 caller_frame = frame.f_back.f_back49 full_path = caller_frame.f_code.co_filename50 function = caller_frame.f_code.co_name51 lineno = caller_frame.f_lineno52 53 filename = os.path.basename(full_path)54 55 return {56 'filename': filename,57 'function': function,58 'lineno': lineno59 }60 finally:61 del frame62 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 87logger = Logger(level="INFO")88 89 90CONFIG = {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 None111 },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": 2124 },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 133DEFAULT_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 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',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 'Baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'149}150 151class AuthTokenManager:152 def __init__(self):153 self.token_model_map = {}154 self.expired_tokens = set()155 self.token_status_map = {}156 157 self.model_config = {158 "grok-2": {159 "RequestFrequency": 30,160 "ExpirationTime": 1 * 60 * 60 * 1000 # 1小时161 },162 "grok-3": {163 "RequestFrequency": 20,164 "ExpirationTime": 2 * 60 * 60 * 1000 # 2小时165 },166 "grok-3-deepsearch": {167 "RequestFrequency": 10,168 "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时169 },170 "grok-3-reasoning": {171 "RequestFrequency": 10,172 "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时173 }174 }175 self.token_reset_switch = False176 self.token_reset_timer = None177 178 def add_token(self, token):179 sso = token.split("sso=")[1].split(";")[0]180 for model in self.model_config.keys():181 if model not in self.token_model_map:182 self.token_model_map[model] = []183 if sso not in self.token_status_map:184 self.token_status_map[sso] = {}185 186 existing_token_entry = next((entry for entry in self.token_model_map[model] if entry["token"] == token), None)187 188 if not existing_token_entry:189 self.token_model_map[model].append({190 "token": token,191 "RequestCount": 0,192 "AddedTime": int(time.time() * 1000),193 "StartCallTime": None194 })195 196 if model not in self.token_status_map[sso]:197 self.token_status_map[sso][model] = {198 "isValid": True,199 "invalidatedTime": None,200 "totalRequestCount": 0201 }202 203 def set_token(self, token):204 models = list(self.model_config.keys())205 self.token_model_map = {model: [{206 "token": token,207 "RequestCount": 0,208 "AddedTime": int(time.time() * 1000),209 "StartCallTime": None210 }] for model in models}211 212 sso = token.split("sso=")[1].split(";")[0]213 self.token_status_map[sso] = {model: {214 "isValid": True,215 "invalidatedTime": None,216 "totalRequestCount": 0217 } for model in models}218 219 def delete_token(self, token):220 try:221 sso = token.split("sso=")[1].split(";")[0]222 for model in self.token_model_map:223 self.token_model_map[model] = [entry for entry in self.token_model_map[model] if entry["token"] != token]224 225 if sso in self.token_status_map:226 del self.token_status_map[sso]227 228 logger.info(f"令牌已成功移除: {token}", "TokenManager")229 return True230 except Exception as error:231 logger.error(f"令牌删除失败: {str(error)}")232 return False233 def reduce_token_request_count(self, model_id, count):234 try:235 normalized_model = self.normalize_model_name(model_id)236 237 if normalized_model not in self.token_model_map:238 logger.error(f"模型 {normalized_model} 不存在", "TokenManager")239 return False240 241 if not self.token_model_map[normalized_model]:242 logger.error(f"模型 {normalized_model} 没有可用的token", "TokenManager")243 return False244 245 token_entry = self.token_model_map[normalized_model][0]246 247 # 确保RequestCount不会小于0248 new_count = max(0, token_entry["RequestCount"] - count)249 reduction = token_entry["RequestCount"] - new_count250 251 token_entry["RequestCount"] = new_count252 253 # 更新token状态254 if token_entry["token"]:255 sso = token_entry["token"].split("sso=")[1].split(";")[0]256 if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:257 self.token_status_map[sso][normalized_model]["totalRequestCount"] = max(258 0, 259 self.token_status_map[sso][normalized_model]["totalRequestCount"] - reduction260 )261 return True262 263 except Exception as error:264 logger.error(f"重置校对token请求次数时发生错误: {str(error)}", "TokenManager")265 return False266 def get_next_token_for_model(self, model_id, is_return=False):267 normalized_model = self.normalize_model_name(model_id)268 269 if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:270 return None271 272 token_entry = self.token_model_map[normalized_model][0]273 if is_return:274 return token_entry["token"]275 276 if token_entry:277 if token_entry["StartCallTime"] is None:278 token_entry["StartCallTime"] = int(time.time() * 1000)279 280 if not self.token_reset_switch:281 self.start_token_reset_process()282 self.token_reset_switch = True283 284 token_entry["RequestCount"] += 1285 286 if token_entry["RequestCount"] > self.model_config[normalized_model]["RequestFrequency"]:287 self.remove_token_from_model(normalized_model, token_entry["token"])288 next_token_entry = self.token_model_map[normalized_model][0] if self.token_model_map[normalized_model] else None289 return next_token_entry["token"] if next_token_entry else None290 291 sso = token_entry["token"].split("sso=")[1].split(";")[0]292 if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:293 if token_entry["RequestCount"] == self.model_config[normalized_model]["RequestFrequency"]:294 self.token_status_map[sso][normalized_model]["isValid"] = False295 self.token_status_map[sso][normalized_model]["invalidatedTime"] = int(time.time() * 1000)296 self.token_status_map[sso][normalized_model]["totalRequestCount"] += 1297 298 return token_entry["token"]299 300 return None301 302 def remove_token_from_model(self, model_id, token):303 normalized_model = self.normalize_model_name(model_id)304 305 if normalized_model not in self.token_model_map:306 logger.error(f"模型 {normalized_model} 不存在", "TokenManager")307 return False308 309 model_tokens = self.token_model_map[normalized_model]310 token_index = next((i for i, entry in enumerate(model_tokens) if entry["token"] == token), -1)311 312 if token_index != -1:313 removed_token_entry = model_tokens.pop(token_index)314 self.expired_tokens.add((315 removed_token_entry["token"],316 normalized_model,317 int(time.time() * 1000)318 ))319 320 if not self.token_reset_switch:321 self.start_token_reset_process()322 self.token_reset_switch = True323 324 logger.info(f"模型{model_id}的令牌已失效,已成功移除令牌: {token}", "TokenManager")325 return True326 327 logger.error(f"在模型 {normalized_model} 中未找到 token: {token}", "TokenManager")328 return False329 330 def get_expired_tokens(self):331 return list(self.expired_tokens)332 333 def normalize_model_name(self, model):334 if model.startswith('grok-') and 'deepsearch' not in model and 'reasoning' not in model:335 return '-'.join(model.split('-')[:2])336 return model337 338 def get_token_count_for_model(self, model_id):339 normalized_model = self.normalize_model_name(model_id)340 return len(self.token_model_map.get(normalized_model, []))341 342 def get_remaining_token_request_capacity(self):343 remaining_capacity_map = {}344 345 for model in self.model_config.keys():346 model_tokens = self.token_model_map.get(model, [])347 model_request_frequency = self.model_config[model]["RequestFrequency"]348 349 total_used_requests = sum(token_entry.get("RequestCount", 0) for token_entry in model_tokens)350 351 remaining_capacity = (len(model_tokens) * model_request_frequency) - total_used_requests352 remaining_capacity_map[model] = max(0, remaining_capacity)353 354 return remaining_capacity_map355 356 def get_token_array_for_model(self, model_id):357 normalized_model = self.normalize_model_name(model_id)358 return self.token_model_map.get(normalized_model, [])359 360 def start_token_reset_process(self):361 def reset_expired_tokens():362 now = int(time.time() * 1000)363 364 tokens_to_remove = set()365 for token_info in self.expired_tokens:366 token, model, expired_time = token_info367 expiration_time = self.model_config[model]["ExpirationTime"]368 369 if now - expired_time >= expiration_time:370 if not any(entry["token"] == token for entry in self.token_model_map.get(model, [])):371 if model not in self.token_model_map:372 self.token_model_map[model] = []373 374 self.token_model_map[model].append({375 "token": token,376 "RequestCount": 0,377 "AddedTime": now,378 "StartCallTime": None379 })380 381 sso = token.split("sso=")[1].split(";")[0]382 if sso in self.token_status_map and model in self.token_status_map[sso]:383 self.token_status_map[sso][model]["isValid"] = True384 self.token_status_map[sso][model]["invalidatedTime"] = None385 self.token_status_map[sso][model]["totalRequestCount"] = 0386 387 tokens_to_remove.add(token_info)388 389 self.expired_tokens -= tokens_to_remove390 391 for model in self.model_config.keys():392 if model not in self.token_model_map:393 continue394 395 for token_entry in self.token_model_map[model]:396 if not token_entry.get("StartCallTime"):397 continue398 399 expiration_time = self.model_config[model]["ExpirationTime"]400 if now - token_entry["StartCallTime"] >= expiration_time:401 sso = token_entry["token"].split("sso=")[1].split(";")[0]402 if sso in self.token_status_map and model in self.token_status_map[sso]:403 self.token_status_map[sso][model]["isValid"] = True404 self.token_status_map[sso][model]["invalidatedTime"] = None405 self.token_status_map[sso][model]["totalRequestCount"] = 0406 407 token_entry["RequestCount"] = 0408 token_entry["StartCallTime"] = None409 410 import threading411 # 启动一个线程执行定时任务,每小时执行一次412 def run_timer():413 while True:414 reset_expired_tokens()415 time.sleep(3600)416 417 timer_thread = threading.Thread(target=run_timer)418 timer_thread.daemon = True419 timer_thread.start()420 421 def get_all_tokens(self):422 all_tokens = set()423 for model_tokens in self.token_model_map.values():424 for entry in model_tokens:425 all_tokens.add(entry["token"])426 return list(all_tokens)427 def get_current_token(self, model_id):428 normalized_model = self.normalize_model_name(model_id)429 430 if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:431 return None432 433 token_entry = self.token_model_map[normalized_model][0]434 return token_entry["token"]435 436 def get_token_status_map(self):437 return self.token_status_map438 439class Utils:440 @staticmethod441 def organize_search_results(search_results):442 if not search_results or 'results' not in search_results:443 return ''444 445 results = search_results['results']446 formatted_results = []447 448 for index, result in enumerate(results):449 title = result.get('title', '未知标题')450 url = result.get('url', '#')451 preview = result.get('preview', '无预览内容')452 453 formatted_result = f"\r\n<details><summary>资料[{index}]: {title}</summary>\r\n{preview}\r\n\n[Link]({url})\r\n</details>"454 formatted_results.append(formatted_result)455 456 return '\n\n'.join(formatted_results)457 458 @staticmethod459 def create_auth_headers(model, is_return=False):460 return token_manager.get_next_token_for_model(model, is_return)461 462 @staticmethod463 def get_proxy_options():464 proxy = CONFIG["API"]["PROXY"]465 proxy_options = {}466 467 if proxy:468 logger.info(f"使用代理: {proxy}", "Server")469 470 if proxy.startswith("socks5://"):471 proxy_options["proxy"] = proxy472 473 if '@' in proxy:474 auth_part = proxy.split('@')[0].split('://')[1]475 if ':' in auth_part:476 username, password = auth_part.split(':')477 proxy_options["proxy_auth"] = (username, password)478 else:479 proxy_options["proxies"] = {"https": proxy, "http": proxy} 480 return proxy_options481 482class GrokApiClient:483 def __init__(self, model_id):484 if model_id not in CONFIG["MODELS"]:485 raise ValueError(f"不支持的模型: {model_id}")486 self.model_id = CONFIG["MODELS"][model_id]487 488 def process_message_content(self, content):489 if isinstance(content, str):490 return content491 return None492 493 def get_image_type(self, base64_string):494 mime_type = 'image/jpeg'495 if 'data:image' in base64_string:496 import re497 matches = re.search(r'data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,', base64_string)498 if matches:499 mime_type = matches.group(1)500 501 extension = mime_type.split('/')[1]502 file_name = f"image.{extension}"503 504 return {505 "mimeType": mime_type,506 "fileName": file_name507 }508 def upload_base64_file(self, message, model):509 try:510 message_base64 = base64.b64encode(message.encode('utf-8')).decode('utf-8')511 upload_data = {512 "fileName": "message.txt",513 "fileMimeType": "text/plain",514 "content": message_base64515 }516 517 logger.info("发送文字文件请求", "Server")518 cookie = f"{Utils.create_auth_headers(model, True)};{CONFIG['SERVER']['CF_CLEARANCE']}" 519 proxy_options = Utils.get_proxy_options()520 response = curl_requests.post(521 "https://grok.com/rest/app-chat/upload-file",522 headers={523 **DEFAULT_HEADERS,524 "Cookie":cookie525 },526 json=upload_data,527 impersonate="chrome133a",528 verify=False,529 **proxy_options530 )531 532 if response.status_code != 200:533 logger.error(f"上传文件失败,状态码:{response.status_code}", "Server")534 raise Exception(f"上传文件失败,状态码:{response.status_code}")535 536 result = response.json()537 logger.info(f"上传文件成功: {result}", "Server")538 return result.get("fileMetadataId", "")539 540 except Exception as error:541 logger.error(str(error), "Server")542 raise Exception(f"上传文件失败,状态码:{response.status_code}")543 def upload_base64_image(self, base64_data, url):544 try:545 if 'data:image' in base64_data:546 image_buffer = base64_data.split(',')[1]547 else:548 image_buffer = base64_data549 550 image_info = self.get_image_type(base64_data)551 mime_type = image_info["mimeType"]552 file_name = image_info["fileName"]553 554 upload_data = {555 "rpc": "uploadFile",556 "req": {557 "fileName": file_name,558 "fileMimeType": mime_type,559 "content": image_buffer560 }561 }562 563 logger.info("发送图片请求", "Server")564 565 proxy_options = Utils.get_proxy_options()566 response = curl_requests.post(567 url,568 headers={569 **DEFAULT_HEADERS,570 "Cookie":CONFIG["SERVER"]['COOKIE']571 },572 json=upload_data,573 impersonate="chrome133a",574 verify=False,575 **proxy_options576 )577 578 if response.status_code != 200:579 logger.error(f"上传图片失败,状态码:{response.status_code}", "Server")580 return ''581 582 result = response.json()583 logger.info(f"上传图片成功: {result}", "Server")584 return result.get("fileMetadataId", "")585 586 except Exception as error:587 logger.error(str(error), "Server")588 return ''589 590 def prepare_chat_request(self, request):591 if ((request["model"] == 'grok-2-imageGen' or request["model"] == 'grok-3-imageGen') and592 not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"] and593 request.get("stream", False)):594 raise ValueError("该模型流式输出需要配置PICGO或者TUMY图床密钥!")595 596 todo_messages = request["messages"]597 if request["model"] in ['grok-2-imageGen', 'grok-3-imageGen', 'grok-3-deepsearch']:598 last_message = todo_messages[-1]599 if last_message["role"] != 'user':600 raise ValueError('此模型最后一条消息必须是用户消息!')601 todo_messages = [last_message]602 file_attachments = []603 messages = ''604 last_role = None605 last_content = ''606 message_length = 0607 convert_to_file = False608 last_message_content = ''609 search = request["model"] in ['grok-2-search', 'grok-3-search']610 611 # 移除<think>标签及其内容和base64图片612 def remove_think_tags(text):613 import re614 text = re.sub(r'<think>[\s\S]*?<\/think>', '', text).strip()615 text = re.sub(r'!\[image\]\(data:.*?base64,.*?\)', '[图片]', text)616 return text617 618 def process_content(content):619 if isinstance(content, list):620 text_content = ''621 for item in content:622 if item["type"] == 'image_url':623 text_content += ("[图片]" if not text_content else '\n[图片]')624 elif item["type"] == 'text':625 text_content += (remove_think_tags(item["text"]) if not text_content else '\n' + remove_think_tags(item["text"]))626 return text_content627 elif isinstance(content, dict) and content is not None:628 if content["type"] == 'image_url':629 return "[图片]"630 elif content["type"] == 'text':631 return remove_think_tags(content["text"])632 return remove_think_tags(self.process_message_content(content))633 634 for current in todo_messages:635 role = 'assistant' if current["role"] == 'assistant' else 'user'636 is_last_message = current == todo_messages[-1]637 638 if is_last_message and "content" in current:639 if isinstance(current["content"], list):640 for item in current["content"]:641 if item["type"] == 'image_url':642 processed_image = self.upload_base64_image(643 item["image_url"]["url"],644 f"{CONFIG['API']['BASE_URL']}/api/rpc"645 )646 if processed_image:647 file_attachments.append(processed_image)648 elif isinstance(current["content"], dict) and current["content"].get("type") == 'image_url':649 processed_image = self.upload_base64_image(650 current["content"]["image_url"]["url"],651 f"{CONFIG['API']['BASE_URL']}/api/rpc"652 )653 if processed_image:654 file_attachments.append(processed_image)655 656 657 text_content = process_content(current.get("content", ""))658 if is_last_message and convert_to_file:659 last_message_content = f"{role.upper()}: {text_content or '[图片]'}\n"660 continue661 if text_content or (is_last_message and file_attachments):662 if role == last_role and text_content:663 last_content += '\n' + text_content664 messages = messages[:messages.rindex(f"{role.upper()}: ")] + f"{role.upper()}: {last_content}\n"665 else:666 messages += f"{role.upper()}: {text_content or '[图片]'}\n"667 last_content = text_content668 last_role = role669 message_length += len(messages)670 if message_length >= 40000:671 convert_to_file = True672 673 if convert_to_file:674 file_id = self.upload_base64_file(messages, request["model"])675 if file_id:676 file_attachments.insert(0, file_id)677 messages = last_message_content.strip()678 if messages.strip() == '':679 if convert_to_file:680 messages = '基于txt文件内容进行回复:'681 else:682 raise ValueError('消息内容为空!')683 return {684 "temporary": CONFIG["API"].get("IS_TEMP_CONVERSATION", False),685 "modelName": self.model_id,686 "message": messages.strip(),687 "fileAttachments": file_attachments[:4],688 "imageAttachments": [],689 "disableSearch": False,690 "enableImageGeneration": True,691 "returnImageBytes": False,692 "returnRawGrokInXaiRequest": False,693 "enableImageStreaming": False,694 "imageGenerationCount": 1,695 "forceConcise": False,696 "toolOverrides": {697 "imageGen": request["model"] in ['grok-2-imageGen', 'grok-3-imageGen'],698 "webSearch": search,699 "xSearch": search,700 "xMediaSearch": search,701 "trendsSearch": search,702 "xPostAnalyze": search703 },704 "enableSideBySide": True,705 "isPreset": False,706 "sendFinalMetadata": True,707 "customInstructions": "",708 "deepsearchPreset": "default" if request["model"] == 'grok-3-deepsearch' else "",709 "isReasoning": request["model"] == 'grok-3-reasoning'710 }711 712class MessageProcessor:713 @staticmethod714 def create_chat_response(message, model, is_stream=False):715 base_response = {716 "id": f"chatcmpl-{uuid.uuid4()}",717 "created": int(time.time()),718 "model": model719 }720 721 if is_stream:722 return {723 **base_response,724 "object": "chat.completion.chunk",725 "choices": [{726 "index": 0,727 "delta": {728 "content": message729 }730 }]731 }732 733 return {734 **base_response,735 "object": "chat.completion",736 "choices": [{737 "index": 0,738 "message": {739 "role": "assistant",740 "content": message741 },742 "finish_reason": "stop"743 }],744 "usage": None745 }746 747def process_model_response(response, model):748 result = {"token": None, "imageUrl": None}749 750 if CONFIG["IS_IMG_GEN"]:751 if response.get("cachedImageGenerationResponse") and not CONFIG["IS_IMG_GEN2"]:752 result["imageUrl"] = response["cachedImageGenerationResponse"]["imageUrl"]753 return result754 755 if model == 'grok-2':756 result["token"] = response.get("token")757 elif model in ['grok-2-search', 'grok-3-search']:758 if response.get("webSearchResults") and CONFIG["ISSHOW_SEARCH_RESULTS"]:759 result["token"] = f"\r\n<think>{Utils.organize_search_results(response['webSearchResults'])}</think>\r\n"760 else:761 result["token"] = response.get("token")762 elif model == 'grok-3':763 result["token"] = response.get("token")764 elif model == 'grok-3-deepsearch':765 if response.get("messageStepId") and not CONFIG["SHOW_THINKING"]:766 return result767 if response.get("messageStepId") and not CONFIG["IS_THINKING"]:768 result["token"] = "<think>" + response.get("token", "")769 CONFIG["IS_THINKING"] = True770 elif not response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "final":771 result["token"] = "</think>" + response.get("token", "")772 CONFIG["IS_THINKING"] = False773 elif (response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "assistant") or response.get("messageTag") == "final":774 result["token"] = response.get("token")775 elif model == 'grok-3-reasoning':776 if response.get("isThinking") and not CONFIG["SHOW_THINKING"]:777 return result778 779 if response.get("isThinking") and not CONFIG["IS_THINKING"]:780 result["token"] = "<think>" + response.get("token", "")781 CONFIG["IS_THINKING"] = True782 elif not response.get("isThinking") and CONFIG["IS_THINKING"]:783 result["token"] = "</think>" + response.get("token", "")784 CONFIG["IS_THINKING"] = False785 else:786 result["token"] = response.get("token")787 788 return result789 790def handle_image_response(image_url):791 max_retries = 2792 retry_count = 0793 image_base64_response = None794 795 while retry_count < max_retries:796 try:797 proxy_options = Utils.get_proxy_options()798 image_base64_response = curl_requests.get(799 f"https://assets.grok.com/{image_url}",800 headers={801 **DEFAULT_HEADERS,802 "Cookie":CONFIG["SERVER"]['COOKIE']803 },804 impersonate="chrome133a",805 **proxy_options806 )807 808 if image_base64_response.status_code == 200:809 break810 811 retry_count += 1812 if retry_count == max_retries:813 raise Exception(f"上游服务请求失败! status: {image_base64_response.status_code}")814 815 time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)816 817 except Exception as error:818 logger.error(str(error), "Server")819 retry_count += 1820 if retry_count == max_retries:821 raise822 823 time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)824 825 image_buffer = image_base64_response.content826 827 if not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"]:828 base64_image = base64.b64encode(image_buffer).decode('utf-8')829 image_content_type = image_base64_response.headers.get('content-type', 'image/jpeg')830 return f""831 832 logger.info("开始上传图床", "Server")833 834 if CONFIG["API"]["PICGO_KEY"]:835 files = {'source': ('image.jpg', image_buffer, 'image/jpeg')}836 headers = {837 "X-API-Key": CONFIG["API"]["PICGO_KEY"]838 }839 840 response_url = requests.post(841 "https://www.picgo.net/api/1/upload",842 files=files,843 headers=headers844 )845 846 if response_url.status_code != 200:847 return "生图失败,请查看PICGO图床密钥是否设置正确"848 else:849 logger.info("生图成功", "Server")850 result = response_url.json()851 return f""852 853 854 elif CONFIG["API"]["TUMY_KEY"]:855 files = {'file': ('image.jpg', image_buffer, 'image/jpeg')}856 headers = {857 "Accept": "application/json",858 'Authorization': f"Bearer {CONFIG['API']['TUMY_KEY']}"859 }860 861 response_url = requests.post(862 "https://tu.my/api/v1/upload",863 files=files,864 headers=headers865 )866 867 if response_url.status_code != 200:868 return "生图失败,请查看TUMY图床密钥是否设置正确"869 else:870 try:871 result = response_url.json()872 logger.info("生图成功", "Server")873 return f""874 except Exception as error:875 logger.error(str(error), "Server")876 return "生图失败,请查看TUMY图床密钥是否设置正确"877 878def handle_non_stream_response(response, model):879 try:880 logger.info("开始处理非流式响应", "Server")881 882 stream = response.iter_lines()883 full_response = ""884 885 CONFIG["IS_THINKING"] = False886 CONFIG["IS_IMG_GEN"] = False887 CONFIG["IS_IMG_GEN2"] = False888 889 for chunk in stream:890 if not chunk:891 continue892 try:893 line_json = json.loads(chunk.decode("utf-8").strip())894 if line_json.get("error"):895 logger.error(json.dumps(line_json, indent=2), "Server")896 return json.dumps({"error": "RateLimitError"}) + "\n\n"897 898 response_data = line_json.get("result", {}).get("response")899 if not response_data:900 continue901 902 if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):903 CONFIG["IS_IMG_GEN"] = True904 905 result = process_model_response(response_data, model)906 907 if result["token"]:908 full_response += result["token"]909 910 if result["imageUrl"]:911 CONFIG["IS_IMG_GEN2"] = True912 return handle_image_response(result["imageUrl"])913 914 except json.JSONDecodeError:915 continue916 except Exception as e:917 logger.error(f"处理流式响应行时出错: {str(e)}", "Server")918 continue919 920 return full_response921 except Exception as error:922 logger.error(str(error), "Server")923 raise924def handle_stream_response(response, model):925 def generate():926 logger.info("开始处理流式响应", "Server")927 928 stream = response.iter_lines()929 CONFIG["IS_THINKING"] = False930 CONFIG["IS_IMG_GEN"] = False931 CONFIG["IS_IMG_GEN2"] = False932 933 for chunk in stream:934 if not chunk:935 continue936 try:937 line_json = json.loads(chunk.decode("utf-8").strip())938 if line_json.get("error"):939 logger.error(json.dumps(line_json, indent=2), "Server")940 yield json.dumps({"error": "RateLimitError"}) + "\n\n"941 return942 943 response_data = line_json.get("result", {}).get("response")944 if not response_data:945 continue946 947 if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):948 CONFIG["IS_IMG_GEN"] = True949 950 result = process_model_response(response_data, model)951 952 if result["token"]:953 yield f"data: {json.dumps(MessageProcessor.create_chat_response(result['token'], model, True))}\n\n"954 955 if result["imageUrl"]:956 CONFIG["IS_IMG_GEN2"] = True957 image_data = handle_image_response(result["imageUrl"])958 yield f"data: {json.dumps(MessageProcessor.create_chat_response(image_data, model, True))}\n\n"959 960 except json.JSONDecodeError:961 continue962 except Exception as e:963 logger.error(f"处理流式响应行时出错: {str(e)}", "Server")964 continue965 966 yield "data: [DONE]\n\n"967 return generate()968 969def initialization():970 sso_array = os.getenv("SSO", "").split(',')971 logger.info("开始加载令牌", "Server")972 for sso in sso_array:973 if sso:974 token_manager.add_token(f"sso-rw={sso};sso={sso}")975 976 logger.info(f"成功加载令牌: {json.dumps(token_manager.get_all_tokens(), indent=2)}", "Server")977 logger.info(f"令牌加载完成,共加载: {len(token_manager.get_all_tokens())}个令牌", "Server")978 979 if CONFIG["API"]["PROXY"]:980 logger.info(f"代理已设置: {CONFIG['API']['PROXY']}", "Server")981 982logger.info("初始化完成", "Server")983 984 985app = Flask(__name__)986app.wsgi_app = ProxyFix(app.wsgi_app)987app.secret_key = os.getenv('FLASK_SECRET_KEY') or secrets.token_hex(16)988app.json.sort_keys = False989 990@app.route('/manager/login', methods=['GET', 'POST'])991def manager_login():992 if CONFIG["ADMIN"]["MANAGER_SWITCH"]:993 if request.method == 'POST':994 password = request.form.get('password')995 if password == CONFIG["ADMIN"]["PASSWORD"]:996 session['is_logged_in'] = True997 return redirect('/manager')998 return render_template('login.html', error=True)999 return render_template('login.html', error=False)1000 else:1001 return redirect('/')1002 1003def check_auth():1004 return session.get('is_logged_in', False)1005 1006@app.route('/manager')1007def manager():1008 if not check_auth():1009 return redirect('/manager/login')1010 return render_template('manager.html')1011 1012@app.route('/manager/api/get')1013def get_manager_tokens():1014 if not check_auth():1015 return jsonify({"error": "Unauthorized"}), 4011016 return jsonify(token_manager.get_token_status_map())1017 1018@app.route('/manager/api/add', methods=['POST'])1019def add_manager_token():1020 if not check_auth():1021 return jsonify({"error": "Unauthorized"}), 4011022 try:1023 sso = request.json.get('sso')1024 if not sso:1025 return jsonify({"error": "SSO token is required"}), 4001026 token_manager.add_token(f"sso-rw={sso};sso={sso}")1027 return jsonify({"success": True})1028 except Exception as e:1029 return jsonify({"error": str(e)}), 5001030 1031@app.route('/manager/api/delete', methods=['POST'])1032def delete_manager_token():1033 if not check_auth():1034 return jsonify({"error": "Unauthorized"}), 4011035 try:1036 sso = request.json.get('sso')1037 if not sso:1038 return jsonify({"error": "SSO token is required"}), 4001039 token_manager.delete_token(f"sso-rw={sso};sso={sso}")1040 return jsonify({"success": True})1041 except Exception as e:1042 return jsonify({"error": str(e)}), 5001043 1044@app.route('/manager/api/cf_clearance', methods=['POST']) 1045def setCf_Manager_clearance():1046 if not check_auth():1047 return jsonify({"error": "Unauthorized"}), 4011048 try:1049 cf_clearance = request.json.get('cf_clearance')1050 if not cf_clearance:1051 return jsonify({"error": "cf_clearance is required"}), 4001052 CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance1053 return jsonify({"success": True})1054 except Exception as e:1055 return jsonify({"error": str(e)}), 5001056 1057 1058@app.route('/get/tokens', methods=['GET'])1059def get_tokens():1060 auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')1061 if CONFIG["API"]["IS_CUSTOM_SSO"]:1062 return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 4031063 elif auth_token != CONFIG["API"]["API_KEY"]:1064 return jsonify({"error": 'Unauthorized'}), 4011065 return jsonify(token_manager.get_token_status_map())1066 1067@app.route('/add/token', methods=['POST'])1068def add_token():1069 auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')1070 if CONFIG["API"]["IS_CUSTOM_SSO"]:1071 return jsonify({"error": '自定义的SSO令牌模式无法添加sso令牌'}), 4031072 elif auth_token != CONFIG["API"]["API_KEY"]:1073 return jsonify({"error": 'Unauthorized'}), 4011074 1075 try:1076 sso = request.json.get('sso')1077 token_manager.add_token(f"sso-rw={sso};sso={sso}")1078 return jsonify(token_manager.get_token_status_map().get(sso, {})), 2001079 except Exception as error:1080 logger.error(str(error), "Server")1081 return jsonify({"error": '添加sso令牌失败'}), 5001082 1083@app.route('/set/cf_clearance', methods=['POST'])1084def setCf_clearance():1085 auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')1086 if auth_token != CONFIG["API"]["API_KEY"]:1087 return jsonify({"error": 'Unauthorized'}), 4011088 try:1089 cf_clearance = request.json.get('cf_clearance')1090 CONFIG["SERVER"]['CF_CLEARANCE'] = cf_clearance1091 return jsonify({"message": '设置cf_clearance成功'}), 2001092 except Exception as error:1093 logger.error(str(error), "Server")1094 return jsonify({"error": '设置cf_clearance失败'}), 5001095 1096@app.route('/delete/token', methods=['POST'])1097def delete_token():1098 auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')1099 if CONFIG["API"]["IS_CUSTOM_SSO"]:1100 return jsonify({"error": '自定义的SSO令牌模式无法删除sso令牌'}), 4031101 elif auth_token != CONFIG["API"]["API_KEY"]:1102 return jsonify({"error": 'Unauthorized'}), 4011103 1104 try:1105 sso = request.json.get('sso')1106 token_manager.delete_token(f"sso-rw={sso};sso={sso}")1107 return jsonify({"message": '删除sso令牌成功'}), 2001108 except Exception as error:1109 logger.error(str(error), "Server")1110 return jsonify({"error": '删除sso令牌失败'}), 5001111 1112@app.route('/v1/models', methods=['GET'])1113def get_models():1114 return jsonify({1115 "object": "list",1116 "data": [1117 {1118 "id": model,1119 "object": "model",1120 "created": int(time.time()),1121 "owned_by": "grok"1122 }1123 for model in CONFIG["MODELS"].keys()1124 ]1125 })1126 1127@app.route('/v1/chat/completions', methods=['POST'])1128def chat_completions():1129 response_status_code = 5001130 try:1131 auth_token = request.headers.get('Authorization',1132 '').replace('Bearer ', '')1133 if auth_token:1134 if CONFIG["API"]["IS_CUSTOM_SSO"]:1135 result = f"sso={auth_token};sso-rw={auth_token}"1136 token_manager.set_token(result)1137 elif auth_token != CONFIG["API"]["API_KEY"]:1138 return jsonify({"error": 'Unauthorized'}), 4011139 else:1140 return jsonify({"error": 'API_KEY缺失'}), 4011141 1142 data = request.json1143 model = data.get("model")1144 stream = data.get("stream", False)1145 1146 retry_count = 01147 grok_client = GrokApiClient(model)1148 request_payload = grok_client.prepare_chat_request(data)1149 1150 1151 while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:1152 retry_count += 11153 CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(model)1154 1155 if not CONFIG["API"]["SIGNATURE_COOKIE"]:1156 raise ValueError('该模型无可用令牌')1157 1158 logger.info(1159 f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}","Server")1160 logger.info(1161 f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}","Server")1162 1163 if CONFIG['SERVER']['CF_CLEARANCE']:1164 CONFIG["SERVER"]['COOKIE'] = f"{CONFIG['API']['SIGNATURE_COOKIE']};{CONFIG['SERVER']['CF_CLEARANCE']}" 1165 else:1166 CONFIG["SERVER"]['COOKIE'] = CONFIG['API']['SIGNATURE_COOKIE']1167 logger.info(json.dumps(request_payload,indent=2),"Server")1168 try:1169 proxy_options = Utils.get_proxy_options()1170 response = curl_requests.post(1171 f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",1172 headers={1173 **DEFAULT_HEADERS, 1174 "Cookie":CONFIG["SERVER"]['COOKIE']1175 },1176 json=request_payload,1177 impersonate="chrome133a",1178 verify=False,1179 stream=True,1180 **proxy_options)1181 logger.info(CONFIG["SERVER"]['COOKIE'],"Server")1182 if response.status_code == 200:1183 response_status_code = 2001184 logger.info("请求成功", "Server")1185 logger.info(f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}","Server")1186 1187 try:1188 if stream:1189 return Response(stream_with_context(1190 handle_stream_response(response, model)),content_type='text/event-stream')1191 else:1192 content = handle_non_stream_response(response, model)1193 return jsonify(1194 MessageProcessor.create_chat_response(content, model))1195 1196 except Exception as error:1197 logger.error(str(error), "Server")1198 if CONFIG["API"]["IS_CUSTOM_SSO"]:1199 raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")1200 token_manager.remove_token_from_model(model, CONFIG["API"]["SIGNATURE_COOKIE"])