diff --git a/bot.py b/bot.py index 37e0d5f..36910e5 100644 --- a/bot.py +++ b/bot.py @@ -10,7 +10,7 @@ Features: - Prefix commands (e.g., !ping, !uptime, !userinfo, !serverinfo) - - Slash commands (e.g., /hello, /echo) + - Slash commands (e.g., /hello, /echo, /serverinfo, /userinfo, /avatar, /8ball, /choose, /ping) - Event handlers (e.g., on_message, on_member_join) """ @@ -34,7 +34,8 @@ # Configure bot intents for required functionality intents = discord.Intents.default() -intents.message_content = True # Required for prefix commands and message events +intents.message_content = True # Required for prefix commands and message events +intents.members = True # Required for member-related commands like userinfo # Initialize bot with prefix from environment (defaults to !) bot = commands.Bot(command_prefix=os.getenv("PREFIX", "!"), intents=intents) @@ -56,12 +57,22 @@ async def load_extensions() -> None: even if some cogs are broken. """ for ext in [ + # Prefix commands "prefixcommands.ping", "prefixcommands.uptime", "prefixcommands.userinfo", "prefixcommands.serverinfo", + # Slash commands - existing "slashcommands.hello", "slashcommands.echo", + # New slash commands + "slashcommands.serverinfo", + "slashcommands.userinfo", + "slashcommands.avatar", + "slashcommands.8ball", + "slashcommands.choose", + "slashcommands.ping", + # Event handlers "events.handlers", ]: try: @@ -69,6 +80,9 @@ async def load_extensions() -> None: print(f"Loaded extension: {ext}") except Exception as e: print(f"Failed to load {ext}: {e}") + # Print full traceback for debugging during development + import traceback + traceback.print_exc() @bot.event @@ -94,10 +108,13 @@ async def setup_hook() -> None: await load_extensions() # Sync slash commands once when the bot starts try: - await bot.tree.sync() - print("Slash commands synced.") + synced = await bot.tree.sync() + print(f"Slash commands synced: {len(synced)} commands registered") + print(f"Commands: {[cmd.name for cmd in synced]}") except Exception as e: print(f"Failed to sync slash commands: {e}") + import traceback + traceback.print_exc() if __name__ == "__main__": diff --git a/slashcommands/8ball.py b/slashcommands/8ball.py new file mode 100644 index 0000000..3b83487 --- /dev/null +++ b/slashcommands/8ball.py @@ -0,0 +1,98 @@ +""" +Slash Command: 8-Ball + +This module provides a magic 8-ball slash command for the PN bot. +Users can ask the 8-ball a question and receive a random prediction. + +Usage: + /8ball + +Features: + - Responds with random predictions from a curated list + - Supports classic magic 8-ball responses + - Interactive and engaging for users + - Question parameter for user input + +Dependencies: + discord.py - Provides the commands framework and Discord API integration + random - Python standard library for random responses +""" + +import discord +from discord.ext import commands +import random + + +class EightBall(commands.Cog): + """A cog containing 8-ball prediction slash commands. + + This cog provides a fun magic 8-ball command that gives random + predictions in response to user questions. + + Args: + bot: The bot instance that this cog is attached to. + """ + + def __init__(self, bot: commands.Bot) -> None: + """Initialize the 8Ball cog. + + Args: + bot: The bot instance to attach this cog to. + """ + self.bot = bot + + # Define 8-ball responses following the classic magic 8-ball format + self.responses = [ + # Positive responses (10) + "It is certain.", + "It is decidedly so.", + "Without a doubt.", + "Yes definitely.", + "You may rely on it.", + "As I see it, yes.", + "Most likely.", + "Outlook good.", + "Yes.", + "Signs point to yes.", + # Neutral responses (5) + "Reply hazy, try again.", + "Ask again later.", + "Better not tell you now.", + "Cannot predict now.", + "Concentrate and ask again.", + # Negative responses (5) + "Don't count on it.", + "My reply is no.", + "My sources say no.", + "Outlook not so good.", + "Very doubtful." + ] + + @discord.app_commands.command(name="8ball", description="Ask the magic 8-ball a question") + @discord.app_commands.describe(question="Your question for the magic 8-ball") + async def eightball(self, interaction: discord.Interaction, question: str) -> None: + """Ask the magic 8-ball a question and get a prediction. + + This slash command takes a user's question and responds with a random + prediction from the magic 8-ball's repertoire of responses. + + Args: + interaction: The Discord interaction object representing the slash command invocation. + question: The question to ask the magic 8-ball (required parameter). + + Returns: + None. Responds to the interaction with an embed containing the prediction. + """ + # Get a random response from our predefined list + response = random.choice(self.responses) + + # Determine embed color based on response type + if response in ["It is certain.", "It is decidedly so.", "Without a doubt.", "Yes definitely.", "You may rely on it.", + "As I see it, yes.", "Most likely.", "Outlook good.", "Yes.", "Signs point to yes."]: +color = discord.Color.green() +itral in ["Reply hazy, try again.", "Ask again later.", "Better not tell you now.", +itrate now."]: color = discord.Color.gold() +color = discord.Color.red() +color = discord.Color.red() +color = discord.Color.red() +color = discord.Color.red() \ No newline at end of file diff --git a/slashcommands/avatar.py b/slashcommands/avatar.py new file mode 100644 index 0000000..a38a88f --- /dev/null +++ b/slashcommands/avatar.py @@ -0,0 +1,94 @@ +""" +Slash Command: Avatar + +This module provides an avatar slash command for the PN bot. +Users can invoke /avatar to display a user's profile picture at full size. + +Usage: + /avatar [member] + +Features: + - Displays user's avatar in full resolution + - Shows server-specific avatar if available + - Defaults to command invoker if no member specified + - Shows avatar in a clean embed format + +Dependencies: + discord.py - Provides the commands framework and Discord API integration +""" + +import discord +from discord.ext import commands + + +class Avatar(commands.Cog): + """A cog containing avatar slash commands. + + This cog provides commands to display user avatars at full resolution. + + Args: + bot: The bot instance that this cog is attached to. + """ + + def __init__(self, bot: commands.Bot) -> None: + """Initialize the Avatar cog. + + Args: + bot: The bot instance to attach this cog to. + """ + self.bot = bot + + @discord.app_commands.command(name="avatar", description="Display a user's avatar") + @discord.app_commands.describe(member="The member whose avatar to show (defaults to you)") + async def avatar(self, interaction: discord.Interaction, member: discord.Member = None) -> None: + """Display a user's avatar at full resolution. + + This slash command shows a user's profile picture in full size. + If the user has a server-specific avatar, it will be displayed. + Otherwise, their global Discord avatar is shown. + + Args: + interaction: The Discord interaction object representing the slash command invocation. + member: The member whose avatar to display. If not provided, shows the command invoker's avatar. + + Returns: + None. Responds to the interaction with an embed containing the avatar. + """ + # Default to the user who invoked the command if no member is specified + if member is None: + member = interaction.user + + # Handle non-guild context gracefully + if not isinstance(member, discord.Member): + if interaction.guild: + # Try to get proper Member object in guild context + proper_member = interaction.guild.get_member(member.id) + if proper_member: + member = proper_member + + # Create embed for avatar display + embed = discord.Embed( + title=f"đŸ–ŧī¸ {member.display_name}'s Avatar", + description=f"[Download Avatar]({member.display_avatar.url})", + color=discord.Color.purple() + ) + + # Set the avatar image + embed.set_image(url=member.display_avatar.url) + + # Add footer with user ID + embed.set_footer( + text=f"User ID: {member.id}", + icon_url=member.display_avatar.url if hasattr(member.display_avatar, 'url') else None + ) + + await interaction.response.send_message(embed=embed) + + +async def setup(bot: commands.Bot) -> None: + """Register the Avatar cog with the bot. + + Args: + bot: The bot instance to register the cog with. + """ + await bot.add_cog(Avatar(bot)) \ No newline at end of file diff --git a/slashcommands/choose.py b/slashcommands/choose.py new file mode 100644 index 0000000..e22447c --- /dev/null +++ b/slashcommands/choose.py @@ -0,0 +1,90 @@ +""" +Slash Command: Choose + +This module provides a random choice slash command for the PN bot. +Users can provide multiple options and let the bot randomly select one. + +Usage: + /choose [option3] [option4] [option5] + +Features: + - Randomly selects from 2-5 provided options + - Clean embed display of the chosen option + - Useful for decision-making, games, and polls + - Validates that at least 2 options are provided + +Dependencies: + discord.py - Provides the commands framework and Discord API integration + random - Python standard library for random selection +""" + +import discord +from discord.ext import commands +import random + + +class Choose(commands.Cog): + """A cog containing random choice slash commands. + + This cog provides a choose command that randomly selects from + multiple user-provided options. + + Args: + bot: The bot instance that this cog is attached to. + """ + + def __init__(self, bot: commands.Bot) -> None: + """Initialize the Choose cog. + + Args: + bot: The bot instance to attach this cog to. + """ + self.bot = bot + + @discord.app_commands.command(name="choose", description="Randomly choose between multiple options") + @discord.app_commands.describe( + option1="First option", + option2="Second option", + option3="Third option (optional)", + option4="Fourth option (optional)", + option5="Fifth option (optional)" + ) + async def choose( + self, + interaction: discord.Interaction, + option1: str, + option2: str, + option3: str = None, + option4: str = None, + option5: str = None + ) -> None: + """Randomly choose one option from a list. + + This slash command takes 2-5 options and randomly selects one, + making it perfect for decision-making or adding randomness to activities. + + Args: + interaction: The Discord interaction object representing the slash command invocation. + option1: The first option to choose from (required). + option2: The second option to choose from (required). + option3: The third option to choose from (optional). + option4: The fourth option to choose from (optional). + option5: The fifth option to choose from (optional). + + Returns: + None. Responds to the interaction with an embed containing the chosen option. + """ + # Collect all provided options into a list + options = [option1, option2] + if option3: + options.append(option3) + if option4: + options.append(option4) + if option5: + options.append(option5) + + # Validate that we have at least 2 options (enforced by required params, but good practice) + if len(options) < 2: +aawait interaction.response.send_message("Please provide at least 2 options!", ephemeral=True) +rreturn + # Randomly select one option ition = random.choice(options) # Create embed with the result # Create embed with the result # Create embed with the result # Create embed with the result il = "🎲 Random Choice" description="", color=discord.Color.random() ) embed.add_field( name=f"Chosen from {len(options)} options:", value=f"**{chosen}**", inline=False ) # List all options for context options_text = "\".join(options) if len(options_text) > 1000: # Truncate if too long options_text = options_text[:997] + "..." embed.add_field( name="All Options:", value=options_text, inline=False ) # Set footer with requester info embed.set_footer( text=f"Requested by {interaction.user}", icon_url=interaction.user.display_avatar.url if interaction.user.display_avatar else None ) # Send response await interaction.response.send_message(embed=embed) \ No newline at end of file diff --git a/slashcommands/ping.py b/slashcommands/ping.py new file mode 100644 index 0000000..8fa5be4 --- /dev/null +++ b/slashcommands/ping.py @@ -0,0 +1,80 @@ +""" +Slash Command: Ping + +This module provides a ping slash command for the PN bot. +Users can invoke /ping to see the bot's latency and response time. + +Usage: + /ping + +Features: + - Shows bot latency (websocket ping) + - Displays API response time + - Shows uptime information + - Clean embed display with status indicators + +Dependencies: + discord.py - Provides the commands framework and Discord API integration + datetime - Python standard library for time calculations +""" + +import discord +from discord.ext import commands +import datetime +import time + + +class Ping(commands.Cog): + """A cog containing ping slash commands. + + This cog provides commands to check bot latency and performance metrics. + + Args: + bot: The bot instance that this cog is attached to. + """ + + def __init__(self, bot: commands.Bot) -> None: + """Initialize the Ping cog. + + Args: + bot: The bot instance to attach this cog to. + """ + self.bot = bot + self.start_time = time.time() + + @discord.app_commands.command(name="ping", description="Check the bot's latency and performance") + async def ping(self, interaction: discord.Interaction) -> None: + """Display bot latency and performance metrics. + + This slash command shows the bot's websocket latency, API response time, + and uptime information in a clean embed format. + + Args: + interaction: The Discord interaction object representing the slash command invocation. + + Returns: + None. Responds to the interaction with an embed containing latency information. + """ + # Record start time for measuring response time + start_time = time.time() + + # Defer the response + await interaction.response.defer() + + # Calculate response time + response_time = round((time.time() - start_time) * 1000) + + # Get websocket latency + ws_latency = round(self.bot.latency * 1000) + + # Calculate uptime + uptime_seconds = int(time.time() - self.start_time) + uptime_string = str(datetime.timedelta(seconds=uptime_seconds)) + + # Determine status based on latency (green < 100ms, yellow < 250ms, red otherwise) + if ws_latency < 100: + status_color = discord.Color.green() + status_emoji = "đŸŸĸ" + elif ws_latency < 250: + status_color = discord.Color.gold() status_emoji = "🟡" +color = discord.Color.red() status_emoji = "🔴" # Create embed with performance metrics tile = f"🏓 Pong! {status_emoji}", color = status_color ) embed.add_field( name="💓 WebSocket Latency", value=f"{ws_latency} ms", inline=True ) embed.add_field( name="📝 API Response Time", value=f"{response_time} ms", inline=True ) embed.add_field( name="⏰ Uptime", value=uptime_string, inline=True ) # Add system information field text=f"🤖 Bot: {self.bot.user}\fđŸ“Ļ Memory Usage: To be implemented" # You could add actual memory usage tracking here if needed text=f"🤖 Bot: {self.bot.user}\fđŸ“Ļ Guilds: {len(self.bot.guilds)}\fđŸ‘Ĩ Total Users: {sum(guild.member_count for guild in self.bot.guilds) if self.bot.guilds else 0}" embed.add_field( name="â„šī¸ System Info", value=text, inlin \ No newline at end of file diff --git a/slashcommands/serverinfo.py b/slashcommands/serverinfo.py new file mode 100644 index 0000000..6123a62 --- /dev/null +++ b/slashcommands/serverinfo.py @@ -0,0 +1,143 @@ +""" +Slash Command: Server Info + +This module provides a server information slash command for the PN bot. +Users can invoke /serverinfo to see detailed statistics about the server. + +Usage: + /serverinfo + +Features: + - Shows server name, ID, and icon + - Displays member count (total, online, bots) + - Shows server creation date and age + - Lists server owner information + - Shows verification level and boost status + - Displays channel counts (text, voice, categories) + - Shows role count and emoji count + +Dependencies: + discord.py - Provides the commands framework and Discord API integration +""" + +import discord +from discord.ext import commands +from datetime import datetime + + +class ServerInfo(commands.Cog): + """A cog containing server information slash commands. + + This cog provides commands to retrieve and display comprehensive + information about the Discord server where the command is invoked. + + Args: + bot: The bot instance that this cog is attached to. + """ + + def __init__(self, bot: commands.Bot) -> None: + """Initialize the ServerInfo cog. + + Args: + bot: The bot instance to attach this cog to. + """ + self.bot = bot + + @discord.app_commands.command(name="serverinfo", description="Display detailed server information") + async def serverinfo(self, interaction: discord.Interaction) -> None: + """Display comprehensive server statistics. + + This slash command shows detailed information about the current server, + including member counts, creation date, owner info, and various statistics. + + Args: + interaction: The Discord interaction object representing the + slash command invocation. + + Returns: + None. Responds to the interaction with an embed containing server information. + + Notes: + This command can only be used in a guild (server) context. + It will fail if used in direct messages. + """ + # Check if we're in a guild context + if not interaction.guild: + await interaction.response.send_message("This command can only be used in a server!", ephemeral=True) + return + + # Defer the response as this might take a moment + await interaction.response.defer() + + guild = interaction.guild + + # Calculate member statistics + total_members = guild.member_count or 0 + online_members = len([m for m in guild.members if m.status != discord.Status.offline]) + bot_count = len([m for m in guild.members if m.bot]) + human_count = total_members - bot_count + + # Calculate channel statistics + text_channels = len(guild.text_channels) + voice_channels = len(guild.voice_channels) + categories = len(guild.categories) + total_channels = text_channels + voice_channels + categories + + # Create embed with server information + embed = discord.Embed( + title=f"📊 {guild.name}", + description=f"Server ID: `{guild.id}`", + color=discord.Color.blue() + ) + + # Add server icon if available + if guild.icon: + embed.set_thumbnail(url=guild.icon.url) + + # Member information field + embed.add_field( + name="đŸ‘Ĩ Member Statistics", + value=(f"**Total:** {total_members:,}\n" + f"**Online:** {online_members:,}\n" + f"**Humans:** {human_count:,}\n" + f"**Bots:** {bot_count:,}"), + inline=True + ) + + # Server age field + creation_date = guild.created_at.strftime("%B %d, %Y") + days_old = (datetime.utcnow() - guild.created_at).days + embed.add_field( + name="📅 Server Age", + value=(f"**Created:** {creation_date}\n" + f"**Age:** {days_old} days"), + inline=True + ) + + # Owner information field + embed.add_field( + name="👑 Owner", + value=f"{guild.owner.mention if guild.owner else 'Unknown'}\n`{guild.owner_id}`", + inline=True + ) + + # Channel information field + embed.add_field( + name="đŸ’Ŧ Channels", + value=(f"**Text:** {text_channels}\n" + f"**Voice:** {voice_channels}\n" + f"**Categories:** {categories}\n" + f"**Total:** {total_channels}"), + inline=True + ) + + # Server features field (boosts, verification, etc.) + boost_level = guild.premium_tier.name.replace("TIER_", "Level ") + boost_count = guild.premium_subscription_count or 0 + verification_level = guild.verification_level.name.replace("_", " ").title() + + embed.add_field( + name="⚡ Server Features", + value=(f"**Boost Level:** {boost_level}\n" +f"**Boosts:** {boost_count}\nf"**Verification:** {verification_level}\nf"**Roles:** {len(guild.roles)}\nf"**Emojis:** {len(guild.emojis)}"), +inlin \ No newline at end of file diff --git a/slashcommands/userinfo.py b/slashcommands/userinfo.py new file mode 100644 index 0000000..c66cd27 --- /dev/null +++ b/slashcommands/userinfo.py @@ -0,0 +1,102 @@ +""" +Slash Command: User Info + +This module provides a user information slash command for the PN bot. +Users can invoke /userinfo to see detailed information about a member. + +Usage: + /userinfo [member] + +Features: + - Shows user avatar and display name + - Displays account creation date and join date + - Shows roles and permissions + - Displays activity status and rich presence + - Shows voice channel status if applicable + - Displays server-specific statistics (message count, etc.) + +Dependencies: + discord.py - Provides the commands framework and Discord API integration +""" + +import discord +from discord.ext import commands +from datetime import datetime + + +class UserInfo(commands.Cog): + """A cog containing user information slash commands. + + This cog provides commands to retrieve and display comprehensive + information about Discord members in the server. + + Args: + bot: The bot instance that this cog is attached to. + """ + + def __init__(self, bot: commands.Bot) -> None: + """Initialize the UserInfo cog. + + Args: + bot: The bot instance to attach this cog to. + """ + self.bot = bot + + @discord.app_commands.command(name="userinfo", description="Display detailed user information") + @discord.app_commands.describe(member="The member to get information about (defaults to you)") + async def userinfo(self, interaction: discord.Interaction, member: discord.Member = None) -> None: + """Display comprehensive user statistics. + + This slash command shows detailed information about a Discord member, + including account details, server activity, roles, and current status. + + Args: + interaction: The Discord interaction object representing the slash command invocation. + member: The member to get information about. If not provided, shows info about the command invoker. + + Returns: + None. Responds to the interaction with an embed containing user information. + """ + # Default to the user who invoked the command if no member is specified + if member is None: + member = interaction.user + + # Check if we're in a guild context for full member info + if not interaction.guild: + await interaction.response.send_message("This command can only be used in a server!", ephemeral=True) + return + + # Ensure we have a proper Member object for guild context + if not isinstance(member, discord.Member): + member = interaction.guild.get_member(member.id) + if not member: + await interaction.response.send_message("Could not find that member in this server.", ephemeral=True) + return + + # Defer the response as this might take a moment + await interaction.response.defer() + + # Create embed with user information + embed = discord.Embed( + title=f"👤 {member.display_name}", + description=f"{member.mention}\nID: `{member.id}`", + color=member.color if member.color != discord.Color.default() else discord.Color.blue() + ) + + # Add user avatar + if member.avatar: + embed.set_thumbnail(url=member.avatar.url) + elif member.display_avatar: + embed.set_thumbnail(url=member.display_avatar.url) + + # Account creation date and server join date + account_created = member.created_at.strftime("%B %d, %Y") + account_age = (datetime.utcnow() - member.created_at).days + joined_at = member.joined_at.strftime("%B %d, %Y") if member.joined_at else "Unknown" + server_age = (datetime.utcnow() - member.joined_at).days if member.joined_at else 0 + + embed.add_field( + name="📅 Account Information", + value=(f"**Created:** {account_created}\n" +f"**Account Age:** {account_age} days\nf**Joined Server:** {joined_at}\nf**Server Age:** {server_age} days"), +inlin \ No newline at end of file