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

@@ -1,6 +1,16 @@
import html
import traceback
import time
from datetime import datetime
from aiogram import Router, F
from aiogram.types import CallbackQuery
from helper_bot.handlers.voice.constants import CALLBACK_SAVE, CALLBACK_DELETE
from helper_bot.handlers.voice.services import AudioFileService
from logs.custom_logger import logger
from aiogram import Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery
@@ -185,3 +195,60 @@ async def change_page(
message_id=call.message.message_id,
reply_markup=keyboard
)
@callback_router.callback_query(F.data == CALLBACK_SAVE)
async def save_voice_message(
call: CallbackQuery,
bot_db: MagicData("bot_db")
):
try:
# Создаем сервис для работы с аудио файлами
audio_service = AudioFileService(bot_db)
# Получаем ID пользователя из базы
user_id = bot_db.get_user_id_by_message_id_for_voice_bot(call.message.message_id)
# Генерируем имя файла
file_name = audio_service.generate_file_name(user_id)
# Собираем инфо о сообщении
time_UTC = int(time.time())
date_added = datetime.fromtimestamp(time_UTC)
# Сохраняем в базу данных
audio_service.save_audio_file(file_name, user_id, date_added)
# Скачиваем и сохраняем файл
await audio_service.download_and_save_audio(call.bot, call.message.message_id, file_name)
# Удаляем сообщение из предложки
await call.bot.delete_message(
chat_id=bot_db.settings['Telegram']['group_for_posts'],
message_id=call.message.message_id
)
await call.answer(text='Сохранено!', cache_time=3)
except Exception as e:
logger.error(f"Ошибка при сохранении голосового сообщения: {e}")
await call.answer(text='Ошибка при сохранении!', cache_time=3)
@callback_router.callback_query(F.data == CALLBACK_DELETE)
async def delete_voice_message(
call: CallbackQuery,
bot_db: MagicData("bot_db")
):
try:
# Удаляем сообщение из предложки
await call.bot.delete_message(
chat_id=bot_db.settings['Telegram']['group_for_posts'],
message_id=call.message.message_id
)
await call.answer(text='Удалено!', cache_time=3)
except Exception as e:
logger.error(f"Ошибка при удалении голосового сообщения: {e}")
await call.answer(text='Ошибка при удалении!', cache_time=3)

View File

@@ -17,7 +17,8 @@ BUTTON_TEXTS: Final[Dict[str, str]] = {
"LEAVE_CHAT": "Выйти из чата",
"RETURN_TO_BOT": "Вернуться в бота",
"WANT_STICKERS": "🤪Хочу стикеры",
"CONNECT_ADMIN": "📩Связаться с админами"
"CONNECT_ADMIN": "📩Связаться с админами",
"VOICE_BOT": "🎤Голосовой бот"
}
# Button to command mapping for metrics
@@ -27,7 +28,8 @@ BUTTON_COMMAND_MAPPING: Final[Dict[str, str]] = {
"Выйти из чата": "leave_chat",
"Вернуться в бота": "return_to_bot",
"🤪Хочу стикеры": "want_stickers",
"📩Связаться с админами": "connect_admin"
"📩Связаться с админами": "connect_admin",
"🎤Голосовой бот": "voice_bot"
}
# Error messages

View File

