diff --git a/src/guardden/cogs/help.py b/src/guardden/cogs/help.py index 9841490..dad1143 100644 --- a/src/guardden/cogs/help.py +++ b/src/guardden/cogs/help.py @@ -49,57 +49,137 @@ class GuardDenHelpCommand(commands.HelpCommand): """Get description for a cog.""" return self.CATEGORY_DESCRIPTIONS.get(cog_name, "Commands") + def _get_permission_info(self, command: commands.Command) -> tuple[str, discord.Color]: + """Get permission requirement text and color for a command.""" + # Check cog-level restrictions + if command.cog: + cog_name = command.cog.qualified_name + if cog_name == "Admin": + return "🔒 Admin Only", discord.Color.red() + elif cog_name == "Moderation": + return "🛡️ Moderator/Owner", discord.Color.orange() + elif cog_name == "WordlistSync": + return "🔒 Admin Only", discord.Color.red() + + # Check command-level checks + if hasattr(command.callback, "__commands_checks__"): + checks = command.callback.__commands_checks__ + for check in checks: + check_name = getattr(check, "__name__", "") + if "is_owner" in check_name: + return "👑 Bot Owner Only", discord.Color.dark_red() + elif "has_permissions" in check_name or "administrator" in check_name: + return "🔒 Admin Only", discord.Color.red() + + return "👥 Everyone", discord.Color.green() + async def send_bot_help(self, mapping: dict) -> None: - """Send the main help menu showing all categories.""" - embed = discord.Embed( - title="GuardDen Help Menu", - description="A comprehensive Discord moderation bot", + """Send the main help menu showing all commands with detailed information.""" + embeds = [] + prefix = self.context.clean_prefix + + # Create overview embed + overview = discord.Embed( + title="📚 GuardDen Help - All Commands", + description=f"A comprehensive Discord moderation bot\n\n" + f"**Legend:**\n" + f"👥 Everyone can use | 🛡️ Moderators/Owners | 🔒 Admins | 👑 Bot Owner", color=discord.Color.blue(), ) + overview.set_footer(text=f"Prefix: {prefix} (customizable per server)") + embeds.append(overview) - # Filter and display categories + # Collect all commands organized by category for cog, cog_commands in mapping.items(): if cog is None: continue - # Filter commands the user can actually run - filtered = await self.filter_commands(cog_commands, sort=True) - if not filtered: + # Get all commands (don't filter by permissions for full overview) + all_commands = sorted(cog_commands, key=lambda c: c.qualified_name) + if not all_commands: continue cog_name = cog.qualified_name display_name = self.get_cog_display_name(cog_name) - description = self.get_cog_description(cog_name) - embed.add_field( - name=display_name, - value=description, - inline=False, + # Create embed for this category + embed = discord.Embed( + title=display_name, + description=self.get_cog_description(cog_name), + color=discord.Color.gold() if "Admin" in display_name else discord.Color.blue(), ) - # Add usage instructions - prefix = self.context.clean_prefix - embed.add_field( - name="Usage", - value=f"Use `{prefix}help ` for category commands\n" - f"Use `{prefix}help ` for detailed help", - inline=False, - ) + # Add each command with full details + for command in all_commands: + perm_text, _ = self._get_permission_info(command) - embed.set_footer(text=f"Prefix: {prefix} (customizable per server)") + # Build command signature with all parameters + signature_parts = [command.name] + if command.signature: + signature_parts.append(command.signature) + full_signature = f"{prefix}{' '.join(signature_parts)}" + + # Build description + desc_parts = [] + + # Add help text + if command.help: + desc_parts.append(command.help.split("\n")[0]) + else: + desc_parts.append("No description available") + + # Add aliases if present + if command.aliases: + desc_parts.append(f"*Aliases: {', '.join(command.aliases)}*") + + # Add permission requirement + desc_parts.append(f"**Permission:** {perm_text}") + + # Add parameter details if present + if command.clean_params: + param_details = [] + for param_name, param in command.clean_params.items(): + if param.default is param.empty: + param_details.append(f"`{param_name}` (required)") + else: + default_val = param.default if param.default is not None else "None" + param_details.append(f"`{param_name}` (default: {default_val})") + + if param_details: + desc_parts.append(f"**Options:** {', '.join(param_details)}") + + # Handle subcommands for groups + if isinstance(command, commands.Group): + subcommands = list(command.commands) + if subcommands: + subcommand_names = ", ".join( + f"`{cmd.name}`" for cmd in sorted(subcommands, key=lambda c: c.name) + ) + desc_parts.append(f"**Subcommands:** {subcommand_names}") + + description = "\n".join(desc_parts) + + embed.add_field( + name=f"`{full_signature}`", + value=description, + inline=False, + ) + + embeds.append(embed) + + # Send all embeds channel = self.get_destination() - await channel.send(embed=embed) + for embed in embeds: + await channel.send(embed=embed) async def send_cog_help(self, cog: commands.Cog) -> None: """Send help for a specific category/cog.""" - # Filter commands the user can run - filtered = await self.filter_commands(cog.get_commands(), sort=True) + # Get all commands (show all, not just what user can run) + all_commands = sorted(cog.get_commands(), key=lambda c: c.qualified_name) - if not filtered: - await self.get_destination().send( - f"No commands available in this category or you lack permissions to use them." - ) + if not all_commands: + await self.get_destination().send(f"No commands available in this category.") return cog_name = cog.qualified_name @@ -107,12 +187,16 @@ class GuardDenHelpCommand(commands.HelpCommand): embed = discord.Embed( title=f"{display_name} Commands", - description=cog.description or "Commands in this category", + description=f"{cog.description or 'Commands in this category'}\n\n" + f"**Legend:** 👥 Everyone | 🛡️ Moderators/Owners | 🔒 Admins | 👑 Bot Owner", color=discord.Color.gold() if "Admin" in display_name else discord.Color.blue(), ) - # Group commands by type (regular vs groups) - for command in filtered: + # Show each command with full details + for command in all_commands: + # Get permission info + perm_text, _ = self._get_permission_info(command) + # Get command signature signature = self.get_command_signature(command) @@ -120,30 +204,34 @@ class GuardDenHelpCommand(commands.HelpCommand): desc_parts = [] if command.help: desc_parts.append(command.help.split("\n")[0]) # First line only + else: + desc_parts.append("No description available") + if command.aliases: desc_parts.append(f"*Aliases: {', '.join(command.aliases)}*") - description = "\n".join(desc_parts) if desc_parts else "No description available" + # Add permission info + desc_parts.append(f"**Permission:** {perm_text}") + + # Add parameter info + if command.clean_params: + param_count = len(command.clean_params) + required_count = sum( + 1 for p in command.clean_params.values() if p.default is p.empty + ) + desc_parts.append( + f"**Parameters:** {required_count} required, {param_count - required_count} optional" + ) + + description = "\n".join(desc_parts) embed.add_field( - name=signature, + name=f"`{signature}`", value=description, inline=False, ) - # Add permission requirements if applicable - if hasattr(cog, "cog_check"): - # Try to determine permissions - footer_parts = [] - - # Check common permission patterns - if "Admin" in display_name or "Config" in display_name: - footer_parts.append("Requires: Administrator permission") - elif "Moderation" in display_name: - footer_parts.append("Requires: Kick Members or Ban Members permission") - - if footer_parts: - embed.set_footer(text=" | ".join(footer_parts)) + embed.set_footer(text=f"Use {self.context.clean_prefix}help for detailed info") channel = self.get_destination() await channel.send(embed=embed) @@ -199,10 +287,12 @@ class GuardDenHelpCommand(commands.HelpCommand): async def send_command_help(self, command: commands.Command) -> None: """Send help for a specific command.""" + perm_text, perm_color = self._get_permission_info(command) + embed = discord.Embed( title=f"Command: {command.qualified_name}", description=command.help or "No description available", - color=discord.Color.green(), + color=perm_color, ) # Add usage @@ -213,6 +303,13 @@ class GuardDenHelpCommand(commands.HelpCommand): inline=False, ) + # Add permission requirement prominently + embed.add_field( + name="Permission Required", + value=perm_text, + inline=False, + ) + # Add aliases if command.aliases: embed.add_field( @@ -225,12 +322,20 @@ class GuardDenHelpCommand(commands.HelpCommand): if command.clean_params: params_text = [] for param_name, param in command.clean_params.items(): + # Get parameter annotation for type hint + param_type = "" + if param.annotation is not param.empty: + type_name = getattr(param.annotation, "__name__", str(param.annotation)) + param_type = f" ({type_name})" + # Determine if required or optional if param.default is param.empty: - params_text.append(f"`{param_name}` - Required parameter") + params_text.append(f"`{param_name}`{param_type} - **Required**") else: default_val = param.default if param.default is not None else "None" - params_text.append(f"`{param_name}` - Optional (default: {default_val})") + params_text.append( + f"`{param_name}`{param_type} - Optional (default: `{default_val}`)" + ) if params_text: embed.add_field( @@ -239,26 +344,10 @@ class GuardDenHelpCommand(commands.HelpCommand): inline=False, ) - # Add permission requirements - footer_parts = [] - if hasattr(command.callback, "__commands_checks__"): - # Try to extract permission requirements - checks = command.callback.__commands_checks__ - for check in checks: - check_name = getattr(check, "__name__", "") - if "has_permissions" in check_name: - footer_parts.append("Requires specific permissions") - elif "is_owner" in check_name: - footer_parts.append("Requires bot owner") - elif "guild_only" in check_name: - footer_parts.append("Guild only (no DMs)") - + # Add category info if command.cog: cog_name = command.cog.qualified_name - footer_parts.append(f"Category: {self.get_cog_display_name(cog_name)}") - - if footer_parts: - embed.set_footer(text=" | ".join(footer_parts)) + embed.set_footer(text=f"Category: {self.get_cog_display_name(cog_name)}") channel = self.get_destination() await channel.send(embed=embed)