AaI_Spirit_Kings_2 / bot_manager.py
16dvnk's picture
fix
2742de6
import asyncio
import threading
import os
from datetime import datetime
from extensions import db # βœ… Safe import
from models import BotStatus, BotLog
from discord_bot import run_bot
class BotManager:
def __init__(self):
self.bot_thread = None
self.loop = None
self.is_running = False
self._stop_event = None
# Sync with database after all attributes are set
try:
self._sync_with_database()
except Exception as e:
print(f"Could not sync with database during init: {e}")
def log_callback(self, level, message):
"""Callback function to handle bot logs"""
from app import app # πŸ” Lazy import to avoid circular dependency
with app.app_context():
try:
log_entry = BotLog(level=level, message=message)
db.session.add(log_entry)
db.session.commit()
# Keep only last 1000 log entries
total_logs = BotLog.query.count()
if total_logs > 1000:
oldest_logs = BotLog.query.order_by(BotLog.id).limit(total_logs - 1000).all()
for log in oldest_logs:
db.session.delete(log)
db.session.commit()
except Exception as e:
print(f"Failed to log to database: {e}")
def _run_bot_in_thread(self):
"""Run the bot in a separate thread with its own event loop"""
from app import app # πŸ” Lazy import
try:
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
self._stop_event = asyncio.Event()
with app.app_context():
status = BotStatus.get_current_status()
status.is_running = True
status.started_at = datetime.utcnow()
status.last_error = None
db.session.commit()
self.log_callback("INFO", "Starting Discord bot...")
self.loop.run_until_complete(run_bot(self.log_callback))
except Exception as e:
self.log_callback("ERROR", f"Bot thread error: {e}")
with app.app_context():
status = BotStatus.get_current_status()
status.is_running = False
status.last_error = str(e)
db.session.commit()
finally:
self.is_running = False
with app.app_context():
status = BotStatus.get_current_status()
status.is_running = False
db.session.commit()
def start_bot(self):
"""Start the Discord bot in a separate thread"""
from app import app # πŸ” Lazy import
if self.is_running:
return False, "Bot is already running"
# Check if required environment variables are set
if not os.getenv("DISCORD_TOKEN"):
return False, "DISCORD_TOKEN not set in environment variables"
if not os.getenv("SPACE_URL"):
return False, "SPACE_URL not set in environment variables"
try:
self.bot_thread = threading.Thread(target=self._run_bot_in_thread, daemon=True)
self.bot_thread.start()
self.is_running = True
with app.app_context():
status = BotStatus.get_current_status()
status.restart_count += 1
db.session.commit()
return True, "Bot started successfully"
except Exception as e:
self.log_callback("ERROR", f"Failed to start bot: {e}")
return False, f"Failed to start bot: {e}"
def stop_bot(self):
"""Stop the Discord bot"""
from app import app # πŸ” Lazy import
if not self.is_running:
return False, "Bot is not running"
try:
self.log_callback("INFO", "Stopping Discord bot...")
if self.loop and not self.loop.is_closed():
# Schedule the stop event to be set
asyncio.run_coroutine_threadsafe(self._stop_bot_async(), self.loop)
# Wait for the thread to finish
if self.bot_thread and self.bot_thread.is_alive():
self.bot_thread.join(timeout=10)
self.is_running = False
with app.app_context():
status = BotStatus.get_current_status()
status.is_running = False
db.session.commit()
return True, "Bot stopped successfully"
except Exception as e:
self.log_callback("ERROR", f"Failed to stop bot: {e}")
return False, f"Failed to stop bot: {e}"
async def _stop_bot_async(self):
"""Async method to stop the bot gracefully"""
if self._stop_event:
self._stop_event.set()
def restart_bot(self):
"""Restart the Discord bot"""
self.log_callback("INFO", "Restarting Discord bot...")
stop_success, stop_msg = self.stop_bot()
if not stop_success:
return False, f"Failed to stop bot: {stop_msg}"
# Wait a moment for cleanup
import time
time.sleep(2)
start_success, start_msg = self.start_bot()
if not start_success:
return False, f"Failed to start bot: {start_msg}"
return True, "Bot restarted successfully"
def _sync_with_database(self):
"""Sync bot manager state with database on startup"""
from app import app # πŸ” Lazy import
try:
with app.app_context():
status = BotStatus.get_current_status()
# If database shows bot is running but manager thinks it's not, reset database
if status.is_running and not self.is_running:
status.is_running = False
status.last_error = "Bot was reset due to application restart"
db.session.commit()
self.log_callback("INFO", "Reset bot status after application restart")
except Exception as e:
print(f"Could not sync with database: {e}")
def get_status(self):
"""Get current bot status"""
from app import app # πŸ” Lazy import
with app.app_context():
status = BotStatus.get_current_status()
return {
'is_running': self.is_running,
'database_status': status.to_dict(),
'thread_alive': self.bot_thread.is_alive() if self.bot_thread else False
}
# Global bot manager instance
bot_manager = BotManager()