import discord import os import random import asyncio import json import logging import aiohttp from datetime import datetime, timedelta from datetime import time as datetime_time from pathlib import Path from dotenv import load_dotenv from discord import app_commands from discord.ext import commands, tasks from discord.utils import get from itertools import cycle from mcrcon import MCRcon # Create logs directory if it doesn't exist Path("logs").mkdir(exist_ok=True) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(f'logs/discord_{datetime.now().strftime("%Y-%m-%d")}.log'), logging.StreamHandler() ] ) logger = logging.getLogger('discord_bot') load_dotenv() # Configuration - Environment variables with defaults GUILD_ID = int(os.getenv('GUILD_ID', '605864889927467181')) AUDIT_CHANNEL_ID = int(os.getenv('AUDIT_CHANNEL_ID', '1340861451392520233')) ANNOUNCEMENT_CHANNEL_ID = int(os.getenv('ANNOUNCEMENT_CHANNEL_ID', '605864889940050088')) ADMIN_ROLE_ID = int(os.getenv('ADMIN_ROLE_ID', '605867042104541194')) MINECRAFT_ROLE_ID = int(os.getenv('MINECRAFT_ROLE_ID', '821163520942145556')) HYTALE_ROLE_ID = int(os.getenv('HYTALE_ROLE_ID', '1460750779848589548')) COOL_KIDS_ROLE_ID = int(os.getenv('COOL_KIDS_ROLE_ID', '788968178117902347')) OWNER_ID = int(os.getenv('OWNER_ID', '238728085342519296')) REACTION_MESSAGE_ID = int(os.getenv('REACTION_MESSAGE_ID', '744047519696420914')) MINECRAFT_RCON_HOST = os.getenv('MINECRAFT_RCON_HOST', '10.10.10.67') MINECRAFT_RCON_PASSWORD = os.getenv('MINECRAFT_RCON_PASSWORD', '') PELICAN_URL = os.getenv('PELICAN_URL', 'http://10.10.10.67') PELICAN_API_KEY = os.getenv('PELICAN_API_KEY', '') # Client API key HYTALE_SERVER_UUID = os.getenv('HYTALE_SERVER_UUID', '7a656836-c3f3-491e-ac55-66affe435e72') STATUS_UPDATE_INTERVAL = int(os.getenv('STATUS_UPDATE_INTERVAL', '300')) # 5 minutes default # Emoji to role mapping for reaction roles EMOJI_ROLE_MAP = { "Overwatch": "(Overwatch)", "Minecraft": "(Minecraft)", "LeagueOfLegends": "(League Of Legends)", "ClashRoyale": "(Clash Royale)", "CSGO": "(CSGO)", "CivilizationVI": "(Civilization VI)", "Python": "Computer Nerd", "computer": "Computer Nerd", "Valorant": "(Valorant)", "Ark": "(Ark Survival Evolved)", "AmongUs": "(Among Us)", "RainbowSixSiege": "(Rainbow Six Siege)", "Phasmophobia": "(Phasmophobia)", "StardewValley": "(Stardew Valley)", "Tarkov": "(Tarkov)", "LethalCompany": "(Lethal Company)", "BTD": "(Balloons Tower Defense)", "HellDivers": "(Hell Divers)", "ABI": "(Arena Breakout Infinite)", "UnoReverse": "(Uno)", "Hytale": "(Hytale)" } class CustomBot(commands.Bot): def __init__(self): intents = discord.Intents.all() intents.message_content = True super().__init__( command_prefix=".", intents=intents ) self.status_cycle = cycle([ "The Lotus Guild is boomin", "lotusguild.org", "Ranked Minesweeper" ]) self.remove_command("help") async def setup_hook(self): logger.info("Syncing commands...") await self.tree.sync() logger.info("Commands synced!") @tasks.loop(seconds=STATUS_UPDATE_INTERVAL) async def change_status(self): try: await self.change_presence(activity=discord.Game(next(self.status_cycle))) except Exception as e: logger.error(f"Error changing status: {e}", exc_info=True) @change_status.before_loop async def before_change_status(self): await self.wait_until_ready() async def on_ready(self): logger.info(f"Bot logged in as: {self.user.name}") logger.info(f"Registered commands: {[cmd.name for cmd in self.tree.get_commands()]}") self.change_status.start() client = CustomBot() def is_owner(): async def predicate(ctx): return ctx.author.id == OWNER_ID return commands.check(predicate) def get_role_from_emoji(emoji_name: str) -> str: """Helper function to map emoji names to role names.""" return EMOJI_ROLE_MAP.get(emoji_name, emoji_name) async def send_audit_log(message: str, color: int = 0x980000): """Send a message to the audit log channel.""" try: audit_channel = client.get_channel(AUDIT_CHANNEL_ID) if audit_channel: embed = discord.Embed( description=message, color=color, timestamp=datetime.now() ) await audit_channel.send(embed=embed) except Exception as e: logger.error(f"Failed to send audit log: {e}", exc_info=True) async def is_valid_minecraft_username(username: str) -> bool: """Validate Minecraft username using Mojang API asynchronously.""" url = f'https://api.mojang.com/users/profiles/minecraft/{username}' try: async with aiohttp.ClientSession() as session: async with session.get(url) as response: return response.status == 200 except Exception as e: logger.error(f"Error validating Minecraft username: {e}") return False def has_role_check(role_id: int): """Check if user has a specific role (for slash commands).""" async def predicate(interaction: discord.Interaction) -> bool: return any(role.id == role_id for role in interaction.user.roles) return app_commands.check(predicate) @client.event async def on_message(message): try: logger.info(f"Channel: [{str(message.channel)}] User: {str(message.author)} Content: {message.content}") await client.process_commands(message) except Exception as e: logger.error(f"Error in on_message: {e}", exc_info=True) @client.event async def on_member_join(member): try: logger.info(f"New member joined: {member.name} (ID: {member.id})") await send_audit_log(f"✨ Member Joined\nUser: {member.mention}\nAccount Created: {member.created_at}") except Exception as e: logger.error(f"Error in on_member_join: {e}", exc_info=True) @client.event async def on_member_remove(member): try: logger.info(f"Member left: {member.name} (ID: {member.id})") # Fix: Use member.name instead of deprecated discriminator await send_audit_log(f"👋 Member Left\nUser: {member.name} ({member.id})") except Exception as e: logger.error(f"Error in on_member_remove: {e}", exc_info=True) @client.event async def on_message_delete(message): try: logger.warning(f"Message deleted in {message.channel}: {message.author}: {message.content}") await send_audit_log(f"🗑️ Message Deleted\nChannel: {message.channel}\nUser: {message.author}\nContent: {message.content}") except Exception as e: logger.error(f"Error in on_message_delete: {e}", exc_info=True) @client.event async def on_voice_state_update(member, before, after): try: if before.channel != after.channel: if after.channel: await send_audit_log(f"🎤 Voice Join\nUser: {member.mention}\nChannel: {after.channel.name}") else: await send_audit_log(f"🎤 Voice Leave\nUser: {member.mention}\nChannel: {before.channel.name}") except Exception as e: logger.error(f"Error in on_voice_state_update: {e}", exc_info=True) @client.event async def on_integration_create(integration): try: await send_audit_log(f"🔌 New Integration Added\nName: {integration.name}\nType: {integration.type}") except Exception as e: logger.error(f"Error in on_integration_create: {e}", exc_info=True) @client.event async def on_scheduled_event_create(event): try: await send_audit_log(f"📅 Event Created\nName: {event.name}\nStart Time: {event.start_time}") except Exception as e: logger.error(f"Error in on_scheduled_event_create: {e}", exc_info=True) @client.event async def on_scheduled_event_update(before, after): try: await send_audit_log(f"📝 Event Updated\nName: {after.name}\nChanges Made") except Exception as e: logger.error(f"Error in on_scheduled_event_update: {e}", exc_info=True) @client.event async def on_member_update(before, after): """Unified handler for all member update events.""" try: # Check for timeout changes if before.timed_out_until != after.timed_out_until: await send_audit_log(f"⏰ Member Timeout\nUser: {after.mention}\nUntil: {after.timed_out_until}") # Check for server boost changes if before.premium_since != after.premium_since: await send_audit_log(f"⭐ Server Boost\nUser: {after.mention} boosted the server!") # Check for nickname changes if before.nick != after.nick: await send_audit_log(f"📛 Nickname Change\nUser: {before.mention}\nBefore: {before.nick}\nAfter: {after.nick}") # Check for role changes if before.roles != after.roles: logger.info(f"Role change for {before.name}: {before.roles} -> {after.roles}") await send_audit_log( f"👥 Role Update\nUser: {before.mention}\n" f"Before: {', '.join([r.name for r in before.roles])}\n" f"After: {', '.join([r.name for r in after.roles])}" ) except Exception as e: logger.error(f"Error in on_member_update: {e}", exc_info=True) @client.event async def on_member_ban(guild, user): try: await send_audit_log(f"🔨 Member Banned\nUser: {user.name}") except Exception as e: logger.error(f"Error in on_member_ban: {e}", exc_info=True) @client.event async def on_guild_role_update(before, after): try: if before.permissions != after.permissions: await send_audit_log(f"🔑 Role Permissions Updated\nRole: {after.name}") except Exception as e: logger.error(f"Error in on_guild_role_update: {e}", exc_info=True) @client.event async def on_guild_emoji_create(emoji): try: await send_audit_log(f"😄 New Emoji Added\nName: {emoji.name}") except Exception as e: logger.error(f"Error in on_guild_emoji_create: {e}", exc_info=True) @client.event async def on_guild_sticker_create(sticker): try: await send_audit_log(f"🎯 New Sticker Added\nName: {sticker.name}") except Exception as e: logger.error(f"Error in on_guild_sticker_create: {e}", exc_info=True) @client.event async def on_guild_role_create(role): try: await send_audit_log(f"👑 Role Created\nName: {role.name}\nColor: {role.color}") except Exception as e: logger.error(f"Error in on_guild_role_create: {e}", exc_info=True) @client.event async def on_guild_role_delete(role): try: await send_audit_log(f"🗑️ Role Deleted\nName: {role.name}") except Exception as e: logger.error(f"Error in on_guild_role_delete: {e}", exc_info=True) @client.event async def on_message_edit(before, after): try: # Skip if content didn't change (e.g. embed or pin updates) if before.content == after.content: return logger.info(f"Message edited in {before.channel}\nBefore: {before.content}\nAfter: {after.content}") await send_audit_log( f"✏️ Message Edited\n" f"Channel: {before.channel.mention}\n" f"User: {before.author.mention}\n" f"Before: {before.content}\n" f"After: {after.content}" ) except Exception as e: logger.error(f"Error in on_message_edit: {e}", exc_info=True) @client.event async def on_guild_channel_create(channel): try: logger.info(f"Channel created: {channel.name}") except Exception as e: logger.error(f"Error in on_guild_channel_create: {e}", exc_info=True) @client.event async def on_guild_channel_delete(channel): try: logger.info(f"Channel deleted: {channel.name}") except Exception as e: logger.error(f"Error in on_guild_channel_delete: {e}", exc_info=True) @client.event async def on_raw_reaction_add(payload): """Handle reaction role additions.""" try: if payload.message_id != REACTION_MESSAGE_ID: return guild = client.get_guild(payload.guild_id) if not guild: return member = await guild.fetch_member(payload.user_id) if not member: logger.warning(f"Member not found: {payload.user_id}") return # Get role name from emoji using helper function role_name = get_role_from_emoji(payload.emoji.name) role = discord.utils.get(guild.roles, name=role_name) if role: await member.add_roles(role) try: member_role = discord.utils.get(guild.roles, name='Member') if member_role: await member.add_roles(member_role) except Exception as e: logger.error(f"Failed to add Member role: {e}") logger.info(f"Role {role_name} added to {member.name}") else: logger.warning(f"Role not found: {role_name}") except Exception as e: logger.error(f"Error in on_raw_reaction_add: {e}", exc_info=True) @client.event async def on_raw_reaction_remove(payload): """Handle reaction role removals.""" try: if payload.message_id != REACTION_MESSAGE_ID: return guild = client.get_guild(payload.guild_id) if not guild: return member = await guild.fetch_member(payload.user_id) if not member: logger.warning(f"Member not found: {payload.user_id}") return # Get role name from emoji using helper function role_name = get_role_from_emoji(payload.emoji.name) role = discord.utils.get(guild.roles, name=role_name) if role: await member.remove_roles(role) logger.info(f"Role {role_name} removed from {member.name}") else: logger.warning(f"Role not found: {role_name}") except Exception as e: logger.error(f"Error in on_raw_reaction_remove: {e}", exc_info=True) @client.tree.command(name="help", description="Shows all available commands") async def help_command(interaction: discord.Interaction): try: embed = discord.Embed( color=discord.Color.from_rgb(152, 0, 0), title="Command List", description="Describes what commands do and how to use them." ) embed.set_author(name="Help Page (Lotus Bot)", icon_url="https://lotusguild.org/Lotus.png") embed.set_image(url="https://lotusguild.org/favicon.ico") embed.set_thumbnail(url="https://lotusguild.org/favicon.ico") # Basic Commands embed.add_field(name="/help", value="Shows this help message with all available commands", inline=False) embed.add_field(name="/ping", value="Shows the bot's response time in milliseconds", inline=False) # Fun Commands embed.add_field(name="/8ball", value="Ask the magic 8-ball a question and receive an answer", inline=False) embed.add_field(name="/fortune", value="Get your fortune cookie message", inline=False) embed.add_field(name="/flip", value="Flip a coin and get heads or tails", inline=False) embed.add_field(name="/roll", value="Roll dice (format: NdS, example: 2d6)", inline=False) embed.add_field(name="/random", value="Generate a random number between specified range", inline=False) embed.add_field(name="/rps", value="Play Rock Paper Scissors against the bot", inline=False) embed.add_field(name="/poll", value="Create a simple yes/no poll", inline=False) # Game Commands embed.add_field(name="/agent", value="Get a random Valorant agent with their role", inline=False) embed.add_field(name="/champion", value="Get a random League of Legends champion with their lane", inline=False) embed.add_field(name="/minecraft", value="Whitelists a player on the Minecraft server and shows server info", inline=False) embed.add_field(name="/hytale", value="Request whitelist on the Hytale server", inline=False) embed.add_field(name="/problem", value="Express your opinion about Canada geese", inline=False) # Interaction Commands embed.add_field(name="/kill", value="Kill another user with a random animation", inline=False) embed.add_field(name="/punch", value="Punch another user with a random animation", inline=False) embed.add_field(name="/hug", value="Hug another user with a random animation", inline=False) embed.add_field(name="/revive", value="Revive another user with a random animation", inline=False) # Admin Commands (Only visible to admins) if discord.utils.get(interaction.user.roles, id=ADMIN_ROLE_ID): embed.add_field(name="🛡️ Admin Commands", value="---------------", inline=False) embed.add_field(name="/clear", value="Clears specified number of messages from the channel", inline=False) embed.add_field(name="/lockdown", value="Lock a channel to prevent messages", inline=False) embed.add_field(name="/unlock", value="Unlock a previously locked channel", inline=False) embed.set_footer(text="Made by https://lotusguild.org") await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in help command: {e}", exc_info=True) await interaction.response.send_message("An error occurred while showing help.", ephemeral=True) @client.tree.command(name="ping", description="Check the bot's latency") async def ping(interaction: discord.Interaction): try: await interaction.response.send_message(f"Pong! {round(client.latency * 1000)}ms") except Exception as e: logger.error(f"Error in ping command: {e}", exc_info=True) @client.tree.command(name="problem", description="Express your opinion about Canada geese") async def problem(interaction: discord.Interaction): try: media_file = Path("media/canadagoose.gif") if not media_file.exists(): logger.warning(f"Media file not found: {media_file}") await interaction.response.send_message( "If you got a problem with canada gooses then you got a problem with me and I suggest you let that one marinate." ) else: await interaction.response.send_message( "If you got a problem with canada gooses then you got a problem with me and I suggest you let that one marinate.", file=discord.File(str(media_file)) ) except Exception as e: logger.error(f"Error in problem command: {e}", exc_info=True) await interaction.response.send_message("An error occurred.", ephemeral=True) @client.tree.command(name="clear", description="Clear messages from the channel") @app_commands.describe(amount="Number of messages to delete") @has_role_check(ADMIN_ROLE_ID) async def clear(interaction: discord.Interaction, amount: int = 5): try: await interaction.response.defer(ephemeral=True) deleted = await interaction.channel.purge(limit=amount) await interaction.followup.send(f"Successfully cleared {len(deleted)} messages!", ephemeral=True) except Exception as e: logger.error(f"Error in clear command: {e}", exc_info=True) await interaction.followup.send("Failed to clear messages.", ephemeral=True) @client.tree.command(name="lockdown", description="Lock a channel") @has_role_check(ADMIN_ROLE_ID) async def lockdown(interaction: discord.Interaction, channel: discord.TextChannel = None): try: channel = channel or interaction.channel await channel.set_permissions(interaction.guild.default_role, send_messages=False) await send_audit_log(f"🔒 Channel Locked\nChannel: {channel.name}") await interaction.response.send_message(f"Locked {channel.mention}") except Exception as e: logger.error(f"Error in lockdown command: {e}", exc_info=True) await interaction.response.send_message("Failed to lock channel.", ephemeral=True) @client.tree.command(name="unlock", description="Unlock a channel") @has_role_check(ADMIN_ROLE_ID) async def unlock(interaction: discord.Interaction, channel: discord.TextChannel = None): try: channel = channel or interaction.channel await channel.set_permissions(interaction.guild.default_role, send_messages=True) await send_audit_log(f"🔓 Channel Unlocked\nChannel: {channel.name}") await interaction.response.send_message(f"Unlocked {channel.mention}") except Exception as e: logger.error(f"Error in unlock command: {e}", exc_info=True) await interaction.response.send_message("Failed to unlock channel.", ephemeral=True) @client.tree.command(name="minecraft", description="Whitelist a player on the Minecraft server") @app_commands.describe(minecraft_username="The Minecraft username to whitelist") @has_role_check(MINECRAFT_ROLE_ID) async def minecraft(interaction: discord.Interaction, minecraft_username: str): try: # Validate username asynchronously if not await is_valid_minecraft_username(minecraft_username): await interaction.response.send_message("Invalid MC Username", ephemeral=True) return # Check if RCON password is configured if not MINECRAFT_RCON_PASSWORD: logger.error("MINECRAFT_RCON_PASSWORD not configured") await interaction.response.send_message( "Server configuration error. Please contact an administrator.", ephemeral=True ) return await interaction.response.defer() try: with MCRcon(MINECRAFT_RCON_HOST, MINECRAFT_RCON_PASSWORD) as mcr: response = mcr.command(f"whitelist add {minecraft_username}") logger.info(f"RCON response: {response}") except Exception as e: logger.error(f"RCON error: {e}", exc_info=True) await interaction.followup.send( "An error occurred while whitelisting the player (jared will fix just let him know)." ) return minecraft_embed = discord.Embed( color=discord.Color.from_rgb(152, 0, 0), title="Minecraft" ) minecraft_embed.set_author( name="(Lotus Bot)", icon_url="https://photos.lotusguild.org/api/assets/3c4eb2da-0d06-407f-bdb7-c9e4cf795f0a/thumbnail?key=4aoZxX5-FHE3m_Ywwz1uGo3iNW53kmFztxfUw91PdOgphPNxayLFicNuxPvit1OYTpY&size=preview&c=jUqDBQAWF9iId3J%2FyAeIcIAICEd4d3BzSA%3D%3D" ) minecraft_embed.set_image( url="https://photos.lotusguild.org/api/assets/3c4eb2da-0d06-407f-bdb7-c9e4cf795f0a/thumbnail?key=4aoZxX5-FHE3m_Ywwz1uGo3iNW53kmFztxfUw91PdOgphPNxayLFicNuxPvit1OYTpY&size=preview&c=jUqDBQAWF9iId3J%2FyAeIcIAICEd4d3BzSA%3D%3D" ) minecraft_embed.set_thumbnail( url="https://photos.lotusguild.org/api/assets/3c4eb2da-0d06-407f-bdb7-c9e4cf795f0a/thumbnail?key=4aoZxX5-FHE3m_Ywwz1uGo3iNW53kmFztxfUw91PdOgphPNxayLFicNuxPvit1OYTpY&size=preview&c=jUqDBQAWF9iId3J%2FyAeIcIAICEd4d3BzSA%3D%3D" ) minecraft_embed.add_field(name="You", value="have been whitelisted on the SMP", inline=False) minecraft_embed.add_field(name="Server Address", value="minecraft.lotusguild.org", inline=False) minecraft_embed.add_field(name="Version", value="1.21.11", inline=False) minecraft_embed.set_footer(text="Thanks for using Lotus Minecraft Server!") await interaction.followup.send(embed=minecraft_embed) except Exception as e: logger.error(f"Error in minecraft command: {e}", exc_info=True) if not interaction.response.is_done(): await interaction.response.send_message("An error occurred.", ephemeral=True) else: await interaction.followup.send("An error occurred.", ephemeral=True) @client.tree.command(name="hytale", description="Request whitelist on the Hytale server") @app_commands.describe(hytale_username="Your Hytale username") @has_role_check(HYTALE_ROLE_ID) async def hytale(interaction: discord.Interaction, hytale_username: str): """Request whitelist on the Hytale server""" try: await interaction.response.defer() # Validate username if not hytale_username.replace('_', '').replace('-', '').isalnum(): await interaction.followup.send("Invalid username. Use only letters, numbers, _ and -", ephemeral=True) return if len(hytale_username) < 3 or len(hytale_username) > 16: await interaction.followup.send("Username must be 3-16 characters", ephemeral=True) return # Log to audit await send_audit_log( f"🎮 Hytale Whitelist Request\nUser: {interaction.user.mention}\nUsername: `{hytale_username}`\nAction: Run `/whitelist add {hytale_username}`", color=0x00ff00 ) # Response embed embed = discord.Embed(color=discord.Color.from_rgb(152, 0, 0), title="Hytale") embed.set_author(name="(Lotus Bot)", icon_url="https://photos.lotusguild.org/api/assets/3c4eb2da-0d06-407f-bdb7-c9e4cf795f0a/thumbnail?key=wd3-Z4zFdrR6WBUfXLnBN8RgeT9tivgQwT6iDN3T0AaBIOfyIuYrbEszABB8OvUplFM&size=preview&c=jUqDBQAWF9iId3J%2FyAeIcIAICEd4d3BzSA%3D%3D") embed.add_field(name="Request Submitted", value=f"Whitelist request for `{hytale_username}` sent!", inline=False) embed.add_field(name="Server Address", value="hytale.lotusguild.org", inline=False) embed.add_field(name="Status", value="An admin will whitelist you shortly!", inline=False) embed.set_footer(text="Welcome to Lotus Hytale Server!") await interaction.followup.send(embed=embed) logger.info(f"Hytale whitelist request: {hytale_username} by {interaction.user.name}") except Exception as e: logger.error(f"Error in hytale command: {e}", exc_info=True) if not interaction.response.is_done(): await interaction.response.send_message("An error occurred.", ephemeral=True) else: await interaction.followup.send("An error occurred.", ephemeral=True) # Track last usage per user ask_cooldowns = {} COOLDOWN_MINUTES = 2 @client.tree.command(name="ask", description="Ask a question to Lotus LLM") @app_commands.describe(question="Your question for the AI") @has_role_check(COOL_KIDS_ROLE_ID) async def ask(interaction: discord.Interaction, question: str): try: # Check cooldown user_id = interaction.user.id current_time = datetime.now() if user_id in ask_cooldowns: time_diff = current_time - ask_cooldowns[user_id] if time_diff < timedelta(minutes=COOLDOWN_MINUTES): remaining = COOLDOWN_MINUTES - (time_diff.seconds / 60) await interaction.response.send_message( f"Please wait {remaining:.1f} minutes before asking another question!", ephemeral=True ) return await interaction.response.defer() # Select model based on user ID model = "lotusllmben" if user_id == 460640040096104459 else "lotusllm" logger.info(f"Sending question to Ollama: {question}") async with aiohttp.ClientSession() as session: async with session.post( "http://10.10.10.157:11434/api/generate", json={ "model": model, "prompt": question, "stream": True } ) as response: full_response = "" async for line in response.content: try: chunk = json.loads(line) logger.info(f"Received chunk: {chunk}") if "response" in chunk: full_response += chunk["response"] except json.JSONDecodeError as e: logger.error(f"Failed to parse JSON: {e}") logger.error(f"Raw line: {line}") # Update cooldown timestamp ask_cooldowns[user_id] = current_time embed = discord.Embed( title="Lotus LLM", description=full_response if full_response else "No response received from server", color=discord.Color.from_rgb(152, 0, 0) ) embed.add_field(name="Question", value=question, inline=False) embed.set_footer(text=f"Asked by {interaction.user.display_name}") await interaction.followup.send(embed=embed) except Exception as e: logger.error(f"Error in ask command: {e}", exc_info=True) if not interaction.response.is_done(): await interaction.response.send_message("An error occurred.", ephemeral=True) else: await interaction.followup.send("An error occurred.", ephemeral=True) @client.tree.command(name="8ball", description="Ask the magic 8-ball a question") @app_commands.describe(question="What would you like to ask the 8ball?") async def eight_ball(interaction: discord.Interaction, question: str): try: possible_responses = [ # Positive answers "It is certain", "Without a doubt", "You may rely on it", "Yes definitely", "It is decidedly so", "As I see it, yes", "Most likely", "Yes sir!", "Hell yeah my dude", "100% easily", # Neutral answers "Reply hazy try again", "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again", "Idk bro", # Negative answers "Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful", "Hell no", "Prolly not" ] embed = discord.Embed( color=discord.Color.from_rgb(152, 0, 0), title="🎱 Magic 8-Ball" ) embed.add_field(name="Question", value=question, inline=False) embed.add_field(name="Answer", value=random.choice(possible_responses), inline=False) embed.set_footer(text=f"Asked by {interaction.user.display_name}") await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in 8ball command: {e}", exc_info=True) @client.tree.command(name="fortune", description="Get your fortune cookie message") async def fortune(interaction: discord.Interaction): try: possible_responses = [ # Humorous fortunes "If you eat something & nobody sees you eat it, it has no calories", "Your pet is plotting world domination", "Error 404: Fortune not found. Try again after system reboot", "The fortune you seek is in another cookie", # Classic fortunes with a twist "A journey of a thousand miles begins with ordering delivery", "You will find great fortune... in between your couch cushions", "A true friend is someone who tells you when your stream is muted", # Gaming-themed fortunes "Your next competitive match will be legendary", "The cake is still a lie", "Press Alt+F4 for instant success", "You will not encounter any campers today", "Your tank will have a healer", "No one will steal your pentakill", "Your random teammate will have a mic", "You will find diamonds on your first dig", "The boss will drop the rare loot", "Your speedrun will be WR pace", "No lag spikes in your next match", "Your gaming chair will grant you powers", "The RNG gods will bless you", "You will not get third partied", "Your squad will actually stick together", "The enemy team will forfeit at 15", "Your aim will be crispy today", "You will escape the backrooms", "The imposter will not sus you", "Your Minecraft bed will remain unbroken", "You will get Play of the Game", # Internet culture fortunes "Your next meme will go viral", "Someone is talking about you in their Discord server", "Your FBI agent thinks you're hilarious", "Your next TikTok will hit the FYP, if the government doesn't ban it first", "Someone will actually read your Twitter thread", "Your DMs will be blessed with quality memes today", "Touch grass (respectfully)", "The algorithm will be in your favor today", "Your next Spotify shuffle will hit different", "Someone saved your Instagram post", "Your Reddit comment will get gold", "POV: You're about to go viral", "Main character energy detected", "No cap, you're gonna have a great day fr fr", "Your rizz levels are increasing", "You will not get ratio'd today", "Someone will actually use your custom emoji", "Your next selfie will be iconic", # Original favorites "Buy a dolphin - your life will have a porpoise", "Stop procrastinating - starting tomorrow", "Catch fire with enthusiasm - people will come for miles to watch you burn", # Tech & computer nerd fortunes "Your code will compile on the first try today", "A semicolon will save your day", "The bug you've been hunting is just a typo", "Your next Git commit will be perfect", "You will find the solution on the first StackOverflow link", "Your Docker container will build without errors", "The cloud is just someone else's computer", "Your backup strategy will soon prove its worth", "A mechanical keyboard is in your future", "You will finally understand regex... maybe", "Your CSS will align perfectly on the first try", "Someone will star your GitHub repo today", "Your Linux installation will not break after updates", "You will remember to push your changes before shutdown", "Your code comments will actually make sense in 6 months", "The missing curly brace is on line 247", "Have you tried turning it off and on again?", "Your next pull request will be merged without comments", "Your keyboard RGB will sync perfectly today", "You will find that memory leak", "Your next algorithm will have O(1) complexity", "The force quit was strong with this one", "Ctrl+S will save you today", "Your next Python script will need no debugging", "Your next API call will return 200 OK" ] embed = discord.Embed( color=discord.Color.from_rgb(152, 0, 0), title="🥠 Fortune Cookie" ) embed.add_field(name="Your Fortune", value=random.choice(possible_responses), inline=False) embed.set_footer(text=f"Cracked open by {interaction.user.display_name}") await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in fortune command: {e}", exc_info=True) @client.tree.command(name="flip", description="Flip a coin") async def flip(interaction: discord.Interaction): try: result = random.choice(["Heads", "Tails"]) embed = discord.Embed(title="🪙 Coin Flip", color=discord.Color.from_rgb(152, 0, 0)) embed.add_field(name="Result", value=result) await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in flip command: {e}", exc_info=True) @client.tree.command(name="roll", description="Roll dice (e.g. 2d6)") @app_commands.describe(dice="Format: NdS (N=number of dice, S=sides)") async def roll(interaction: discord.Interaction, dice: str = "1d6"): try: num, sides = map(int, dice.lower().split('d')) results = [random.randint(1, sides) for _ in range(num)] embed = discord.Embed(title="🎲 Dice Roll", color=discord.Color.from_rgb(152, 0, 0)) embed.add_field(name="Results", value=f"Rolls: {results}\nTotal: {sum(results)}") await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in roll command: {e}", exc_info=True) await interaction.response.send_message("Please use format: NdS (example: 2d6)", ephemeral=True) @client.tree.command(name="random", description="Generate a random number") @app_commands.describe(min="Minimum number", max="Maximum number") async def random_number(interaction: discord.Interaction, min: int = 1, max: int = 100): try: result = random.randint(min, max) embed = discord.Embed(title="🎯 Random Number", color=discord.Color.from_rgb(152, 0, 0)) embed.add_field(name="Result", value=str(result)) await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in random command: {e}", exc_info=True) @client.tree.command(name="rps", description="Play Rock Paper Scissors") @app_commands.describe(choice="Your choice: rock, paper, or scissors") async def rps(interaction: discord.Interaction, choice: str): try: choices = ["rock", "paper", "scissors"] bot_choice = random.choice(choices) choice = choice.lower() if choice not in choices: await interaction.response.send_message("Please choose rock, paper, or scissors!", ephemeral=True) return embed = discord.Embed(title="✂️ Rock Paper Scissors", color=discord.Color.from_rgb(152, 0, 0)) embed.add_field(name="Your Choice", value=choice.capitalize()) embed.add_field(name="Bot's Choice", value=bot_choice.capitalize()) if choice == bot_choice: result = "It's a tie!" elif (choice == "rock" and bot_choice == "scissors") or \ (choice == "paper" and bot_choice == "rock") or \ (choice == "scissors" and bot_choice == "paper"): result = "You win!" else: result = "Bot wins!" embed.add_field(name="Result", value=result, inline=False) await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in rps command: {e}", exc_info=True) @client.tree.command(name="poll", description="Create a simple yes/no poll") @app_commands.describe(question="The poll question") async def poll(interaction: discord.Interaction, question: str): try: embed = discord.Embed(title="📊 Poll", description=question, color=discord.Color.from_rgb(152, 0, 0)) embed.set_footer(text=f"Created by {interaction.user.display_name}") await interaction.response.send_message(embed=embed) message = await interaction.original_response() await message.add_reaction("👍") await message.add_reaction("👎") except Exception as e: logger.error(f"Error in poll command: {e}", exc_info=True) @client.tree.command(name="kill", description="Kill another user") @app_commands.describe(member="The user to kill") async def kill(interaction: discord.Interaction, member: discord.Member): try: kills = ["media/kill.gif", "media/kill1.gif", "media/kill2.gif", "media/kill3.gif"] available_kills = [k for k in kills if Path(k).exists()] if not available_kills: logger.warning("No kill media files found") await interaction.response.send_message(f"You killed {member}") else: await interaction.response.send_message( f"You killed {member}", file=discord.File(random.choice(available_kills)) ) except Exception as e: logger.error(f"Error in kill command: {e}", exc_info=True) @client.tree.command(name="punch", description="Punch another user") @app_commands.describe(member="The user to punch") async def punch(interaction: discord.Interaction, member: discord.Member): try: punches = ["media/punch.gif", "media/punch1.gif", "media/punch2.gif", "media/punch3.gif"] available_punches = [p for p in punches if Path(p).exists()] if not available_punches: logger.warning("No punch media files found") await interaction.response.send_message(f"You punched {member}") else: await interaction.response.send_message( f"You punched {member}", file=discord.File(random.choice(available_punches)) ) except Exception as e: logger.error(f"Error in punch command: {e}", exc_info=True) @client.tree.command(name="hug", description="Hug another user") @app_commands.describe(member="The user to hug") async def hug(interaction: discord.Interaction, member: discord.Member): try: hugs = ["media/hug.gif", "media/hug1.gif", "media/hug2.gif", "media/hug3.gif"] available_hugs = [h for h in hugs if Path(h).exists()] if not available_hugs: logger.warning("No hug media files found") await interaction.response.send_message(f"{member} has been squeezed tightly!") else: await interaction.response.send_message( f"{member} has been squeezed tightly!", file=discord.File(random.choice(available_hugs)) ) except Exception as e: logger.error(f"Error in hug command: {e}", exc_info=True) @client.tree.command(name="revive", description="Revive another user") @app_commands.describe(member="The user to revive") async def revive(interaction: discord.Interaction, member: discord.Member): try: revives = ["media/revive.gif", "media/revive1.gif", "media/revive2.gif", "media/revive3.gif"] available_revives = [r for r in revives if Path(r).exists()] if not available_revives: logger.warning("No revive media files found") await interaction.response.send_message(f"{member} has been brought back to life") else: await interaction.response.send_message( f"{member} has been brought back to life", file=discord.File(random.choice(available_revives)) ) except Exception as e: logger.error(f"Error in revive command: {e}", exc_info=True) @client.tree.command(name="agent", description="Get a random Valorant agent") async def agent(interaction: discord.Interaction): try: agents = { "Duelists": ["Jett", "Phoenix", "Raze", "Reyna", "Yoru", "Neon", "Iso"], "Controllers": ["Brimstone", "Viper", "Omen", "Astra", "Harbor"], "Initiators": ["Sova", "Breach", "Skye", "KAY/O", "Fade", "Gekko"], "Sentinels": ["Killjoy", "Cypher", "Sage", "Chamber", "Deadlock"] } # Pick a random category and agent category = random.choice(list(agents.keys())) agent = random.choice(agents[category]) embed = discord.Embed( title="🎯 Valorant Agent Picker", color=discord.Color.from_rgb(152, 0, 0) ) embed.add_field(name="Selected Agent", value=agent, inline=False) embed.add_field(name="Role", value=category, inline=False) embed.set_footer(text=f"Selected for {interaction.user.display_name}") await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in agent command: {e}", exc_info=True) @client.tree.command(name="champion", description="Get a random League of Legends champion") async def champion(interaction: discord.Interaction): try: champions = { "Top": ["Aatrox", "Camille", "Darius", "Fiora", "Garen", "Irelia", "Jax", "K'Sante", "Malphite", "Mordekaiser", "Nasus", "Ornn", "Riven", "Sett", "Teemo"], "Jungle": ["Bel'Veth", "Diana", "Elise", "Evelynn", "Graves", "Hecarim", "Kayn", "Kindred", "Lee Sin", "Master Yi", "Nidalee", "Rammus", "Viego", "Warwick"], "Mid": ["Ahri", "Akali", "Annie", "Cassiopeia", "Fizz", "Katarina", "LeBlanc", "Lux", "Orianna", "Syndra", "Twisted Fate", "Veigar", "Yasuo", "Zed"], "Bot": ["Aphelios", "Ashe", "Caitlyn", "Draven", "Ezreal", "Jhin", "Jinx", "Kai'Sa", "Lucian", "Miss Fortune", "Samira", "Tristana", "Vayne", "Xayah"], "Support": ["Blitzcrank", "Brand", "Janna", "Leona", "Lulu", "Morgana", "Nautilus", "Pyke", "Rakan", "Senna", "Seraphine", "Soraka", "Thresh", "Yuumi"] } # Pick a random lane and champion lane = random.choice(list(champions.keys())) champion = random.choice(champions[lane]) embed = discord.Embed( title="⚔️ League Champion Picker", color=discord.Color.from_rgb(152, 0, 0) ) embed.add_field(name="Selected Champion", value=champion, inline=False) embed.add_field(name="Lane", value=lane, inline=False) embed.set_footer(text=f"Selected for {interaction.user.display_name}") await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in champion command: {e}", exc_info=True) @client.tree.command(name="trivia", description="Play a trivia game") async def trivia(interaction: discord.Interaction): try: class TriviaView(discord.ui.View): def __init__(self, correct_answer): super().__init__() self.correct_answer = correct_answer @discord.ui.button(label="A", style=discord.ButtonStyle.primary) async def answer_a(self, interaction: discord.Interaction, button: discord.ui.Button): if "A" == self.correct_answer: await interaction.response.send_message("Correct!", ephemeral=True) else: await interaction.response.send_message("Wrong!", ephemeral=True) # Example usage embed = discord.Embed(title="Trivia Question", description="What is 2+2?") embed.add_field(name="A", value="4") embed.add_field(name="B", value="5") await interaction.response.send_message(embed=embed, view=TriviaView("A")) except Exception as e: logger.error(f"Error in trivia command: {e}", exc_info=True) @client.tree.command(name="userinfo", description="Get information about a user") @app_commands.describe(member="The user to get information about") async def userinfo(interaction: discord.Interaction, member: discord.Member): try: embed = discord.Embed(title="User Information", color=discord.Color.from_rgb(152, 0, 0)) embed.add_field(name="Joined Server", value=member.joined_at.strftime("%Y-%m-%d")) embed.add_field(name="Account Created", value=member.created_at.strftime("%Y-%m-%d")) embed.add_field(name="Roles", value=", ".join([role.name for role in member.roles[1:]])) await interaction.response.send_message(embed=embed) except Exception as e: logger.error(f"Error in userinfo command: {e}", exc_info=True) @client.event async def on_command_error(ctx, error): """Handle prefix command errors.""" try: if isinstance(error, commands.CommandNotFound): await ctx.send("Command not found!") logger.error(f"Command error: {str(error)}") elif isinstance(error, commands.MissingPermissions): await ctx.send("You don't have permission to use this command!") logger.error(f"Command error: {str(error)}") except Exception as e: logger.error(f"Error in on_command_error: {e}", exc_info=True) @client.tree.error async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError): """Handle slash command errors.""" try: if isinstance(error, app_commands.CheckFailure): await interaction.response.send_message( "You don't have permission to use this command!", ephemeral=True ) logger.warning(f"Permission denied for {interaction.user} on command {interaction.command.name}") elif isinstance(error, app_commands.CommandOnCooldown): await interaction.response.send_message( f"This command is on cooldown. Try again in {error.retry_after:.2f} seconds.", ephemeral=True ) else: await interaction.response.send_message( "An error occurred while executing this command.", ephemeral=True ) logger.error(f"App command error: {str(error)}", exc_info=True) except Exception as e: logger.error(f"Error in on_app_command_error: {e}", exc_info=True) if __name__ == "__main__": token = os.getenv('DISCORD_TOKEN') if not token: logger.error("DISCORD_TOKEN not found in environment variables") exit(1) logger.info("Starting bot...") client.run(token)