#!/usr/bin/env python3 """ Help command for the MeshCore Bot Provides help information for commands and general usage """ import sqlite3 from collections import defaultdict from .base_command import BaseCommand from ..models import MeshMessage class HelpCommand(BaseCommand): """Handles the help command""" # Plugin metadata name = "help" keywords = ['help'] description = "Shows commands. Use 'help ' for details." category = "basic" def get_help_text(self) -> str: return self.translate('commands.help.description') async def execute(self, message: MeshMessage) -> bool: """Execute the help command""" # The help command is now handled by keyword matching in the command manager # This is just a placeholder for future functionality self.logger.debug("Help command executed (handled by keyword matching)") return True def get_specific_help(self, command_name: str, message: MeshMessage = None) -> str: """Get help text for a specific command""" # Map command aliases to their actual command names command_aliases = { 't': 't_phrase', 'advert': 'advert', 'test': 'test', 'ping': 'ping', 'help': 'help' } # Normalize the command name normalized_name = command_aliases.get(command_name, command_name) # Get the command instance command = self.bot.command_manager.commands.get(normalized_name) if command: # Pass message context to get_help_text if the method supports it if hasattr(command, 'get_help_text') and callable(getattr(command, 'get_help_text')): try: help_text = command.get_help_text(message) except TypeError: # Fallback for commands that don't accept message parameter help_text = command.get_help_text() else: help_text = self.translate('commands.help.no_help') return self.translate('commands.help.specific', command=command_name, help_text=help_text) else: available = self.get_available_commands_list() return self.translate('commands.help.unknown', command=command_name, available=available) def get_general_help(self) -> str: """Get general help text""" commands_list = self.get_available_commands_list() help_text = self.translate('commands.help.general', commands_list=commands_list) help_text += self.translate('commands.help.usage_examples') help_text += self.translate('commands.help.custom_syntax') return help_text def get_available_commands_list(self) -> str: """Get a list of most popular commands in descending order, showing only one variant per command""" try: # Use the plugin loader's keyword mappings to map keywords/aliases to primary command names plugin_loader = self.bot.command_manager.plugin_loader keyword_mappings = plugin_loader.keyword_mappings.copy() if hasattr(plugin_loader, 'keyword_mappings') else {} # Build a set of all primary command names and ensure they map to themselves primary_names = set() for cmd_name, cmd_instance in self.bot.command_manager.commands.items(): primary_name = cmd_instance.name if hasattr(cmd_instance, 'name') else cmd_name primary_names.add(primary_name) # Ensure primary name maps to itself in keyword_mappings keyword_mappings[primary_name.lower()] = primary_name # Query the database for command usage statistics command_counts = defaultdict(int) try: with sqlite3.connect(self.bot.db_manager.db_path) as conn: cursor = conn.cursor() # Check if command_stats table exists cursor.execute(""" SELECT name FROM sqlite_master WHERE type='table' AND name='command_stats' """) if cursor.fetchone(): # Query command usage cursor.execute(""" SELECT command_name, COUNT(*) as count FROM command_stats GROUP BY command_name """) for row in cursor.fetchall(): command_name = row[0] count = row[1] # Map keyword/alias to primary command name # First try the plugin_loader's keyword_mappings primary_name = keyword_mappings.get(command_name.lower()) # If not found in mappings, check if it's already a primary name if primary_name is None: if command_name in primary_names: primary_name = command_name else: # Try to find which command this belongs to by checking all commands for cmd_name, cmd_instance in self.bot.command_manager.commands.items(): # Check if command_name matches the command's name cmd_primary = cmd_instance.name if hasattr(cmd_instance, 'name') else cmd_name if cmd_primary == command_name: primary_name = cmd_primary break # Check if it's a keyword of this command if hasattr(cmd_instance, 'keywords'): if command_name.lower() in [k.lower() for k in cmd_instance.keywords]: primary_name = cmd_primary break # If still not found, use the command_name as-is if primary_name is None: primary_name = command_name command_counts[primary_name] += count except Exception as e: self.logger.debug(f"Error querying command stats: {e}") # If stats table doesn't exist or query fails, fall back to all commands for cmd_name in self.bot.command_manager.commands.keys(): primary_name = self.bot.command_manager.commands[cmd_name].name if hasattr(self.bot.command_manager.commands[cmd_name], 'name') else cmd_name command_counts[primary_name] = 0 # If we have stats, sort by count descending, otherwise use all commands if command_counts: # Sort by count descending, then by name for consistency sorted_commands = sorted( command_counts.items(), key=lambda x: (-x[1], x[0]) ) # Extract just the command names (only primary names, no aliases) command_names = [name for name, _ in sorted_commands] else: # Fallback: use all primary command names command_names = sorted([ cmd.name if hasattr(cmd, 'name') else name for name, cmd in self.bot.command_manager.commands.items() ]) # Return comma-separated list return ', '.join(command_names) except Exception as e: self.logger.error(f"Error getting available commands list: {e}") # Fallback to simple list of all command names command_names = sorted([ cmd.name if hasattr(cmd, 'name') else name for name, cmd in self.bot.command_manager.commands.items() ]) return ', '.join(command_names)