|
|
"""FastAPI application initialization""" |
|
|
from fastapi import FastAPI |
|
|
from fastapi.responses import HTMLResponse, FileResponse |
|
|
from fastapi.staticfiles import StaticFiles |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
from contextlib import asynccontextmanager |
|
|
from pathlib import Path |
|
|
|
|
|
from .core.config import config |
|
|
from .core.database import Database |
|
|
from .services.flow_client import FlowClient |
|
|
from .services.proxy_manager import ProxyManager |
|
|
from .services.token_manager import TokenManager |
|
|
from .services.load_balancer import LoadBalancer |
|
|
from .services.concurrency_manager import ConcurrencyManager |
|
|
from .services.generation_handler import GenerationHandler |
|
|
from .api import routes, admin |
|
|
|
|
|
|
|
|
@asynccontextmanager |
|
|
async def lifespan(app: FastAPI): |
|
|
"""Application lifespan manager""" |
|
|
|
|
|
print("=" * 60) |
|
|
print("Flow2API Starting...") |
|
|
print("=" * 60) |
|
|
|
|
|
|
|
|
config_dict = config.get_raw_config() |
|
|
|
|
|
|
|
|
is_first_startup = not db.db_exists() |
|
|
|
|
|
|
|
|
await db.init_db() |
|
|
|
|
|
|
|
|
if is_first_startup: |
|
|
print("🎉 First startup detected. Initializing database and configuration from setting.toml...") |
|
|
await db.init_config_from_toml(config_dict, is_first_startup=True) |
|
|
print("✓ Database and configuration initialized successfully.") |
|
|
else: |
|
|
print("🔄 Existing database detected. Checking for missing tables and columns...") |
|
|
await db.check_and_migrate_db(config_dict) |
|
|
print("✓ Database migration check completed.") |
|
|
|
|
|
|
|
|
admin_config = await db.get_admin_config() |
|
|
if admin_config: |
|
|
config.set_admin_username_from_db(admin_config.username) |
|
|
config.set_admin_password_from_db(admin_config.password) |
|
|
config.api_key = admin_config.api_key |
|
|
|
|
|
|
|
|
cache_config = await db.get_cache_config() |
|
|
config.set_cache_enabled(cache_config.cache_enabled) |
|
|
config.set_cache_timeout(cache_config.cache_timeout) |
|
|
config.set_cache_base_url(cache_config.cache_base_url or "") |
|
|
|
|
|
|
|
|
generation_config = await db.get_generation_config() |
|
|
config.set_image_timeout(generation_config.image_timeout) |
|
|
config.set_video_timeout(generation_config.video_timeout) |
|
|
|
|
|
|
|
|
debug_config = await db.get_debug_config() |
|
|
config.set_debug_enabled(debug_config.enabled) |
|
|
|
|
|
|
|
|
captcha_config = await db.get_captcha_config() |
|
|
config.set_captcha_method(captcha_config.captcha_method) |
|
|
config.set_yescaptcha_api_key(captcha_config.yescaptcha_api_key) |
|
|
config.set_yescaptcha_base_url(captcha_config.yescaptcha_base_url) |
|
|
|
|
|
|
|
|
browser_service = None |
|
|
if captcha_config.captcha_method == "personal": |
|
|
try: |
|
|
from .services.browser_captcha_personal import PLAYWRIGHT_AVAILABLE |
|
|
if PLAYWRIGHT_AVAILABLE: |
|
|
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)") |
|
|
else: |
|
|
print("⚠️ Playwright not available. Please use YesCaptcha service instead.") |
|
|
|
|
|
await db.update_captcha_config( |
|
|
captcha_method="yescaptcha", |
|
|
yescaptcha_api_key=captcha_config.yescaptcha_api_key, |
|
|
yescaptcha_base_url=captcha_config.yescaptcha_base_url |
|
|
) |
|
|
print("✓ Captcha method automatically switched to yescaptcha") |
|
|
except ImportError: |
|
|
print("⚠️ Playwright not available. Please use YesCaptcha service instead.") |
|
|
|
|
|
await db.update_captcha_config( |
|
|
captcha_method="yescaptcha", |
|
|
yescaptcha_api_key=captcha_config.yescaptcha_api_key, |
|
|
yescaptcha_base_url=captcha_config.yescaptcha_base_url |
|
|
) |
|
|
print("✓ Captcha method automatically switched to yescaptcha") |
|
|
elif captcha_config.captcha_method == "browser": |
|
|
try: |
|
|
from .services.browser_captcha import PLAYWRIGHT_AVAILABLE |
|
|
if PLAYWRIGHT_AVAILABLE: |
|
|
from .services.browser_captcha import BrowserCaptchaService |
|
|
browser_service = await BrowserCaptchaService.get_instance(db) |
|
|
print("✓ Browser captcha service initialized (headless mode)") |
|
|
else: |
|
|
print("⚠️ Playwright not available. Please use YesCaptcha service instead.") |
|
|
|
|
|
await db.update_captcha_config( |
|
|
captcha_method="yescaptcha", |
|
|
yescaptcha_api_key=captcha_config.yescaptcha_api_key, |
|
|
yescaptcha_base_url=captcha_config.yescaptcha_base_url |
|
|
) |
|
|
print("✓ Captcha method automatically switched to yescaptcha") |
|
|
except ImportError: |
|
|
print("⚠️ Playwright not available. Please use YesCaptcha service instead.") |
|
|
|
|
|
await db.update_captcha_config( |
|
|
captcha_method="yescaptcha", |
|
|
yescaptcha_api_key=captcha_config.yescaptcha_api_key, |
|
|
yescaptcha_base_url=captcha_config.yescaptcha_base_url |
|
|
) |
|
|
print("✓ Captcha method automatically switched to yescaptcha") |
|
|
|
|
|
|
|
|
tokens = await token_manager.get_all_tokens() |
|
|
await concurrency_manager.initialize(tokens) |
|
|
|
|
|
|
|
|
await generation_handler.file_cache.start_cleanup_task() |
|
|
|
|
|
|
|
|
import asyncio |
|
|
async def auto_unban_task(): |
|
|
"""定时任务:每小时检查并解禁429被禁用的token""" |
|
|
while True: |
|
|
try: |
|
|
await asyncio.sleep(3600) |
|
|
await token_manager.auto_unban_429_tokens() |
|
|
except Exception as e: |
|
|
print(f"❌ Auto-unban task error: {e}") |
|
|
|
|
|
auto_unban_task_handle = asyncio.create_task(auto_unban_task()) |
|
|
|
|
|
print(f"✓ Database initialized") |
|
|
print(f"✓ Total tokens: {len(tokens)}") |
|
|
print(f"✓ Cache: {'Enabled' if config.cache_enabled else 'Disabled'} (timeout: {config.cache_timeout}s)") |
|
|
print(f"✓ File cache cleanup task started") |
|
|
print(f"✓ 429 auto-unban task started (runs every hour)") |
|
|
print(f"✓ Server running on http://{config.server_host}:{config.server_port}") |
|
|
print("=" * 60) |
|
|
|
|
|
yield |
|
|
|
|
|
|
|
|
print("Flow2API Shutting down...") |
|
|
|
|
|
await generation_handler.file_cache.stop_cleanup_task() |
|
|
|
|
|
auto_unban_task_handle.cancel() |
|
|
try: |
|
|
await auto_unban_task_handle |
|
|
except asyncio.CancelledError: |
|
|
pass |
|
|
|
|
|
if browser_service: |
|
|
await browser_service.close() |
|
|
print("✓ Browser captcha service closed") |
|
|
print("✓ File cache cleanup task stopped") |
|
|
print("✓ 429 auto-unban task stopped") |
|
|
|
|
|
|
|
|
|
|
|
db = Database() |
|
|
proxy_manager = ProxyManager(db) |
|
|
flow_client = FlowClient(proxy_manager) |
|
|
token_manager = TokenManager(db, flow_client) |
|
|
concurrency_manager = ConcurrencyManager() |
|
|
load_balancer = LoadBalancer(token_manager, concurrency_manager) |
|
|
generation_handler = GenerationHandler( |
|
|
flow_client, |
|
|
token_manager, |
|
|
load_balancer, |
|
|
db, |
|
|
concurrency_manager, |
|
|
proxy_manager |
|
|
) |
|
|
|
|
|
|
|
|
routes.set_generation_handler(generation_handler) |
|
|
admin.set_dependencies(token_manager, proxy_manager, db) |
|
|
|
|
|
|
|
|
app = FastAPI( |
|
|
title="Flow2API", |
|
|
description="OpenAI-compatible API for Google VideoFX (Veo)", |
|
|
version="1.0.0", |
|
|
lifespan=lifespan |
|
|
) |
|
|
|
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["*"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
|
|
|
|
|
|
app.include_router(routes.router) |
|
|
app.include_router(admin.router) |
|
|
|
|
|
|
|
|
tmp_dir = Path(__file__).parent.parent / "tmp" |
|
|
tmp_dir.mkdir(exist_ok=True) |
|
|
app.mount("/tmp", StaticFiles(directory=str(tmp_dir)), name="tmp") |
|
|
|
|
|
|
|
|
static_path = Path(__file__).parent.parent / "static" |
|
|
|
|
|
|
|
|
@app.get("/", response_class=HTMLResponse) |
|
|
async def index(): |
|
|
"""Redirect to login page""" |
|
|
login_file = static_path / "login.html" |
|
|
if login_file.exists(): |
|
|
return FileResponse(str(login_file)) |
|
|
return HTMLResponse(content="<h1>Flow2API</h1><p>Frontend not found</p>", status_code=404) |
|
|
|
|
|
|
|
|
@app.get("/login", response_class=HTMLResponse) |
|
|
async def login_page(): |
|
|
"""Login page""" |
|
|
login_file = static_path / "login.html" |
|
|
if login_file.exists(): |
|
|
return FileResponse(str(login_file)) |
|
|
return HTMLResponse(content="<h1>Login Page Not Found</h1>", status_code=404) |
|
|
|
|
|
|
|
|
@app.get("/manage", response_class=HTMLResponse) |
|
|
async def manage_page(): |
|
|
"""Management console page""" |
|
|
manage_file = static_path / "manage.html" |
|
|
if manage_file.exists(): |
|
|
return FileResponse(str(manage_file)) |
|
|
return HTMLResponse(content="<h1>Management Page Not Found</h1>", status_code=404) |
|
|
|