added help

This commit is contained in:
2026-01-25 17:15:17 +01:00
parent f50b7fc5f0
commit 9c59413cfa
2 changed files with 294 additions and 1 deletions

View File

@@ -35,7 +35,7 @@ class GuardDen(commands.Bot):
super().__init__( super().__init__(
command_prefix=self._get_prefix, command_prefix=self._get_prefix,
intents=intents, intents=intents,
help_command=commands.DefaultHelpCommand(), help_command=None, # Set by help cog
) )
# Services # Services
@@ -120,6 +120,7 @@ class GuardDen(commands.Bot):
"guardden.cogs.verification", "guardden.cogs.verification",
"guardden.cogs.health", "guardden.cogs.health",
"guardden.cogs.wordlist_sync", "guardden.cogs.wordlist_sync",
"guardden.cogs.help",
] ]
failed_cogs = [] failed_cogs = []

292
src/guardden/cogs/help.py Normal file
View 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")