import asyncio from datetime import datetime from pathlib import Path from aiogram import Router, types, F from aiogram.filters import Command, StateFilter, MagicData from aiogram.fsm.context import FSMContext from aiogram.types import FSInputFile from helper_bot.filters.main import ChatTypeFilter from helper_bot.middlewares.blacklist_middleware import BlacklistMiddleware from helper_bot.middlewares.dependencies_middleware import DependenciesMiddleware from helper_bot.utils import messages from helper_bot.utils.helper_func import get_first_name, update_user_info, check_user_emoji, send_voice_message from logs.custom_logger import logger from helper_bot.handlers.voice.constants import * from helper_bot.handlers.voice.services import VoiceBotService from helper_bot.handlers.voice.utils import get_last_message_text, validate_voice_message, get_user_emoji_safe from helper_bot.keyboards.keyboards import get_main_keyboard, get_reply_keyboard_for_voice from helper_bot.handlers.private.constants import BUTTON_TEXTS class VoiceHandlers: def __init__(self, db, settings): self.db = db self.settings = settings self.router = Router() self._setup_handlers() self._setup_middleware() def _setup_middleware(self): self.router.message.middleware(DependenciesMiddleware()) self.router.message.middleware(BlacklistMiddleware()) def _setup_handlers(self): # Обработчик кнопки "Голосовой бот" self.router.message.register( self.voice_bot_button_handler, ChatTypeFilter(chat_type=["private"]), F.text == BUTTON_TEXTS["VOICE_BOT"] ) # Команды self.router.message.register( self.restart_function, ChatTypeFilter(chat_type=["private"]), Command(CMD_RESTART) ) self.router.message.register( self.handle_emoji_message, ChatTypeFilter(chat_type=["private"]), Command(CMD_EMOJI) ) self.router.message.register( self.help_function, ChatTypeFilter(chat_type=["private"]), Command(CMD_HELP) ) self.router.message.register( self.start, ChatTypeFilter(chat_type=["private"]), Command(CMD_START) ) # Дополнительные команды self.router.message.register( self.refresh_listen_function, ChatTypeFilter(chat_type=["private"]), Command(CMD_REFRESH) ) # Обработчики состояний и кнопок self.router.message.register( self.standup_write, StateFilter(STATE_START), ChatTypeFilter(chat_type=["private"]), F.text == BTN_SPEAK ) self.router.message.register( self.suggest_voice, StateFilter(STATE_STANDUP_WRITE), ChatTypeFilter(chat_type=["private"]), ) self.router.message.register( self.standup_listen_audio, StateFilter(STATE_START), ChatTypeFilter(chat_type=["private"]), F.text == BTN_LISTEN ) async def voice_bot_button_handler(self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings")): """Обработчик кнопки 'Голосовой бот' из основной клавиатуры""" await self.start(message, state, bot_db, settings) async def restart_function( self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db") ): await message.forward(chat_id=bot_db.settings['Telegram']['group_for_logs']) await update_user_info(VOICE_BOT_NAME, message) check_user_emoji(message) markup = get_main_keyboard() await message.answer(text='Я перезапущен!', reply_markup=markup) await state.set_state(STATE_START) async def handle_emoji_message( self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db") ): await message.forward(chat_id=bot_db.settings['Telegram']['group_for_logs']) user_emoji = check_user_emoji(message) await state.set_state(STATE_START) if user_emoji is not None: await message.answer(f'Твоя эмодзя - {user_emoji}', parse_mode='HTML') async def help_function( self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings") ): await message.forward(chat_id=settings['Telegram']['group_for_logs']) await update_user_info(VOICE_BOT_NAME, message) help_message = messages.get_message(get_first_name(message), 'HELP_MESSAGE') await message.answer( text=help_message, disable_web_page_preview=not bot_db.settings['Telegram']['preview_link'] ) await state.set_state(STATE_START) async def start( self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings") ): await state.set_state(STATE_START) await message.forward(chat_id=settings['Telegram']['group_for_logs']) await update_user_info(VOICE_BOT_NAME, message) user_emoji = get_user_emoji_safe(bot_db, message.from_user.id) # Создаем сервис и отправляем приветственные сообщения voice_service = VoiceBotService(bot_db, settings) await voice_service.send_welcome_messages(message, user_emoji) async def refresh_listen_function( self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings") ): await message.forward(chat_id=settings['Telegram']['group_for_logs']) await update_user_info(VOICE_BOT_NAME, message) markup = get_main_keyboard() # Очищаем прослушивания через сервис voice_service = VoiceBotService(bot_db, settings) voice_service.clear_user_listenings(message.from_user.id) listenings_cleared_message = messages.get_message(get_first_name(message), 'LISTENINGS_CLEARED_MESSAGE') await message.answer( text=listenings_cleared_message, disable_web_page_preview=not settings['Telegram']['preview_link'], reply_markup=markup ) await state.set_state(STATE_START) async def standup_write( self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings") ): await message.forward(chat_id=settings['Telegram']['group_for_logs']) markup = types.ReplyKeyboardRemove() record_voice_message = messages.get_message(get_first_name(message), 'RECORD_VOICE_MESSAGE') await message.answer(text=record_voice_message, reply_markup=markup) try: message_with_date = get_last_message_text(bot_db) if message_with_date: await message.answer(text=message_with_date, parse_mode="html") except Exception as e: logger.error(f'Не удалось получить дату последнего сообщения - {e}') await state.set_state(STATE_STANDUP_WRITE) async def suggest_voice( self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings") ): logger.info( f"Вызов функции suggest_voice. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}" ) await message.forward(chat_id=settings['Telegram']['group_for_logs']) markup = get_main_keyboard() if validate_voice_message(message): markup_for_voice = get_reply_keyboard_for_voice() # Отправляем аудио в приватный канал sent_message = await send_voice_message( settings['Telegram']['group_for_posts'], message, message.voice.file_id, markup_for_voice ) # Сохраняем в базу инфо о посте bot_db.set_user_id_and_message_id_for_voice_bot(sent_message.message_id, message.from_user.id) # Отправляем юзеру ответ и возвращаем его в меню voice_saved_message = messages.get_message(get_first_name(message), 'VOICE_SAVED_MESSAGE') await message.answer(text=voice_saved_message, reply_markup=markup) await state.set_state(STATE_START) else: unknown_content_message = messages.get_message(get_first_name(message), 'UNKNOWN_CONTENT_MESSAGE') await message.forward(chat_id=settings['Telegram']['group_for_logs']) await message.answer(text=unknown_content_message, reply_markup=markup) await state.set_state(STATE_STANDUP_WRITE) async def standup_listen_audio( self, message: types.Message, bot_db: MagicData("bot_db"), settings: MagicData("settings") ): markup = get_main_keyboard() # Создаем сервис для работы с аудио voice_service = VoiceBotService(bot_db, settings) try: # Получаем случайное аудио audio_data = voice_service.get_random_audio(message.from_user.id) if not audio_data: no_audio_message = messages.get_message(get_first_name(message), 'NO_AUDIO_MESSAGE') await message.answer(text=no_audio_message, reply_markup=markup) try: message_with_date = get_last_message_text(bot_db) if message_with_date: await message.answer(text=message_with_date, parse_mode="html") except Exception as e: logger.error(f'Не удалось получить последнюю дату {e}') return audio_for_user, date_added, user_emoji = audio_data # Получаем путь к файлу path = Path(f'{VOICE_USERS_DIR}/{audio_for_user}.ogg') # Проверяем существование файла if not path.exists(): logger.error(f"Файл не найден: {path}") await message.answer( text="Файл аудио не найден. Обратитесь к администратору.", reply_markup=markup ) return # Проверяем размер файла if path.stat().st_size == 0: logger.error(f"Файл пустой: {path}") await message.answer( text="Файл аудио поврежден. Обратитесь к администратору.", reply_markup=markup ) return voice = FSInputFile(path) # Формируем подпись if user_emoji: caption = f'{user_emoji}\nДата записи: {date_added}' else: caption = f'Дата записи: {date_added}' try: await message.bot.send_voice( chat_id=message.chat.id, voice=voice, caption=caption, reply_markup=markup ) # Маркируем сообщение как прослушанное только после успешной отправки voice_service.mark_audio_as_listened(audio_for_user, message.from_user.id) # Получаем количество оставшихся аудио только после успешной отправки remaining_count = voice_service.get_remaining_audio_count(message.from_user.id) - 1 await message.answer( text=f'Осталось непрослушанных: {remaining_count}', reply_markup=markup ) except Exception as voice_error: if "VOICE_MESSAGES_FORBIDDEN" in str(voice_error): # Если голосовые сообщения запрещены, отправляем информативное сообщение logger.info(f"Пользователь {message.from_user.id} запретил получение голосовых сообщений") privacy_message = "🔇 К сожалению, у тебя закрыт доступ к получению голосовых сообщений.\n\nДля продолжения взаимодействия с ботом необходимо дать возможность мне присылать войсы в настройках приватности Telegram.\n\n💡 Как это сделать:\n1. Открой настройки Telegram\n2. Перейди в 'Конфиденциальность и безопасность'\n3. Выбери 'Голосовые сообщения'\n4. Разреши получение от 'Всех' или добавь меня в исключения" await message.answer(text=privacy_message, reply_markup=markup) return # Выходим без записи о прослушивании else: raise voice_error except Exception as e: logger.error(f"Ошибка при прослушивании аудио: {e}") await message.answer( text="Произошла ошибка при получении аудио. Попробуйте позже.", reply_markup=markup )