169 lines
7.5 KiB
Python
169 lines
7.5 KiB
Python
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)
|