@@ -72,12 +72,13 @@ class PrivateHandlers:
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
async def handle_emoji_message(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle emoji command"""

View File

@@ -0,0 +1,3 @@
from .voice_handler import VoiceHandlers
__all__ = ["VoiceHandlers"]

View File

@@ -0,0 +1,56 @@
from typing import Final, Dict
# Voice bot constants
VOICE_BOT_NAME = "voice"
# States
STATE_START = "START"
STATE_STANDUP_WRITE = "STANDUP_WRITE"
# Commands
CMD_START = "start"
CMD_HELP = "help"
CMD_RESTART = "restart"
CMD_EMOJI = "emoji"
CMD_REFRESH = "refresh"
# Command to command mapping for metrics
COMMAND_MAPPING: Final[Dict[str, str]] = {
"start": "voice_start",
"help": "voice_help",
"restart": "voice_restart",
"emoji": "voice_emoji",
"refresh": "voice_refresh"
}
# Button texts
BTN_SPEAK = "🎤Высказаться"
BTN_LISTEN = "🎧Послушать"
# Button to command mapping for metrics
BUTTON_COMMAND_MAPPING: Final[Dict[str, str]] = {
"🎤Высказаться": "voice_speak",
"🎧Послушать": "voice_listen"
}
# Callback data
CALLBACK_SAVE = "save"
CALLBACK_DELETE = "delete"
# Callback to command mapping for metrics
CALLBACK_COMMAND_MAPPING: Final[Dict[str, str]] = {
"save": "voice_save",
"delete": "voice_delete"
}
# File paths
VOICE_USERS_DIR = "voice_users"
STICK_DIR = "Stick"
STICK_PATTERN = "Hello_*"
# Time delays
STICKER_DELAY = 0.3
MESSAGE_DELAY_1 = 1.0
MESSAGE_DELAY_2 = 1.5
MESSAGE_DELAY_3 = 1.3
MESSAGE_DELAY_4 = 0.8

View File

@@ -0,0 +1,23 @@
class VoiceBotError(Exception):
"""Базовое исключение для voice_bot"""
pass
class VoiceMessageError(VoiceBotError):
"""Ошибка при работе с голосовыми сообщениями"""
pass
class AudioProcessingError(VoiceBotError):
"""Ошибка при обработке аудио"""
pass
class DatabaseError(VoiceBotError):
"""Ошибка базы данных"""
pass
class FileOperationError(VoiceBotError):
"""Ошибка при работе с файлами"""
pass

View File

@@ -0,0 +1,263 @@
import random
import asyncio
from datetime import datetime
from pathlib import Path
from typing import List, Optional, Tuple
from aiogram.types import FSInputFile
from helper_bot.handlers.voice.exceptions import VoiceMessageError, AudioProcessingError, DatabaseError, FileOperationError
from helper_bot.handlers.voice.constants import (
VOICE_USERS_DIR, STICK_DIR, STICK_PATTERN, STICKER_DELAY,
MESSAGE_DELAY_1, MESSAGE_DELAY_2, MESSAGE_DELAY_3, MESSAGE_DELAY_4
)
from logs.custom_logger import logger
class VoiceMessage:
"""Модель голосового сообщения"""
def __init__(self, file_name: str, user_id: int, date_added: datetime, file_id: int):
self.file_name = file_name
self.user_id = user_id
self.date_added = date_added
self.file_id = file_id
class VoiceBotService:
"""Сервис для работы с голосовыми сообщениями"""
def __init__(self, bot_db, settings):
self.bot_db = bot_db
self.settings = settings
async def get_welcome_sticker(self) -> Optional[FSInputFile]:
"""Получить случайный приветственный стикер"""
try:
name_stick_hello = list(Path(STICK_DIR).rglob(STICK_PATTERN))
if not name_stick_hello:
return None
random_stick_hello = random.choice(name_stick_hello)
random_stick_hello = FSInputFile(path=random_stick_hello)
logger.info(f"Стикер успешно получен. Наименование стикера: {random_stick_hello}")
return random_stick_hello
except Exception as e:
logger.error(f"Ошибка при получении стикера: {e}")
if self.settings['Settings']['logs']:
await self._send_error_to_logs(f'Отправка приветственных стикеров лажает. Ошибка: {e}')
return None
async def send_welcome_messages(self, message, user_emoji: str):
"""Отправить приветственные сообщения"""
try:
# Отправляем стикер
sticker = await self.get_welcome_sticker()
if sticker:
await message.answer_sticker(sticker)
await asyncio.sleep(STICKER_DELAY)
# Отправляем приветственное сообщение
markup = self._get_main_keyboard()
await message.answer(
text="<b>Привет.</b>",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
await asyncio.sleep(STICKER_DELAY)
# Отправляем описание
await message.answer(
text="<i>Здесь можно послушать голосовые сообщения от совершенно незнакомых людей из Бийска</i>",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
await asyncio.sleep(MESSAGE_DELAY_1)
# Отправляем аналогию
await message.answer(
text="Это почти как написать письмо, положить его в бутылку и швырнуть в океан. Никогда не узнаешь, послушал его кто-то или нет и ответить тоже не получится..",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
await asyncio.sleep(MESSAGE_DELAY_2)
# Отправляем правила
await message.answer(
text="Записывать можно всё что угодно — никаких правил нет. Главное — твой голос, <i>хотя бы на 5-10 секунд</i>",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
await asyncio.sleep(MESSAGE_DELAY_3)
# Отправляем информацию об анонимности
await message.answer(
text="Здесь всё анонимно: тот, кому я отправлю твое сообщение, не узнает ни твое имя, ни твой аккаунт (так что можно не стесняться говорить то, что не стал(а) бы выкладывать в собственные соцсети)",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
await asyncio.sleep(MESSAGE_DELAY_4)
# Отправляем предложения
await message.answer(
text="Если не знаешь, что сказать, можешь просто прочитать любое текстовое сообщение из недавно полученных или отправленных (или спеть, рассказать стихотворенье)",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
await asyncio.sleep(MESSAGE_DELAY_4)
# Отправляем информацию об эмодзи
await message.answer(
text=f"Любые войсы будут помечены эмоджи. <b>Твой эмоджи - </b>{user_emoji}Таким эмоджи будут помечены твои сообщения для других Но другие люди не узнают кто за каким эмоджи скрывается:)",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
await asyncio.sleep(MESSAGE_DELAY_4)
# Отправляем информацию о помощи
await message.answer(
text="Так же можешь ознакомиться с инструкцией к боту по команде /help",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
await asyncio.sleep(MESSAGE_DELAY_4)
# Отправляем финальное сообщение
await message.answer(
text="<b>Ну всё, достаточно инструкций. записывайся! Микрофон твой - </b> 🎤",
parse_mode='html',
reply_markup=markup,
disable_web_page_preview=not self.settings['Telegram']['preview_link']
)
except Exception as e:
logger.error(f"Ошибка при отправке приветственных сообщений: {e}")
raise VoiceMessageError(f"Не удалось отправить приветственные сообщения: {e}")
def get_random_audio(self, user_id: int) -> Optional[Tuple[str, str, str]]:
"""Получить случайное аудио для прослушивания"""
try:
check_audio = self.bot_db.check_listen_audio(user_id=user_id)
list_audio = list(check_audio)
if not list_audio:
return None
# Получаем случайное аудио
number_element = random.randint(0, len(list_audio) - 1)
audio_for_user = check_audio[number_element]
# Получаем информацию об авторе
user_id_author = self.bot_db.get_user_id_by_file_name(audio_for_user)
date_added = self.bot_db.get_date_by_file_name(audio_for_user)
user_emoji = self.bot_db.check_emoji_for_user(user_id_author)
return audio_for_user, date_added, user_emoji
except Exception as e:
logger.error(f"Ошибка при получении случайного аудио: {e}")
raise AudioProcessingError(f"Не удалось получить случайное аудио: {e}")
def mark_audio_as_listened(self, file_name: str, user_id: int) -> None:
"""Пометить аудио как прослушанное"""
try:
self.bot_db.mark_listened_audio(file_name, user_id=user_id)
except Exception as e:
logger.error(f"Ошибка при пометке аудио как прослушанного: {e}")
raise DatabaseError(f"Не удалось пометить аудио как прослушанное: {e}")
def clear_user_listenings(self, user_id: int) -> None:
"""Очистить прослушивания пользователя"""
try:
self.bot_db.delete_listen_count_for_user(user_id)
except Exception as e:
logger.error(f"Ошибка при очистке прослушиваний: {e}")
raise DatabaseError(f"Не удалось очистить прослушивания: {e}")
def get_remaining_audio_count(self, user_id: int) -> int:
"""Получить количество оставшихся непрослушанных аудио"""
try:
check_audio = self.bot_db.check_listen_audio(user_id=user_id)
return len(list(check_audio))
except Exception as e:
logger.error(f"Ошибка при получении количества аудио: {e}")
raise DatabaseError(f"Не удалось получить количество аудио: {e}")
def _get_main_keyboard(self):
"""Получить основную клавиатуру"""
from helper_bot.keyboards.keyboards import get_main_keyboard
return get_main_keyboard()
async def _send_error_to_logs(self, message: str) -> None:
"""Отправить ошибку в логи"""
try:
from helper_bot.utils.helper_func import send_voice_message
await send_voice_message(
self.settings['Telegram']['important_logs'],
None,
None,
None
)
except Exception as e:
logger.error(f"Не удалось отправить ошибку в логи: {e}")
class AudioFileService:
"""Сервис для работы с аудио файлами"""
def __init__(self, bot_db):
self.bot_db = bot_db
def generate_file_name(self, user_id: int) -> str:
"""Сгенерировать имя файла для аудио"""
try:
# Проверяем есть ли запись о файле в базе данных
is_having_audio_from_user = self.bot_db.get_last_user_audio_record(user_id=user_id)
if is_having_audio_from_user is False:
# Если нет, то генерируем имя файла
file_name = f'message_from_{user_id}_number_1'
else:
# Иначе берем последнюю запись из БД, добавляем к ней 1
file_name = self.bot_db.get_path_for_audio_record(user_id=user_id)
file_id = self.bot_db.get_id_for_audio_record(user_id) + 1
path = Path(f'voice_users/{file_name}.ogg')
if path.exists():
file_name = f'message_from_{user_id}_number_{file_id}'
return file_name
except Exception as e:
logger.error(f"Ошибка при генерации имени файла: {e}")
raise FileOperationError(f"Не удалось сгенерировать имя файла: {e}")
def save_audio_file(self, file_name: str, user_id: int, date_added: datetime) -> None:
"""Сохранить информацию об аудио файле в базу данных"""
try:
self.bot_db.add_audio_record(file_name, user_id, date_added)
except Exception as e:
logger.error(f"Ошибка при сохранении аудио файла в БД: {e}")
raise DatabaseError(f"Не удалось сохранить аудио файл в БД: {e}")
async def download_and_save_audio(self, bot, message_id: int, file_name: str) -> None:
"""Скачать и сохранить аудио файл"""
try:
# Получаем информацию о файле
file_info = await bot.get_file(file_id=bot.get_message(message_id).voice.file_id)
downloaded_file = await bot.download_file(file_path=file_info.file_path)
# Сохраняем файл
with open(f'voice_users/{file_name}.ogg', 'wb') as new_file:
new_file.write(downloaded_file.read())
except Exception as e:
logger.error(f"Ошибка при скачивании и сохранении аудио: {e}")
raise FileOperationError(f"Не удалось скачать и сохранить аудио: {e}")

View File

@@ -0,0 +1,95 @@
import time
import html
from datetime import datetime
from typing import Optional
from helper_bot.handlers.voice.exceptions import DatabaseError
from logs.custom_logger import logger
def format_time_ago(date_from_db: str) -> Optional[str]:
"""Форматировать время с момента последней записи"""
try:
if date_from_db is None:
return None
parse_date = datetime.strptime(date_from_db, "%Y-%m-%d %H:%M:%S")
last_voice_time_timestamp = time.mktime(parse_date.timetuple())
time_now_timestamp = time.time()
date_difference = time_now_timestamp - last_voice_time_timestamp
# Считаем минуты, часы, дни
much_minutes_ago = round(date_difference / 60, 0)
much_hour_ago = round(date_difference / 3600, 0)
much_days_ago = int(round(much_hour_ago / 24, 0))
message_with_date = ''
if much_minutes_ago <= 60:
word_minute = plural_time(1, much_minutes_ago)
# Экранируем потенциально проблемные символы
word_minute_escaped = html.escape(word_minute)
message_with_date = f'<b>Последнее сообщение было записано {word_minute_escaped} назад</b>'
elif much_minutes_ago > 60 and much_hour_ago <= 24:
word_hour = plural_time(2, much_hour_ago)
# Экранируем потенциально проблемные символы
word_hour_escaped = html.escape(word_hour)
message_with_date = f'<b>Последнее сообщение было записано {word_hour_escaped} назад</b>'
elif much_hour_ago > 24:
word_day = plural_time(3, much_days_ago)
# Экранируем потенциально проблемные символы
word_day_escaped = html.escape(word_day)
message_with_date = f'<b>Последнее сообщение было записано {word_day_escaped} назад</b>'
return message_with_date
except Exception as e:
logger.error(f"Ошибка при форматировании времени: {e}")
return None
def plural_time(type: int, n: float) -> str:
"""Форматировать множественное число для времени"""
word = []
if type == 1:
word = ['минуту', 'минуты', 'минут']
elif type == 2:
word = ['час', 'часа', 'часов']
elif type == 3:
word = ['день', 'дня', 'дней']
else:
return str(int(n))
if n % 10 == 1 and n % 100 != 11:
p = 0
elif 2 <= n % 10 <= 4 and (n % 100 < 10 or n % 100 >= 20):
p = 1
else:
p = 2
new_number = int(n)
return str(new_number) + ' ' + word[p]
def get_last_message_text(bot_db) -> Optional[str]:
"""Получить текст сообщения о времени последней записи"""
try:
date_from_db = bot_db.last_date_audio()
return format_time_ago(date_from_db)
except Exception as e:
logger.error(f"Не удалось получить дату последнего сообщения - {e}")
return None
def validate_voice_message(message) -> bool:
"""Проверить валидность голосового сообщения"""
return message.content_type == 'voice'
def get_user_emoji_safe(bot_db, user_id: int) -> str:
"""Безопасно получить эмодзи пользователя"""
try:
user_emoji = bot_db.check_emoji_for_user(user_id)
return user_emoji if user_emoji else "😊"
except Exception as e:
logger.error(f"Ошибка при получении эмодзи пользователя {user_id}: {e}")
return "😊"

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
)