ia_bot/main.py
Benjamyn 43ca943cb8 feat: A.B.I.N.A.S.H Discord bot with GIF emotes, slash commands, and music
- Core bot with slash command tree (discord.py 2.7.x compatible)
- Commands: /ping, /info, /hello, /emoji, /app-emoji
- Admin: /upload-emoji, /list-emojis, /delete-emoji
- GIF emote detection and reaction handling
- Message reactions with percentage-based triggers and cooldowns
- Voice/music cog with YTDLSource, skip, volume, pause, resume, stop
- Global error handler and voice state change logging
- Environment config via .env (DISCORD_TOKEN, COMMAND_PREFIX, etc.)
2026-08-27 16:05:27 +10:00

133 lines
4.8 KiB
Python

"""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()