diff --git a/.gitignore b/.gitignore index 6b0d2df..6fa57a0 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,7 @@ logs.txt *.cache browser_data + +data +config/setting.toml +config/setting_warp.toml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5fcdf41..fc337cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,10 @@ FROM python:3.11-slim WORKDIR /app +# 使用清华镜像源加速 apt (Debian bookworm) +RUN sed -i 's|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/debian.sources \ + && sed -i 's|security.debian.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/debian.sources + # 安装 Playwright 所需的系统依赖 RUN apt-get update && apt-get install -y \ libnss3 \ @@ -21,9 +25,14 @@ RUN apt-get update && apt-get install -y \ libcairo2 \ && rm -rf /var/lib/apt/lists/* -# 安装 Python 依赖 +# 安装 Python 依赖(使用清华 PyPI 镜像) COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --no-cache-dir -r requirements.txt \ + -i https://pypi.tuna.tsinghua.edu.cn/simple/ \ + --trusted-host pypi.tuna.tsinghua.edu.cn + +# 设置 Playwright 下载镜像(使用 npmmirror) +ENV PLAYWRIGHT_DOWNLOAD_HOST=https://registry.npmmirror.com/-/binary/playwright # 安装 Playwright 浏览器 RUN playwright install chromium diff --git a/config/setting.toml b/config/setting.toml index 051cfd3..3dbd623 100644 --- a/config/setting.toml +++ b/config/setting.toml @@ -12,7 +12,7 @@ max_poll_attempts = 200 [server] host = "0.0.0.0" -port = 8000 +port = 18282 [debug] enabled = false @@ -21,8 +21,8 @@ log_responses = true mask_token = true [proxy] -proxy_enabled = false -proxy_url = "" +proxy_enabled = true +proxy_url = "http://localhost:7897" [generation] image_timeout = 300 diff --git a/config/setting_example.toml b/config/setting_example.toml new file mode 100644 index 0000000..3dbd623 --- /dev/null +++ b/config/setting_example.toml @@ -0,0 +1,42 @@ +[global] +api_key = "han1234" +admin_username = "admin" +admin_password = "admin" + +[flow] +labs_base_url = "https://labs.google/fx/api" +api_base_url = "https://aisandbox-pa.googleapis.com/v1" +timeout = 120 +poll_interval = 3.0 +max_poll_attempts = 200 + +[server] +host = "0.0.0.0" +port = 18282 + +[debug] +enabled = false +log_requests = true +log_responses = true +mask_token = true + +[proxy] +proxy_enabled = true +proxy_url = "http://localhost:7897" + +[generation] +image_timeout = 300 +video_timeout = 1500 + +[admin] +error_ban_threshold = 3 + +[cache] +enabled = false +timeout = 7200 # 缓存超时时间(秒), 默认2小时 +base_url = "" # 缓存文件访问的基础URL, 留空则使用服务器地址 + +[captcha] +captcha_method = "browser" # 打码方式: yescaptcha 或 browser +yescaptcha_api_key = "" # YesCaptcha API密钥 +yescaptcha_base_url = "https://api.yescaptcha.com" diff --git a/config/setting_warp_example.toml b/config/setting_warp_example.toml new file mode 100644 index 0000000..ac58190 --- /dev/null +++ b/config/setting_warp_example.toml @@ -0,0 +1,42 @@ +[global] +api_key = "han1234" +admin_username = "admin" +admin_password = "admin" + +[flow] +labs_base_url = "https://labs.google/fx/api" +api_base_url = "https://aisandbox-pa.googleapis.com/v1" +timeout = 120 +poll_interval = 3.0 +max_poll_attempts = 200 + +[server] +host = "0.0.0.0" +port = 8000 + +[debug] +enabled = false +log_requests = true +log_responses = true +mask_token = true + +[proxy] +proxy_enabled = true +proxy_url = "socks5://warp:1080" + +[generation] +image_timeout = 300 +video_timeout = 1500 + +[admin] +error_ban_threshold = 3 + +[cache] +enabled = false +timeout = 7200 # 缓存超时时间(秒), 默认2小时 +base_url = "" # 缓存文件访问的基础URL, 留空则使用服务器地址 + +[captcha] +captcha_method = "browser" # 打码方式: yescaptcha 或 browser +yescaptcha_api_key = "" # YesCaptcha API密钥 +yescaptcha_base_url = "https://api.yescaptcha.com" diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..8e55b4e --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,17 @@ +version: '3.8' + +services: + flow2api: + build: + context: . + dockerfile: Dockerfile + image: flow2api:local + container_name: flow2api + ports: + - "38000:8000" + volumes: + - ./data:/app/data + - ./config/setting.toml:/app/config/setting.toml + environment: + - PYTHONUNBUFFERED=1 + restart: unless-stopped diff --git a/docker-compose.proxy.yml b/docker-compose.proxy.yml index 2748871..0cb3265 100644 --- a/docker-compose.proxy.yml +++ b/docker-compose.proxy.yml @@ -5,7 +5,7 @@ services: image: thesmallhancat/flow2api:latest container_name: flow2api ports: - - "8000:8000" + - "38000:8000" volumes: - ./data:/app/data - ./config/setting_warp.toml:/app/config/setting.toml @@ -22,7 +22,7 @@ services: devices: - /dev/net/tun:/dev/net/tun ports: - - "1080:1080" + - "31080:1080" environment: - WARP_SLEEP=2 cap_add: @@ -33,4 +33,4 @@ services: - net.ipv6.conf.all.disable_ipv6=0 - net.ipv4.conf.all.src_valid_mark=1 volumes: - - ./data:/var/lib/cloudflare-warp \ No newline at end of file + - ./data:/var/lib/cloudflare-warp diff --git a/docker-compose.yml b/docker-compose.yml index 7cab3d4..b664e54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: image: thesmallhancat/flow2api:latest container_name: flow2api ports: - - "8000:8000" + - "38000:8000" volumes: - ./data:/app/data - ./config/setting.toml:/app/config/setting.toml diff --git a/src/main.py b/src/main.py index 0b4e1fd..01c43f2 100644 --- a/src/main.py +++ b/src/main.py @@ -68,18 +68,46 @@ async def lifespan(app: FastAPI): # Load captcha configuration from database captcha_config = await db.get_captcha_config() - config.set_captcha_method(captcha_config.captcha_method) + + # Helper function to detect headless/Docker environment + def is_headless_environment() -> bool: + """Check if running in a headless environment (Docker, no display, etc.)""" + import os + # Check for DISPLAY environment variable (X11) + if not os.environ.get("DISPLAY"): + # Check if running in Docker + if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_CONTAINER"): + return True + # Check for common CI/container indicators + if os.environ.get("CI") or os.environ.get("KUBERNETES_SERVICE_HOST"): + return True + # No DISPLAY and not explicitly local + return True + return False + + # Determine effective captcha method + effective_captcha_method = captcha_config.captcha_method + + # Auto-downgrade personal mode to browser mode in headless environments + if captcha_config.captcha_method == "personal" and is_headless_environment(): + print("⚠️ WARNING: 'personal' captcha mode requires a display (X Server).") + print(" Detected headless environment (Docker/No Display).") + print(" Auto-switching to 'browser' (headless) mode.") + print(" To use 'personal' mode, run Flow2API on a machine with a display.") + effective_captcha_method = "browser" + + config.set_captcha_method(effective_captcha_method) config.set_yescaptcha_api_key(captcha_config.yescaptcha_api_key) config.set_yescaptcha_base_url(captcha_config.yescaptcha_base_url) # Initialize browser captcha service if needed browser_service = None - if captcha_config.captcha_method == "personal": + if effective_captcha_method == "personal": from .services.browser_captcha_personal import BrowserCaptchaService browser_service = await BrowserCaptchaService.get_instance(db) await browser_service.open_login_window() print("✓ Browser captcha service initialized (webui mode)") - elif captcha_config.captcha_method == "browser": + elif effective_captcha_method == "browser": from .services.browser_captcha import BrowserCaptchaService browser_service = await BrowserCaptchaService.get_instance(db) print("✓ Browser captcha service initialized (headless mode)") @@ -135,7 +163,7 @@ async def lifespan(app: FastAPI): # Initialize components db = Database() proxy_manager = ProxyManager(db) -flow_client = FlowClient(proxy_manager) +flow_client = FlowClient(proxy_manager, db) token_manager = TokenManager(db, flow_client) concurrency_manager = ConcurrencyManager() load_balancer = LoadBalancer(token_manager, concurrency_manager) diff --git a/src/services/flow_client.py b/src/services/flow_client.py index 924e068..6c6d5e8 100644 --- a/src/services/flow_client.py +++ b/src/services/flow_client.py @@ -12,8 +12,9 @@ from ..core.config import config class FlowClient: """VideoFX API客户端""" - def __init__(self, proxy_manager): + def __init__(self, proxy_manager, db=None): self.proxy_manager = proxy_manager + self.db = db # Database instance for captcha config self.labs_base_url = config.flow_labs_base_url # https://labs.google/fx/api self.api_base_url = config.flow_api_base_url # https://aisandbox-pa.googleapis.com/v1 self.timeout = config.flow_timeout @@ -691,7 +692,7 @@ class FlowClient: if captcha_method == "personal": try: from .browser_captcha_personal import BrowserCaptchaService - service = await BrowserCaptchaService.get_instance(self.proxy_manager) + service = await BrowserCaptchaService.get_instance(self.db) return await service.get_token(project_id) except Exception as e: debug_logger.log_error(f"[reCAPTCHA Browser] error: {str(e)}") @@ -700,7 +701,7 @@ class FlowClient: elif captcha_method == "browser": try: from .browser_captcha import BrowserCaptchaService - service = await BrowserCaptchaService.get_instance(self.proxy_manager) + service = await BrowserCaptchaService.get_instance(self.db) return await service.get_token(project_id) except Exception as e: debug_logger.log_error(f"[reCAPTCHA Browser] error: {str(e)}")