import logging from discord.ext import commands import os import json logger = logging.getLogger(__name__) CONFIG_FILE = 'config.json' REQUIRED_MESSAGES = ['upper_desc', 'footer', 'pfx_message'] class HelpCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot self.help_config = self._load_and_validate_config() self.cogs_dir = os.path.join( os.path.dirname(__file__), self.help_config["settings"]["cogs_dir"] ) def _load_and_validate_config(self) -> dict: path = os.path.join(os.path.dirname(__file__), CONFIG_FILE) if not os.path.exists(path): raise FileNotFoundError(f"Help cog config not found at {path}") with open(path, 'r', encoding='utf-8') as f: config = json.load(f) messages = config.get("messages", {}) missing_msgs = [key for key in REQUIRED_MESSAGES if key not in messages] if missing_msgs: raise KeyError(f"Help config 'messages' is missing required keys: {missing_msgs}") settings = config.get("settings", {}) settings.setdefault("cogs_dir", "..") settings.setdefault("column_width", 20) settings.setdefault("command_pfx", "!") config["messages"] = messages config["settings"] = settings logger.info("Help cog config loaded successfully") return config def _build_help_text(self, prefix: str) -> list[str]: settings = self.help_config["settings"] column_width = settings["column_width"] lines = [] if not os.path.exists(self.cogs_dir): logger.error(f"Cogs directory not found at: {self.cogs_dir}") return lines for item in sorted(os.listdir(self.cogs_dir)): cog_dir = os.path.join(self.cogs_dir, item) if not os.path.isdir(cog_dir): continue config_path = os.path.join(cog_dir, CONFIG_FILE) if not os.path.exists(config_path): continue try: with open(config_path, 'r', encoding='utf-8') as f: cog_config = json.load(f) except (json.JSONDecodeError, OSError) as e: logger.warning(f"Failed to load config for cog '{item}': {e}") continue description = cog_config.get('description', '') usages = cog_config.get('usages', []) hidden = cog_config.get('hidden', False) if not usages or hidden: continue for usage in usages: usage_text = usage if usage.startswith(prefix) else f"{prefix}{usage}" lines.append(f"{usage_text:<{column_width}} : {description}") return lines @commands.command(name="help") async def help(self, ctx: commands.Context): cfg_msgs = self.help_config["messages"] cfg_sets = self.help_config["settings"] prefix = ctx.prefix or cfg_sets['command_pfx'] lines = self._build_help_text(prefix) if not lines: await ctx.send("No commands available.") return help_text = ( f"{cfg_msgs['upper_desc']}\n" f"```\n" f"{chr(10).join(lines)}\n" f"```\n" f"{cfg_msgs['pfx_message']} `{cfg_sets['command_pfx']}`\n" f"{cfg_msgs['footer']}" ) await ctx.send(help_text) async def setup(bot: commands.Bot): await bot.add_cog(HelpCog(bot))