import discord from discord.ext import commands import asyncio import logging import os import json from dotenv import load_dotenv CONFIG_FILE = 'bot_config.json' logger = logging.getLogger(__name__) def setup_logging() -> None: logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('bot.log', encoding='utf-8'), logging.StreamHandler() ] ) def load_config() -> dict: if not os.path.exists(CONFIG_FILE): raise FileNotFoundError(f"Main bot config not found at {CONFIG_FILE}") with open(CONFIG_FILE, 'r', encoding='utf-8') as f: config = json.load(f) # Apply fallbacks settings = config.get("settings", {}) settings.setdefault("command_prefix", "!") settings.setdefault("sync_commands", True) config["settings"] = settings config.setdefault("cogs", []) return config class Bot(commands.Bot): def __init__(self, config: dict): self.config = config intents = discord.Intents.default() intents.message_content = True intents.reactions = True intents.members = True intents.guilds = True intents.messages = True super().__init__( command_prefix=self.config["settings"]["command_prefix"], intents=intents, help_command=None, ) async def setup_hook(self) -> None: for cog in self.config["cogs"]: try: await self.load_extension(cog) logger.info("Loaded cog: %s", cog) except Exception as e: logger.error("Failed to load cog %s: %s", cog, e, exc_info=True) if self.config["settings"]["sync_commands"]: try: await self.tree.sync() logger.info("Application commands synced successfully") except Exception as e: logger.error("Failed to sync application commands: %s", e) async def on_ready(self) -> None: if not hasattr(self, '_ready_fired'): self._ready_fired = True logger.info("Bot ready: %s (id: %s)", self.user, self.user.id) async def main() -> None: setup_logging() load_dotenv() token = os.getenv('DISCORD_TOKEN') if not token: logger.critical("DISCORD_TOKEN environment variable is missing; Shutting down") return try: config = load_config() except Exception as e: logger.critical("Failed to load configuration: %s", e) return bot = Bot(config) try: await bot.start(token) except discord.LoginFailure: logger.critical("Improper DISCORD_TOKEN passed; Shutting down") if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: logger.info("Bot shutdown via keyboard interrupt")