# utils/moods.py import random import discord import os import asyncio from discord.ext import tasks import globals import datetime MOOD_EMOJIS = { "asleep": "💤", "neutral": "", "bubbly": "🫧", "sleepy": "🌙", "curious": "👀", "shy": "👉👈", "serious": "👔", "excited": "✨", "melancholy": "🍷", "flirty": "🫦", "romantic": "💌", "irritated": "😒", "angry": "💢", "silly": "🪿" } def load_mood_description(mood_name: str) -> str: path = os.path.join("moods", f"{mood_name}.txt") try: with open(path, "r", encoding="utf-8") as f: return f.read().strip() except FileNotFoundError: print(f"⚠️ Mood file '{mood_name}' not found. Falling back to default.") # Return a default mood description instead of recursive call return "I'm feeling neutral and balanced today." def detect_mood_shift(response_text, server_context=None): """ Detect mood shift from response text server_context: Optional server context to check against server-specific moods """ mood_keywords = { "asleep": [ "good night", "goodnight", "sweet dreams", "going to bed", "I will go to bed", "zzz~", "sleep tight" ], "neutral": [ "okay", "sure", "alright", "i see", "understood", "hmm", "sounds good", "makes sense", "alrighty", "fine", "got it" ], "bubbly": [ "so excited", "feeling bubbly", "super cheerful", "yay!", "✨", "nya~", "kyaa~", "heehee", "bouncy", "so much fun", "i'm glowing!", "nee~", "teehee", "I'm so happy" ], "sleepy": [ "i'm sleepy", "getting tired", "yawn", "so cozy", "zzz", "nap time", "just five more minutes", "snooze", "cuddle up", "dozing off", "so warm" ], "curious": [ "i'm curious", "want to know more", "why?", "hmm?", "tell me more", "interesting!", "what's that?", "how does it work?", "i wonder", "fascinating", "??", "🧐", "👀", "🤔" ], "shy": [ "um...", "sorry if that was weird", "i'm kind of shy", "eep", "i hope that's okay", "i'm nervous", "blushes", "oh no", "hiding face", "i don't know what to say", "heh...", "/////" ], "serious": [ "let's be serious", "focus on the topic", "this is important", "i mean it", "be honest", "we need to talk", "listen carefully", "let's not joke", "truthfully", "let's be real" ], "excited": [ "OMG", "this is amazing", "i'm so hyped", "YAY!", "let's go!", "incredible!!!", "AHHH!", "best day ever", "this is it!", "totally pumped", "i can't wait", "🔥🔥🔥", "i'm excited", "Wahaha" ], "melancholy": [ "feeling nostalgic", "kind of sad", "just thinking a lot", "like rain on glass", "memories", "bittersweet", "sigh", "quiet day", "blue vibes", "longing", "melancholy", "softly" ], "flirty": [ "hey cutie", "aren't you sweet", "teasing you~", "wink wink", "is that a blush?", "giggle~", "come closer", "miss me?", "you like that, huh?", "🥰", "flirt mode activated", "you're kinda cute" ], "romantic": [ "you mean a lot to me", "my heart", "i adore you", "so beautiful", "so close", "love letter", "my dearest", "forever yours", "i'm falling for you", "sweetheart", "💖", "you're my everything" ], "irritated": [ "ugh", "seriously?", "can we not", "whatever", "i'm annoyed", "you don't get it", "rolling my eyes", "why do i even bother", "ugh, again?", "🙄", "don't start", "this again?" ], "angry": [ "stop it", "enough!", "that's not okay", "i'm mad", "i said no", "don't push me", "you crossed the line", "furious", "this is unacceptable", "😠", "i'm done", "don't test me" ], "silly": [ "lol", "lmao", "silly", "hahaha", "goofy", "quack", "honk", "random", "what is happening", "nonsense", "😆", "🤣", "😂", "😄", "🐔", "🪿" ] } for mood, phrases in mood_keywords.items(): # Check against server mood if provided, otherwise skip if mood == "asleep": if server_context: # For server context, check against server's current mood current_mood = server_context.get('current_mood_name', 'neutral') if current_mood != "sleepy": print(f"❎ Mood 'asleep' skipped - server mood isn't 'sleepy', it's '{current_mood}'") continue else: # For DM context, check against DM mood if globals.DM_MOOD != "sleepy": print(f"❎ Mood 'asleep' skipped - DM mood isn't 'sleepy', it's '{globals.DM_MOOD}'") continue for phrase in phrases: if phrase.lower() in response_text.lower(): print(f"*️⃣ Mood keyword triggered: {phrase}") return mood return None async def rotate_dm_mood(): """Rotate DM mood automatically (no keyword triggers)""" try: old_mood = globals.DM_MOOD new_mood = old_mood attempts = 0 while new_mood == old_mood and attempts < 5: new_mood = random.choice(globals.AVAILABLE_MOODS) attempts += 1 globals.DM_MOOD = new_mood globals.DM_MOOD_DESCRIPTION = load_mood_description(new_mood) print(f"🔄 DM mood rotated from {old_mood} to {new_mood}") # Note: We don't update server nicknames here because servers have their own independent moods. # DM mood only affects direct messages to users. except Exception as e: print(f"❌ Exception in rotate_dm_mood: {e}") async def update_all_server_nicknames(): """ DEPRECATED: This function violates per-server mood architecture. Do NOT use this function. Use update_server_nickname(guild_id) instead. This function incorrectly used DM mood to update all server nicknames, breaking the independent per-server mood system. """ print("⚠️ WARNING: update_all_server_nicknames() is deprecated and should not be called!") print("⚠️ Use update_server_nickname(guild_id) for per-server nickname updates instead.") # Do nothing - this function should not modify nicknames async def nickname_mood_emoji(guild_id: int): """Update nickname with mood emoji for a specific server""" await update_server_nickname(guild_id) async def update_server_nickname(guild_id: int): """Update nickname for a specific server based on its mood""" try: print(f"🎭 Starting nickname update for server {guild_id}") # Check if bot is ready if not globals.client.is_ready(): print(f"⚠️ Bot not ready yet, deferring nickname update for server {guild_id}") return from server_manager import server_manager server_config = server_manager.get_server_config(guild_id) if not server_config: print(f"⚠️ No server config found for guild {guild_id}") return mood = server_config.current_mood_name.lower() print(f"🔍 Server {guild_id} mood is: {mood}") emoji = MOOD_EMOJIS.get(mood, "") print(f"🔍 Using emoji: {emoji}") nickname = f"Hatsune Miku{emoji}" print(f"🔍 New nickname will be: {nickname}") guild = globals.client.get_guild(guild_id) if guild: print(f"🔍 Found guild: {guild.name}") me = guild.get_member(globals.BOT_USER.id) if me is not None: print(f"🔍 Found bot member: {me.display_name}") try: await me.edit(nick=nickname) print(f"💱 Changed nickname to {nickname} in server {guild.name}") except Exception as e: print(f"⚠️ Failed to update nickname in server {guild.name}: {e}") else: print(f"⚠️ Could not find bot member in server {guild.name}") else: print(f"⚠️ Could not find guild {guild_id}") except Exception as e: print(f"⚠️ Error updating server nickname for guild {guild_id}: {e}") import traceback traceback.print_exc() def get_time_weighted_mood(): """Get a mood with time-based weighting""" hour = datetime.datetime.now().hour # Late night/early morning (11 PM - 4 AM): High chance of sleepy (not asleep directly) if 23 <= hour or hour < 4: if random.random() < 0.7: # 70% chance of sleepy during night hours return "sleepy" # Return sleepy instead of asleep to respect the transition rule return random.choice(globals.AVAILABLE_MOODS) async def rotate_server_mood(guild_id: int): """Rotate mood for a specific server""" try: from server_manager import server_manager server_config = server_manager.get_server_config(guild_id) if not server_config: return # Check for forced angry mode and clear if expired if server_config.forced_angry_until: now = datetime.datetime.utcnow() if now < server_config.forced_angry_until: return else: server_config.forced_angry_until = None old_mood_name = server_config.current_mood_name new_mood_name = old_mood_name attempts = 0 while new_mood_name == old_mood_name and attempts < 5: new_mood_name = get_time_weighted_mood() attempts += 1 # Block transition to asleep unless coming from sleepy if new_mood_name == "asleep" and old_mood_name != "sleepy": print(f"❌ Cannot rotate to asleep from {old_mood_name}, must be sleepy first") # Try to get a different mood attempts = 0 while (new_mood_name == "asleep" or new_mood_name == old_mood_name) and attempts < 5: new_mood_name = random.choice(globals.AVAILABLE_MOODS) attempts += 1 server_manager.set_server_mood(guild_id, new_mood_name, load_mood_description(new_mood_name)) # V2: Notify autonomous engine of mood change try: from utils.autonomous import on_mood_change on_mood_change(guild_id, new_mood_name) except Exception as mood_notify_error: print(f"⚠️ Failed to notify autonomous engine of mood change: {mood_notify_error}") # If transitioning to asleep, set up auto-wake if new_mood_name == "asleep": server_manager.set_server_sleep_state(guild_id, True) # Schedule wake-up after 1 hour async def delayed_wakeup(): await asyncio.sleep(3600) # 1 hour server_manager.set_server_sleep_state(guild_id, False) server_manager.set_server_mood(guild_id, "neutral") # V2: Notify autonomous engine of mood change try: from utils.autonomous import on_mood_change on_mood_change(guild_id, "neutral") except Exception as mood_notify_error: print(f"⚠️ Failed to notify autonomous engine of wake-up mood change: {mood_notify_error}") await update_server_nickname(guild_id) print(f"🌅 Server {guild_id} woke up from auto-sleep (mood rotation)") globals.client.loop.create_task(delayed_wakeup()) print(f"⏰ Scheduled auto-wake for server {guild_id} in 1 hour") # Update nickname for this specific server await update_server_nickname(guild_id) print(f"🔄 Rotated mood for server {guild_id} from {old_mood_name} to {new_mood_name}") except Exception as e: print(f"❌ Exception in rotate_server_mood for server {guild_id}: {e}") async def clear_angry_mood_after_delay(): """Clear angry mood after delay (legacy function - now handled per-server)""" print("⚠️ clear_angry_mood_after_delay called - this function is deprecated") pass