138 lines
3.5 KiB
Python
138 lines
3.5 KiB
Python
|
|
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)
|