"""Entry point for the Discord bot. Usage: python main.py """ import asyncio import os import sys import logging # Add project root to path project_root = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, project_root) # Load environment variables from dotenv import load_dotenv load_dotenv() # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) import discord from discord.ext import commands from discord.app_commands import Command as AppCommand, CommandTree from handlers.gif_emotes import setup as setup_gif_emotes from handlers.reactions import setup as setup_reactions from handlers.voice import setup as setup_voice from cogs.admin import setup as setup_admin from commands.ping import ping, info from commands.hello import hello from commands.emoji import emoji, app_emoji from commands.admin import upload_emoji, list_emojis, delete_emoji # ─── Bot Setup ─────────────────────────────────────────────────────────────── intents = discord.Intents.default() intents.message_content = True intents.members = True bot = commands.Bot( command_prefix="!", intents=intents, ) tree: CommandTree = bot.tree # ─── Slash Commands ────────────────────────────────────────────────────────── # Register slash commands from modules tree.add_command(AppCommand(name="ping", callback=ping, description="Check bot latency.")) tree.add_command(AppCommand(name="info", callback=info, description="Show bot information.")) tree.add_command(AppCommand(name="hello", callback=hello, description="Say hello to someone.")) tree.add_command(AppCommand(name="emoji", callback=emoji, description="Send a custom emoji by name.")) tree.add_command(AppCommand(name="app-emoji", callback=app_emoji, description="Fetch an app-owned emoji by ID.")) tree.add_command(AppCommand(name="upload-emoji", callback=upload_emoji, description="Upload a custom emoji to the guild.")) tree.add_command(AppCommand(name="list-emojis", callback=list_emojis, description="List all custom emojis in the guild.")) tree.add_command(AppCommand(name="delete-emoji", callback=delete_emoji, description="Delete a custom emoji.")) # ─── Event Handlers ────────────────────────────────────────────────────────── @bot.event async def on_ready() -> None: """Entry point after successful login.""" assert bot.user is not None print(f"Logged in as {bot.user} (ID: {bot.user.id})") print(f"Bot is running with {len(bot.guilds)} guilds") # Sync slash commands await tree.sync() print("Slash commands synced.") # Register handlers await setup_gif_emotes(bot) await setup_reactions(bot) await setup_voice(bot) await setup_admin(bot) print("All handlers registered.") @bot.event async def on_command_error(ctx: commands.Context, error: commands.CommandError) -> None: """Global error handler for all commands.""" if isinstance(error, commands.CommandNotFound): await ctx.send("Command not found. Type `!help` for a list of commands.") elif isinstance(error, commands.CheckFailure): await ctx.send("You do not have permission to use this command.") elif isinstance(error, commands.MissingPermissions): await ctx.send("You don't have permission to use this command.") elif isinstance(error, commands.MissingRequiredArgument): await ctx.send(f"Missing argument: {error.param.name}.") else: await ctx.send(f"An error occurred: {error}") @bot.event async def on_voice_state_update( member: discord.Member, before: discord.VoiceState, after: discord.VoiceState, ) -> None: """Handle voice state changes.""" if after.channel_id is None and before.channel_id is not None: print(f"{member.name} left voice channel.") elif after.channel_id is not None and before.channel_id is None: print(f"{member.name} joined voice channel.") # ─── Entry Point ───────────────────────────────────────────────────────────── def main() -> None: """Run the bot.""" token = os.getenv("DISCORD_TOKEN") if not token: print("Error: DISCORD_TOKEN environment variable is not set.") print("Create a .env file with your Discord bot token.") sys.exit(1) asyncio.run(bot.start(token)) if __name__ == "__main__": main()