Files
miku-discord/bot/routes/activities.py

157 lines
6.7 KiB
Python
Raw Normal View History

"""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"})