import discord from discord.ext import commands import os import logging # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # Define intents # You might need to adjust these based on the features your bot uses intents = discord.Intents.default() intents.message_content = True # Required to read message content in most cases intents.members = True # Required for member-related events and fetching members intents.presences = True # Initialize the bot # command_prefix is the character(s) that trigger bot commands (e.g., !command) bot = commands.Bot(command_prefix='!', intents=intents) # --- Event Handlers --- @bot.event async def on_ready(): """Logs when the bot is ready and connected to Discord.""" logging.info(f'Logged in as {bot.user.name} ({bot.user.id})') logging.info('------') logging.info('Loading Modules') logging.info('------') await load_extensions() # Load command modules when the bot is ready logging.info('Registering slash commands') logging.info('------') for server in bot.guilds: num_synced = await bot.tree.sync(guild=discord.Object(id=server.id)) logging.info(f"Registered {num_synced} commands for server ID: '{server.id}'") @bot.event async def on_command_error(ctx, error): """Handles errors that occur when running commands.""" if isinstance(error, commands.CommandNotFound): # Ignore CommandNotFound errors to avoid spamming the console/chat return elif isinstance(error, commands.MissingRequiredArgument): await ctx.send(f'Error: Missing required argument(s). Usage: `{ctx.command.usage or "No usage info provided."}`') elif isinstance(error, commands.BadArgument): await ctx.send(f'Error: Invalid argument(s) provided. Usage: `{ctx.command.usage or "No usage info provided."}`') elif isinstance(error, commands.MissingPermissions): await ctx.send("Error: You don't have the necessary permissions to run this command.") elif isinstance(error, commands.BotMissingPermissions): await ctx.send("Error: I don't have the necessary permissions to run this command.") else: # Log other errors for debugging logging.error(f'Ignoring exception in command {ctx.command}:', exc_info=error) await ctx.send(f'An unexpected error occurred: {error}') # --- Extension (Command Module) Loading --- async def load_extensions(): """Loads all command modules from the 'commands' directory.""" logging.info("Attempting to load extensions...") commands_dir = 'commands' if not os.path.exists(commands_dir): logging.warning(f"'{commands_dir}' directory not found. No commands will be loaded.") return for filename in os.listdir(commands_dir): # Check if the file is a Python file and not a hidden file if filename.endswith('.py') and not filename.startswith('_'): # Construct the module path (e.g., commands.example) module_name = f'{commands_dir}.{filename[:-3]}' try: await bot.load_extension(module_name) logging.info(f'Successfully loaded extension: {module_name}') except Exception as e: logging.error(f'Failed to load extension {module_name}: {e}', exc_info=True) async def unload_extensions(): """Unloads all loaded command modules.""" logging.info("Attempting to unload extensions...") for extension in list(bot.extensions): # Iterate over a copy as unloading modifies the list try: await bot.unload_extension(extension) logging.info(f'Successfully unloaded extension: {extension}') except Exception as e: logging.error(f'Failed to unload extension {extension}: {e}', exc_info=True) async def reload_extensions(): """Reloads all loaded command modules.""" logging.info("Attempting to reload extensions...") await unload_extensions() await load_extensions() logging.info("Extensions reloaded.") # --- Admin Commands (Optional, for managing extensions) --- # You might want to restrict these commands to specific users or roles @bot.command(name='load', hidden=True) @commands.is_owner() # Requires the user to be the bot owner (set via application settings) async def load_command(ctx, extension_name: str): """Loads a specific command extension.""" module_name = f'commands.{extension_name}' try: await bot.load_extension(module_name) await ctx.send(f'Successfully loaded extension: `{module_name}`') logging.info(f'Manual load: Successfully loaded extension: {module_name}') except commands.ExtensionAlreadyLoaded: await ctx.send(f'Extension `{module_name}` is already loaded.') except commands.ExtensionNotFound: await ctx.send(f'Extension `{module_name}` not found.') except Exception as e: await ctx.send(f'Failed to load extension `{module_name}`: {e}') logging.error(f'Manual load: Failed to load extension {module_name}: {e}', exc_info=True) @bot.command(name='unload', hidden=True) @commands.is_owner() async def unload_command(ctx, extension_name: str): """Unloads a specific command extension.""" module_name = f'commands.{extension_name}' try: await bot.unload_extension(module_name) await ctx.send(f'Successfully unloaded extension: `{module_name}`') logging.info(f'Manual unload: Successfully unloaded extension: {module_name}') except commands.ExtensionNotFound: await ctx.send(f'Extension `{module_name}` not found or not loaded.') except Exception as e: await ctx.send(f'Failed to unload extension `{module_name}`: {e}') logging.error(f'Manual unload: Failed to unload extension {module_name}: {e}', exc_info=True) @bot.command(name='reload', hidden=True) @commands.is_owner() async def reload_command(ctx, extension_name: str = None): """Reloads a specific command extension or all extensions.""" if extension_name: module_name = f'commands.{extension_name}' try: await bot.reload_extension(module_name) await ctx.send(f'Successfully reloaded extension: `{module_name}`') logging.info(f'Manual reload: Successfully reloaded extension: {module_name}') except commands.ExtensionNotFound: await ctx.send(f'Extension `{module_name}` not found or not loaded.') except Exception as e: await ctx.send(f'Failed to reload extension `{module_name}`: {e}') logging.error(f'Manual reload: Failed to reload extension {module_name}: {e}', exc_info=True) else: # Reload all extensions if no specific name is provided await reload_extensions() await ctx.send('All extensions reloaded.') # --- Running the Bot --- if __name__ == "__main__": # Get the bot token from environment variables # It's recommended to use environment variables for sensitive information DISCORD_BOT_TOKEN = os.getenv('DISCORD_BOT_TOKEN') if DISCORD_BOT_TOKEN is None: logging.error("DISCORD_BOT_TOKEN environment variable not set.") logging.error("Please set the DISCORD_BOT_TOKEN environment variable with your bot's token.") else: try: # Run the bot bot.run(DISCORD_BOT_TOKEN) except discord.errors.LoginFailure: logging.error("Invalid Discord bot token provided. Please check your token.") except Exception as e: logging.error(f"An error occurred while running the bot: {e}", exc_info=True)