"""Core plugin: ping, help, version, plugin management.""" from collections import Counter from derp import __version__ from derp.plugin import command @command("ping", help="Check if the bot is alive") async def cmd_ping(bot, message): """Respond with pong.""" await bot.reply(message, "pong") @command("help", help="List commands or show command/plugin help") async def cmd_help(bot, message): """Show available commands, or help for a specific command or plugin. Usage: !help [command|plugin] """ channel = message.target if message.is_channel else None parts = message.text.split(None, 2) if len(parts) > 1: name = parts[1].lower().lstrip(bot.prefix) # Check command first handler = bot.registry.commands.get(name) if handler and bot._plugin_allowed(handler.plugin, channel): help_text = handler.help or "No help available." await bot.reply(message, f"{bot.prefix}{name} -- {help_text}") return # Check plugin module = bot.registry._modules.get(name) if module and bot._plugin_allowed(name, channel): desc = (getattr(module, "__doc__", "") or "").split("\n")[0].strip() cmds = sorted( k for k, v in bot.registry.commands.items() if v.plugin == name and bot._plugin_allowed(v.plugin, channel) ) lines = [f"{name} -- {desc}" if desc else name] if cmds: lines.append(f"Commands: {', '.join(bot.prefix + c for c in cmds)}") await bot.reply(message, " | ".join(lines)) return await bot.reply(message, f"Unknown command or plugin: {name}") return # List all commands visible in this channel names = sorted( k for k, v in bot.registry.commands.items() if bot._plugin_allowed(v.plugin, channel) ) await bot.reply(message, ", ".join(names)) @command("version", help="Show bot version") async def cmd_version(bot, message): """Report the running version.""" await bot.reply(message, f"derp {__version__} ({bot.name})") @command("uptime", help="Show how long the bot has been running") async def cmd_uptime(bot, message): """Report bot uptime.""" import time elapsed = int(time.monotonic() - bot._started) days, rem = divmod(elapsed, 86400) hours, rem = divmod(rem, 3600) minutes, secs = divmod(rem, 60) parts = [] if days: parts.append(f"{days}d") if hours: parts.append(f"{hours}h") if minutes: parts.append(f"{minutes}m") parts.append(f"{secs}s") await bot.reply(message, f"up {' '.join(parts)}") @command("load", help="Hot-load a plugin: !load ", admin=True) async def cmd_load(bot, message): """Load a new plugin from the plugins directory.""" parts = message.text.split(None, 2) if len(parts) < 2: await bot.reply(message, "Usage: !load ") return name = parts[1].lower() ok, reason = bot.load_plugin(name) if ok: await bot.reply(message, f"Loaded plugin: {name} ({reason})") else: await bot.reply(message, f"Failed to load plugin: {reason}") @command("reload", help="Reload a plugin: !reload ", admin=True) async def cmd_reload(bot, message): """Unload and reload a plugin, picking up file changes.""" parts = message.text.split(None, 2) if len(parts) < 2: await bot.reply(message, "Usage: !reload ") return name = parts[1].lower() ok, reason = bot.reload_plugin(name) if ok: await bot.reply(message, f"Reloaded plugin: {name}") else: await bot.reply(message, f"Failed to reload plugin: {reason}") @command("unload", help="Unload a plugin: !unload ", admin=True) async def cmd_unload(bot, message): """Unload a plugin, removing all its handlers.""" parts = message.text.split(None, 2) if len(parts) < 2: await bot.reply(message, "Usage: !unload ") return name = parts[1].lower() ok, reason = bot.unload_plugin(name) if ok: await bot.reply(message, f"Unloaded plugin: {name}") else: await bot.reply(message, f"Failed to unload plugin: {reason}") @command("plugins", help="List loaded plugins") async def cmd_plugins(bot, message): """List all loaded plugins with handler counts.""" counts: Counter[str] = Counter() for handler in bot.registry.commands.values(): counts[handler.plugin] += 1 for handlers in bot.registry.events.values(): for handler in handlers: counts[handler.plugin] += 1 parts = [f"{name} ({counts[name]})" for name in sorted(counts)] await bot.reply(message, f"Plugins: {', '.join(parts)}") @command("whoami", help="Show your hostmask and permission tier") async def cmd_whoami(bot, message): """Display the sender's hostmask and permission level.""" prefix = message.prefix or "unknown" tier = bot._get_tier(message) tags = [tier] if message.prefix and message.prefix in bot._opers: tags.append("IRCOP") await bot.reply(message, f"{prefix} [{', '.join(tags)}]") @command("admins", help="Show configured permission tiers and detected opers", admin=True) async def cmd_admins(bot, message): """Display configured permission tiers and known IRC operators.""" parts = [] if bot._admins: parts.append(f"Admin: {', '.join(bot._admins)}") else: parts.append("Admin: (none)") if bot._operators: parts.append(f"Oper: {', '.join(bot._operators)}") if bot._trusted: parts.append(f"Trusted: {', '.join(bot._trusted)}") if bot._opers: parts.append(f"IRCOPs: {', '.join(sorted(bot._opers))}") else: parts.append("IRCOPs: (none)") await bot.reply(message, " | ".join(parts)) @command("state", help="Inspect plugin state: !state ...", admin=True) async def cmd_state(bot, message): """Manage the plugin state store. Usage: !state list List keys !state get Get a value !state del Delete a key !state clear Clear all state """ parts = message.text.split() if len(parts) < 3: await bot.reply(message, "Usage: !state [key]") return action = parts[1].lower() plugin = parts[2] if action == "list": keys = bot.state.keys(plugin) if keys: await bot.reply(message, f"{plugin}: {', '.join(keys)}") else: await bot.reply(message, f"{plugin}: (no keys)") elif action == "get": if len(parts) < 4: await bot.reply(message, "Usage: !state get ") return key = parts[3] value = bot.state.get(plugin, key) if value is not None: await bot.reply(message, f"{plugin}.{key} = {value}") else: await bot.reply(message, f"{plugin}.{key}: not set") elif action == "del": if len(parts) < 4: await bot.reply(message, "Usage: !state del ") return key = parts[3] if bot.state.delete(plugin, key): await bot.reply(message, f"Deleted {plugin}.{key}") else: await bot.reply(message, f"{plugin}.{key}: not found") elif action == "clear": count = bot.state.clear(plugin) await bot.reply(message, f"Cleared {count} key(s) from {plugin}") else: await bot.reply(message, "Usage: !state [key]")