CoolFace
Apppublic

DoraeHope/GrokPythonTo2

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
app.py1066 linesDownload Raw Back to root
1import os2import json3import uuid4import time5import base646import sys7import inspect8from loguru import logger9 10import requests11from flask import Flask, request, Response, jsonify, stream_with_context12from curl_cffi import requests as curl_requests13from werkzeug.middleware.proxy_fix import ProxyFix14 15 16class Logger:17    def __init__(self, level="INFO", colorize=True, format=None):18        logger.remove()19 20        if format is None:21            format = (22                "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "23                "<level>{level: <8}</level> | "24                "<cyan>{extra[filename]}</cyan>:<cyan>{extra[function]}</cyan>:<cyan>{extra[lineno]}</cyan> | "25                "<level>{message}</level>"26            )27 28        logger.add(29            sys.stderr,30            level=level,31            format=format,32            colorize=colorize,33            backtrace=True,34            diagnose=True35        )36 37        self.logger = logger38 39    def _get_caller_info(self):40        frame = inspect.currentframe()41        try:42            caller_frame = frame.f_back.f_back43            full_path = caller_frame.f_code.co_filename44            function = caller_frame.f_code.co_name45            lineno = caller_frame.f_lineno46 47            filename = os.path.basename(full_path)48 49            return {50                'filename': filename,51                'function': function,52                'lineno': lineno53            }54        finally:55            del frame56 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}")76 77    async def request_logger(self, request):78        caller_info = self._get_caller_info()79        self.logger.bind(**caller_info).info(f"请求: {request.method} {request.path}", "Request")80 81logger = Logger(level="INFO")82 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-reasoning": "grok-3"94    },95    "API": {96        "IS_TEMP_CONVERSATION": os.environ.get("IS_TEMP_CONVERSATION", "true").lower() == "true",97        "IS_CUSTOM_SSO": os.environ.get("IS_CUSTOM_SSO", "false").lower() == "true",98        "BASE_URL": "https://grok.com",99        "API_KEY": os.environ.get("API_KEY", "sk-123456"),100        "SIGNATURE_COOKIE": None,101        "PICGO_KEY": os.environ.get("PICGO_KEY") or None,102        "TUMY_KEY": os.environ.get("TUMY_KEY") or None,103        "RETRY_TIME": 1000,104        "PROXY": os.environ.get("PROXY") or None105    },106    "SERVER": {107        "PORT": int(os.environ.get("PORT", 5200))108    },109    "RETRY": {110        "MAX_ATTEMPTS": 2111    },112    "SHOW_THINKING": os.environ.get("SHOW_THINKING") == "true",113    "IS_THINKING": False,114    "IS_IMG_GEN": False,115    "IS_IMG_GEN2": False,116    "ISSHOW_SEARCH_RESULTS": os.environ.get("ISSHOW_SEARCH_RESULTS", "true").lower() == "true"117}118 119 120DEFAULT_HEADERS = {121    'Accept': '*/*',122    'Accept-Language': 'zh-CN,zh;q=0.9',123    'Accept-Encoding': 'gzip, deflate, br, zstd',124    'Content-Type': 'text/plain;charset=UTF-8',125    'Connection': 'keep-alive',126    'Origin': 'https://grok.com',127    'Priority': 'u=1, i',128    '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',129    'Sec-Ch-Ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',130    'Sec-Ch-Ua-Mobile': '?0',131    'Sec-Ch-Ua-Platform': '"macOS"',132    'Sec-Fetch-Dest': 'empty',133    'Sec-Fetch-Mode': 'cors',134    'Sec-Fetch-Site': 'same-origin',135    'Baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'136}137 138class AuthTokenManager:139    def __init__(self):140        self.token_model_map = {}141        self.expired_tokens = set()142        self.token_status_map = {}143 144        self.model_config = {145            "grok-2": {146                "RequestFrequency": 30,147                "ExpirationTime": 1 * 60 * 60 * 1000  # 1小时148            },149            "grok-3": {150                "RequestFrequency": 20,151                "ExpirationTime": 2 * 60 * 60 * 1000  # 2小时152            },153            "grok-3-deepsearch": {154                "RequestFrequency": 10,155                "ExpirationTime": 24 * 60 * 60 * 1000  # 24小时156            },157            "grok-3-reasoning": {158                "RequestFrequency": 10,159                "ExpirationTime": 24 * 60 * 60 * 1000  # 24小时160            }161        }162        self.token_reset_switch = False163        self.token_reset_timer = None164 165    def add_token(self, token):166        sso = token.split("sso=")[1].split(";")[0]167        for model in self.model_config.keys():168            if model not in self.token_model_map:169                self.token_model_map[model] = []170            if sso not in self.token_status_map:171                self.token_status_map[sso] = {}172 173            existing_token_entry = next((entry for entry in self.token_model_map[model] if entry["token"] == token), None)174 175            if not existing_token_entry:176                self.token_model_map[model].append({177                    "token": token,178                    "RequestCount": 0,179                    "AddedTime": int(time.time() * 1000),180                    "StartCallTime": None181                })182 183                if model not in self.token_status_map[sso]:184                    self.token_status_map[sso][model] = {185                        "isValid": True,186                        "invalidatedTime": None,187                        "totalRequestCount": 0188                    }189 190    def set_token(self, token):191        models = list(self.model_config.keys())192        self.token_model_map = {model: [{193            "token": token,194            "RequestCount": 0,195            "AddedTime": int(time.time() * 1000),196            "StartCallTime": None197        }] for model in models}198 199        sso = token.split("sso=")[1].split(";")[0]200        self.token_status_map[sso] = {model: {201            "isValid": True,202            "invalidatedTime": None,203            "totalRequestCount": 0204        } for model in models}205 206    def delete_token(self, token):207        try:208            sso = token.split("sso=")[1].split(";")[0]209            for model in self.token_model_map:210                self.token_model_map[model] = [entry for entry in self.token_model_map[model] if entry["token"] != token]211 212            if sso in self.token_status_map:213                del self.token_status_map[sso]214 215            logger.info(f"令牌已成功移除: {token}", "TokenManager")216            return True217        except Exception as error:218            logger.error(f"令牌删除失败: {str(error)}")219            return False220 221    def get_next_token_for_model(self, model_id):222        normalized_model = self.normalize_model_name(model_id)223 224        if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:225            return None226 227        token_entry = self.token_model_map[normalized_model][0]228 229        if token_entry:230            if token_entry["StartCallTime"] is None:231                token_entry["StartCallTime"] = int(time.time() * 1000)232 233            if not self.token_reset_switch:234                self.start_token_reset_process()235                self.token_reset_switch = True236 237            token_entry["RequestCount"] += 1238 239            if token_entry["RequestCount"] > self.model_config[normalized_model]["RequestFrequency"]:240                self.remove_token_from_model(normalized_model, token_entry["token"])241                next_token_entry = self.token_model_map[normalized_model][0] if self.token_model_map[normalized_model] else None242                return next_token_entry["token"] if next_token_entry else None243 244            sso = token_entry["token"].split("sso=")[1].split(";")[0]245            if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:246                if token_entry["RequestCount"] == self.model_config[normalized_model]["RequestFrequency"]:247                    self.token_status_map[sso][normalized_model]["isValid"] = False248                    self.token_status_map[sso][normalized_model]["invalidatedTime"] = int(time.time() * 1000)249                self.token_status_map[sso][normalized_model]["totalRequestCount"] += 1250 251            return token_entry["token"]252 253        return None254 255    def remove_token_from_model(self, model_id, token):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        model_tokens = self.token_model_map[normalized_model]263        token_index = next((i for i, entry in enumerate(model_tokens) if entry["token"] == token), -1)264 265        if token_index != -1:266            removed_token_entry = model_tokens.pop(token_index)267            self.expired_tokens.add((268                removed_token_entry["token"],269                normalized_model,270                int(time.time() * 1000)271            ))272 273            if not self.token_reset_switch:274                self.start_token_reset_process()275                self.token_reset_switch = True276 277            logger.info(f"模型{model_id}的令牌已失效,已成功移除令牌: {token}", "TokenManager")278            return True279 280        logger.error(f"在模型 {normalized_model} 中未找到 token: {token}", "TokenManager")281        return False282 283    def get_expired_tokens(self):284        return list(self.expired_tokens)285 286    def normalize_model_name(self, model):287        if model.startswith('grok-') and 'deepsearch' not in model and 'reasoning' not in model:288            return '-'.join(model.split('-')[:2])289        return model290 291    def get_token_count_for_model(self, model_id):292        normalized_model = self.normalize_model_name(model_id)293        return len(self.token_model_map.get(normalized_model, []))294 295    def get_remaining_token_request_capacity(self):296        remaining_capacity_map = {}297 298        for model in self.model_config.keys():299            model_tokens = self.token_model_map.get(model, [])300            model_request_frequency = self.model_config[model]["RequestFrequency"]301 302            total_used_requests = sum(token_entry.get("RequestCount", 0) for token_entry in model_tokens)303 304            remaining_capacity = (len(model_tokens) * model_request_frequency) - total_used_requests305            remaining_capacity_map[model] = max(0, remaining_capacity)306 307        return remaining_capacity_map308 309    def get_token_array_for_model(self, model_id):310        normalized_model = self.normalize_model_name(model_id)311        return self.token_model_map.get(normalized_model, [])312 313    def start_token_reset_process(self):314        def reset_expired_tokens():315            now = int(time.time() * 1000)316 317            tokens_to_remove = set()318            for token_info in self.expired_tokens:319                token, model, expired_time = token_info320                expiration_time = self.model_config[model]["ExpirationTime"]321 322                if now - expired_time >= expiration_time:323                    if not any(entry["token"] == token for entry in self.token_model_map.get(model, [])):324                        if model not in self.token_model_map:325                            self.token_model_map[model] = []326 327                        self.token_model_map[model].append({328                            "token": token,329                            "RequestCount": 0,330                            "AddedTime": now,331                            "StartCallTime": None332                        })333 334                    sso = token.split("sso=")[1].split(";")[0]335                    if sso in self.token_status_map and model in self.token_status_map[sso]:336                        self.token_status_map[sso][model]["isValid"] = True337                        self.token_status_map[sso][model]["invalidatedTime"] = None338                        self.token_status_map[sso][model]["totalRequestCount"] = 0339 340                    tokens_to_remove.add(token_info)341 342            self.expired_tokens -= tokens_to_remove343 344            for model in self.model_config.keys():345                if model not in self.token_model_map:346                    continue347 348                for token_entry in self.token_model_map[model]:349                    if not token_entry.get("StartCallTime"):350                        continue351 352                    expiration_time = self.model_config[model]["ExpirationTime"]353                    if now - token_entry["StartCallTime"] >= expiration_time:354                        sso = token_entry["token"].split("sso=")[1].split(";")[0]355                        if sso in self.token_status_map and model in self.token_status_map[sso]:356                            self.token_status_map[sso][model]["isValid"] = True357                            self.token_status_map[sso][model]["invalidatedTime"] = None358                            self.token_status_map[sso][model]["totalRequestCount"] = 0359 360                        token_entry["RequestCount"] = 0361                        token_entry["StartCallTime"] = None362 363        import threading364        # 启动一个线程执行定时任务,每小时执行一次365        def run_timer():366            while True:367                reset_expired_tokens()368                time.sleep(3600)369 370        timer_thread = threading.Thread(target=run_timer)371        timer_thread.daemon = True372        timer_thread.start()373 374    def get_all_tokens(self):375        all_tokens = set()376        for model_tokens in self.token_model_map.values():377            for entry in model_tokens:378                all_tokens.add(entry["token"])379        return list(all_tokens)380 381    def get_token_status_map(self):382        return self.token_status_map383 384class Utils:385    @staticmethod386    def organize_search_results(search_results):387        if not search_results or 'results' not in search_results:388            return ''389 390        results = search_results['results']391        formatted_results = []392 393        for index, result in enumerate(results):394            title = result.get('title', '未知标题')395            url = result.get('url', '#')396            preview = result.get('preview', '无预览内容')397 398            formatted_result = f"\r\n<details><summary>资料[{index}]: {title}</summary>\r\n{preview}\r\n\n[Link]({url})\r\n</details>"399            formatted_results.append(formatted_result)400 401        return '\n\n'.join(formatted_results)402 403    @staticmethod404    def create_auth_headers(model):405        return token_manager.get_next_token_for_model(model)406 407    @staticmethod408    def get_proxy_options():409        proxy = CONFIG["API"]["PROXY"]410        proxy_options = {}411 412        if proxy:413            logger.info(f"使用代理: {proxy}", "Server")414            proxy_options["proxies"] = {"https": proxy, "http": proxy}415 416            if proxy.startswith("socks5://"):417                proxy_options["proxies"] = {"https": proxy, "http": proxy}418                proxy_options["proxy_type"] = "socks5"419 420        return proxy_options421 422class GrokApiClient:423    def __init__(self, model_id):424        if model_id not in CONFIG["MODELS"]:425            raise ValueError(f"不支持的模型: {model_id}")426        self.model_id = CONFIG["MODELS"][model_id]427 428    def process_message_content(self, content):429        if isinstance(content, str):430            return content431        return None432 433    def get_image_type(self, base64_string):434        mime_type = 'image/jpeg'435        if 'data:image' in base64_string:436            import re437            matches = re.search(r'data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,', base64_string)438            if matches:439                mime_type = matches.group(1)440 441        extension = mime_type.split('/')[1]442        file_name = f"image.{extension}"443 444        return {445            "mimeType": mime_type,446            "fileName": file_name447        }448 449    def upload_base64_image(self, base64_data, url):450        try:451            if 'data:image' in base64_data:452                image_buffer = base64_data.split(',')[1]453            else:454                image_buffer = base64_data455 456            image_info = self.get_image_type(base64_data)457            mime_type = image_info["mimeType"]458            file_name = image_info["fileName"]459 460            upload_data = {461                "rpc": "uploadFile",462                "req": {463                    "fileName": file_name,464                    "fileMimeType": mime_type,465                    "content": image_buffer466                }467            }468 469            logger.info("发送图片请求", "Server")470 471            proxy_options = Utils.get_proxy_options()472            response = curl_requests.post(473                url,474                headers={475                    **DEFAULT_HEADERS,476                    "Cookie": CONFIG["API"]["SIGNATURE_COOKIE"]477                },478                json=upload_data,479                impersonate="chrome133a",480                **proxy_options481            )482 483            if response.status_code != 200:484                logger.error(f"上传图片失败,状态码:{response.status_code}", "Server")485                return ''486 487            result = response.json()488            logger.info(f"上传图片成功: {result}", "Server")489            return result.get("fileMetadataId", "")490 491        except Exception as error:492            logger.error(str(error), "Server")493            return ''494 495    def prepare_chat_request(self, request):496        if ((request["model"] == 'grok-2-imageGen' or request["model"] == 'grok-3-imageGen') and497            not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"] and498            request.get("stream", False)):499            raise ValueError("该模型流式输出需要配置PICGO或者TUMY图床密钥!")500 501        todo_messages = request["messages"]502        if request["model"] in ['grok-2-imageGen', 'grok-3-imageGen', 'grok-3-deepsearch']:503            last_message = todo_messages[-1]504            if last_message["role"] != 'user':505                raise ValueError('此模型最后一条消息必须是用户消息!')506            todo_messages = [last_message]507 508        file_attachments = []509        messages = ''510        last_role = None511        last_content = ''512        search = request["model"] in ['grok-2-search', 'grok-3-search']513 514        # 移除<think>标签及其内容和base64图片515        def remove_think_tags(text):516            import re517            text = re.sub(r'<think>[\s\S]*?<\/think>', '', text).strip()518            text = re.sub(r'!\[image\]\(data:.*?base64,.*?\)', '[图片]', text)519            return text520 521        def process_content(content):522            if isinstance(content, list):523                text_content = ''524                for item in content:525                    if item["type"] == 'image_url':526                        text_content += ("[图片]" if not text_content else '\n[图片]')527                    elif item["type"] == 'text':528                        text_content += (remove_think_tags(item["text"]) if not text_content else '\n' + remove_think_tags(item["text"]))529                return text_content530            elif isinstance(content, dict) and content is not None:531                if content["type"] == 'image_url':532                    return "[图片]"533                elif content["type"] == 'text':534                    return remove_think_tags(content["text"])535            return remove_think_tags(self.process_message_content(content))536 537        for current in todo_messages:538            role = 'assistant' if current["role"] == 'assistant' else 'user'539            is_last_message = current == todo_messages[-1]540 541            if is_last_message and "content" in current:542                if isinstance(current["content"], list):543                    for item in current["content"]:544                        if item["type"] == 'image_url':545                            processed_image = self.upload_base64_image(546                                item["image_url"]["url"],547                                f"{CONFIG['API']['BASE_URL']}/api/rpc"548                            )549                            if processed_image:550                                file_attachments.append(processed_image)551                elif isinstance(current["content"], dict) and current["content"].get("type") == 'image_url':552                    processed_image = self.upload_base64_image(553                        current["content"]["image_url"]["url"],554                        f"{CONFIG['API']['BASE_URL']}/api/rpc"555                    )556                    if processed_image:557                        file_attachments.append(processed_image)558 559 560            text_content = process_content(current.get("content", ""))561 562            if text_content or (is_last_message and file_attachments):563                if role == last_role and text_content:564                    last_content += '\n' + text_content565                    messages = messages[:messages.rindex(f"{role.upper()}: ")] + f"{role.upper()}: {last_content}\n"566                else:567                    messages += f"{role.upper()}: {text_content or '[图片]'}\n"568                    last_content = text_content569                    last_role = role570 571        return {572            "temporary": CONFIG["API"]["IS_TEMP_CONVERSATION"],573            "modelName": self.model_id,574            "message": messages.strip(),575            "fileAttachments": file_attachments[:4],576            "imageAttachments": [],577            "disableSearch": False,578            "enableImageGeneration": True,579            "returnImageBytes": False,580            "returnRawGrokInXaiRequest": False,581            "enableImageStreaming": False,582            "imageGenerationCount": 1,583            "forceConcise": False,584            "toolOverrides": {585                "imageGen": request["model"] in ['grok-2-imageGen', 'grok-3-imageGen'],586                "webSearch": search,587                "xSearch": search,588                "xMediaSearch": search,589                "trendsSearch": search,590                "xPostAnalyze": search591            },592            "enableSideBySide": True,593            "isPreset": False,594            "sendFinalMetadata": True,595            "customInstructions": "",596            "deepsearchPreset": "default" if request["model"] == 'grok-3-deepsearch' else "",597            "isReasoning": request["model"] == 'grok-3-reasoning'598        }599 600class MessageProcessor:601    @staticmethod602    def create_chat_response(message, model, is_stream=False):603        base_response = {604            "id": f"chatcmpl-{uuid.uuid4()}",605            "created": int(time.time()),606            "model": model607        }608 609        if is_stream:610            return {611                **base_response,612                "object": "chat.completion.chunk",613                "choices": [{614                    "index": 0,615                    "delta": {616                        "content": message617                    }618                }]619            }620 621        return {622            **base_response,623            "object": "chat.completion",624            "choices": [{625                "index": 0,626                "message": {627                    "role": "assistant",628                    "content": message629                },630                "finish_reason": "stop"631            }],632            "usage": None633        }634 635def process_model_response(response, model):636    result = {"token": None, "imageUrl": None}637 638    if CONFIG["IS_IMG_GEN"]:639        if response.get("cachedImageGenerationResponse") and not CONFIG["IS_IMG_GEN2"]:640            result["imageUrl"] = response["cachedImageGenerationResponse"]["imageUrl"]641        return result642 643    if model == 'grok-2':644        result["token"] = response.get("token")645    elif model in ['grok-2-search', 'grok-3-search']:646        if response.get("webSearchResults") and CONFIG["ISSHOW_SEARCH_RESULTS"]:647            result["token"] = f"\r\n<think>{Utils.organize_search_results(response['webSearchResults'])}</think>\r\n"648        else:649            result["token"] = response.get("token")650    elif model == 'grok-3':651        result["token"] = response.get("token")652    elif model == 'grok-3-deepsearch':653        if response.get("messageStepId") and not CONFIG["SHOW_THINKING"]:654            return result655        if response.get("messageStepId") and not CONFIG["IS_THINKING"]:656            result["token"] = "<think>" + response.get("token", "")657            CONFIG["IS_THINKING"] = True658        elif not response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "final":659            result["token"] = "</think>" + response.get("token", "")660            CONFIG["IS_THINKING"] = False661        elif (response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "assistant") or response.get("messageTag") == "final":662            result["token"] = response.get("token")663    elif model == 'grok-3-reasoning':664        if response.get("isThinking") and not CONFIG["SHOW_THINKING"]:665            return result666 667        if response.get("isThinking") and not CONFIG["IS_THINKING"]:668            result["token"] = "<think>" + response.get("token", "")669            CONFIG["IS_THINKING"] = True670        elif not response.get("isThinking") and CONFIG["IS_THINKING"]:671            result["token"] = "</think>" + response.get("token", "")672            CONFIG["IS_THINKING"] = False673        else:674            result["token"] = response.get("token")675 676    return result677 678def handle_image_response(image_url):679    max_retries = 2680    retry_count = 0681    image_base64_response = None682 683    while retry_count < max_retries:684        try:685            proxy_options = Utils.get_proxy_options()686            image_base64_response = curl_requests.get(687                f"https://assets.grok.com/{image_url}",688                headers={689                    **DEFAULT_HEADERS,690                    "Cookie": CONFIG["API"]["SIGNATURE_COOKIE"]691                },692                impersonate="chrome120",693                **proxy_options694            )695 696            if image_base64_response.status_code == 200:697                break698 699            retry_count += 1700            if retry_count == max_retries:701                raise Exception(f"上游服务请求失败! status: {image_base64_response.status_code}")702 703            time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)704 705        except Exception as error:706            logger.error(str(error), "Server")707            retry_count += 1708            if retry_count == max_retries:709                raise710 711            time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)712 713    image_buffer = image_base64_response.content714 715    if not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"]:716        base64_image = base64.b64encode(image_buffer).decode('utf-8')717        image_content_type = image_base64_response.headers.get('content-type', 'image/jpeg')718        return f"![image](data:{image_content_type};base64,{base64_image})"719 720    logger.info("开始上传图床", "Server")721 722    if CONFIG["API"]["PICGO_KEY"]:723        files = {'source': ('image.jpg', image_buffer, 'image/jpeg')}724        headers = {725            "X-API-Key": CONFIG["API"]["PICGO_KEY"]726        }727 728        response_url = requests.post(729            "https://www.picgo.net/api/1/upload",730            files=files,731            headers=headers732        )733 734        if response_url.status_code != 200:735            return "生图失败,请查看PICGO图床密钥是否设置正确"736        else:737            logger.info("生图成功", "Server")738            result = response_url.json()739            return f"![image]({result['image']['url']})"740 741 742    elif CONFIG["API"]["TUMY_KEY"]:743        files = {'file': ('image.jpg', image_buffer, 'image/jpeg')}744        headers = {745            "Accept": "application/json",746            'Authorization': f"Bearer {CONFIG['API']['TUMY_KEY']}"747        }748 749        response_url = requests.post(750            "https://tu.my/api/v1/upload",751            files=files,752            headers=headers753        )754 755        if response_url.status_code != 200:756            return "生图失败,请查看TUMY图床密钥是否设置正确"757        else:758            try:759                result = response_url.json()760                logger.info("生图成功", "Server")761                return f"![image]({result['data']['links']['url']})"762            except Exception as error:763                logger.error(str(error), "Server")764                return "生图失败,请查看TUMY图床密钥是否设置正确"765 766def handle_non_stream_response(response, model):767    try:768        logger.info("开始处理非流式响应", "Server")769 770        stream = response.iter_lines()771        full_response = ""772 773        CONFIG["IS_THINKING"] = False774        CONFIG["IS_IMG_GEN"] = False775        CONFIG["IS_IMG_GEN2"] = False776 777        for chunk in stream:778            if not chunk:779                continue780            try:781                line_json = json.loads(chunk.decode("utf-8").strip())782                if line_json.get("error"):783                    logger.error(json.dumps(line_json, indent=2), "Server")784                    return json.dumps({"error": "RateLimitError"}) + "\n\n"785 786                response_data = line_json.get("result", {}).get("response")787                if not response_data:788                    continue789 790                if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):791                    CONFIG["IS_IMG_GEN"] = True792 793                result = process_model_response(response_data, model)794 795                if result["token"]:796                    full_response += result["token"]797 798                if result["imageUrl"]:799                    CONFIG["IS_IMG_GEN2"] = True800                    return handle_image_response(result["imageUrl"])801 802            except json.JSONDecodeError:803                continue804            except Exception as e:805                logger.error(f"处理流式响应行时出错: {str(e)}", "Server")806                continue807 808        return full_response809    except Exception as error:810        logger.error(str(error), "Server")811        raise812def handle_stream_response(response, model):813    def generate():814        logger.info("开始处理流式响应", "Server")815 816        stream = response.iter_lines()817        CONFIG["IS_THINKING"] = False818        CONFIG["IS_IMG_GEN"] = False819        CONFIG["IS_IMG_GEN2"] = False820 821        for chunk in stream:822            if not chunk:823                continue824            try:825                line_json = json.loads(chunk.decode("utf-8").strip())826                if line_json.get("error"):827                    logger.error(json.dumps(line_json, indent=2), "Server")828                    yield json.dumps({"error": "RateLimitError"}) + "\n\n"829                    return830 831                response_data = line_json.get("result", {}).get("response")832                if not response_data:833                    continue834 835                if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):836                    CONFIG["IS_IMG_GEN"] = True837 838                result = process_model_response(response_data, model)839 840                if result["token"]:841                    yield f"data: {json.dumps(MessageProcessor.create_chat_response(result['token'], model, True))}\n\n"842 843                if result["imageUrl"]:844                    CONFIG["IS_IMG_GEN2"] = True845                    image_data = handle_image_response(result["imageUrl"])846                    yield f"data: {json.dumps(MessageProcessor.create_chat_response(image_data, model, True))}\n\n"847 848            except json.JSONDecodeError:849                continue850            except Exception as e:851                logger.error(f"处理流式响应行时出错: {str(e)}", "Server")852                continue853 854        yield "data: [DONE]\n\n"855    return generate()856 857def initialization():858    sso_array = os.environ.get("SSO", "").split(',')859    logger.info("开始加载令牌", "Server")860    for sso in sso_array:861        if sso:862            token_manager.add_token(f"sso-rw={sso};sso={sso}")863 864    logger.info(f"成功加载令牌: {json.dumps(token_manager.get_all_tokens(), indent=2)}", "Server")865    logger.info(f"令牌加载完成,共加载: {len(token_manager.get_all_tokens())}个令牌", "Server")866 867    if CONFIG["API"]["PROXY"]:868        logger.info(f"代理已设置: {CONFIG['API']['PROXY']}", "Server")869 870logger.info("初始化完成", "Server")871 872 873app = Flask(__name__)874app.wsgi_app = ProxyFix(app.wsgi_app)875 876 877@app.before_request878def log_request_info():879    logger.info(f"{request.method} {request.path}", "Request")880 881@app.route('/get/tokens', methods=['GET'])882def get_tokens():883    auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')884    if CONFIG["API"]["IS_CUSTOM_SSO"]:885        return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 403886    elif auth_token != CONFIG["API"]["API_KEY"]:887        return jsonify({"error": 'Unauthorized'}), 401888 889    return jsonify(token_manager.get_token_status_map())890 891@app.route('/add/token', methods=['POST'])892def add_token():893    auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')894    if CONFIG["API"]["IS_CUSTOM_SSO"]:895        return jsonify({"error": '自定义的SSO令牌模式无法添加sso令牌'}), 403896    elif auth_token != CONFIG["API"]["API_KEY"]:897        return jsonify({"error": 'Unauthorized'}), 401898 899    try:900        sso = request.json.get('sso')901        token_manager.add_token(f"sso-rw={sso};sso={sso}")902        return jsonify(token_manager.get_token_status_map().get(sso, {})), 200903    except Exception as error:904        logger.error(str(error), "Server")905        return jsonify({"error": '添加sso令牌失败'}), 500906 907@app.route('/delete/token', methods=['POST'])908def delete_token():909    auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')910    if CONFIG["API"]["IS_CUSTOM_SSO"]:911        return jsonify({"error": '自定义的SSO令牌模式无法删除sso令牌'}), 403912    elif auth_token != CONFIG["API"]["API_KEY"]:913        return jsonify({"error": 'Unauthorized'}), 401914 915    try:916        sso = request.json.get('sso')917        token_manager.delete_token(f"sso-rw={sso};sso={sso}")918        return jsonify({"message": '删除sso令牌成功'}), 200919    except Exception as error:920        logger.error(str(error), "Server")921        return jsonify({"error": '删除sso令牌失败'}), 500922 923@app.route('/v1/models', methods=['GET'])924def get_models():925    return jsonify({926        "object": "list",927        "data": [928            {929                "id": model,930                "object": "model",931                "created": int(time.time()),932                "owned_by": "grok"933            }934            for model in CONFIG["MODELS"].keys()935        ]936    })937 938@app.route('/v1/chat/completions', methods=['POST'])939def chat_completions():940    try:941        auth_token = request.headers.get('Authorization',942                                         '').replace('Bearer ', '')943        if auth_token:944            if CONFIG["API"]["IS_CUSTOM_SSO"]:945                result = f"sso={auth_token};sso-rw={auth_token}"946                token_manager.set_token(result)947            elif auth_token != CONFIG["API"]["API_KEY"]:948                return jsonify({"error": 'Unauthorized'}), 401949        else:950            return jsonify({"error": 'API_KEY缺失'}), 401951 952        data = request.json953        model = data.get("model")954        stream = data.get("stream", False)955 956        retry_count = 0957        grok_client = GrokApiClient(model)958        request_payload = grok_client.prepare_chat_request(data)959 960        while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:961            retry_count += 1962            CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(963                model)964 965            if not CONFIG["API"]["SIGNATURE_COOKIE"]:966                raise ValueError('该模型无可用令牌')967 968            logger.info(969                f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}",970                "Server")971            logger.info(972                f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}",973                "Server")974 975            try:976                proxy_options = Utils.get_proxy_options()977                response = curl_requests.post(978                    f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",979                    headers={980                        **DEFAULT_HEADERS, "Cookie":981                        CONFIG["API"]["SIGNATURE_COOKIE"]982                    },983                    data=json.dumps(request_payload),984                    impersonate="chrome133a",985                    stream=True,986                    **proxy_options)987                if response.status_code == 200:988                    logger.info("请求成功", "Server")989                    logger.info(990                        f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",991                        "Server")992 993                    try:994                        if stream:995                            return Response(stream_with_context(996                                handle_stream_response(response, model)),997                                            content_type='text/event-stream')998                        else:999                            content = handle_non_stream_response(1000                                response, model)1001                            return jsonify(1002                                MessageProcessor.create_chat_response(1003                                    content, model))1004 1005                    except Exception as error:1006                        logger.error(str(error), "Server")1007                        if CONFIG["API"]["IS_CUSTOM_SSO"]:1008                            raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")1009 1010                        token_manager.remove_token_from_model(1011                            model, CONFIG["API"]["SIGNATURE_COOKIE"])1012                        if token_manager.get_token_count_for_model(model) == 0:1013                            raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")1014 1015                elif response.status_code == 429:1016                    if CONFIG["API"]["IS_CUSTOM_SSO"]:1017                        raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")1018 1019                    token_manager.remove_token_from_model(1020                        model, CONFIG["API"]["SIGNATURE_COOKIE"])1021                    if token_manager.get_token_count_for_model(model) == 0:1022                        raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")1023 1024                else:1025                    if CONFIG["API"]["IS_CUSTOM_SSO"]:1026                        raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")1027 1028                    logger.error(f"令牌异常错误状态!status: {response.status_code}",1029                                 "Server")1030                    token_manager.remove_token_from_model(1031                        model, CONFIG["API"]["SIGNATURE_COOKIE"])1032                    logger.info(1033                        f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",1034                        "Server")1035 1036            except Exception as e:1037                logger.error(f"请求处理异常: {str(e)}", "Server")1038                if CONFIG["API"]["IS_CUSTOM_SSO"]:1039                    raise1040                continue1041 1042        raise ValueError('当前模型所有令牌都已耗尽')1043 1044    except Exception as error:1045        logger.error(str(error), "ChatAPI")1046        return jsonify(1047            {"error": {1048                "message": str(error),1049                "type": "server_error"1050            }}), 5001051 1052@app.route('/', defaults={'path': ''})1053@app.route('/<path:path>')1054def catch_all(path):1055    return 'api运行正常', 2001056 1057if __name__ == '__main__':1058    token_manager = AuthTokenManager()1059    initialization()1060 1061    app.run(1062        host='0.0.0.0',1063        port=CONFIG["SERVER"]["PORT"],1064        debug=False1065    )1066