diff --git a/modules/__init__.py b/modules/__init__.py index 78738c3..2735fcf 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -11,6 +11,7 @@ from .inventory import Inventory from .gym_battles import GymBattles from .team_builder import TeamBuilder from .npc_events import NPCEventsModule +from .backup_commands import BackupCommands __all__ = [ 'CoreCommands', @@ -22,5 +23,6 @@ __all__ = [ 'Inventory', 'GymBattles', 'TeamBuilder', - 'NPCEventsModule' + 'NPCEventsModule', + 'BackupCommands' ] \ No newline at end of file diff --git a/modules/backup_commands.py b/modules/backup_commands.py index bbb202c..1952e39 100644 --- a/modules/backup_commands.py +++ b/modules/backup_commands.py @@ -14,8 +14,8 @@ from config import ADMIN_USER class BackupCommands(BaseModule): """Module for database backup management commands.""" - def __init__(self, bot, database): - super().__init__(bot, database) + def __init__(self, bot, database, game_engine): + super().__init__(bot, database, game_engine) self.backup_manager = BackupManager() self.scheduler = BackupScheduler(self.backup_manager) self.scheduler_task = None @@ -23,14 +23,19 @@ class BackupCommands(BaseModule): # Setup logging self.logger = logging.getLogger(__name__) - # Start the scheduler - self._start_scheduler() + # Initialize scheduler flag (will be started when needed) + self._scheduler_started = False - def _start_scheduler(self): + async def _start_scheduler(self): """Start the backup scheduler task.""" - if self.scheduler_task is None or self.scheduler_task.done(): - self.scheduler_task = asyncio.create_task(self.scheduler.start_scheduler()) - self.logger.info("Backup scheduler started") + if not self._scheduler_started and (self.scheduler_task is None or self.scheduler_task.done()): + try: + self.scheduler_task = asyncio.create_task(self.scheduler.start_scheduler()) + self._scheduler_started = True + self.logger.info("Backup scheduler started") + except RuntimeError: + # No event loop running, scheduler will be started later + self.logger.info("No event loop available, scheduler will start when commands are used") def get_commands(self): """Return list of available backup commands.""" @@ -41,6 +46,10 @@ class BackupCommands(BaseModule): async def handle_command(self, channel, nickname, command, args): """Handle backup-related commands.""" + # Start scheduler if not already running + if not self._scheduler_started: + await self._start_scheduler() + # Check if user has admin privileges for backup commands if not await self._is_admin(nickname): self.send_message(channel, f"{nickname}: Backup commands require admin privileges.") diff --git a/run_bot_with_reconnect.py b/run_bot_with_reconnect.py index e1ae0c4..fdea080 100644 --- a/run_bot_with_reconnect.py +++ b/run_bot_with_reconnect.py @@ -20,7 +20,7 @@ from src.game_engine import GameEngine from src.irc_connection_manager import IRCConnectionManager, ConnectionState from src.rate_limiter import RateLimiter, get_command_category from src.npc_events import NPCEventsManager -from modules import CoreCommands, Exploration, BattleSystem, PetManagement, Achievements, Admin, Inventory, GymBattles, TeamBuilder, NPCEventsModule +from modules import CoreCommands, Exploration, BattleSystem, PetManagement, Achievements, Admin, Inventory, GymBattles, TeamBuilder, NPCEventsModule, BackupCommands from webserver import PetBotWebServer from config import IRC_CONFIG, RATE_LIMIT_CONFIG @@ -62,6 +62,10 @@ class PetBotWithReconnect: # Rate limiting self.rate_limiter = None + # Message queue for thread-safe IRC messaging + import queue + self.message_queue = queue.Queue() + # Statistics self.startup_time = datetime.now() self.command_count = 0 @@ -82,6 +86,9 @@ class PetBotWithReconnect: # Load game data self.logger.info("🔄 Loading game data...") await self.game_engine.load_game_data() + + # Set bot reference for weather announcements + self.game_engine.bot = self self.logger.info("✅ Game data loaded") # Initialize NPC events manager @@ -125,6 +132,7 @@ class PetBotWithReconnect: self.logger.info("🔄 Starting background tasks...") asyncio.create_task(self.background_validation_task()) asyncio.create_task(self.connection_stats_task()) + asyncio.create_task(self.message_queue_processor()) asyncio.create_task(self.npc_events.start_background_task()) self.logger.info("✅ Background tasks started") @@ -146,7 +154,8 @@ class PetBotWithReconnect: Inventory, GymBattles, TeamBuilder, - NPCEventsModule + NPCEventsModule, + BackupCommands ] self.modules = {} @@ -189,6 +198,7 @@ class PetBotWithReconnect: importlib.reload(modules.inventory) importlib.reload(modules.gym_battles) importlib.reload(modules.team_builder) + importlib.reload(modules.backup_commands) importlib.reload(modules) # Reinitialize modules @@ -265,6 +275,24 @@ class PetBotWithReconnect: except Exception as e: self.logger.error(f"❌ Connection stats error: {e}") + async def message_queue_processor(self): + """Background task to process queued messages from other threads.""" + while self.running: + try: + # Check for messages in queue (non-blocking) + try: + target, message = self.message_queue.get_nowait() + await self.send_message(target, message) + self.message_queue.task_done() + except: + # No messages in queue, sleep a bit + await asyncio.sleep(0.1) + + except asyncio.CancelledError: + break + except Exception as e: + self.logger.error(f"❌ Message queue processor error: {e}") + async def on_irc_connect(self): """Called when IRC connection is established.""" self.logger.info("🎉 IRC connection established successfully!") @@ -353,20 +381,13 @@ class PetBotWithReconnect: self.logger.warning(f"No connection manager available to send message to {target}") def send_message_sync(self, target, message): - """Synchronous wrapper for send_message (for compatibility with old modules).""" - if hasattr(self, 'loop') and self.loop and self.loop.is_running(): - # Schedule the coroutine to run in the existing event loop - asyncio.create_task(self.send_message(target, message)) - else: - # Fallback - try to get current loop - try: - loop = asyncio.get_event_loop() - if loop.is_running(): - asyncio.create_task(self.send_message(target, message)) - else: - loop.run_until_complete(self.send_message(target, message)) - except Exception as e: - self.logger.error(f"Failed to send message synchronously: {e}") + """Synchronous wrapper for send_message (for compatibility with web server).""" + try: + # Add message to queue for processing by background task + self.message_queue.put((target, message)) + self.logger.info(f"Queued message for {target}: {message}") + except Exception as e: + self.logger.error(f"Failed to queue message: {e}") async def send_team_builder_pin(self, nickname, pin_code): """Send team builder PIN via private message."""