"""Activities API routes — CRUD for mood-based song/game activity lists.""" from fastapi import APIRouter, Request from fastapi.responses import JSONResponse from utils.logger import get_logger logger = get_logger('api') router = APIRouter() @router.get("/activities") def get_all_activities(): """Return the full activities data (normal + evil sections, all moods).""" from utils.activities import get_all_activities return get_all_activities() @router.get("/activities/{section}/{mood}") def get_mood_activities(section: str, mood: str): """Return activities for a specific mood. Args: section: "normal" or "evil" mood: mood name (e.g. "bubbly", "aggressive") """ if section not in ("normal", "evil"): return JSONResponse(status_code=400, content={"error": "Section must be 'normal' or 'evil'"}) from utils.activities import get_activities_for_mood activities = get_activities_for_mood(mood, is_evil=(section == "evil")) return {"section": section, "mood": mood, "activities": activities} @router.post("/activities/{section}/{mood}") async def set_mood_activities(section: str, mood: str, request: Request): """Update activities for a specific mood. Body: {"activities": [{"type": "listening"|"playing", "name": "...", "weight": 1}]} """ if section not in ("normal", "evil"): return JSONResponse(status_code=400, content={"error": "Section must be 'normal' or 'evil'"}) try: data = await request.json() except Exception: return JSONResponse(status_code=400, content={"error": "Invalid JSON body"}) activities = data.get("activities") if activities is None: return JSONResponse(status_code=400, content={"error": "Request body must include 'activities' list"}) if not isinstance(activities, list): return JSONResponse(status_code=400, content={"error": "'activities' must be a list"}) try: from utils.activities import set_activities_for_mood set_activities_for_mood(mood, is_evil=(section == "evil"), activities=activities) logger.info(f"Updated activities for {section}/{mood}: {len(activities)} entries") return {"status": "ok", "section": section, "mood": mood, "count": len(activities)} except ValueError as e: return JSONResponse(status_code=400, content={"error": str(e)}) except Exception as e: logger.error(f"Failed to save activities for {section}/{mood}: {e}") return JSONResponse(status_code=500, content={"error": "Internal server error"}) @router.post("/activities/reload") def reload_activities(): """Force reload activities from disk (useful after hand-editing the YAML).""" from utils.activities import _load_activities data = _load_activities(force=True) normal_count = sum(len(v) for v in data.get("normal", {}).values()) evil_count = sum(len(v) for v in data.get("evil", {}).values()) logger.info(f"Force-reloaded activities: {normal_count} normal entries, {evil_count} evil entries") return {"status": "ok", "normal_entries": normal_count, "evil_entries": evil_count} # ══════════════════════════════════════════════════════════════════════════════ # Manual Override — set / clear / release current activity # ══════════════════════════════════════════════════════════════════════════════ @router.get("/activities/current") def get_current_activity(): """Return the bot's current activity and override status.""" from utils.activities import get_current_activity, is_manual_override_active activity = get_current_activity() override = is_manual_override_active() result = { "activity": activity, # dict or null "manual_override": override, } return result @router.post("/activities/current") async def set_current_activity(request: Request): """Manually set the bot's activity (bypasses mood system for 30 min). Body: {"type": "listening"|"playing"|"watching"|"competing"|"streaming", "name": "...", "state": "..." (optional), "url": "..." (required for streaming)} """ try: data = await request.json() except Exception: return JSONResponse(status_code=400, content={"error": "Invalid JSON body"}) activity_type = data.get("type", "").lower().strip() name = data.get("name", "").strip() state = data.get("state") or None url = data.get("url") or None # Pre-validate before passing to activity module if not activity_type: return JSONResponse(status_code=400, content={"error": "'type' is required"}) if not name: return JSONResponse(status_code=400, content={"error": "'name' is required"}) if len(name) > 128: return JSONResponse(status_code=400, content={"error": f"'name' exceeds 128 characters ({len(name)})"}) try: from utils.activities import set_activity_manual await set_activity_manual(activity_type, name, state=state, url=url) return {"status": "ok", "activity": {"type": activity_type, "name": name, "state": state, "url": url}} except ValueError as e: return JSONResponse(status_code=400, content={"error": str(e)}) except RuntimeError as e: return JSONResponse(status_code=503, content={"error": str(e)}) except Exception as e: logger.error(f"Failed to set manual activity: {e}") return JSONResponse(status_code=500, content={"error": "Internal server error"}) @router.delete("/activities/current") async def clear_current_activity(): """Manually clear the bot's activity (stays idle, override stays active).""" try: from utils.activities import clear_activity_manual await clear_activity_manual() return {"status": "ok", "activity": None, "manual_override": True} except Exception as e: logger.error(f"Failed to clear manual activity: {e}") return JSONResponse(status_code=500, content={"error": "Internal server error"}) @router.post("/activities/current/auto") async def release_to_auto(): """Release manual override and return to automatic mood-based activity.""" try: from utils.activities import release_manual_override await release_manual_override() return {"status": "ok", "manual_override": False} except Exception as e: logger.error(f"Failed to release manual override: {e}") return JSONResponse(status_code=500, content={"error": "Internal server error"})