Enhance bot functionality and refactor database interactions

- Added `ca-certificates` installation to Dockerfile for improved network security.
- Updated health check command in Dockerfile to include better timeout handling.
- Refactored `run_helper.py` to implement proper signal handling and logging during shutdown.
- Transitioned database operations to an asynchronous model in `async_db.py`, improving performance and responsiveness.
- Updated database schema to support new foreign key relationships and optimized indexing for better query performance.
- Enhanced various bot handlers to utilize async database methods, improving overall efficiency and user experience.
- Removed obsolete database and fix scripts to streamline the project structure.
This commit is contained in:
2025-09-02 18:22:02 +03:00
parent 013892dcb7
commit 1c6a37bc12
59 changed files with 5682 additions and 4204 deletions

View File

@@ -22,7 +22,6 @@ class VoiceMessage:
self.date_added = date_added
self.file_id = file_id
class VoiceBotService:
"""Сервис для работы с голосовыми сообщениями"""
@@ -141,10 +140,10 @@ class VoiceBotService:
logger.error(f"Ошибка при отправке приветственных сообщений: {e}")
raise VoiceMessageError(f"Не удалось отправить приветственные сообщения: {e}")
def get_random_audio(self, user_id: int) -> Optional[Tuple[str, str, str]]:
async 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)
check_audio = await self.bot_db.check_listen_audio(user_id=user_id)
list_audio = list(check_audio)
if not list_audio:
@@ -155,9 +154,9 @@ class VoiceBotService:
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)
user_id_author = await self.bot_db.get_user_id_by_file_name(audio_for_user)
date_added = await self.bot_db.get_date_by_file_name(audio_for_user)
user_emoji = await self.bot_db.get_user_emoji(user_id_author)
return audio_for_user, date_added, user_emoji
@@ -165,26 +164,26 @@ class VoiceBotService:
logger.error(f"Ошибка при получении случайного аудио: {e}")
raise AudioProcessingError(f"Не удалось получить случайное аудио: {e}")
def mark_audio_as_listened(self, file_name: str, user_id: int) -> None:
async 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)
await 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:
async def clear_user_listenings(self, user_id: int) -> None:
"""Очистить прослушивания пользователя"""
try:
self.bot_db.delete_listen_count_for_user(user_id)
await 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:
async def get_remaining_audio_count(self, user_id: int) -> int:
"""Получить количество оставшихся непрослушанных аудио"""
try:
check_audio = self.bot_db.check_listen_audio(user_id=user_id)
check_audio = await self.bot_db.check_listen_audio(user_id=user_id)
return len(list(check_audio))
except Exception as e:
logger.error(f"Ошибка при получении количества аудио: {e}")
@@ -215,23 +214,29 @@ class AudioFileService:
def __init__(self, bot_db):
self.bot_db = bot_db
def generate_file_name(self, user_id: int) -> str:
async 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)
user_audio_count = await self.bot_db.get_user_audio_records_count(user_id=user_id)
if is_having_audio_from_user is False:
if user_audio_count == 0:
# Если нет, то генерируем имя файла
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')
file_name = await self.bot_db.get_path_for_audio_record(user_id=user_id)
if file_name:
# Извлекаем номер из имени файла и увеличиваем на 1
try:
current_number = int(file_name.split('_')[-1])
new_number = current_number + 1
except (ValueError, IndexError):
new_number = user_audio_count + 1
else:
new_number = user_audio_count + 1
if path.exists():
file_name = f'message_from_{user_id}_number_{file_id}'
file_name = f'message_from_{user_id}_number_{new_number}'
return file_name
@@ -239,23 +244,31 @@ class AudioFileService:
logger.error(f"Ошибка при генерации имени файла: {e}")
raise FileOperationError(f"Не удалось сгенерировать имя файла: {e}")
def save_audio_file(self, file_name: str, user_id: int, date_added: datetime) -> None:
async def save_audio_file(self, file_name: str, user_id: int, date_added: datetime, file_id: str) -> None:
"""Сохранить информацию об аудио файле в базу данных"""
try:
self.bot_db.add_audio_record(file_name, user_id, date_added)
await self.bot_db.add_audio_record_simple(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:
async def download_and_save_audio(self, bot, message, file_name: str) -> None:
"""Скачать и сохранить аудио файл"""
try:
# Получаем информацию о файле
file_info = await bot.get_file(file_id=bot.get_message(message_id).voice.file_id)
# Проверяем наличие голосового сообщения
if not message or not message.voice:
raise FileOperationError("Сообщение или голосовое сообщение не найдено")
file_id = message.voice.file_id
file_info = await bot.get_file(file_id=file_id)
downloaded_file = await bot.download_file(file_path=file_info.file_path)
# Создаем директорию если она не существует
import os
os.makedirs(VOICE_USERS_DIR, exist_ok=True)
# Сохраняем файл
with open(f'voice_users/{file_name}.ogg', 'wb') as new_file:
with open(f'{VOICE_USERS_DIR}/{file_name}.ogg', 'wb') as new_file:
new_file.write(downloaded_file.read())
except Exception as e:

View File

@@ -70,26 +70,30 @@ def plural_time(type: int, n: float) -> str:
return str(new_number) + ' ' + word[p]
def get_last_message_text(bot_db) -> Optional[str]:
async 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)
date_from_db = await bot_db.last_date_audio()
if date_from_db is None:
return None
# Преобразуем UNIX timestamp в строку для format_time_ago
date_string = datetime.fromtimestamp(date_from_db).strftime("%Y-%m-%d %H:%M:%S")
return format_time_ago(date_string)
except Exception as e:
logger.error(f"Не удалось получить дату последнего сообщения - {e}")
return None
def validate_voice_message(message) -> bool:
async def validate_voice_message(message) -> bool:
"""Проверить валидность голосового сообщения"""
return message.content_type == 'voice'
def get_user_emoji_safe(bot_db, user_id: int) -> str:
async 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 "😊"
user_emoji = await bot_db.get_user_emoji(user_id)
return user_emoji if user_emoji and user_emoji != "Смайл еще не определен" else "😊"
except Exception as e:
logger.error(f"Ошибка при получении эмодзи пользователя {user_id}: {e}")
return "😊"

