Initial commit: Miku Discord Bot
This commit is contained in:
654
bot/server_manager.py
Normal file
654
bot/server_manager.py
Normal file
@@ -0,0 +1,654 @@
|
||||
# server_manager.py
|
||||
|
||||
import json
|
||||
import os
|
||||
import asyncio
|
||||
from typing import Dict, List, Optional, Set
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime, timedelta
|
||||
import discord
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.triggers.date import DateTrigger
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@dataclass
|
||||
class ServerConfig:
|
||||
"""Configuration for a single Discord server"""
|
||||
guild_id: int
|
||||
guild_name: str
|
||||
autonomous_channel_id: int
|
||||
autonomous_channel_name: str
|
||||
bedtime_channel_ids: List[int]
|
||||
enabled_features: Set[str] # autonomous, bedtime, monday_video, etc.
|
||||
autonomous_interval_minutes: int = 15
|
||||
conversation_detection_interval_minutes: int = 3
|
||||
bedtime_hour: int = 21
|
||||
bedtime_minute: int = 0
|
||||
bedtime_hour_end: int = 21 # End of bedtime range (default 11PM)
|
||||
bedtime_minute_end: int = 59 # End of bedtime range (default 11:59PM)
|
||||
monday_video_hour: int = 4
|
||||
monday_video_minute: int = 30
|
||||
# Per-server mood tracking
|
||||
current_mood_name: str = "neutral"
|
||||
current_mood_description: str = ""
|
||||
previous_mood_name: str = "neutral"
|
||||
is_sleeping: bool = False
|
||||
sleepy_responses_left: int = None
|
||||
angry_wakeup_timer = None
|
||||
forced_angry_until = None
|
||||
just_woken_up: bool = False
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict):
|
||||
# Convert set back from list, or handle old string format
|
||||
if 'enabled_features' in data:
|
||||
if isinstance(data['enabled_features'], list):
|
||||
data['enabled_features'] = set(data['enabled_features'])
|
||||
elif isinstance(data['enabled_features'], str):
|
||||
# Handle old string format like "{'bedtime', 'monday_video', 'autonomous'}"
|
||||
try:
|
||||
# Remove the outer braces and split by comma
|
||||
features_str = data['enabled_features'].strip('{}')
|
||||
features_list = [f.strip().strip("'\"") for f in features_str.split(',') if f.strip()]
|
||||
data['enabled_features'] = set(features_list)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to parse enabled_features string '{data['enabled_features']}': {e}")
|
||||
# Fallback to default features
|
||||
data['enabled_features'] = {"autonomous", "bedtime", "monday_video"}
|
||||
return cls(**data)
|
||||
|
||||
class ServerManager:
|
||||
"""Manages multiple Discord servers with independent configurations"""
|
||||
|
||||
def __init__(self, config_file: str = "memory/servers_config.json"):
|
||||
self.config_file = config_file
|
||||
self.servers: Dict[int, ServerConfig] = {}
|
||||
self.schedulers: Dict[int, AsyncIOScheduler] = {}
|
||||
self.server_memories: Dict[int, Dict] = {}
|
||||
self.load_config()
|
||||
|
||||
def load_config(self):
|
||||
"""Load server configurations from file"""
|
||||
if os.path.exists(self.config_file):
|
||||
try:
|
||||
with open(self.config_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
for guild_id_str, server_data in data.items():
|
||||
guild_id = int(guild_id_str)
|
||||
self.servers[guild_id] = ServerConfig.from_dict(server_data)
|
||||
self.server_memories[guild_id] = {}
|
||||
print(f"📋 Loaded config for server: {server_data['guild_name']} (ID: {guild_id})")
|
||||
|
||||
# After loading, check if we need to repair the config
|
||||
self.repair_config()
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to load server config: {e}")
|
||||
self._create_default_config()
|
||||
else:
|
||||
self._create_default_config()
|
||||
|
||||
def repair_config(self):
|
||||
"""Repair corrupted configuration data and save it back"""
|
||||
try:
|
||||
needs_repair = False
|
||||
for server in self.servers.values():
|
||||
# Check if enabled_features is a string (corrupted)
|
||||
if isinstance(server.enabled_features, str):
|
||||
needs_repair = True
|
||||
print(f"🔧 Repairing corrupted enabled_features for server: {server.guild_name}")
|
||||
# Re-parse the features
|
||||
try:
|
||||
features_str = server.enabled_features.strip('{}')
|
||||
features_list = [f.strip().strip("'\"") for f in features_str.split(',') if f.strip()]
|
||||
server.enabled_features = set(features_list)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to repair enabled_features for {server.guild_name}: {e}")
|
||||
server.enabled_features = {"autonomous", "bedtime", "monday_video"}
|
||||
|
||||
if needs_repair:
|
||||
print("🔧 Saving repaired configuration...")
|
||||
self.save_config()
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to repair config: {e}")
|
||||
|
||||
def _create_default_config(self):
|
||||
"""Create default configuration for backward compatibility"""
|
||||
default_server = ServerConfig(
|
||||
guild_id=759889672804630530,
|
||||
guild_name="Default Server",
|
||||
autonomous_channel_id=761014220707332107,
|
||||
autonomous_channel_name="miku-chat",
|
||||
bedtime_channel_ids=[761014220707332107],
|
||||
enabled_features={"autonomous", "bedtime", "monday_video"},
|
||||
autonomous_interval_minutes=10,
|
||||
conversation_detection_interval_minutes=3
|
||||
)
|
||||
self.servers[default_server.guild_id] = default_server
|
||||
self.server_memories[default_server.guild_id] = {}
|
||||
self.save_config()
|
||||
print("📋 Created default server configuration")
|
||||
|
||||
def save_config(self):
|
||||
"""Save server configurations to file"""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
|
||||
config_data = {}
|
||||
for guild_id, server in self.servers.items():
|
||||
# Convert the server config to dict, but handle sets properly
|
||||
server_dict = server.to_dict()
|
||||
# Convert set to list for JSON serialization
|
||||
if 'enabled_features' in server_dict and isinstance(server_dict['enabled_features'], set):
|
||||
server_dict['enabled_features'] = list(server_dict['enabled_features'])
|
||||
config_data[str(guild_id)] = server_dict
|
||||
|
||||
with open(self.config_file, "w", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=2)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to save server config: {e}")
|
||||
|
||||
def add_server(self, guild_id: int, guild_name: str, autonomous_channel_id: int,
|
||||
autonomous_channel_name: str, bedtime_channel_ids: List[int] = None,
|
||||
enabled_features: Set[str] = None) -> bool:
|
||||
"""Add a new server configuration"""
|
||||
if guild_id in self.servers:
|
||||
print(f"⚠️ Server {guild_id} already exists")
|
||||
return False
|
||||
|
||||
if bedtime_channel_ids is None:
|
||||
bedtime_channel_ids = [autonomous_channel_id]
|
||||
|
||||
if enabled_features is None:
|
||||
enabled_features = {"autonomous", "bedtime", "monday_video"}
|
||||
|
||||
server = ServerConfig(
|
||||
guild_id=guild_id,
|
||||
guild_name=guild_name,
|
||||
autonomous_channel_id=autonomous_channel_id,
|
||||
autonomous_channel_name=autonomous_channel_name,
|
||||
bedtime_channel_ids=bedtime_channel_ids,
|
||||
enabled_features=enabled_features
|
||||
)
|
||||
|
||||
self.servers[guild_id] = server
|
||||
self.server_memories[guild_id] = {}
|
||||
self.save_config()
|
||||
print(f"✅ Added new server: {guild_name} (ID: {guild_id})")
|
||||
return True
|
||||
|
||||
def remove_server(self, guild_id: int) -> bool:
|
||||
"""Remove a server configuration"""
|
||||
if guild_id not in self.servers:
|
||||
return False
|
||||
|
||||
server_name = self.servers[guild_id].guild_name
|
||||
del self.servers[guild_id]
|
||||
|
||||
# Stop and remove scheduler
|
||||
if guild_id in self.schedulers:
|
||||
self.schedulers[guild_id].shutdown()
|
||||
del self.schedulers[guild_id]
|
||||
|
||||
# Remove memory
|
||||
if guild_id in self.server_memories:
|
||||
del self.server_memories[guild_id]
|
||||
|
||||
self.save_config()
|
||||
print(f"🗑️ Removed server: {server_name} (ID: {guild_id})")
|
||||
return True
|
||||
|
||||
def get_server_config(self, guild_id: int) -> Optional[ServerConfig]:
|
||||
"""Get configuration for a specific server"""
|
||||
return self.servers.get(guild_id)
|
||||
|
||||
def get_all_servers(self) -> List[ServerConfig]:
|
||||
"""Get all server configurations"""
|
||||
return list(self.servers.values())
|
||||
|
||||
def update_server_config(self, guild_id: int, **kwargs) -> bool:
|
||||
"""Update configuration for a specific server"""
|
||||
if guild_id not in self.servers:
|
||||
return False
|
||||
|
||||
server = self.servers[guild_id]
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(server, key):
|
||||
setattr(server, key, value)
|
||||
|
||||
self.save_config()
|
||||
print(f"✅ Updated config for server: {server.guild_name}")
|
||||
return True
|
||||
|
||||
def get_server_memory(self, guild_id: int, key: str = None):
|
||||
"""Get or set server-specific memory"""
|
||||
if guild_id not in self.server_memories:
|
||||
self.server_memories[guild_id] = {}
|
||||
|
||||
if key is None:
|
||||
return self.server_memories[guild_id]
|
||||
|
||||
return self.server_memories[guild_id].get(key)
|
||||
|
||||
def set_server_memory(self, guild_id: int, key: str, value):
|
||||
"""Set server-specific memory"""
|
||||
if guild_id not in self.server_memories:
|
||||
self.server_memories[guild_id] = {}
|
||||
|
||||
self.server_memories[guild_id][key] = value
|
||||
|
||||
# ========== Mood Management Methods ==========
|
||||
def get_server_mood(self, guild_id: int) -> tuple[str, str]:
|
||||
"""Get current mood name and description for a server"""
|
||||
if guild_id not in self.servers:
|
||||
return "neutral", ""
|
||||
|
||||
server = self.servers[guild_id]
|
||||
return server.current_mood_name, server.current_mood_description
|
||||
|
||||
def set_server_mood(self, guild_id: int, mood_name: str, mood_description: str = None):
|
||||
"""Set mood for a specific server"""
|
||||
if guild_id not in self.servers:
|
||||
return False
|
||||
|
||||
server = self.servers[guild_id]
|
||||
server.previous_mood_name = server.current_mood_name
|
||||
server.current_mood_name = mood_name
|
||||
|
||||
if mood_description:
|
||||
server.current_mood_description = mood_description
|
||||
else:
|
||||
# Load mood description if not provided
|
||||
try:
|
||||
from utils.moods import load_mood_description
|
||||
server.current_mood_description = load_mood_description(mood_name)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to load mood description for {mood_name}: {e}")
|
||||
server.current_mood_description = f"I'm feeling {mood_name} today."
|
||||
|
||||
self.save_config()
|
||||
print(f"😊 Server {server.guild_name} mood changed to: {mood_name}")
|
||||
print(f"😊 Mood description: {server.current_mood_description[:100]}{'...' if len(server.current_mood_description) > 100 else ''}")
|
||||
return True
|
||||
|
||||
def get_server_sleep_state(self, guild_id: int) -> bool:
|
||||
"""Get sleep state for a specific server"""
|
||||
if guild_id not in self.servers:
|
||||
return False
|
||||
return self.servers[guild_id].is_sleeping
|
||||
|
||||
def set_server_sleep_state(self, guild_id: int, sleeping: bool):
|
||||
"""Set sleep state for a specific server"""
|
||||
if guild_id not in self.servers:
|
||||
return False
|
||||
|
||||
server = self.servers[guild_id]
|
||||
server.is_sleeping = sleeping
|
||||
self.save_config()
|
||||
return True
|
||||
|
||||
def get_server_mood_state(self, guild_id: int) -> dict:
|
||||
"""Get complete mood state for a specific server"""
|
||||
if guild_id not in self.servers:
|
||||
return {}
|
||||
|
||||
server = self.servers[guild_id]
|
||||
return {
|
||||
"current_mood_name": server.current_mood_name,
|
||||
"current_mood_description": server.current_mood_description,
|
||||
"previous_mood_name": server.previous_mood_name,
|
||||
"is_sleeping": server.is_sleeping,
|
||||
"sleepy_responses_left": server.sleepy_responses_left,
|
||||
"forced_angry_until": server.forced_angry_until,
|
||||
"just_woken_up": server.just_woken_up
|
||||
}
|
||||
|
||||
def set_server_mood_state(self, guild_id: int, **kwargs):
|
||||
"""Set multiple mood state properties for a server"""
|
||||
if guild_id not in self.servers:
|
||||
return False
|
||||
|
||||
server = self.servers[guild_id]
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(server, key):
|
||||
setattr(server, key, value)
|
||||
|
||||
self.save_config()
|
||||
return True
|
||||
|
||||
def setup_server_scheduler(self, guild_id: int, client: discord.Client):
|
||||
"""Setup independent scheduler for a specific server"""
|
||||
if guild_id not in self.servers:
|
||||
print(f"⚠️ Cannot setup scheduler for unknown server: {guild_id}")
|
||||
return
|
||||
|
||||
server_config = self.servers[guild_id]
|
||||
|
||||
# Create new scheduler for this server
|
||||
scheduler = AsyncIOScheduler()
|
||||
|
||||
# Add autonomous speaking job
|
||||
if "autonomous" in server_config.enabled_features:
|
||||
scheduler.add_job(
|
||||
self._run_autonomous_for_server,
|
||||
IntervalTrigger(minutes=server_config.autonomous_interval_minutes),
|
||||
args=[guild_id, client],
|
||||
id=f"autonomous_{guild_id}"
|
||||
)
|
||||
|
||||
# Add autonomous reaction job (parallel to speaking, runs every 20 minutes)
|
||||
if "autonomous" in server_config.enabled_features:
|
||||
scheduler.add_job(
|
||||
self._run_autonomous_reaction_for_server,
|
||||
IntervalTrigger(minutes=20),
|
||||
args=[guild_id, client],
|
||||
id=f"autonomous_reaction_{guild_id}"
|
||||
)
|
||||
|
||||
# Note: Conversation detection is now handled by V2 system via message events
|
||||
# No need for separate scheduler job
|
||||
|
||||
# Add Monday video job
|
||||
if "monday_video" in server_config.enabled_features:
|
||||
scheduler.add_job(
|
||||
self._send_monday_video_for_server,
|
||||
CronTrigger(day_of_week='mon', hour=server_config.monday_video_hour, minute=server_config.monday_video_minute),
|
||||
args=[guild_id, client],
|
||||
id=f"monday_video_{guild_id}"
|
||||
)
|
||||
|
||||
# Add bedtime reminder job
|
||||
if "bedtime" in server_config.enabled_features:
|
||||
print(f"⏰ Setting up bedtime scheduler for server {server_config.guild_name}")
|
||||
print(f" Random time range: {server_config.bedtime_hour:02d}:{server_config.bedtime_minute:02d} - {server_config.bedtime_hour_end:02d}:{server_config.bedtime_minute_end:02d}")
|
||||
scheduler.add_job(
|
||||
self._schedule_random_bedtime_for_server,
|
||||
CronTrigger(hour=server_config.bedtime_hour, minute=server_config.bedtime_minute),
|
||||
args=[guild_id, client],
|
||||
id=f"bedtime_schedule_{guild_id}"
|
||||
)
|
||||
|
||||
# Add mood rotation job (every hour)
|
||||
scheduler.add_job(
|
||||
self._rotate_server_mood,
|
||||
IntervalTrigger(hours=1),
|
||||
args=[guild_id, client],
|
||||
id=f"mood_rotation_{guild_id}"
|
||||
)
|
||||
|
||||
self.schedulers[guild_id] = scheduler
|
||||
scheduler.start()
|
||||
print(f"⏰ Started scheduler for server: {server_config.guild_name}")
|
||||
|
||||
def start_all_schedulers(self, client: discord.Client):
|
||||
"""Start schedulers for all servers"""
|
||||
print("🚀 Starting all server schedulers...")
|
||||
|
||||
for guild_id in self.servers:
|
||||
self.setup_server_scheduler(guild_id, client)
|
||||
|
||||
# Start DM mood rotation scheduler
|
||||
self.setup_dm_mood_scheduler(client)
|
||||
# Start Figurine DM scheduler
|
||||
self.setup_figurine_updates_scheduler(client)
|
||||
|
||||
print(f"✅ Started {len(self.servers)} server schedulers + DM mood scheduler")
|
||||
|
||||
def update_server_bedtime_job(self, guild_id: int, client: discord.Client):
|
||||
"""Update just the bedtime job for a specific server without restarting all schedulers"""
|
||||
server_config = self.servers.get(guild_id)
|
||||
if not server_config:
|
||||
print(f"⚠️ No server config found for guild {guild_id}")
|
||||
return False
|
||||
|
||||
scheduler = self.schedulers.get(guild_id)
|
||||
if not scheduler:
|
||||
print(f"⚠️ No scheduler found for guild {guild_id}")
|
||||
return False
|
||||
|
||||
# Remove existing bedtime job if it exists
|
||||
bedtime_job_id = f"bedtime_schedule_{guild_id}"
|
||||
try:
|
||||
scheduler.remove_job(bedtime_job_id)
|
||||
print(f"🗑️ Removed old bedtime job for server {guild_id}")
|
||||
except Exception as e:
|
||||
print(f"ℹ️ No existing bedtime job to remove for server {guild_id}: {e}")
|
||||
|
||||
# Add new bedtime job with updated configuration
|
||||
if "bedtime" in server_config.enabled_features:
|
||||
print(f"⏰ Updating bedtime scheduler for server {server_config.guild_name}")
|
||||
print(f" New random time range: {server_config.bedtime_hour:02d}:{server_config.bedtime_minute:02d} - {server_config.bedtime_hour_end:02d}:{server_config.bedtime_minute_end:02d}")
|
||||
scheduler.add_job(
|
||||
self._schedule_random_bedtime_for_server,
|
||||
CronTrigger(hour=server_config.bedtime_hour, minute=server_config.bedtime_minute),
|
||||
args=[guild_id, client],
|
||||
id=bedtime_job_id
|
||||
)
|
||||
print(f"✅ Updated bedtime job for server {server_config.guild_name}")
|
||||
return True
|
||||
else:
|
||||
print(f"ℹ️ Bedtime feature not enabled for server {guild_id}")
|
||||
return True
|
||||
|
||||
def setup_dm_mood_scheduler(self, client: discord.Client):
|
||||
"""Setup DM mood rotation scheduler"""
|
||||
try:
|
||||
from utils.moods import rotate_dm_mood
|
||||
|
||||
# Create DM mood rotation job (every 2 hours)
|
||||
scheduler = AsyncIOScheduler()
|
||||
scheduler.add_job(
|
||||
rotate_dm_mood,
|
||||
IntervalTrigger(hours=2),
|
||||
id="dm_mood_rotation"
|
||||
)
|
||||
|
||||
scheduler.start()
|
||||
self.schedulers["dm_mood"] = scheduler
|
||||
print("🔄 DM mood rotation scheduler started (every 2 hours)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to setup DM mood scheduler: {e}")
|
||||
|
||||
def _enqueue_figurine_send(self, client: discord.Client):
|
||||
"""Enqueue the figurine DM send task in the client's loop."""
|
||||
try:
|
||||
from utils.figurine_notifier import send_figurine_dm_to_all_subscribers
|
||||
if client.loop and client.loop.is_running():
|
||||
client.loop.create_task(send_figurine_dm_to_all_subscribers(client))
|
||||
print("✅ Figurine DM send task queued")
|
||||
else:
|
||||
print("⚠️ Client loop not available for figurine DM send")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error enqueuing figurine DM: {e}")
|
||||
|
||||
def _schedule_one_figurine_send_today(self, scheduler: AsyncIOScheduler, client: discord.Client):
|
||||
"""Schedule one figurine DM send at a random non-evening time today (or tomorrow if time passed)."""
|
||||
now = datetime.now()
|
||||
# Define non-evening hours: 08:00-17:59
|
||||
random_hour = random.randint(8, 17)
|
||||
random_minute = random.randint(0, 59)
|
||||
target_time = now.replace(hour=random_hour, minute=random_minute, second=0, microsecond=0)
|
||||
if target_time <= now:
|
||||
target_time = target_time + timedelta(days=1)
|
||||
print(f"🗓️ Scheduling figurine DM at {target_time.strftime('%Y-%m-%d %H:%M')} (random non-evening)")
|
||||
scheduler.add_job(
|
||||
self._enqueue_figurine_send,
|
||||
DateTrigger(run_date=target_time),
|
||||
args=[client],
|
||||
id=f"figurine_dm_{int(target_time.timestamp())}",
|
||||
replace_existing=False
|
||||
)
|
||||
|
||||
def setup_figurine_updates_scheduler(self, client: discord.Client):
|
||||
"""Create a daily scheduler that schedules one random non-evening figurine DM send per day."""
|
||||
try:
|
||||
scheduler = AsyncIOScheduler()
|
||||
# Every day at 07:30, schedule today's random send (will roll to tomorrow if time passed)
|
||||
scheduler.add_job(
|
||||
self._schedule_one_figurine_send_today,
|
||||
CronTrigger(hour=7, minute=30),
|
||||
args=[scheduler, client],
|
||||
id="figurine_daily_scheduler"
|
||||
)
|
||||
# Also schedule one immediately on startup for today/tomorrow
|
||||
self._schedule_one_figurine_send_today(scheduler, client)
|
||||
scheduler.start()
|
||||
self.schedulers["figurine_dm"] = scheduler
|
||||
print("🗓️ Figurine updates scheduler started")
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to setup figurine updates scheduler: {e}")
|
||||
|
||||
def stop_all_schedulers(self):
|
||||
"""Stop all schedulers"""
|
||||
print("🛑 Stopping all schedulers...")
|
||||
|
||||
for scheduler in self.schedulers.values():
|
||||
try:
|
||||
scheduler.shutdown()
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error stopping scheduler: {e}")
|
||||
|
||||
self.schedulers.clear()
|
||||
print("✅ All schedulers stopped")
|
||||
|
||||
# Implementation of autonomous functions - these integrate with the autonomous system
|
||||
def _run_autonomous_for_server(self, guild_id: int, client: discord.Client):
|
||||
"""Run autonomous behavior for a specific server - called by APScheduler"""
|
||||
try:
|
||||
# V2: Use the new context-aware autonomous system
|
||||
from utils.autonomous import autonomous_tick
|
||||
# Create an async task in the client's event loop
|
||||
if client.loop and client.loop.is_running():
|
||||
client.loop.create_task(autonomous_tick(guild_id))
|
||||
print(f"✅ [V2] Autonomous tick queued for server {guild_id}")
|
||||
else:
|
||||
print(f"⚠️ Client loop not available for autonomous tick in server {guild_id}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error in autonomous tick for server {guild_id}: {e}")
|
||||
|
||||
def _run_autonomous_reaction_for_server(self, guild_id: int, client: discord.Client):
|
||||
"""Run autonomous reaction for a specific server - called by APScheduler"""
|
||||
try:
|
||||
# V2: Use the new context-aware reaction system
|
||||
from utils.autonomous import autonomous_reaction_tick
|
||||
# Create an async task in the client's event loop
|
||||
if client.loop and client.loop.is_running():
|
||||
client.loop.create_task(autonomous_reaction_tick(guild_id))
|
||||
print(f"✅ [V2] Autonomous reaction queued for server {guild_id}")
|
||||
else:
|
||||
print(f"⚠️ Client loop not available for autonomous reaction in server {guild_id}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error in autonomous reaction for server {guild_id}: {e}")
|
||||
|
||||
def _run_conversation_detection_for_server(self, guild_id: int, client: discord.Client):
|
||||
"""Run conversation detection for a specific server - called by APScheduler"""
|
||||
try:
|
||||
from utils.autonomous import miku_detect_and_join_conversation_for_server
|
||||
# Create an async task in the client's event loop
|
||||
if client.loop and client.loop.is_running():
|
||||
client.loop.create_task(miku_detect_and_join_conversation_for_server(guild_id))
|
||||
print(f"✅ Conversation detection queued for server {guild_id}")
|
||||
else:
|
||||
print(f"⚠️ Client loop not available for conversation detection in server {guild_id}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error in conversation detection for server {guild_id}: {e}")
|
||||
|
||||
def _send_monday_video_for_server(self, guild_id: int, client: discord.Client):
|
||||
"""Send Monday video for a specific server - called by APScheduler"""
|
||||
try:
|
||||
from utils.scheduled import send_monday_video_for_server
|
||||
# Create an async task in the client's event loop
|
||||
if client.loop and client.loop.is_running():
|
||||
client.loop.create_task(send_monday_video_for_server(guild_id))
|
||||
print(f"✅ Monday video queued for server {guild_id}")
|
||||
else:
|
||||
print(f"⚠️ Client loop not available for Monday video in server {guild_id}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error in Monday video for server {guild_id}: {e}")
|
||||
|
||||
def _schedule_random_bedtime_for_server(self, guild_id: int, client: discord.Client):
|
||||
"""Schedule bedtime reminder for a specific server at a random time within the configured range"""
|
||||
print(f"⏰ Bedtime scheduler triggered for server {guild_id} at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
# Get server config to determine the random time range
|
||||
server_config = self.servers.get(guild_id)
|
||||
if not server_config:
|
||||
print(f"⚠️ No server config found for guild {guild_id}")
|
||||
return
|
||||
|
||||
# Calculate random time within the bedtime range
|
||||
start_minutes = server_config.bedtime_hour * 60 + server_config.bedtime_minute
|
||||
end_minutes = server_config.bedtime_hour_end * 60 + server_config.bedtime_minute_end
|
||||
|
||||
print(f"🕐 Bedtime range calculation: {server_config.bedtime_hour:02d}:{server_config.bedtime_minute:02d} ({start_minutes} min) to {server_config.bedtime_hour_end:02d}:{server_config.bedtime_minute_end:02d} ({end_minutes} min)")
|
||||
|
||||
# Handle case where end time is next day (e.g., 23:30 to 00:30)
|
||||
if end_minutes <= start_minutes:
|
||||
end_minutes += 24 * 60 # Add 24 hours
|
||||
print(f"🌙 Cross-midnight range detected, adjusted end to {end_minutes} minutes")
|
||||
|
||||
random_minutes = random.randint(start_minutes, end_minutes)
|
||||
print(f"🎲 Random time selected: {random_minutes} minutes from midnight")
|
||||
|
||||
# Convert back to hours and minutes
|
||||
random_hour = (random_minutes // 60) % 24
|
||||
random_minute = random_minutes % 60
|
||||
|
||||
# Calculate delay until the random time
|
||||
now = datetime.now()
|
||||
target_time = now.replace(hour=random_hour, minute=random_minute, second=0, microsecond=0)
|
||||
|
||||
# If the target time has already passed today, schedule for tomorrow
|
||||
if target_time <= now:
|
||||
target_time += timedelta(days=1)
|
||||
|
||||
delay_seconds = (target_time - now).total_seconds()
|
||||
|
||||
print(f"🎲 Random bedtime for server {server_config.guild_name}: {random_hour:02d}:{random_minute:02d} (in {delay_seconds/60:.1f} minutes)")
|
||||
|
||||
# Schedule the actual bedtime reminder
|
||||
try:
|
||||
from utils.scheduled import send_bedtime_reminder_for_server
|
||||
|
||||
def send_bedtime_delayed():
|
||||
if client.loop and client.loop.is_running():
|
||||
client.loop.create_task(send_bedtime_reminder_for_server(guild_id, client))
|
||||
print(f"✅ Random bedtime reminder sent for server {guild_id}")
|
||||
else:
|
||||
print(f"⚠️ Client loop not available for bedtime reminder in server {guild_id}")
|
||||
|
||||
# Use the scheduler to schedule the delayed bedtime reminder
|
||||
scheduler = self.schedulers.get(guild_id)
|
||||
if scheduler:
|
||||
scheduler.add_job(
|
||||
send_bedtime_delayed,
|
||||
DateTrigger(run_date=target_time),
|
||||
id=f"bedtime_reminder_{guild_id}_{int(target_time.timestamp())}"
|
||||
)
|
||||
print(f"✅ Bedtime reminder scheduled for server {guild_id} at {target_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
else:
|
||||
print(f"⚠️ No scheduler found for server {guild_id}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error scheduling bedtime reminder for server {guild_id}: {e}")
|
||||
|
||||
def _rotate_server_mood(self, guild_id: int, client: discord.Client):
|
||||
"""Rotate mood for a specific server - called by APScheduler"""
|
||||
try:
|
||||
from utils.moods import rotate_server_mood
|
||||
# Create an async task in the client's event loop
|
||||
if client.loop and client.loop.is_running():
|
||||
client.loop.create_task(rotate_server_mood(guild_id))
|
||||
print(f"✅ Mood rotation queued for server {guild_id}")
|
||||
else:
|
||||
print(f"⚠️ Client loop not available for mood rotation in server {guild_id}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error in mood rotation for server {guild_id}: {e}")
|
||||
|
||||
# Global instance
|
||||
server_manager = ServerManager()
|
||||
Reference in New Issue
Block a user