Update voice bot functionality and clean up project structure

- Added voice message handling capabilities, including saving and deleting audio messages via callback queries.
- Refactored audio record management in the database to remove unnecessary fields and streamline operations.
- Introduced new keyboard options for voice interactions in the bot.
- Updated `.gitignore` to include voice user files for better project organization.
- Removed obsolete voice bot handler files to simplify the codebase.
This commit is contained in:
2025-09-01 19:17:05 +03:00
parent d128e54694
commit 2d40f4496e
29 changed files with 757 additions and 724 deletions

View File

@@ -0,0 +1,336 @@
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'Осталось непрослушанных: <b>{remaining_count}</b>',
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
)