from fastapi import FastAPI, Request, Depends, HTTPException from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.responses import StreamingResponse, HTMLResponse from fastapi.background import BackgroundTasks from contextlib import asynccontextmanager import requests from curl_cffi import requests as cffi_requests import uuid import json import time from typing import Optional import asyncio import base64 import tempfile import os import re import threading import logging from dotenv import load_dotenv from playwright.sync_api import sync_playwright # 加载环境变量 load_dotenv(override=True) # 配置日志 logging.basicConfig( level=logging.INFO, # 改为 INFO 级别 format='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # 修改全局数据存储 global_data = { "cookie": None, "cookies": None, "last_update": 0, "cookie_expires": 0 # 添加 cookie 过期时间 } @asynccontextmanager async def lifespan(app: FastAPI): # 启动时获取 cookie logger.info("Starting FastAPI application, initializing cookie fetcher...") # 创建并启动线程 cookie_thread = threading.Thread(target=get_cookie_with_retry) cookie_thread.daemon = True # 设置为守护线程 cookie_thread.start() logger.info("Cookie fetcher thread started") yield # 关闭时清理资源 logger.info("Shutting down FastAPI application") global_data["cookie"] = None global_data["cookies"] = None global_data["last_update"] = 0 def get_cookie_with_retry(max_retries=3, retry_delay=5): """带重试机制的获取 cookie 函数""" retries = 0 while retries < max_retries: logger.info(f"Cookie fetching attempt {retries + 1}/{max_retries}") cookie = get_cookie() if cookie: logger.info("Successfully retrieved cookie") return cookie retries += 1 if retries < max_retries: logger.info(f"Retrying cookie fetch in {retry_delay} seconds...") time.sleep(retry_delay) logger.error(f"Failed to fetch cookie after {max_retries} attempts") return None app = FastAPI(lifespan=lifespan) security = HTTPBearer() # OpenAI API Key 配置,可以通过环境变量覆盖 OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", None) logger.info(f"OPENAI_API_KEY is set: {OPENAI_API_KEY is not None}") logger.info(f"OPENAI_API_KEY value: {OPENAI_API_KEY}") def get_cookie(): try: logger.info("Starting cookie retrieval process...") with sync_playwright() as p: try: # 启动浏览器 logger.info("Launching browser...") browser = p.chromium.launch( headless=True, args=[ '--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-software-rasterizer', '--disable-extensions', '--disable-setuid-sandbox', '--no-first-run', '--no-zygote', '--single-process', '--window-size=1920,1080', '--disable-blink-features=AutomationControlled' # 禁用自动化控制检测 ] ) logger.info("Browser launched successfully") # 创建上下文,添加更多浏览器特征 logger.info("Creating browser context...") context = browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', locale='en-US', timezone_id='America/New_York', permissions=['geolocation'], extra_http_headers={ 'Accept-Language': 'en-US,en;q=0.9', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Sec-Ch-Ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"macOS"', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1' } ) logger.info("Browser context created successfully") # 创建页面 logger.info("Creating new page...") page = context.new_page() logger.info("Page created successfully") # 设置页面超时 page.set_default_timeout(60000) # 访问目标网站 logger.info("Navigating to target website...") page.goto("https://chat.akash.network/", timeout=50000) # 等待页面加载 logger.info("Waiting for page load...") try: # 首先等待 DOM 加载完成 page.wait_for_load_state("domcontentloaded", timeout=30000) logger.info("DOM content loaded") # 等待一段时间,让 Cloudflare 检查完成 logger.info("Waiting for Cloudflare check...") time.sleep(5) # 尝试点击页面,模拟用户行为 try: page.mouse.move(100, 100) page.mouse.click(100, 100) logger.info("Simulated user interaction") except Exception as e: logger.warning(f"Failed to simulate user interaction: {e}") # 再次等待一段时间 time.sleep(5) except Exception as e: logger.warning(f"Timeout waiting for load state: {e}") # 获取 cookies logger.info("Getting cookies...") cookies = context.cookies() if not cookies: logger.error("No cookies found") browser.close() return None # 检查是否有 cf_clearance cookie cf_cookie = next((cookie for cookie in cookies if cookie['name'] == 'cf_clearance'), None) if not cf_cookie: logger.error("cf_clearance cookie not found") browser.close() return None # 构建 cookie 字符串 cookie_str = '; '.join([f"{cookie['name']}={cookie['value']}" for cookie in cookies]) global_data["cookie"] = cookie_str global_data["cookies"] = cookies # 保存完整的 cookies 列表 global_data["last_update"] = time.time() # 查找 session_token cookie 的过期时间 session_cookie = next((cookie for cookie in cookies if cookie['name'] == 'session_token'), None) if session_cookie and 'expires' in session_cookie and session_cookie['expires'] > 0: global_data["cookie_expires"] = session_cookie['expires'] logger.info(f"Session token expires at: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(session_cookie['expires']))}") else: # 如果没有明确的过期时间,默认设置为1小时后过期 global_data["cookie_expires"] = time.time() + 3600 logger.info("No explicit expiration in session_token cookie, setting default 1 hour expiration") logger.info("Successfully retrieved cookies") browser.close() return cookie_str except Exception as e: logger.error(f"Error in browser operations: {e}") logger.error(f"Error type: {type(e)}") import traceback logger.error(f"Traceback: {traceback.format_exc()}") return None except Exception as e: logger.error(f"Error fetching cookie: {str(e)}") logger.error(f"Error type: {type(e)}") import traceback logger.error(f"Traceback: {traceback.format_exc()}") return None # 添加刷新 cookie 的函数 async def refresh_cookie(): logger.info("Refreshing cookie due to 401 error") # 标记 cookie 为过期 global_data["cookie_expires"] = 0 # 获取新的 cookie return get_cookie() async def check_and_update_cookie(background_tasks: BackgroundTasks): # 如果 cookie 不存在或已过期,则更新 current_time = time.time() if not global_data["cookie"] or current_time >= global_data["cookie_expires"]: logger.info("Cookie expired or not available, refreshing...") background_tasks.add_task(get_cookie) else: logger.info("Using existing cookie") async def get_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)): token = credentials.credentials logger.info(f"Received token: {token}") # 如果设置了 OPENAI_API_KEY,则需要验证 if OPENAI_API_KEY is not None: # 去掉 Bearer 前缀后再比较 clean_token = token.replace("Bearer ", "") if token.startswith("Bearer ") else token logger.info(f"Clean token: {clean_token}") if clean_token != OPENAI_API_KEY: logger.error(f"Token mismatch. Expected: {OPENAI_API_KEY}, Got: {clean_token}") raise HTTPException( status_code=401, detail="Invalid API key" ) logger.info("API key validation passed") return True async def validate_cookie(background_tasks: BackgroundTasks): # 检查并更新 cookie(如果需要) await check_and_update_cookie(background_tasks) # 等待 cookie 初始化完成 max_wait = 30 # 最大等待时间(秒) start_time = time.time() while not global_data["cookie"] and time.time() - start_time < max_wait: await asyncio.sleep(1) logger.info("Waiting for cookie initialization...") # 检查是否有有效的 cookie if not global_data["cookie"]: logger.error("Cookie not available after waiting") raise HTTPException( status_code=503, detail="Service temporarily unavailable - Cookie not available" ) logger.info("Cookie validation passed") return global_data["cookie"] async def check_image_status(session: requests.Session, job_id: str, headers: dict) -> Optional[str]: """检查图片生成状态并获取生成的图片""" max_retries = 30 for attempt in range(max_retries): try: print(f"\nAttempt {attempt + 1}/{max_retries} for job {job_id}") response = session.get( f'https://chat.akash.network/api/image-status?ids={job_id}', headers=headers ) print(f"Status response code: {response.status_code}") status_data = response.json() if status_data and isinstance(status_data, list) and len(status_data) > 0: job_info = status_data[0] status = job_info.get('status') print(f"Job status: {status}") # 只有当状态为 completed 时才处理结果 if status == "completed": result = job_info.get("result") if result and not result.startswith("Failed"): print("Got valid result, attempting upload...") image_url = await upload_to_xinyew(result, job_id) if image_url: print(f"Successfully uploaded image: {image_url}") return image_url print("Image upload failed") return None print("Invalid result received") return None elif status == "failed": print(f"Job {job_id} failed") return None # 如果状态是其他(如 pending),继续等待 await asyncio.sleep(1) continue except Exception as e: print(f"Error checking status: {e}") return None print(f"Timeout waiting for job {job_id}") return None @app.get("/", response_class=HTMLResponse) async def health_check(): """Health check endpoint""" # 检查 cookie 状态 cookie_status = "ok" if global_data["cookie"] is not None else "error" cookie_status_color = "#4CAF50" if cookie_status == "ok" else "#f44336" status = { "status": cookie_status, "version": "1.0.0", "cookie_status": { "available": global_data["cookie"] is not None, "last_update": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(global_data["last_update"])) if global_data["last_update"] > 0 else None, "expires": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(global_data["cookie_expires"])) if global_data["cookie_expires"] > 0 else None } } html = f"""