mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
335 lines
13 KiB
Python
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) |