Refactor activity system: energy-based probability, manual override, all 5 activity types

- Rewrite utils/activities.py with mood energy-driven activity probability
  (high-energy moods like excited/bubbly show activity ~80-85% of the time,
  low-energy moods like sleepy/melancholy only ~15-25%)
- Add manual override system with 30-min auto-expiry for Web UI control
- Support all 5 Discord activity types: listening, playing, watching,
  competing, streaming (with purple LIVE badge via discord.Streaming)
- Add current activity tracking (get_current_activity)
- Add force=True param to update_bot_presence for on_ready (bot.py)
- Add 4 new API routes for manual override:
  GET/POST/DELETE /activities/current, POST /activities/current/auto
- Expand activities.yaml from 139 to 157 entries, adding watching,
  competing, and streaming entries across 11 moods
- Update Web UI: activity type dropdown with all 5 types, conditional
  URL field for streaming, 'Current Activity' override panel with
  set/clear/auto controls, type-aware icons and labels
This commit is contained in:
2026-04-27 23:39:18 +03:00
parent 9bc618b526
commit d6cdb89e42
5 changed files with 556 additions and 49 deletions

View File

@@ -71,3 +71,70 @@ def reload_activities():
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)}
"""
data = await request.json()
activity_type = data.get("type", "").lower().strip()
name = data.get("name", "").strip()
state = data.get("state") or None
url = data.get("url") or None
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"})