diff --git a/bot/activities.yaml b/bot/activities.yaml index e78828e..e1af982 100644 --- a/bot/activities.yaml +++ b/bot/activities.yaml @@ -32,6 +32,11 @@ normal: name: 'Hatsune Miku: Project DIVA Future Tone' weight: 1 state: Rhythm Game + - type: streaming + name: VOCALOID Covers + weight: 1 + state: on YouTube + url: https://www.youtube.com/watch?v=CGbYfNq3iZQ excited: - type: listening name: Melt @@ -65,6 +70,14 @@ normal: name: Muse Dash weight: 2 state: Rhythm Game + - type: streaming + name: rhythm game gameplay + weight: 1 + url: https://www.youtube.com/watch?v=3J8EeHxg3po + - type: competing + name: Beat Saber Tournament + weight: 1 + state: Ranked neutral: - type: listening name: Miku Miku ni Shite Ageru♪ @@ -94,6 +107,14 @@ normal: name: 'Project SEKAI: Colorful Stage!' weight: 2 state: Rhythm Game + - type: watching + name: YouTube + weight: 2 + state: Music Videos + - type: competing + name: osu! + weight: 1 + state: Ranked Match sleepy: - type: listening name: Yuki no Hahen @@ -156,6 +177,14 @@ normal: name: 'The Legend of Zelda: Tears of the Kingdom' weight: 2 state: Adventure + - type: watching + name: VOCALOID tutorials + weight: 1 + state: on YouTube + - type: watching + name: science documentaries + weight: 1 + state: Discovery Channel shy: - type: listening name: Koi wo Sensou @@ -210,6 +239,10 @@ normal: name: Civilization VI weight: 2 state: 4X Strategy + - type: watching + name: chess tournament + weight: 1 + state: PGN Livestream melancholy: - type: listening name: Kokoro @@ -260,6 +293,10 @@ normal: name: 'Project SEKAI: Colorful Stage!' weight: 2 state: Rhythm Game + - type: streaming + name: karaoke stream + weight: 1 + url: https://www.youtube.com/watch?v=CGbYfNq3iZQ romantic: - type: listening name: Romeo and Cinderella @@ -306,6 +343,10 @@ normal: name: Elden Ring weight: 2 state: Action RPG + - type: watching + name: rage compilations + weight: 1 + state: YouTube angry: - type: listening name: Two-Faced Lovers @@ -331,6 +372,14 @@ normal: name: Hades weight: 2 state: Roguelike + - type: competing + name: Valorant + weight: 1 + state: Ranked + - type: streaming + name: speedrun attempts + weight: 1 + url: https://www.youtube.com/watch?v=3J8EeHxg3po silly: - type: listening name: PoPiPo @@ -364,6 +413,14 @@ normal: name: Fall Guys weight: 2 state: Party Game + - type: competing + name: Fall Guys + weight: 2 + state: Tournament Mode + - type: watching + name: funny fails compilation + weight: 1 + state: YouTube test: - type: playing name: G @@ -390,6 +447,10 @@ evil: name: Devil May Cry 5 weight: 2 state: Action + - type: competing + name: DOOM Eternal + weight: 2 + state: Ultra Nightmare cunning: - type: listening name: Gekkabijin @@ -503,6 +564,10 @@ evil: name: Neon White weight: 2 state: FPS Platformer + - type: streaming + name: chaos speedrun + weight: 1 + url: https://www.youtube.com/watch?v=3J8EeHxg3po jealous: - type: listening name: Rotten Girl Grotesque Romance @@ -583,3 +648,7 @@ evil: name: Crusader Kings III weight: 2 state: Grand Strategy + - type: watching + name: world domination tutorials + weight: 1 + state: YouTube diff --git a/bot/bot.py b/bot/bot.py index 53f99d4..7e1a8cb 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -140,9 +140,9 @@ async def on_ready(): try: from utils.activities import update_bot_presence if globals.EVIL_MODE: - await update_bot_presence(globals.EVIL_DM_MOOD, is_evil=True) + await update_bot_presence(globals.EVIL_DM_MOOD, is_evil=True, force=True) else: - await update_bot_presence(globals.DM_MOOD, is_evil=False) + await update_bot_presence(globals.DM_MOOD, is_evil=False, force=True) except Exception as e: logger.error(f"Failed to set initial presence: {e}") diff --git a/bot/routes/activities.py b/bot/routes/activities.py index 108d139..1db36c8 100644 --- a/bot/routes/activities.py +++ b/bot/routes/activities.py @@ -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"}) diff --git a/bot/static/index.html b/bot/static/index.html index 74b70cd..50eec74 100644 --- a/bot/static/index.html +++ b/bot/static/index.html @@ -481,7 +481,7 @@ .act-entry-icon { font-size: 1.1rem; min-width: 24px; text-align: center; } .act-entry input[type="text"] { flex: 1; } .act-entry input[type="number"] { width: 55px; } - .act-entry select { width: 110px; } + .act-entry select { width: 130px; } .act-toolbar { display: flex; gap: 0.5rem; @@ -1362,6 +1362,35 @@ + +