import asyncio import json import os from pathlib import Path from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.responses import FileResponse, HTMLResponse app = FastAPI() OUTPUT_DIR = Path("output") SETTINGS_FILE = Path("bot_settings.json") DEFAULT_SETTINGS = { "subreddits": ["AITAH", "relationship_advice", "confessions", "tifu"], "whisper_model": "base", "voice": "en-US-ChristopherNeural", "min_score": 1000, "min_words": 200, "max_words": 1500, } log_clients: list[WebSocket] = [] bot_running = False def load_settings() -> dict: if SETTINGS_FILE.exists(): return json.loads(SETTINGS_FILE.read_text()) return DEFAULT_SETTINGS.copy() def save_settings(data: dict): SETTINGS_FILE.write_text(json.dumps(data, indent=2)) @app.get("/", response_class=HTMLResponse) async def index(): return open("templates/index.html").read() @app.get("/api/settings") async def get_settings(): return load_settings() @app.post("/api/settings") async def post_settings(data: dict): save_settings(data) return {"ok": True} @app.get("/api/videos") async def list_videos(): OUTPUT_DIR.mkdir(exist_ok=True) videos = [] for f in sorted(OUTPUT_DIR.glob("*.mp4"), key=lambda x: x.stat().st_mtime, reverse=True): videos.append({ "name": f.name, "size": f.stat().st_size, "url": f"/videos/{f.name}", }) return videos @app.get("/videos/{filename}") async def serve_video(filename: str): path = OUTPUT_DIR / filename if not path.exists(): return {"error": "not found"} return FileResponse(path, media_type="video/mp4") async def broadcast(message: str): dead = [] for ws in log_clients: try: await ws.send_text(message) except Exception: dead.append(ws) for ws in dead: log_clients.remove(ws) @app.post("/api/run") async def run_bot(): global bot_running if bot_running: return {"error": "Bot is already running"} bot_running = True asyncio.create_task(_run_bot_task()) return {"ok": True} async def _run_bot_task(): global bot_running settings = load_settings() env = os.environ.copy() env["WHISPER_MODEL"] = settings.get("whisper_model", "base") env["BOT_VOICE"] = settings.get("voice", "en-US-ChristopherNeural") env["BOT_SUBREDDITS"] = ",".join(settings.get("subreddits", [])) env["BOT_MIN_SCORE"] = str(settings.get("min_score", 1000)) env["BOT_MIN_WORDS"] = str(settings.get("min_words", 200)) env["BOT_MAX_WORDS"] = str(settings.get("max_words", 1500)) try: proc = await asyncio.create_subprocess_exec( "python", "main.py", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, env=env, ) async for line in proc.stdout: await broadcast(line.decode().rstrip()) await proc.wait() await broadcast(f"__DONE__{proc.returncode}") except Exception as e: await broadcast(f"[ERROR] {e}") await broadcast("__DONE__1") finally: bot_running = False @app.websocket("/ws/logs") async def ws_logs(websocket: WebSocket): await websocket.accept() log_clients.append(websocket) if bot_running: await websocket.send_text("__RUNNING__") try: while True: await asyncio.sleep(30) except WebSocketDisconnect: pass finally: if websocket in log_clients: log_clients.remove(websocket)