Enhance bot functionality with new features and improvements

- Added a new `/status` endpoint in `server_prometheus.py` to provide process status information, including uptime and resource usage metrics.
- Implemented a PID manager in `run_helper.py` to track the bot's process, improving monitoring capabilities.
- Introduced a method to delete audio moderation records in `audio_repository.py`, enhancing database management.
- Updated voice message handling in callback handlers to ensure proper deletion of audio moderation records.
- Improved error handling and logging in various services, ensuring better tracking of media processing and file downloads.
- Refactored media handling functions to streamline operations and improve code readability.
- Enhanced metrics tracking for file downloads and media processing, providing better insights into bot performance.
This commit is contained in:
2025-09-04 00:46:45 +03:00
parent ae7bd476bb
commit fc0517c011
17 changed files with 1421 additions and 84 deletions

View File

@@ -1,9 +1,10 @@
import html
import os
import random
import time
from datetime import datetime, timedelta
from time import sleep
from typing import List, Dict, Any, Optional, TYPE_CHECKING
from typing import List, Dict, Any, Optional, TYPE_CHECKING, Union
try:
import emoji as _emoji_lib
@@ -115,31 +116,79 @@ def get_text_message(post_text: str, first_name: str, username: str = None):
return f'Пост из ТГ:\n{safe_post_text}\n\nАвтор поста: {author_info}'
async def download_file(message: types.Message, file_id: str):
async def download_file(message: types.Message, file_id: str, content_type: str = None) -> Optional[str]:
"""
Скачивает файл по file_id из Telegram.
Скачивает файл по file_id из Telegram и сохраняет в соответствующую папку.
Args:
message: сообщение
file_id: File ID фотографии
filename: Имя файла, под которым будет сохранено фото
file_id: File ID файла
content_type: тип контента (photo, video, audio, voice, video_note)
Returns:
Путь к сохраненному файлу, если файл был скачан успешно, иначе None
"""
start_time = time.time()
try:
os.makedirs("files", exist_ok=True)
os.makedirs("files/photos", exist_ok=True)
os.makedirs("files/videos", exist_ok=True)
os.makedirs("files/music", exist_ok=True)
os.makedirs("files/voice", exist_ok=True)
os.makedirs("files/video_notes", exist_ok=True)
# Валидация параметров
if not file_id or not message or not message.bot:
logger.error("download_file: Неверные параметры - file_id, message или bot отсутствуют")
return None
# Определяем папку по типу контента
type_folders = {
'photo': 'photos',
'video': 'videos',
'audio': 'music',
'voice': 'voice',
'video_note': 'video_notes'
}
folder = type_folders.get(content_type, 'other')
base_path = "files"
full_folder_path = os.path.join(base_path, folder)
# Создаем необходимые папки
os.makedirs(base_path, exist_ok=True)
os.makedirs(full_folder_path, exist_ok=True)
logger.debug(f"download_file: Начинаю скачивание файла {file_id} типа {content_type} в папку {folder}")
# Получаем информацию о файле
file = await message.bot.get_file(file_id)
file_path = os.path.join("files", file.file_path)
if not file or not file.file_path:
logger.error(f"download_file: Не удалось получить информацию о файле {file_id}")
return None
# Генерируем уникальное имя файла
original_filename = os.path.basename(file.file_path)
file_extension = os.path.splitext(original_filename)[1] or '.bin'
safe_filename = f"{file_id}{file_extension}"
file_path = os.path.join(full_folder_path, safe_filename)
# Скачиваем файл
await message.bot.download_file(file_path=file.file_path, destination=file_path)
# Проверяем, что файл действительно скачался
if not os.path.exists(file_path):
logger.error(f"download_file: Файл не был скачан - {file_path}")
return None
file_size = os.path.getsize(file_path)
download_time = time.time() - start_time
logger.info(f"download_file: Файл успешно скачан - {file_path}, размер: {file_size} байт, время: {download_time:.2f}с")
# Записываем метрики
metrics.record_file_download(content_type or 'unknown', file_size, download_time)
return file_path
except Exception as e:
logger.error(f"Ошибка скачивания фотографии: {e}")
download_time = time.time() - start_time
logger.error(f"download_file: Ошибка скачивания файла {file_id}: {e}, время: {download_time:.2f}с")
metrics.record_file_download_error(content_type or 'unknown', str(e))
return None
@@ -195,33 +244,123 @@ async def prepare_media_group_from_middlewares(album, post_caption: str = ''):
return media_group
async def add_in_db_media_mediagroup(sent_message, bot_db):
async def add_in_db_media_mediagroup(sent_message: List[types.Message], bot_db: Any, main_post_id: Optional[int] = None) -> bool:
"""
Добавляет контент медиа-группы в базу данных
Args:
sent_message: sent_message объект из Telegram API
bot_db: Экземпляр базы данных
main_post_id: ID основного поста медиагруппы (если не указан, используется последний message_id)
Returns:
None
bool: True если весь контент успешно добавлен, False в случае ошибки
"""
post_id = sent_message[-1].message_id # ID поста (первое сообщение в медиа-группе)
for i, message in enumerate(sent_message):
if message.photo:
file_id = message.photo[-1].file_id
file_path = await download_file(message, file_id=file_id)
await bot_db.add_post_content(post_id, message.message_id, file_path, 'photo')
elif message.video:
file_id = message.video.file_id
file_path = await download_file(message, file_id=file_id)
await bot_db.add_post_content(post_id, message.message_id, file_path, 'video')
start_time = time.time()
try:
# Валидация параметров
if not sent_message or not bot_db or not isinstance(sent_message, list):
logger.error("add_in_db_media_mediagroup: Неверные параметры - sent_message, bot_db или sent_message не является списком")
return False
if len(sent_message) == 0:
logger.warning("add_in_db_media_mediagroup: Пустая медиагруппа")
return False
# Используем переданный main_post_id или ID последнего сообщения
post_id = main_post_id or sent_message[-1].message_id
logger.debug(f"add_in_db_media_mediagroup: Обрабатываю медиагруппу из {len(sent_message)} сообщений, post_id: {post_id}")
processed_count = 0
failed_count = 0
for i, message in enumerate(sent_message):
try:
content_type = None
file_id = None
# Определяем тип контента и file_id
if message.photo:
content_type = 'photo'
file_id = message.photo[-1].file_id
elif message.video:
content_type = 'video'
file_id = message.video.file_id
elif message.audio:
content_type = 'audio'
file_id = message.audio.file_id
elif message.voice:
content_type = 'voice'
file_id = message.voice.file_id
elif message.video_note:
content_type = 'video_note'
file_id = message.video_note.file_id
else:
logger.warning(f"add_in_db_media_mediagroup: Неподдерживаемый тип контента в сообщении {i+1}/{len(sent_message)}")
failed_count += 1
continue
if not file_id:
logger.error(f"add_in_db_media_mediagroup: file_id отсутствует в сообщении {i+1}/{len(sent_message)}")
failed_count += 1
continue
logger.debug(f"add_in_db_media_mediagroup: Обрабатываю {content_type} в сообщении {i+1}/{len(sent_message)}")
# Скачиваем файл
file_path = await download_file(message, file_id=file_id, content_type=content_type)
if not file_path:
logger.error(f"add_in_db_media_mediagroup: Не удалось скачать файл {file_id} в сообщении {i+1}/{len(sent_message)}")
failed_count += 1
continue
# Добавляем в базу данных
success = await bot_db.add_post_content(post_id, message.message_id, file_path, content_type)
if not success:
logger.error(f"add_in_db_media_mediagroup: Не удалось добавить контент в БД для сообщения {i+1}/{len(sent_message)}")
# Удаляем скачанный файл при ошибке БД
try:
os.remove(file_path)
logger.debug(f"add_in_db_media_mediagroup: Удален файл {file_path} после ошибки БД")
except Exception as e:
logger.warning(f"add_in_db_media_mediagroup: Не удалось удалить файл {file_path}: {e}")
failed_count += 1
continue
processed_count += 1
logger.debug(f"add_in_db_media_mediagroup: Успешно обработано сообщение {i+1}/{len(sent_message)}")
except Exception as e:
logger.error(f"add_in_db_media_mediagroup: Ошибка обработки сообщения {i+1}/{len(sent_message)}: {e}")
failed_count += 1
continue
processing_time = time.time() - start_time
if processed_count == 0:
logger.error(f"add_in_db_media_mediagroup: Не удалось обработать ни одного сообщения из медиагруппы {post_id}")
metrics.record_media_processing('media_group', processing_time, False)
return False
if failed_count > 0:
logger.warning(f"add_in_db_media_mediagroup: Обработано {processed_count}/{len(sent_message)} сообщений медиагруппы {post_id}, ошибок: {failed_count}")
else:
# Если нет фото, видео или аудио, или другой контент, пропускаем сообщение
continue
logger.info(f"add_in_db_media_mediagroup: Успешно обработана медиагруппа {post_id} - {processed_count} сообщений, время: {processing_time:.2f}с")
# Записываем метрики
metrics.record_media_processing('media_group', processing_time, failed_count == 0)
return failed_count == 0
except Exception as e:
processing_time = time.time() - start_time
logger.error(f"add_in_db_media_mediagroup: Критическая ошибка обработки медиагруппы: {e}, время: {processing_time:.2f}с")
metrics.record_media_processing('media_group', processing_time, False)
return False
async def add_in_db_media(sent_message, bot_db):
async def add_in_db_media(sent_message: types.Message, bot_db: Any) -> bool:
"""
Добавляет контент одиночного сообщения в базу данных
@@ -230,33 +369,81 @@ async def add_in_db_media(sent_message, bot_db):
bot_db: Экземпляр базы данных
Returns:
None
bool: True если контент успешно добавлен, False в случае ошибки
"""
post_id = sent_message.message_id # ID поста (это же сообщение)
if sent_message.photo:
file_id = sent_message.photo[-1].file_id
file_path = await download_file(sent_message, file_id=file_id)
await bot_db.add_post_content(post_id, sent_message.message_id, file_path, 'photo')
elif sent_message.video:
file_id = sent_message.video.file_id
file_path = await download_file(sent_message, file_id=file_id)
await bot_db.add_post_content(post_id, sent_message.message_id, file_path, 'video')
elif sent_message.voice:
file_id = sent_message.voice.file_id
file_path = await download_file(sent_message, file_id=file_id)
await bot_db.add_post_content(post_id, sent_message.message_id, file_path, 'voice')
elif sent_message.audio:
file_id = sent_message.audio.file_id
file_path = await download_file(sent_message, file_id=file_id)
await bot_db.add_post_content(post_id, sent_message.message_id, file_path, 'audio')
elif sent_message.video_note:
file_id = sent_message.video_note.file_id
file_path = await download_file(sent_message, file_id=file_id)
await bot_db.add_post_content(post_id, sent_message.message_id, file_path, 'video_note')
start_time = time.time()
try:
# Валидация параметров
if not sent_message or not bot_db:
logger.error("add_in_db_media: Неверные параметры - sent_message или bot_db отсутствуют")
return False
post_id = sent_message.message_id # ID поста (это же сообщение)
content_type = None
file_id = None
# Определяем тип контента и file_id
if sent_message.photo:
content_type = 'photo'
file_id = sent_message.photo[-1].file_id
elif sent_message.video:
content_type = 'video'
file_id = sent_message.video.file_id
elif sent_message.voice:
content_type = 'voice'
file_id = sent_message.voice.file_id
elif sent_message.audio:
content_type = 'audio'
file_id = sent_message.audio.file_id
elif sent_message.video_note:
content_type = 'video_note'
file_id = sent_message.video_note.file_id
else:
logger.warning(f"add_in_db_media: Неподдерживаемый тип контента для сообщения {post_id}")
return False
if not file_id:
logger.error(f"add_in_db_media: file_id отсутствует для сообщения {post_id}")
return False
logger.debug(f"add_in_db_media: Обрабатываю {content_type} для сообщения {post_id}")
# Скачиваем файл
file_path = await download_file(sent_message, file_id=file_id, content_type=content_type)
if not file_path:
logger.error(f"add_in_db_media: Не удалось скачать файл {file_id} для сообщения {post_id}")
return False
# Добавляем в базу данных
success = await bot_db.add_post_content(post_id, sent_message.message_id, file_path, content_type)
if not success:
logger.error(f"add_in_db_media: Не удалось добавить контент в БД для сообщения {post_id}")
# Удаляем скачанный файл при ошибке БД
try:
os.remove(file_path)
logger.debug(f"add_in_db_media: Удален файл {file_path} после ошибки БД")
except Exception as e:
logger.warning(f"add_in_db_media: Не удалось удалить файл {file_path}: {e}")
return False
processing_time = time.time() - start_time
logger.info(f"add_in_db_media: Контент успешно добавлен для сообщения {post_id}, тип: {content_type}, время: {processing_time:.2f}с")
# Записываем метрики
metrics.record_media_processing(content_type, processing_time, True)
return True
except Exception as e:
processing_time = time.time() - start_time
logger.error(f"add_in_db_media: Ошибка обработки медиа для сообщения {post_id}: {e}, время: {processing_time:.2f}с")
metrics.record_media_processing(content_type or 'unknown', processing_time, False)
return False
async def send_media_group_message_to_private_chat(chat_id: int, message: types.Message,
media_group: List, bot_db):
media_group: List, bot_db: Any, main_post_id: Optional[int] = None) -> int:
sent_message = await message.bot.send_media_group(
chat_id=chat_id,
media=media_group,
@@ -268,7 +455,9 @@ async def send_media_group_message_to_private_chat(chat_id: int, message: types.
created_at=int(datetime.now().timestamp())
)
await bot_db.add_post(post)
await add_in_db_media_mediagroup(sent_message, bot_db)
success = await add_in_db_media_mediagroup(sent_message, bot_db, main_post_id)
if not success:
logger.warning(f"send_media_group_message_to_private_chat: Не удалось сохранить медиа для медиагруппы {sent_message[-1].message_id}")
message_id = sent_message[-1].message_id
return message_id