"""Entry point for the Discord bot. Usage: ./start_server.sh """ import asyncio import os import sys import logging import random 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 handlers.voice_gen import setup as setup_voice_gen from handlers.love_emotes import setup as setup_love_emotes from handlers.utils import setup as setup_utils from handlers.memes import process_meme from commands.ping import ping from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() # Configure logging logging.basicConfig( level=logging.DEBUG if os.environ.get("APP_DEBUG") == "1" else logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[logging.StreamHandler(sys.stdout), logging.FileHandler("bot.log")] ) # ─── Bot Setup ─────────────────────────────────────────────────────────────── intents = discord.Intents.default() intents.message_content = True intents.members = True intents.messages = True intents.voice_states = True bot = commands.Bot( command_prefix="!", intents=intents, ) tree: CommandTree = bot.tree # ─── Slash Commands ────────────────────────────────────────────────────────── tree.add_command(AppCommand(name="ping", callback=ping, description="Check bot latency.")) # ─── 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") # Register handlers await setup_voice(bot) await setup_love_emotes(bot) await setup_utils(bot) await setup_voice_gen(bot) # Sync slash commands await tree.sync() print("Slash commands synced.") print("All handlers registered.") @bot.event async def on_command_error(ctx: commands.Context, error: commands.CommandError) -> None: """Global error handler for all commands.""" logger = logging.getLogger(__name__) logger.debug(f"Command error for {ctx.author.name} ({ctx.author.id}): {error}") if isinstance(error, commands.CommandNotFound): await ctx.send("Command not found. Type `!list-commands` 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}.") elif isinstance(error, commands.BadArgument): await ctx.send(f"Bad argument: {error.argument}.") elif isinstance(error, commands.CommandOnCooldown): remaining = int(error.retry_after) await ctx.send( f"This command is on cooldown. Try again in {remaining} seconds." ) else: await ctx.send(f"An error occurred: {error.__traceback__.__str__()}") @bot.event async def on_message(message: discord.Message) -> None: """Handle text messages sent in channels (prefix commands).""" # Ignore bot's own messages if message.author == bot.user: return logging.debug(f"Got message: {message.content} with @mentions: {message.mentions}") # Ignore messages in DMs if message.guild is None: return msg = message.content if bot.user in message.mentions: await message.channel.send("Let the rich eat cake!") if msg.startswith("!"): await bot.process_commands(message) # The meme train if random.randint(1, 100) > 25: await process_meme(message) # ─── 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()