Files
bottohelp/core/command_router.py
T

335 lines
13 KiB
Python

"""
Единый маршрутизатор команд для централизованной обработки.
Отвечает за маршрутизацию команд к соответствующим обработчикам.
"""
import logging
from typing import Dict, List, Callable, Awaitable, Any, Optional
from telegram import Update
from telegram.ext import ContextTypes
from .middleware import create_middleware_chain
from .permissions import permission_manager, UserRole
class CommandHandler:
"""
Описание обработчика команды с метаданными.
"""
def __init__(self, handler: Callable, required_role: UserRole = UserRole.USER,
description: str = "", category: str = "general"):
self.handler = handler
self.required_role = required_role
self.description = description
self.category = category
class CommandRouter:
"""
Центральный маршрутизатор команд бота.
Обеспечивает:
- Регистрацию обработчиков команд
- Автоматическую маршрутизацию
- Проверку прав доступа
- Обработку через middleware
"""
def __init__(self, config, metrics=None):
"""
Инициализация маршрутизатора.
Args:
config: Конфигурация приложения
metrics: Сборщик метрик
"""
self.config = config
self.metrics = metrics
self.logger = logging.getLogger(__name__)
# Создаем цепочку middleware
self.middleware_chain = create_middleware_chain(config, metrics)
# Регистрируемые обработчики
self.command_handlers: Dict[str, CommandHandler] = {}
self.callback_handlers: Dict[str, CommandHandler] = {}
self.message_handlers: Dict[str, CommandHandler] = {}
# Категории команд
self.categories = {
'user': 'Пользовательские команды',
'moderation': 'Модерация',
'admin': 'Администрирование',
'system': 'Системные команды',
'games': 'Игры'
}
def register_command_handler(self, command: str, handler: Callable,
required_role: UserRole = UserRole.USER,
description: str = "", category: str = "user"):
"""
Регистрация обработчика команды.
Args:
command: Название команды (без слеша)
handler: Функция обработчик
required_role: Минимальная требуемая роль
description: Описание команды
category: Категория команды
"""
self.command_handlers[command] = CommandHandler(
handler=handler,
required_role=required_role,
description=description,
category=category
)
self.logger.debug(f"Registered command handler: /{command} -> {handler.__name__}")
def register_callback_handler(self, callback_data: str, handler: Callable,
required_role: UserRole = UserRole.USER,
description: str = "", category: str = "user"):
"""
Регистрация обработчика callback запросов.
Args:
callback_data: Callback data
handler: Функция обработчик
required_role: Минимальная требуемая роль
description: Описание
category: Категория
"""
self.callback_handlers[callback_data] = CommandHandler(
handler=handler,
required_role=required_role,
description=description,
category=category
)
def register_message_handler(self, message_type: str, handler: Callable,
required_role: UserRole = UserRole.USER,
description: str = "", category: str = "user"):
"""
Регистрация обработчика сообщений.
Args:
message_type: Тип сообщения (text, voice, audio, etc.)
handler: Функция обработчик
required_role: Минимальная требуемая роль
description: Описание
category: Категория
"""
self.message_handlers[message_type] = CommandHandler(
handler=handler,
required_role=required_role,
description=description,
category=category
)
async def handle_command(self, update: Update, context: ContextTypes):
"""
Обработка команды через middleware и маршрутизацию.
Args:
update: Обновление от Telegram
context: Контекст бота
"""
# Извлекаем команду из сообщения
command = self._extract_command(update)
if not command:
return
# Находим обработчик
handler_info = self.command_handlers.get(command)
if not handler_info:
await self._send_unknown_command(update, command)
return
# Создаем обертку обработчика с проверкой роли
async def wrapped_handler(update: Update, context: ContextTypes):
await handler_info.handler(update, context)
# Обрабатываем через middleware
await self.middleware_chain.process_request(update, context, wrapped_handler)
async def handle_callback(self, update: Update, context: ContextTypes):
"""
Обработка callback запросов.
Args:
update: Обновление от Telegram
context: Контекст бота
"""
if not update.callback_query:
return
callback_data = update.callback_query.data
if not callback_data:
return
self.logger.debug(f"Processing callback: {callback_data}")
# Находим обработчик (точное совпадение или префикс)
handler_info = None
# Сначала ищем точное совпадение
if callback_data in self.callback_handlers:
handler_info = self.callback_handlers[callback_data]
self.logger.debug(f"Found exact match for callback: {callback_data}")
else:
# Ищем по префиксу
for prefix, handler in self.callback_handlers.items():
if callback_data.startswith(prefix):
handler_info = handler
self.logger.debug(f"Found prefix match for callback: {callback_data} -> {prefix}")
break
if not handler_info:
self.logger.warning(f"No handler found for callback: {callback_data}")
await update.callback_query.answer("Неизвестное действие")
return
self.logger.debug(f"Found handler for callback {callback_data}: {handler_info.handler.__name__}")
# Создаем обертку обработчика
async def wrapped_handler(update: Update, context: ContextTypes):
await handler_info.handler(update, context)
# Обрабатываем через middleware
await self.middleware_chain.process_request(update, context, wrapped_handler)
async def handle_message(self, update: Update, context: ContextTypes, message_type: str = "text"):
"""
Обработка сообщений.
Args:
update: Обновление от Telegram
context: Контекст бота
message_type: Тип сообщения
"""
handler_info = self.message_handlers.get(message_type)
if not handler_info:
return
# Создаем обертку обработчика
async def wrapped_handler(update: Update, context: ContextTypes):
await handler_info.handler(update, context)
# Обрабатываем через middleware
await self.middleware_chain.process_request(update, context, wrapped_handler)
def _extract_command(self, update: Update) -> Optional[str]:
"""
Извлечение команды из обновления.
Args:
update: Обновление от Telegram
Returns:
Название команды или None
"""
if not update.message or not update.message.text:
return None
text = update.message.text.strip()
if not text.startswith('/'):
return None
# Извлекаем команду (убираем слеш и username бота)
command = text.split()[0][1:].split('@')[0]
return command
async def _send_unknown_command(self, update: Update, command: str):
"""
Отправка сообщения о неизвестной команде.
Args:
update: Обновление от Telegram
command: Название неизвестной команды
"""
try:
message = f"❌ Неизвестная команда: /{command}\n\nИспользуйте /help для списка доступных команд."
if update.message:
await update.message.reply_text(message)
except Exception as e:
self.logger.error(f"Failed to send unknown command message: {e}")
def get_registered_commands(self) -> Dict[str, Dict[str, Any]]:
"""
Получение списка зарегистрированных команд сгруппированных по категориям.
Returns:
Словарь категорий с командами
"""
result = {}
for category, category_name in self.categories.items():
result[category] = {
'name': category_name,
'commands': {}
}
for command, handler_info in self.command_handlers.items():
category = handler_info.category
if category not in result:
result[category] = {'name': category, 'commands': {}}
result[category]['commands'][command] = {
'description': handler_info.description,
'required_role': handler_info.required_role.value
}
return result
def get_available_commands_for_role(self, user_role: UserRole) -> List[str]:
"""
Получение списка команд доступных для роли.
Args:
user_role: Роль пользователя
Returns:
Список доступных команд
"""
available_commands = []
for command, handler_info in self.command_handlers.items():
if permission_manager.can_execute_command(user_role, command):
available_commands.append(command)
return sorted(available_commands)
def get_command_info(self, command: str) -> Optional[Dict[str, Any]]:
"""
Получение информации о команде.
Args:
command: Название команды
Returns:
Информация о команде или None
"""
handler_info = self.command_handlers.get(command)
if not handler_info:
return None
return {
'command': command,
'description': handler_info.description,
'required_role': handler_info.required_role.value,
'category': handler_info.category
}
# Функция для создания настроенного маршрутизатора
def create_command_router(config, metrics=None):
"""
Создание и настройка маршрутизатора команд.
Args:
config: Конфигурация приложения
metrics: Сборщик метрик
Returns:
CommandRouter: Настроенный маршрутизатор
"""
return CommandRouter(config, metrics)