View File

@@ -98,7 +98,7 @@ class VoiceHandlers:
"""Обработчик кнопки 'Голосовой бот' из основной клавиатуры"""
try:
# Проверяем, получал ли пользователь приветственное сообщение
welcome_received = bot_db.check_voice_bot_welcome_received(message.from_user.id)
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:
@@ -124,7 +124,7 @@ class VoiceHandlers:
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)
check_user_emoji(message)
await check_user_emoji(message)
markup = get_main_keyboard()
await message.answer(text='🎤 Записывайся или слушай!', reply_markup=markup)
await state.set_state(STATE_START)
@@ -136,7 +136,7 @@ class VoiceHandlers:
settings: MagicData("settings")
):
await message.forward(chat_id=settings['Telegram']['group_for_logs'])
user_emoji = check_user_emoji(message)
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')
@@ -167,7 +167,7 @@ class VoiceHandlers:
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)
user_emoji = await get_user_emoji_safe(bot_db, message.from_user.id)
# Создаем сервис и отправляем приветственные сообщения
voice_service = VoiceBotService(bot_db, settings)
@@ -175,7 +175,7 @@ class VoiceHandlers:
# Отмечаем, что пользователь получил приветственное сообщение
try:
bot_db.mark_voice_bot_welcome_received(message.from_user.id)
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"Ошибка при отметке получения приветствия: {e}")
@@ -194,7 +194,7 @@ class VoiceHandlers:
# Очищаем прослушивания через сервис
voice_service = VoiceBotService(bot_db, settings)
voice_service.clear_user_listenings(message.from_user.id)
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(
@@ -218,7 +218,7 @@ class VoiceHandlers:
await message.answer(text=record_voice_message, reply_markup=markup)
try:
message_with_date = get_last_message_text(bot_db)
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:
@@ -240,7 +240,7 @@ class VoiceHandlers:
await message.forward(chat_id=settings['Telegram']['group_for_logs'])
markup = get_main_keyboard()
if validate_voice_message(message):
if await validate_voice_message(message):
markup_for_voice = get_reply_keyboard_for_voice()
# Отправляем аудио в приватный канал
@@ -252,7 +252,7 @@ class VoiceHandlers:
)
# Сохраняем в базу инфо о посте
bot_db.set_user_id_and_message_id_for_voice_bot(sent_message.message_id, message.from_user.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')
@@ -278,13 +278,13 @@ class VoiceHandlers:
try:
# Получаем случайное аудио
audio_data = voice_service.get_random_audio(message.from_user.id)
audio_data = await 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)
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:
@@ -331,10 +331,10 @@ class VoiceHandlers:
)
# Маркируем сообщение как прослушанное только после успешной отправки
voice_service.mark_audio_as_listened(audio_for_user, message.from_user.id)
await 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
remaining_count = await voice_service.get_remaining_audio_count(message.from_user.id) - 1
await message.answer(
text=f'Осталось непрослушанных: <b>{remaining_count}</b>',
reply_markup=markup