Files
meshcore-bot/modules/commands/help_command.py

170 lines
8.2 KiB
Python

#!/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 <command>' 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)