added help
This commit is contained in:
@@ -35,7 +35,7 @@ class GuardDen(commands.Bot):
|
||||
super().__init__(
|
||||
command_prefix=self._get_prefix,
|
||||
intents=intents,
|
||||
help_command=commands.DefaultHelpCommand(),
|
||||
help_command=None, # Set by help cog
|
||||
)
|
||||
|
||||
# Services
|
||||
@@ -120,6 +120,7 @@ class GuardDen(commands.Bot):
|
||||
"guardden.cogs.verification",
|
||||
"guardden.cogs.health",
|
||||
"guardden.cogs.wordlist_sync",
|
||||
"guardden.cogs.help",
|
||||
]
|
||||
|
||||
failed_cogs = []
|
||||
|
||||
292
src/guardden/cogs/help.py
Normal file
292
src/guardden/cogs/help.py
Normal file
@@ -0,0 +1,292 @@
|
||||
"""Custom help command for GuardDen."""
|
||||
|
||||
import logging
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from guardden.bot import GuardDen
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuardDenHelpCommand(commands.HelpCommand):
|
||||
"""Custom help command with embed formatting and permission filtering."""
|
||||
|
||||
# Friendly category names with emojis
|
||||
CATEGORY_NAMES = {
|
||||
"Moderation": "🛡️ Moderation",
|
||||
"Admin": "⚙️ Server Configuration",
|
||||
"Automod": "🤖 Automatic Moderation",
|
||||
"AiModeration": "🧠 AI Moderation",
|
||||
"Verification": "✅ Member Verification",
|
||||
"Health": "💊 System Health",
|
||||
"WordlistSync": "📝 Wordlist Sync",
|
||||
}
|
||||
|
||||
# Category descriptions
|
||||
CATEGORY_DESCRIPTIONS = {
|
||||
"Moderation": "Server moderation tools",
|
||||
"Admin": "Bot settings and configuration",
|
||||
"Automod": "Automatic content filtering rules",
|
||||
"AiModeration": "AI-powered content moderation",
|
||||
"Verification": "New member verification system",
|
||||
"Health": "System diagnostics",
|
||||
"WordlistSync": "Wordlist synchronization",
|
||||
}
|
||||
|
||||
def get_command_signature(self, command: commands.Command) -> str:
|
||||
"""Get the command signature showing usage."""
|
||||
parent = command.full_parent_name
|
||||
alias = command.name if not parent else f"{parent} {command.name}"
|
||||
return f"{self.context.clean_prefix}{alias} {command.signature}"
|
||||
|
||||
def get_cog_display_name(self, cog_name: str) -> str:
|
||||
"""Get user-friendly display name for a cog."""
|
||||
return self.CATEGORY_NAMES.get(cog_name, cog_name)
|
||||
|
||||
def get_cog_description(self, cog_name: str) -> str:
|
||||
"""Get description for a cog."""
|
||||
return self.CATEGORY_DESCRIPTIONS.get(cog_name, "Commands")
|
||||
|
||||
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",
|
||||
color=discord.Color.blue(),
|
||||
)
|
||||
|
||||
# Filter and display categories
|
||||
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:
|
||||
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,
|
||||
)
|
||||
|
||||
# Add usage instructions
|
||||
prefix = self.context.clean_prefix
|
||||
embed.add_field(
|
||||
name="Usage",
|
||||
value=f"Use `{prefix}help <category>` for category commands\n"
|
||||
f"Use `{prefix}help <command>` for detailed help",
|
||||
inline=False,
|
||||
)
|
||||
|
||||
embed.set_footer(text=f"Prefix: {prefix} (customizable per server)")
|
||||
|
||||
channel = self.get_destination()
|
||||
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)
|
||||
|
||||
if not filtered:
|
||||
await self.get_destination().send(
|
||||
f"No commands available in this category or you lack permissions to use them."
|
||||
)
|
||||
return
|
||||
|
||||
cog_name = cog.qualified_name
|
||||
display_name = self.get_cog_display_name(cog_name)
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"{display_name} Commands",
|
||||
description=cog.description or "Commands in this category",
|
||||
color=discord.Color.gold() if "Admin" in display_name else discord.Color.blue(),
|
||||
)
|
||||
|
||||
# Group commands by type (regular vs groups)
|
||||
for command in filtered:
|
||||
# Get command signature
|
||||
signature = self.get_command_signature(command)
|
||||
|
||||
# Build description
|
||||
desc_parts = []
|
||||
if command.help:
|
||||
desc_parts.append(command.help.split("\n")[0]) # First line only
|
||||
if command.aliases:
|
||||
desc_parts.append(f"*Aliases: {', '.join(command.aliases)}*")
|
||||
|
||||
description = "\n".join(desc_parts) if desc_parts else "No description available"
|
||||
|
||||
embed.add_field(
|
||||
name=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))
|
||||
|
||||
channel = self.get_destination()
|
||||
await channel.send(embed=embed)
|
||||
|
||||
async def send_group_help(self, group: commands.Group) -> None:
|
||||
"""Send help for a command group."""
|
||||
embed = discord.Embed(
|
||||
title=f"Command Group: {group.qualified_name}",
|
||||
description=group.help or "No description available",
|
||||
color=discord.Color.blurple(),
|
||||
)
|
||||
|
||||
# Add usage
|
||||
signature = self.get_command_signature(group)
|
||||
embed.add_field(
|
||||
name="Usage",
|
||||
value=f"`{signature}`",
|
||||
inline=False,
|
||||
)
|
||||
|
||||
# List subcommands
|
||||
filtered = await self.filter_commands(group.commands, sort=True)
|
||||
if filtered:
|
||||
subcommands_text = []
|
||||
for command in filtered:
|
||||
sig = f"{self.context.clean_prefix}{command.qualified_name} {command.signature}"
|
||||
desc = command.help.split("\n")[0] if command.help else "No description"
|
||||
subcommands_text.append(f"`{sig}`\n{desc}")
|
||||
|
||||
embed.add_field(
|
||||
name="Subcommands",
|
||||
value="\n\n".join(subcommands_text[:10]), # Limit to 10 to avoid embed size limits
|
||||
inline=False,
|
||||
)
|
||||
|
||||
if len(filtered) > 10:
|
||||
embed.add_field(
|
||||
name="More...",
|
||||
value=f"And {len(filtered) - 10} more subcommands",
|
||||
inline=False,
|
||||
)
|
||||
|
||||
# Add aliases
|
||||
if group.aliases:
|
||||
embed.add_field(
|
||||
name="Aliases",
|
||||
value=", ".join(f"`{alias}`" for alias in group.aliases),
|
||||
inline=False,
|
||||
)
|
||||
|
||||
channel = self.get_destination()
|
||||
await channel.send(embed=embed)
|
||||
|
||||
async def send_command_help(self, command: commands.Command) -> None:
|
||||
"""Send help for a specific command."""
|
||||
embed = discord.Embed(
|
||||
title=f"Command: {command.qualified_name}",
|
||||
description=command.help or "No description available",
|
||||
color=discord.Color.green(),
|
||||
)
|
||||
|
||||
# Add usage
|
||||
signature = self.get_command_signature(command)
|
||||
embed.add_field(
|
||||
name="Usage",
|
||||
value=f"`{signature}`",
|
||||
inline=False,
|
||||
)
|
||||
|
||||
# Add aliases
|
||||
if command.aliases:
|
||||
embed.add_field(
|
||||
name="Aliases",
|
||||
value=", ".join(f"`{alias}`" for alias in command.aliases),
|
||||
inline=False,
|
||||
)
|
||||
|
||||
# Add parameter details if available
|
||||
if command.clean_params:
|
||||
params_text = []
|
||||
for param_name, param in command.clean_params.items():
|
||||
# Determine if required or optional
|
||||
if param.default is param.empty:
|
||||
params_text.append(f"`{param_name}` - Required parameter")
|
||||
else:
|
||||
default_val = param.default if param.default is not None else "None"
|
||||
params_text.append(f"`{param_name}` - Optional (default: {default_val})")
|
||||
|
||||
if params_text:
|
||||
embed.add_field(
|
||||
name="Parameters",
|
||||
value="\n".join(params_text),
|
||||
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)")
|
||||
|
||||
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))
|
||||
|
||||
channel = self.get_destination()
|
||||
await channel.send(embed=embed)
|
||||
|
||||
async def send_error_message(self, error: str) -> None:
|
||||
"""Send an error message."""
|
||||
embed = discord.Embed(
|
||||
title="Help Error",
|
||||
description=error,
|
||||
color=discord.Color.red(),
|
||||
)
|
||||
embed.set_footer(text=f"Use {self.context.clean_prefix}help for available commands")
|
||||
|
||||
channel = self.get_destination()
|
||||
await channel.send(embed=embed)
|
||||
|
||||
async def command_not_found(self, string: str) -> str:
|
||||
"""Handle command not found error."""
|
||||
return f"No command or category called `{string}` found."
|
||||
|
||||
async def subcommand_not_found(self, command: commands.Command, string: str) -> str:
|
||||
"""Handle subcommand not found error."""
|
||||
if isinstance(command, commands.Group) and len(command.all_commands) > 0:
|
||||
return f"Command `{command.qualified_name}` has no subcommand named `{string}`."
|
||||
return f"Command `{command.qualified_name}` has no subcommands."
|
||||
|
||||
|
||||
async def setup(bot: GuardDen) -> None:
|
||||
"""Set up the help command."""
|
||||
bot.help_command = GuardDenHelpCommand()
|
||||
logger.info("Custom help command loaded")
|
||||
Reference in New Issue
Block a user