"""Core Discord bot with intents, slash commands, and event handlers. Compatible with discord.py 2.7.x. """ import os import logging import discord from discord.ext import commands from discord.app_commands import AppCommandGroup as Group # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) # ─── Configuration ──────────────────────────────────────────────────────────── TOKEN = os.getenv("DISCORD_TOKEN") if not TOKEN: raise RuntimeError("DISCORD_TOKEN environment variable is not set.") PREFIX = os.getenv("COMMAND_PREFIX", "!") BOT_NAME = os.getenv("BOT_NAME", "A.B.I.N.A.S.H") GUILD_IDS = os.getenv("GUILD_IDS", "").split(",") # ─── Intents ────────────────────────────────────────────────────────────────── intents = discord.Intents.default() intents.message_content = True intents.members = True intents.guilds = True intents.presences = False # ─── Bot Instance ──────────────────────────────────────────────────────────── bot = commands.Bot( command_prefix=PREFIX, intents=intents, ) # ─── Slash Command Tree ────────────────────────────────────────────────────── tree = Group(bot) # ─── Ping ───────────────────────────────────────────────────────────────────── @tree.command(name="ping", description="Check bot latency.") async def ping(interaction: discord.Interaction) -> None: """Check the bot's latency.""" latency = round(bot.latency * 1000) await interaction.response.send_message( f"Pong! Latency: {latency}ms", ephemeral=True, ) # ─── Hello ─────────────────────────────────────────────────────────────────── @tree.command(name="hello", description="Say hello to someone.") async def hello( interaction: discord.Interaction, name: str = "World", ) -> None: """Greet a user by name.""" await interaction.response.send_message( f"Hello, {name}!", ephemeral=True, ) # ─── Emoji ─────────────────────────────────────────────────────────────────── @tree.command(name="emoji", description="Send a custom emoji by name.") async def emoji(interaction: discord.Interaction, *, name: str) -> None: """Send a custom emoji by name (requires colon prefix).""" emoji_str = f":{name}:" await interaction.response.send_message(emoji_str, ephemeral=True) # ─── App Emoji ─────────────────────────────────────────────────────────────── @tree.command(name="app-emoji", description="Fetch an app-owned emoji by ID.") async def app_emoji( interaction: discord.Interaction, *, emoji_id: int, ) -> None: """Fetch an application-owned emoji by ID.""" try: emoji = await bot.fetch_application_emoji(emoji_id) await interaction.response.send_message( f"Emoji: `{emoji.name}` — {emoji.url}", ephemeral=True, ) except discord.NotFound: await interaction.response.send_message( f"Emoji with ID {emoji_id} not found.", ephemeral=True, ) except discord.HTTPException as e: await interaction.response.send_message( f"Error fetching emoji: {e}", ephemeral=True, ) # ─── Upload Emoji ──────────────────────────────────────────────────────────── @tree.command(name="upload-emoji", description="Upload a custom emoji to the guild.") async def upload_emoji( interaction: discord.Interaction, name: str, file_path: str, ) -> None: """Upload a custom emoji (JPG, PNG, or GIF) to the guild.""" if not interaction.guild: await interaction.response.send_message( "This command can only be used in a server.", ephemeral=True, ) return if not interaction.guild.permissions_for(interaction.user).manage_emojis_and_stickers: await interaction.response.send_message( "You need manage_emojis_and_stickers permission.", ephemeral=True, ) return try: with open(file_path, "rb") as f: image_bytes = f.read() await interaction.guild.create_custom_emoji(name=name, image=image_bytes) await interaction.response.send_message( f"Successfully uploaded emoji `{name}`!", ephemeral=True, ) except FileNotFoundError: await interaction.response.send_message( "Error: File not found.", ephemeral=True, ) except Exception as e: await interaction.response.send_message( f"Error uploading emoji: {e}", ephemeral=True, ) # ─── 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.") @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.""" import asyncio asyncio.run(bot.start(TOKEN)) if __name__ == "__main__": main()