Files
bottohelp/core/unified_router.py
T

325 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Объединенный маршрутизатор для всех типов обновлений.
Интегрирует CommandRouter, MessageTypeRouter и ContextMenuManager.
Архитектура:
- CommandRouter: обработка команд и callback'ов через middleware и проверки прав
- MessageTypeRouter: маршрутизация сообщений по типам контента
- ContextMenuManager: управление контекстными меню и проверка доступности
Алгоритм обработки callback'ов:
1. Проверка роли пользователя и доступности меню
2. Попытка обработки через CommandRouter (новая система)
3. Fallback к MessageTypeRouter при неудаче
Преимущества:
- Централизованная маршрутизация всех типов обновлений
- Гибкая система middleware для команд
- Поддержка ролевой модели доступа
- Легкая расширяемость новыми типами обработчиков
"""
import logging
from typing import Optional
from telegram import Update
from telegram.ext import ContextTypes
from .command_router import CommandRouter
from .message_router import MessageTypeRouter
from .menu_manager import ContextMenuManager
from .permissions import UserRole
class UnifiedMessageRouter:
"""
Объединенный маршрутизатор для всех типов обновлений.
Обеспечивает:
- Единый интерфейс для обработки всех типов сообщений
- Интеграцию с существующими компонентами
- Централизованное управление маршрутизацией
- Логирование и мониторинг
- Правильную обработку callback'ов через CommandRouter с fallback'ом
"""
def __init__(self, command_router: CommandRouter,
message_router: MessageTypeRouter,
menu_manager: ContextMenuManager):
"""
Инициализация объединенного маршрутизатора.
Args:
command_router: Маршрутизатор команд
message_router: Маршрутизатор типов сообщений
menu_manager: Менеджер контекстных меню
"""
self.command_router = command_router
self.message_router = message_router
self.menu_manager = menu_manager
self.logger = logging.getLogger(__name__)
async def handle_update(self, update: Update, context: ContextTypes) -> bool:
"""
Единая точка входа для обработки всех типов обновлений.
Args:
update: Обновление от Telegram
context: Контекст бота
Returns:
True если обновление было успешно обработано
"""
try:
# Определение типа обновления и маршрутизация
if update.message:
return await self._handle_message_update(update, context)
elif update.callback_query:
return await self._handle_callback_update(update, context)
elif update.inline_query:
return await self._handle_inline_query(update, context)
elif update.chosen_inline_result:
return await self._handle_chosen_inline_result(update, context)
elif update.chat_member:
return await self._handle_chat_member_update(update, context)
else:
self.logger.debug("Unhandled update type")
return False
except Exception as e:
self.logger.error(f"Error in unified router: {e}", exc_info=True)
return False
async def _handle_message_update(self, update: Update, context: ContextTypes) -> bool:
"""
Обработка обновлений типа message.
Args:
update: Обновление с сообщением
context: Контекст бота
Returns:
True если сообщение обработано
"""
message = update.message
# Проверка типа сообщения
if message.text and message.text.startswith('/'):
# Команды обрабатываются через CommandRouter
self.logger.debug("Routing to command router")
await self.command_router.handle_command(update, context)
return True
elif message.text:
# Текстовые сообщения через MessageTypeRouter
self.logger.debug("Routing text message")
return await self.message_router.route_text_message(update, context)
elif message.voice:
# Голосовые сообщения
self.logger.debug("Routing voice message")
return await self.message_router.route_voice_message(update, context)
elif message.photo or message.video or message.audio or message.document:
# Медиа-сообщения
self.logger.debug("Routing media message")
return await self.message_router.route_media_message(update, context)
else:
self.logger.debug("Unhandled message type")
return False
async def _handle_callback_update(self, update: Update, context: ContextTypes) -> bool:
"""
Обработка callback запросов с учетом контекста меню.
Алгоритм обработки:
1. Получение роли пользователя для проверки прав доступа
2. Проверка доступности меню (для запросов menu_*)
3. Попытка обработки через CommandRouter (новая система маршрутизации)
4. Fallback к MessageTypeRouter при неудаче
Args:
update: Обновление с callback
context: Контекст бота
Returns:
True если callback обработан
"""
callback_data = update.callback_query.data
self.logger.debug(f"Processing callback: {callback_data}")
# Получение роли пользователя
user_role = await self._get_user_role(update)
# Проверка доступности меню (если это запрос меню)
if callback_data.startswith('menu_'):
if not self.menu_manager.is_menu_available(callback_data, user_role):
await update.callback_query.answer("Недостаточно прав доступа")
return True
# Сначала пытаемся маршрутизировать через CommandRouter (новая система)
try:
result = await self.command_router.handle_callback(update, context)
if result:
return True
except Exception as e:
self.logger.error(f"Error in CommandRouter callback handler: {e}")
# Fallback к MessageTypeRouter
return await self.message_router.route_callback(update, context)
async def _handle_inline_query(self, update: Update, context: ContextTypes) -> bool:
"""
Обработка инлайновых запросов.
Args:
update: Инлайновый запрос
context: Контекст бота
Returns:
True если запрос обработан
"""
self.logger.debug("Processing inline query")
return await self.message_router.route_inline_query(update, context)
async def _handle_chosen_inline_result(self, update: Update, context: ContextTypes) -> bool:
"""
Обработка выбора результата инлайнового запроса.
Args:
update: Выбранный результат
context: Контекст бота
Returns:
True если результат обработан
"""
self.logger.debug("Processing chosen inline result")
# Здесь может быть дополнительная логика обработки выбора
return True
async def _handle_chat_member_update(self, update: Update, context: ContextTypes) -> bool:
"""
Обработка обновлений участников чата.
Args:
update: Обновление участников чата
context: Контекст бота
Returns:
True если обновление обработано
"""
self.logger.debug("Processing chat member update")
# Здесь может быть логика обработки вступления/выхода участников
return True
async def _get_user_role(self, update: Update) -> UserRole:
"""
Получение роли пользователя для данного обновления.
Args:
update: Обновление от Telegram
Returns:
Роль пользователя
"""
try:
user = update.effective_user
if user:
# Использование существующей логики определения роли
from .permissions import permission_manager
# Попытка получить config из Application
try:
from .application import app_instance
if app_instance and hasattr(app_instance, 'config'):
config = app_instance.config
else:
# Создаем минимальный конфиг
class MinimalConfig:
def __init__(self):
self.bot_config = MinimalBotConfig()
class MinimalBotConfig:
def __init__(self):
self.super_admin_ids = []
self.admin_ids = []
self.moderator_ids = []
config = MinimalConfig()
except ImportError:
# Создаем минимальный конфиг если Application недоступен
class MinimalConfig:
def __init__(self):
self.bot_config = MinimalBotConfig()
class MinimalBotConfig:
def __init__(self):
self.super_admin_ids = []
self.admin_ids = []
self.moderator_ids = []
config = MinimalConfig()
return await permission_manager.get_effective_role(
update, user.id, config
)
return UserRole.USER
except Exception as e:
self.logger.error(f"Error getting user role: {e}")
return UserRole.USER
async def get_menu_for_user(self, menu_id: str, user_role: UserRole,
**context) -> Optional[object]:
"""
Получение меню для пользователя через ContextMenuManager.
Args:
menu_id: Идентификатор меню
user_role: Роль пользователя
**context: Дополнительный контекст
Returns:
Объект меню или None
"""
# Определение типа чата для контекстного построения меню
chat_type = context.get('chat_type', 'private')
context['chat_type'] = chat_type
return await self.menu_manager.get_menu_for_user(menu_id, user_role, **context)
def get_registered_handlers_count(self) -> dict:
"""
Получение статистики зарегистрированных обработчиков.
Returns:
Словарь со статистикой
"""
command_handlers = len(self.command_router.command_handlers)
callback_handlers = len(self.command_router.callback_handlers)
message_handlers = self.message_router.get_registered_handlers_count()
return {
'command_handlers': command_handlers,
'callback_handlers': callback_handlers,
'message_handlers': message_handlers,
'total': command_handlers + callback_handlers + sum(message_handlers.values())
}
def clear_menu_cache(self):
"""Очистка кеша меню"""
self.menu_manager.clear_cache()
def create_unified_router(command_router: CommandRouter,
message_router: MessageTypeRouter,
menu_manager: ContextMenuManager) -> UnifiedMessageRouter:
"""
Создание объединенного маршрутизатора.
Args:
command_router: Маршрутизатор команд
message_router: Маршрутизатор типов сообщений
menu_manager: Менеджер меню
Returns:
UnifiedMessageRouter: Настроенный объединенный маршрутизатор
"""
return UnifiedMessageRouter(command_router, message_router, menu_manager)