from typing import Callable, Awaitable, Any, Dict from aiogram import Bot, BaseMiddleware from aiogram.types import TelegramObject, Message, CallbackQuery, Update from middleware.loggers import logger class ErrorHandlingMiddleware(BaseMiddleware): """ Middleware для глобальной обработки ошибок в хендлерах. Зачем нужен: - Централизованная обработка исключений - Уведомление администраторов об ошибках - Graceful degradation при сбоях """ def __init__(self, admin_ids: list[int]): """ Инициализация middleware обработки ошибок. Args: admin_ids: Список ID администраторов для уведомлений """ self.admin_ids = admin_ids super().__init__() async def __call__( self, handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], event: TelegramObject, data: Dict[str, Any] ) -> Any: """ Перехватывает и обрабатывает ошибки в хендлерах. """ try: return await handler(event, data) except Exception as e: # Получаем информацию о пользователе безопасным способом user_str: str = self._extract_user_info(event) # Логируем ошибку error_message: str = f"Ошибка в хендлере: {type(e).__name__}: {str(e)}" logger.error( text=error_message, log_type="HANDLER_ERROR", user=user_str ) # Уведомляем администраторов await self._notify_admins(error_message, event, user_str) # Отправляем пользователю сообщение об ошибке await self._send_error_message(event, user_str) return None @staticmethod def _extract_user_info(event: TelegramObject) -> str: """ Безопасно извлекает информацию о пользователе из события. Args: event: Объект события Returns: Строка с идентификатором пользователя """ user_str: str = "@System" # Для Message и CallbackQuery if isinstance(event, (Message, CallbackQuery)) and hasattr(event, 'from_user') and event.from_user: user = event.from_user user_str = f"@{user.username}" if user.username else f"id{user.id}" # Для Update (который содержит message или callback_query) elif isinstance(event, Update): # Пытаемся найти пользователя в различных полях Update user_object = None if event.message and event.message.from_user: user_object = event.message.from_user elif event.edited_message and event.edited_message.from_user: user_object = event.edited_message.from_user elif event.callback_query and event.callback_query.from_user: user_object = event.callback_query.from_user elif event.channel_post and event.channel_post.from_user: user_object = event.channel_post.from_user elif event.edited_channel_post and event.edited_channel_post.from_user: user_object = event.edited_channel_post.from_user if user_object: user_str = f"@{user_object.username}" if user_object.username else f"id{user_object.id}" return user_str @staticmethod def _extract_event_text(event: TelegramObject) -> str: """ Безопасно извлекает текст из события. Args: event: Объект события Returns: Текст события или пустая строка """ event_text: str = "" # Для Message if isinstance(event, Message) and hasattr(event, 'text') and event.text: event_text: str = event.text # Для CallbackQuery elif isinstance(event, CallbackQuery) and hasattr(event, 'data') and event.data: event_text: str = f"callback: {event.data}" # Для Update elif isinstance(event, Update): if event.message and event.message.text: event_text: str = event.message.text elif event.callback_query and event.callback_query.data: event_text: str = f"callback: {event.callback_query.data}" elif event.edited_message and event.edited_message.text: event_text: str = event.edited_message.text return event_text[:100] + "..." if len(event_text) > 100 else event_text async def _notify_admins( self, error_message: str, event: TelegramObject, user_str: str ) -> None: """Уведомляет администраторов об ошибке.""" bot: Bot = event.bot if hasattr(event, 'bot') else None if bot: for admin_id in self.admin_ids: try: event_info: str = f"Событие: {type(event).__name__}" event_text: str = self._extract_event_text(event) if event_text: event_info += f", текст: {event_text}" full_message: str = ( f"🚨 Ошибка в боте:\n\n" f"Пользователь: {user_str}\n" f"Ошибка: {error_message}\n" f"{event_info}" ) await bot.send_message(admin_id, full_message) logger.info( text=f"Администратор {admin_id} уведомлен об ошибке", log_type="ADMIN_NOTIFIED", user=user_str ) except Exception as e: logger.error( text=f"Не удалось уведомить админа {admin_id}: {e}", log_type="ADMIN_NOTIFY_ERROR", user=user_str ) @staticmethod async def _send_error_message( event: TelegramObject, user_str: str ) -> None: """Отправляет пользователю сообщение об ошибке.""" error_text: str = ( "⚠️ Произошла непредвиденная ошибка. " "Разработчики уже уведомлены и работают над исправлением.\n\n" "Попробуйте повторить действие позже или нажмите /start" ) try: if isinstance(event, Message): await event.answer(error_text) elif isinstance(event, CallbackQuery): await event.message.answer(error_text) await event.answer() elif isinstance(event, Update) and event.message: await event.message.answer(error_text) logger.info( text="Пользователю отправлено сообщение об ошибке", log_type="ERROR_MESSAGE_SENT", user=user_str ) except Exception as e: logger.error( text=f"Не удалось отправить сообщение об ошибке: {e}", log_type="ERROR_MESSAGE_FAILED", user=user_str )