added help
This commit is contained in:
@@ -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
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