- Added methods to delete audio moderation records and retrieve all audio records in async_db.py. - Enhanced AudioRepository with functionality to delete audio records by file name and retrieve all audio message records. - Improved logging for audio record operations to enhance monitoring and debugging capabilities. - Updated related handlers to ensure proper integration of new audio management features.
452 lines
22 KiB
Python
452 lines
22 KiB
Python
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.keyboards import get_reply_keyboard
|
||
from helper_bot.handlers.private.constants import FSM_STATES
|
||
from helper_bot.handlers.private.constants import BUTTON_TEXTS
|
||
|
||
# Local imports - metrics
|
||
from helper_bot.utils.metrics import (
|
||
track_time,
|
||
track_errors,
|
||
db_query_time,
|
||
track_file_operations
|
||
)
|
||
|
||
class VoiceHandlers:
|
||
def __init__(self, db, settings):
|
||
self.db = db.get_db() if hasattr(db, 'get_db') else 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.cancel_handler,
|
||
ChatTypeFilter(chat_type=["private"]),
|
||
F.text == "Отменить"
|
||
)
|
||
|
||
# Обработчик кнопки "Голосовой бот"
|
||
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
|
||
)
|
||
|
||
# Новые обработчики кнопок
|
||
self.router.message.register(
|
||
self.refresh_listen_function,
|
||
ChatTypeFilter(chat_type=["private"]),
|
||
F.text == "🔄Сбросить прослушивания"
|
||
)
|
||
|
||
self.router.message.register(
|
||
self.handle_emoji_message,
|
||
ChatTypeFilter(chat_type=["private"]),
|
||
F.text == "😊Узнать эмодзи"
|
||
)
|
||
|
||
@track_time("voice_bot_button_handler", "voice_handlers")
|
||
@track_errors("voice_handlers", "voice_bot_button_handler")
|
||
async def voice_bot_button_handler(self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings")):
|
||
"""Обработчик кнопки 'Голосовой бот' из основной клавиатуры"""
|
||
logger.info(f"Пользователь {message.from_user.id} ({message.from_user.full_name}) нажал кнопку 'Голосовой бот'")
|
||
try:
|
||
# Проверяем, получал ли пользователь приветственное сообщение
|
||
welcome_received = await bot_db.check_voice_bot_welcome_received(message.from_user.id)
|
||
logger.info(f"Пользователь {message.from_user.id}: welcome_received = {welcome_received}")
|
||
|
||
if welcome_received:
|
||
# Если уже получал приветствие, вызываем restart_function
|
||
logger.info(f"Пользователь {message.from_user.id}: вызываем restart_function")
|
||
await self.restart_function(message, state, bot_db, settings)
|
||
else:
|
||
# Если не получал, вызываем start
|
||
logger.info(f"Пользователь {message.from_user.id}: вызываем start")
|
||
await self.start(message, state, bot_db, settings)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при проверке приветственного сообщения для пользователя {message.from_user.id}: {e}")
|
||
# В случае ошибки вызываем start
|
||
await self.start(message, state, bot_db, settings)
|
||
|
||
@track_time("restart_function", "voice_handlers")
|
||
@track_errors("voice_handlers", "restart_function")
|
||
async def restart_function(
|
||
self,
|
||
message: types.Message,
|
||
state: FSMContext,
|
||
bot_db: MagicData("bot_db"),
|
||
settings: MagicData("settings")
|
||
):
|
||
logger.info(f"Пользователь {message.from_user.id}: вызывается функция restart_function")
|
||
await message.forward(chat_id=settings['Telegram']['group_for_logs'])
|
||
await update_user_info(VOICE_BOT_NAME, message)
|
||
await check_user_emoji(message)
|
||
markup = get_main_keyboard()
|
||
await message.answer(text='🎤 Записывайся или слушай!', reply_markup=markup)
|
||
await state.set_state(STATE_START)
|
||
|
||
@track_time("handle_emoji_message", "voice_handlers")
|
||
@track_errors("voice_handlers", "handle_emoji_message")
|
||
async def handle_emoji_message(
|
||
self,
|
||
message: types.Message,
|
||
state: FSMContext,
|
||
settings: MagicData("settings")
|
||
):
|
||
logger.info(f"Пользователь {message.from_user.id} ({message.from_user.full_name}) запросил информацию об эмодзи")
|
||
await message.forward(chat_id=settings['Telegram']['group_for_logs'])
|
||
user_emoji = await 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')
|
||
|
||
@track_time("help_function", "voice_handlers")
|
||
@track_errors("voice_handlers", "help_function")
|
||
async def help_function(
|
||
self,
|
||
message: types.Message,
|
||
state: FSMContext,
|
||
settings: MagicData("settings")
|
||
):
|
||
logger.info(f"Пользователь {message.from_user.id} ({message.from_user.full_name}) вызвал функцию help_function")
|
||
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 settings['Telegram']['preview_link']
|
||
)
|
||
await state.set_state(STATE_START)
|
||
|
||
@track_time("start", "voice_handlers")
|
||
@track_errors("voice_handlers", "start")
|
||
@db_query_time("mark_voice_bot_welcome_received", "audio_moderate", "update")
|
||
async def start(
|
||
self,
|
||
message: types.Message,
|
||
state: FSMContext,
|
||
bot_db: MagicData("bot_db"),
|
||
settings: MagicData("settings")
|
||
):
|
||
logger.info(f"Пользователь {message.from_user.id} ({message.from_user.full_name}): вызывается функция start")
|
||
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 = await 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)
|
||
logger.info(f"Приветственные сообщения отправлены пользователю {message.from_user.id}")
|
||
|
||
# Отмечаем, что пользователь получил приветственное сообщение
|
||
try:
|
||
await bot_db.mark_voice_bot_welcome_received(message.from_user.id)
|
||
logger.info(f"Пользователь {message.from_user.id}: отмечен как получивший приветствие")
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при отметке получения приветствия для пользователя {message.from_user.id}: {e}")
|
||
|
||
@track_time("cancel_handler", "voice_handlers")
|
||
@track_errors("voice_handlers", "cancel_handler")
|
||
async def cancel_handler(
|
||
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 = await get_reply_keyboard(self.db, message.from_user.id)
|
||
await message.answer(text='Добро пожаловать в меню!', reply_markup=markup, parse_mode='HTML')
|
||
await state.set_state(FSM_STATES["START"])
|
||
logger.info(f"Пользователь {message.from_user.id} возвращен в главное меню")
|
||
|
||
@track_time("refresh_listen_function", "voice_handlers")
|
||
@track_errors("voice_handlers", "refresh_listen_function")
|
||
async def refresh_listen_function(
|
||
self,
|
||
message: types.Message,
|
||
state: FSMContext,
|
||
bot_db: MagicData("bot_db"),
|
||
settings: MagicData("settings")
|
||
):
|
||
logger.info(f"Пользователь {message.from_user.id} ({message.from_user.full_name}) вызвал функцию refresh_listen_function")
|
||
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)
|
||
await 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)
|
||
|
||
|
||
@track_time("standup_write", "voice_handlers")
|
||
@track_errors("voice_handlers", "standup_write")
|
||
async def standup_write(
|
||
self,
|
||
message: types.Message,
|
||
state: FSMContext,
|
||
bot_db: MagicData("bot_db"),
|
||
settings: MagicData("settings")
|
||
):
|
||
logger.info(f"Пользователь {message.from_user.id} ({message.from_user.full_name}) вызвал функцию standup_write")
|
||
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 = await 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'Не удалось получить дату последнего сообщения для пользователя {message.from_user.id}: {e}')
|
||
|
||
await state.set_state(STATE_STANDUP_WRITE)
|
||
|
||
|
||
@track_time("suggest_voice", "voice_handlers")
|
||
@track_errors("voice_handlers", "suggest_voice")
|
||
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 await 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
|
||
)
|
||
logger.info(f"Голосовое сообщение пользователя {message.from_user.id} отправлено в группу постов (message_id: {sent_message.message_id})")
|
||
|
||
# Сохраняем в базу инфо о посте
|
||
await 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:
|
||
logger.warning(f"Голосовое сообщение пользователя {message.from_user.id} не прошло валидацию")
|
||
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)
|
||
|
||
|
||
@track_time("standup_listen_audio", "voice_handlers")
|
||
@track_errors("voice_handlers", "standup_listen_audio")
|
||
@track_file_operations("voice")
|
||
@db_query_time("standup_listen_audio", "audio_moderate", "mixed")
|
||
async def standup_listen_audio(
|
||
self,
|
||
message: types.Message,
|
||
bot_db: MagicData("bot_db"),
|
||
settings: MagicData("settings")
|
||
):
|
||
logger.info(f"Пользователь {message.from_user.id} ({message.from_user.full_name}) запросил прослушивание аудио")
|
||
markup = get_main_keyboard()
|
||
|
||
# Создаем сервис для работы с аудио
|
||
voice_service = VoiceBotService(bot_db, settings)
|
||
|
||
try:
|
||
#TODO: удалить логику из хендлера
|
||
# Получаем случайное аудио
|
||
audio_data = await voice_service.get_random_audio(message.from_user.id)
|
||
|
||
if not audio_data:
|
||
logger.warning(f"Для пользователя {message.from_user.id} не найдено доступных аудио для прослушивания")
|
||
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 = await 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'Не удалось получить последнюю дату для пользователя {message.from_user.id}: {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} для пользователя {message.from_user.id}")
|
||
# Дополнительная диагностика
|
||
logger.error(f"Директория {VOICE_USERS_DIR} существует: {Path(VOICE_USERS_DIR).exists()}")
|
||
if Path(VOICE_USERS_DIR).exists():
|
||
files_in_dir = list(Path(VOICE_USERS_DIR).glob("*.ogg"))
|
||
logger.error(f"Файлы в директории: {[f.name for f in files_in_dir]}")
|
||
|
||
await message.answer(
|
||
text="Файл аудио не найден. Обратитесь к администратору.",
|
||
reply_markup=markup
|
||
)
|
||
return
|
||
|
||
# Проверяем размер файла
|
||
if path.stat().st_size == 0:
|
||
logger.error(f"Файл пустой: {path} для пользователя {message.from_user.id}")
|
||
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}'
|
||
|
||
logger.info(f"Подготовлено голосовое сообщение для пользователя {message.from_user.id}: {caption}")
|
||
|
||
try:
|
||
from helper_bot.utils.rate_limiter import send_with_rate_limit
|
||
|
||
async def _send_voice():
|
||
return await message.bot.send_voice(
|
||
chat_id=message.chat.id,
|
||
voice=voice,
|
||
caption=caption,
|
||
reply_markup=markup
|
||
)
|
||
|
||
await send_with_rate_limit(_send_voice, message.chat.id)
|
||
|
||
# Маркируем сообщение как прослушанное только после успешной отправки
|
||
await voice_service.mark_audio_as_listened(audio_for_user, message.from_user.id)
|
||
|
||
# Получаем количество оставшихся аудио только после успешной отправки
|
||
remaining_count = await voice_service.get_remaining_audio_count(message.from_user.id)
|
||
await message.answer(
|
||
text=f'Осталось непрослушанных: <b>{remaining_count}</b>',
|
||
reply_markup=markup
|
||
)
|
||
|
||
except Exception as voice_error:
|
||
if "VOICE_MESSAGES_FORBIDDEN" in str(voice_error):
|
||
# Если голосовые сообщения запрещены, отправляем информативное сообщение
|
||
logger.warning(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:
|
||
logger.error(f"Ошибка при отправке голосового сообщения пользователю {message.from_user.id}: {voice_error}")
|
||
raise voice_error
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при прослушивании аудио для пользователя {message.from_user.id}: {e}")
|
||
await message.answer(
|
||
text="Произошла ошибка при получении аудио. Попробуйте позже.",
|
||
reply_markup=markup
|
||
)
|