"""Main private handlers module for Telegram bot""" # Standard library imports import asyncio from datetime import datetime # Third-party imports from aiogram import F, Router, types from aiogram.filters import Command, StateFilter from aiogram.fsm.context import FSMContext # Local imports - filters and middlewares from database.async_db import AsyncBotDB from helper_bot.filters.main import ChatTypeFilter # Local imports - utilities from helper_bot.keyboards import (get_reply_keyboard, get_reply_keyboard_for_post) from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat from helper_bot.middlewares.album_middleware import AlbumMiddleware from helper_bot.middlewares.blacklist_middleware import BlacklistMiddleware from helper_bot.utils import messages from helper_bot.utils.helper_func import (check_user_emoji, get_first_name, update_user_info) # Local imports - metrics from helper_bot.utils.metrics import db_query_time, track_errors, track_time # Local imports - modular components from .constants import BUTTON_TEXTS, ERROR_MESSAGES, FSM_STATES from .decorators import error_handler from .services import BotSettings, PostService, StickerService, UserService # Expose sleep for tests (tests patch helper_bot.handlers.private.private_handlers.sleep) sleep = asyncio.sleep class PrivateHandlers: """Main handler class for private messages""" def __init__(self, db: AsyncBotDB, settings: BotSettings, s3_storage=None): self.db = db self.settings = settings self.user_service = UserService(db, settings) self.post_service = PostService(db, settings, s3_storage) self.sticker_service = StickerService(settings) self.router = Router() self.router.message.middleware(AlbumMiddleware(latency=5.0)) self.router.message.middleware(BlacklistMiddleware()) # Register handlers self._register_handlers() def _register_handlers(self): """Register all message handlers""" # Command handlers self.router.message.register( self.handle_emoji_message, ChatTypeFilter(chat_type=["private"]), Command("emoji"), ) self.router.message.register( self.handle_restart_message, ChatTypeFilter(chat_type=["private"]), Command("restart"), ) self.router.message.register( self.handle_start_message, ChatTypeFilter(chat_type=["private"]), Command("start"), ) self.router.message.register( self.handle_start_message, ChatTypeFilter(chat_type=["private"]), F.text == BUTTON_TEXTS["RETURN_TO_BOT"], ) # Button handlers self.router.message.register( self.suggest_post, StateFilter(FSM_STATES["START"]), ChatTypeFilter(chat_type=["private"]), F.text == BUTTON_TEXTS["SUGGEST_POST"], ) self.router.message.register( self.end_message, ChatTypeFilter(chat_type=["private"]), F.text == BUTTON_TEXTS["SAY_GOODBYE"], ) self.router.message.register( self.end_message, ChatTypeFilter(chat_type=["private"]), F.text == BUTTON_TEXTS["LEAVE_CHAT"], ) self.router.message.register( self.stickers, ChatTypeFilter(chat_type=["private"]), F.text == BUTTON_TEXTS["WANT_STICKERS"], ) self.router.message.register( self.connect_with_admin, StateFilter(FSM_STATES["START"]), ChatTypeFilter(chat_type=["private"]), F.text == BUTTON_TEXTS["CONNECT_ADMIN"], ) # State handlers self.router.message.register( self.suggest_router, StateFilter(FSM_STATES["SUGGEST"]), ChatTypeFilter(chat_type=["private"]), ) self.router.message.register( self.resend_message_in_group_for_message, StateFilter(FSM_STATES["PRE_CHAT"]), ChatTypeFilter(chat_type=["private"]), ) self.router.message.register( self.resend_message_in_group_for_message, StateFilter(FSM_STATES["CHAT"]), ChatTypeFilter(chat_type=["private"]), ) @error_handler @track_errors("private_handlers", "handle_emoji_message") @track_time("handle_emoji_message", "private_handlers") async def handle_emoji_message( self, message: types.Message, state: FSMContext, **kwargs ): """Handle emoji command""" await self.user_service.log_user_message(message) user_emoji = await check_user_emoji(message) await state.set_state(FSM_STATES["START"]) if user_emoji is not None: await message.answer(f"Твоя эмодзя - {user_emoji}", parse_mode="HTML") @error_handler @track_errors("private_handlers", "handle_restart_message") @track_time("handle_restart_message", "private_handlers") async def handle_restart_message( self, message: types.Message, state: FSMContext, **kwargs ): """Handle restart command""" markup = await get_reply_keyboard(self.db, message.from_user.id) await self.user_service.log_user_message(message) await state.set_state(FSM_STATES["START"]) await update_user_info("love", message) await check_user_emoji(message) await message.answer("Я перезапущен!", reply_markup=markup, parse_mode="HTML") @error_handler @track_errors("private_handlers", "handle_start_message") @track_time("handle_start_message", "private_handlers") async def handle_start_message( self, message: types.Message, state: FSMContext, **kwargs ): """Handle start command and return to bot button with metrics tracking""" # User service operations with metrics await self.user_service.log_user_message(message) await self.user_service.ensure_user_exists(message) await state.set_state(FSM_STATES["START"]) # Send sticker with metrics await self.sticker_service.send_random_hello_sticker(message) # Send welcome message with metrics markup = await get_reply_keyboard(self.db, message.from_user.id) hello_message = messages.get_message(get_first_name(message), "HELLO_MESSAGE") await message.answer(hello_message, reply_markup=markup, parse_mode="HTML") @error_handler @track_errors("private_handlers", "suggest_post") @track_time("suggest_post", "private_handlers") async def suggest_post(self, message: types.Message, state: FSMContext, **kwargs): """Handle suggest post button""" # User service operations with metrics await self.user_service.update_user_activity(message.from_user.id) await self.user_service.log_user_message(message) await state.set_state(FSM_STATES["SUGGEST"]) markup = types.ReplyKeyboardRemove() suggest_news = messages.get_message(get_first_name(message), "SUGGEST_NEWS") await message.answer(suggest_news, reply_markup=markup) @error_handler @track_errors("private_handlers", "end_message") @track_time("end_message", "private_handlers") async def end_message(self, message: types.Message, state: FSMContext, **kwargs): """Handle goodbye button""" # User service operations with metrics await self.user_service.update_user_activity(message.from_user.id) await self.user_service.log_user_message(message) # Send sticker await self.sticker_service.send_random_goodbye_sticker(message) # Send goodbye message markup = types.ReplyKeyboardRemove() bye_message = messages.get_message(get_first_name(message), "BYE_MESSAGE") await message.answer(bye_message, reply_markup=markup) await state.set_state(FSM_STATES["START"]) @error_handler @track_errors("private_handlers", "suggest_router") @track_time("suggest_router", "private_handlers") async def suggest_router( self, message: types.Message, state: FSMContext, album: list = None, **kwargs ): """Handle post submission in suggest state""" # Проверяем, есть ли механизм для получения полной медиагруппы (для медиагрупп) album_getter = kwargs.get("album_getter") if album_getter and message.media_group_id: # Это медиагруппа - сразу отвечаем пользователю, обработку делаем в фоне markup_for_user = await get_reply_keyboard(self.db, message.from_user.id) success_send_message = messages.get_message( get_first_name(message), "SUCCESS_SEND_MESSAGE" ) await message.answer(success_send_message, reply_markup=markup_for_user) await state.set_state(FSM_STATES["START"]) # В фоне ждем полную медиагруппу и обрабатываем пост async def process_media_group_background(): try: # Ждем полную медиагруппу full_album = await album_getter.get_album(timeout=10.0) if not full_album: return # Обрабатываем пост с полной медиагруппой await self.user_service.update_user_activity(message.from_user.id) await self.post_service.process_post(message, full_album) except Exception as e: from logs.custom_logger import logger logger.error(f"Ошибка при фоновой обработке медиагруппы: {e}") asyncio.create_task(process_media_group_background()) else: # Обычное сообщение или медиагруппа уже собрана - обрабатываем синхронно await self.user_service.update_user_activity(message.from_user.id) if message.media_group_id is None: await self.user_service.log_user_message(message) await self.post_service.process_post(message, album) markup_for_user = await get_reply_keyboard(self.db, message.from_user.id) success_send_message = messages.get_message( get_first_name(message), "SUCCESS_SEND_MESSAGE" ) await message.answer(success_send_message, reply_markup=markup_for_user) await state.set_state(FSM_STATES["START"]) @error_handler @track_errors("private_handlers", "stickers") @track_time("stickers", "private_handlers") @db_query_time("stickers", "stickers", "update") async def stickers(self, message: types.Message, state: FSMContext, **kwargs): """Handle stickers request""" # User service operations with metrics markup = await get_reply_keyboard(self.db, message.from_user.id) await self.db.update_stickers_info(message.from_user.id) await self.user_service.log_user_message(message) await message.answer(text=ERROR_MESSAGES["STICKERS_LINK"], reply_markup=markup) await state.set_state(FSM_STATES["START"]) @error_handler @track_errors("private_handlers", "connect_with_admin") @track_time("connect_with_admin", "private_handlers") async def connect_with_admin( self, message: types.Message, state: FSMContext, **kwargs ): """Handle connect with admin button""" # User service operations with metrics await self.user_service.update_user_activity(message.from_user.id) admin_message = messages.get_message( get_first_name(message), "CONNECT_WITH_ADMIN" ) await message.answer(admin_message, parse_mode="html") await self.user_service.log_user_message(message) await state.set_state(FSM_STATES["PRE_CHAT"]) @error_handler @track_errors("private_handlers", "resend_message_in_group_for_message") @track_time("resend_message_in_group_for_message", "private_handlers") @db_query_time("resend_message_in_group_for_message", "messages", "insert") async def resend_message_in_group_for_message( self, message: types.Message, state: FSMContext, **kwargs ): """Handle messages in admin chat states""" # User service operations with metrics await self.user_service.update_user_activity(message.from_user.id) await message.forward(chat_id=self.settings.group_for_message) current_date = datetime.now() date = int(current_date.timestamp()) await self.db.add_message( message.text, message.from_user.id, message.message_id + 1, date ) question = messages.get_message(get_first_name(message), "QUESTION") user_state = await state.get_state() if user_state == FSM_STATES["PRE_CHAT"]: markup = await get_reply_keyboard(self.db, message.from_user.id) await message.answer(question, reply_markup=markup) await state.set_state(FSM_STATES["START"]) elif user_state == FSM_STATES["CHAT"]: markup = get_reply_keyboard_leave_chat() await message.answer(question, reply_markup=markup) # Factory function to create handlers with dependencies def create_private_handlers( db: AsyncBotDB, settings: BotSettings, s3_storage=None ) -> PrivateHandlers: """Create private handlers instance with dependencies""" return PrivateHandlers(db, settings, s3_storage) # Legacy router for backward compatibility private_router = Router() # Initialize with global dependencies (for backward compatibility) def init_legacy_router(): """Initialize legacy router with global dependencies""" global private_router from helper_bot.utils.base_dependency_factory import get_global_instance bdf = get_global_instance() settings = BotSettings( group_for_posts=bdf.settings["Telegram"]["group_for_posts"], group_for_message=bdf.settings["Telegram"]["group_for_message"], main_public=bdf.settings["Telegram"]["main_public"], group_for_logs=bdf.settings["Telegram"]["group_for_logs"], important_logs=bdf.settings["Telegram"]["important_logs"], preview_link=bdf.settings["Telegram"]["preview_link"], logs=bdf.settings["Settings"]["logs"], test=bdf.settings["Settings"]["test"], ) db = bdf.get_db() s3_storage = bdf.get_s3_storage() handlers = create_private_handlers(db, settings, s3_storage) # Instead of trying to copy handlers, we'll use the new router directly # This maintains backward compatibility while using the new architecture private_router = handlers.router # Initialize legacy router init_legacy_router()