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:
@@ -273,6 +273,9 @@ async def save_voice_message(
|
||||
message_id=call.message.message_id
|
||||
)
|
||||
|
||||
# Удаляем запись из таблицы audio_moderate
|
||||
await bot_db.delete_audio_moderate_record(call.message.message_id)
|
||||
|
||||
await call.answer(text='Сохранено!', cache_time=3)
|
||||
|
||||
except Exception as e:
|
||||
@@ -296,6 +299,9 @@ async def delete_voice_message(
|
||||
message_id=call.message.message_id
|
||||
)
|
||||
|
||||
# Удаляем запись из таблицы audio_moderate
|
||||
await bot_db.delete_audio_moderate_record(call.message.message_id)
|
||||
|
||||
await call.answer(text='Удалено!', cache_time=3)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -331,6 +331,12 @@ class BanService:
|
||||
self.group_for_posts = settings['Telegram']['group_for_posts']
|
||||
self.important_logs = settings['Telegram']['important_logs']
|
||||
|
||||
def _get_bot(self, message) -> Bot:
|
||||
"""Получает бота из контекста сообщения или использует переданного"""
|
||||
if self.bot:
|
||||
return self.bot
|
||||
return message.bot
|
||||
|
||||
@track_time("ban_user_from_post", "ban_service")
|
||||
@track_errors("ban_service", "ban_user_from_post")
|
||||
async def ban_user_from_post(self, call: CallbackQuery) -> None:
|
||||
|
||||
@@ -180,7 +180,9 @@ class PostService:
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
success = await add_in_db_media(sent_message, self.db)
|
||||
if not success:
|
||||
logger.warning(f"handle_photo_post: Не удалось сохранить медиа для поста {sent_message.message_id}")
|
||||
|
||||
@track_time("handle_video_post", "post_service")
|
||||
@track_errors("post_service", "handle_video_post")
|
||||
@@ -202,7 +204,9 @@ class PostService:
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
success = await add_in_db_media(sent_message, self.db)
|
||||
if not success:
|
||||
logger.warning(f"handle_photo_post: Не удалось сохранить медиа для поста {sent_message.message_id}")
|
||||
|
||||
@track_time("handle_video_note_post", "post_service")
|
||||
@track_errors("post_service", "handle_video_note_post")
|
||||
@@ -220,7 +224,9 @@ class PostService:
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
success = await add_in_db_media(sent_message, self.db)
|
||||
if not success:
|
||||
logger.warning(f"handle_photo_post: Не удалось сохранить медиа для поста {sent_message.message_id}")
|
||||
|
||||
@track_time("handle_audio_post", "post_service")
|
||||
@track_errors("post_service", "handle_audio_post")
|
||||
@@ -242,7 +248,9 @@ class PostService:
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
success = await add_in_db_media(sent_message, self.db)
|
||||
if not success:
|
||||
logger.warning(f"handle_photo_post: Не удалось сохранить медиа для поста {sent_message.message_id}")
|
||||
|
||||
@track_time("handle_voice_post", "post_service")
|
||||
@track_errors("post_service", "handle_voice_post")
|
||||
@@ -260,7 +268,9 @@ class PostService:
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
success = await add_in_db_media(sent_message, self.db)
|
||||
if not success:
|
||||
logger.warning(f"handle_photo_post: Не удалось сохранить медиа для поста {sent_message.message_id}")
|
||||
|
||||
@track_time("handle_media_group_post", "post_service")
|
||||
@track_errors("post_service", "handle_media_group_post")
|
||||
@@ -283,7 +293,7 @@ class PostService:
|
||||
# Отправляем медиагруппу в группу для модерации
|
||||
media_group = await prepare_media_group_from_middlewares(album, post_caption)
|
||||
media_group_message_id = await send_media_group_message_to_private_chat(
|
||||
self.settings.group_for_posts, message, media_group, self.db
|
||||
self.settings.group_for_posts, message, media_group, self.db, main_post.message_id
|
||||
)
|
||||
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
@@ -299,10 +299,19 @@ class AudioFileService:
|
||||
logger.info(f"Получена информация о файле: {file_info.file_path}")
|
||||
|
||||
downloaded_file = await bot.download_file(file_path=file_info.file_path)
|
||||
logger.info(f"Файл скачан, размер: {len(downloaded_file.read()) if downloaded_file else 'None'} bytes")
|
||||
|
||||
# Сбрасываем позицию в файле
|
||||
downloaded_file.seek(0)
|
||||
# Проверяем что файл успешно скачан
|
||||
if not downloaded_file:
|
||||
logger.error("Не удалось скачать файл")
|
||||
raise FileOperationError("Не удалось скачать файл")
|
||||
|
||||
# Получаем размер файла без изменения позиции
|
||||
current_pos = downloaded_file.tell()
|
||||
downloaded_file.seek(0, 2) # Переходим в конец файла
|
||||
file_size = downloaded_file.tell()
|
||||
downloaded_file.seek(current_pos) # Возвращаемся в исходную позицию
|
||||
|
||||
logger.info(f"Файл скачан, размер: {file_size} bytes")
|
||||
|
||||
# Создаем директорию если она не существует
|
||||
import os
|
||||
@@ -312,6 +321,9 @@ class AudioFileService:
|
||||
file_path = f'{VOICE_USERS_DIR}/{file_name}.ogg'
|
||||
logger.info(f"Сохраняем файл по пути: {file_path}")
|
||||
|
||||
# Сбрасываем позицию в файле перед сохранением
|
||||
downloaded_file.seek(0)
|
||||
|
||||
# Сохраняем файл
|
||||
with open(file_path, 'wb') as new_file:
|
||||
new_file.write(downloaded_file.read())
|
||||
|
||||
@@ -155,30 +155,27 @@ class MetricsMiddleware(BaseMiddleware):
|
||||
bdf = get_global_instance()
|
||||
bot_db = bdf.get_db()
|
||||
|
||||
await bot_db.connect()
|
||||
|
||||
# Используем правильные методы AsyncBotDB для выполнения запросов
|
||||
# Простой подсчет всех пользователей в базе
|
||||
total_users_query = "SELECT COUNT(DISTINCT user_id) FROM our_users"
|
||||
await bot_db.cursor.execute(total_users_query)
|
||||
total_users_result = await bot_db.cursor.fetchone()
|
||||
total_users = total_users_result[0] if total_users_result else 1
|
||||
daily_users_query = "SELECT COUNT(DISTINCT user_id) as active_users FROM our_users WHERE date_changed > datetime('now', '-1 day')"
|
||||
await bot_db.cursor.execute(daily_users_query)
|
||||
daily_users_result = await bot_db.cursor.fetchone()
|
||||
daily_users = daily_users_result[0] if daily_users_result else 1
|
||||
total_users_query = "SELECT COUNT(DISTINCT user_id) as total FROM our_users"
|
||||
total_users_result = await bot_db.fetch_one(total_users_query)
|
||||
total_users = total_users_result['total'] if total_users_result else 1
|
||||
|
||||
await bot_db.close()
|
||||
# Подсчет активных за день пользователей (date_changed - это Unix timestamp)
|
||||
daily_users_query = "SELECT COUNT(DISTINCT user_id) as daily FROM our_users WHERE date_changed > (strftime('%s', 'now', '-1 day'))"
|
||||
daily_users_result = await bot_db.fetch_one(daily_users_query)
|
||||
daily_users = daily_users_result['daily'] if daily_users_result else 1
|
||||
|
||||
# Устанавливаем метрики с правильными лейблами
|
||||
metrics.set_active_users(daily_users, "daily")
|
||||
metrics.set_active_users(total_users, "total")
|
||||
metrics.set_total_users(total_users)
|
||||
self.logger.info(f"📊 Active users metric updated: {daily_users} (daily), {total_users} (total)")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Failed to update users metric: {e}")
|
||||
# Устанавливаем 1 как fallback
|
||||
metrics.set_active_users(1, "daily")
|
||||
metrics.set_active_users(1, "total")
|
||||
metrics.set_total_users(1)
|
||||
|
||||
async def _record_comprehensive_message_metrics(self, message: Message) -> Dict[str, Any]:
|
||||
"""Record comprehensive message metrics."""
|
||||
|
||||
@@ -31,6 +31,7 @@ class MetricsServer:
|
||||
# Настраиваем роуты
|
||||
self.app.router.add_get('/metrics', self.metrics_handler)
|
||||
self.app.router.add_get('/health', self.health_handler)
|
||||
self.app.router.add_get('/status', self.status_handler)
|
||||
|
||||
async def metrics_handler(self, request: web.Request) -> web.Response:
|
||||
"""Handle /metrics endpoint for Prometheus scraping."""
|
||||
@@ -102,6 +103,96 @@ class MetricsServer:
|
||||
status=500
|
||||
)
|
||||
|
||||
async def status_handler(self, request: web.Request) -> web.Response:
|
||||
"""Handle /status endpoint for process status information."""
|
||||
try:
|
||||
import os
|
||||
import time
|
||||
import psutil
|
||||
|
||||
# Получаем PID текущего процесса
|
||||
current_pid = os.getpid()
|
||||
|
||||
try:
|
||||
# Получаем информацию о процессе
|
||||
process = psutil.Process(current_pid)
|
||||
create_time = process.create_time()
|
||||
uptime_seconds = time.time() - create_time
|
||||
|
||||
# Логируем для диагностики
|
||||
import datetime
|
||||
create_time_str = datetime.datetime.fromtimestamp(create_time).strftime('%Y-%m-%d %H:%M:%S')
|
||||
current_time_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
logger.info(f"Process PID {current_pid}: created at {create_time_str}, current time {current_time_str}, uptime {uptime_seconds:.1f}s")
|
||||
|
||||
# Форматируем uptime
|
||||
if uptime_seconds < 60:
|
||||
uptime_str = f"{int(uptime_seconds)}с"
|
||||
elif uptime_seconds < 3600:
|
||||
minutes = int(uptime_seconds // 60)
|
||||
uptime_str = f"{minutes}м"
|
||||
elif uptime_seconds < 86400:
|
||||
hours = int(uptime_seconds // 3600)
|
||||
minutes = int((uptime_seconds % 3600) // 60)
|
||||
uptime_str = f"{hours}ч {minutes}м"
|
||||
else:
|
||||
days = int(uptime_seconds // 86400)
|
||||
hours = int((uptime_seconds % 86400) // 3600)
|
||||
uptime_str = f"{days}д {hours}ч"
|
||||
|
||||
# Проверяем, что процесс активен
|
||||
if process.is_running():
|
||||
status = "running"
|
||||
else:
|
||||
status = "stopped"
|
||||
|
||||
# Формируем ответ
|
||||
response_data = {
|
||||
"status": status,
|
||||
"pid": current_pid,
|
||||
"uptime": uptime_str,
|
||||
"memory_usage_mb": round(process.memory_info().rss / 1024 / 1024, 2),
|
||||
"cpu_percent": process.cpu_percent(),
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
import json
|
||||
return web.Response(
|
||||
text=json.dumps(response_data, ensure_ascii=False),
|
||||
content_type='application/json',
|
||||
status=200
|
||||
)
|
||||
|
||||
except psutil.NoSuchProcess:
|
||||
# Процесс не найден
|
||||
response_data = {
|
||||
"status": "not_found",
|
||||
"error": "Process not found",
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
import json
|
||||
return web.Response(
|
||||
text=json.dumps(response_data, ensure_ascii=False),
|
||||
content_type='application/json',
|
||||
status=404
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Status check failed: {e}")
|
||||
import json
|
||||
response_data = {
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
return web.Response(
|
||||
text=json.dumps(response_data, ensure_ascii=False),
|
||||
content_type='application/json',
|
||||
status=500
|
||||
)
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Start the HTTP server."""
|
||||
try:
|
||||
@@ -115,6 +206,7 @@ class MetricsServer:
|
||||
logger.info("Available endpoints:")
|
||||
logger.info(f" - /metrics - Prometheus metrics")
|
||||
logger.info(f" - /health - Health check")
|
||||
logger.info(f" - /status - Process status")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start metrics server: {e}")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ constants = {
|
||||
'EMOJI_INFO_MESSAGE': "Любые войсы будут помечены эмоджи. <b>Твой эмоджи - </b>{emoji}Таким эмоджи будут помечены твои сообщения для других Но другие люди не узнают кто за каким эмоджи скрывается:)",
|
||||
'HELP_INFO_MESSAGE': "Так же можешь ознакомиться с инструкцией к боту по команде /help",
|
||||
'FINAL_MESSAGE': "<b>Ну всё, достаточно инструкций. записывайся! Микрофон твой - </b> 🎤",
|
||||
'HELP_MESSAGE': "Скорее всего ответы на твои вопросы есть здесь, ознакомься: https://telegra.ph/Instrukciya-k-botu-Golosa-Bijsk-10-11-2\nЕсли это не поможет, пиши в личку: @Kerrad1",
|
||||
'HELP_MESSAGE': "Когда-нибудь здесь будет инструкция к боту. А пока по вопросам пиши в личку: @Kerrad1 или в Связаться с админами",
|
||||
'VOICE_SAVED_MESSAGE': "Окей, сохранил!👌",
|
||||
'LISTENINGS_CLEARED_MESSAGE': "Прослушивания очищены. Можешь начать слушать заново🤗",
|
||||
'NO_AUDIO_MESSAGE': "Прости, ты прослушал все аудио😔. Возвращайся позже, возможно наша база пополнится",
|
||||
|
||||
@@ -52,6 +52,13 @@ class BotMetrics:
|
||||
registry=self.registry
|
||||
)
|
||||
|
||||
# Total users gauge (отдельная метрика)
|
||||
self.total_users = Gauge(
|
||||
'total_users',
|
||||
'Total number of users in database',
|
||||
registry=self.registry
|
||||
)
|
||||
|
||||
# Database query metrics
|
||||
self.db_query_duration_seconds = Histogram(
|
||||
'db_query_duration_seconds',
|
||||
@@ -110,6 +117,46 @@ class BotMetrics:
|
||||
['activity_type', 'user_type', 'chat_type'],
|
||||
registry=self.registry
|
||||
)
|
||||
|
||||
# File download metrics
|
||||
self.file_downloads_total = Counter(
|
||||
'file_downloads_total',
|
||||
'Total number of file downloads',
|
||||
['content_type', 'status'],
|
||||
registry=self.registry
|
||||
)
|
||||
|
||||
self.file_download_duration_seconds = Histogram(
|
||||
'file_download_duration_seconds',
|
||||
'Time spent downloading files',
|
||||
['content_type'],
|
||||
buckets=[0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0],
|
||||
registry=self.registry
|
||||
)
|
||||
|
||||
self.file_download_size_bytes = Histogram(
|
||||
'file_download_size_bytes',
|
||||
'Size of downloaded files in bytes',
|
||||
['content_type'],
|
||||
buckets=[1024, 10240, 102400, 1048576, 10485760, 104857600, 1073741824],
|
||||
registry=self.registry
|
||||
)
|
||||
|
||||
# Media processing metrics
|
||||
self.media_processing_total = Counter(
|
||||
'media_processing_total',
|
||||
'Total number of media processing operations',
|
||||
['content_type', 'status'],
|
||||
registry=self.registry
|
||||
)
|
||||
|
||||
self.media_processing_duration_seconds = Histogram(
|
||||
'media_processing_duration_seconds',
|
||||
'Time spent processing media',
|
||||
['content_type'],
|
||||
buckets=[0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0],
|
||||
registry=self.registry
|
||||
)
|
||||
|
||||
def record_command(self, command_type: str, handler_type: str = "unknown", user_type: str = "unknown", status: str = "success"):
|
||||
"""Record a bot command execution."""
|
||||
@@ -140,6 +187,10 @@ class BotMetrics:
|
||||
"""Set the number of active users for a specific type."""
|
||||
self.active_users.labels(user_type=user_type).set(count)
|
||||
|
||||
def set_total_users(self, count: int):
|
||||
"""Set the total number of users in database."""
|
||||
self.total_users.set(count)
|
||||
|
||||
def record_db_query(self, query_type: str, duration: float, table_name: str = "unknown", operation: str = "unknown"):
|
||||
"""Record database query duration."""
|
||||
self.db_query_duration_seconds.labels(
|
||||
@@ -168,6 +219,54 @@ class BotMetrics:
|
||||
status=status
|
||||
).observe(duration)
|
||||
|
||||
def record_file_download(self, content_type: str, file_size: int, duration: float):
|
||||
"""Record file download metrics."""
|
||||
self.file_downloads_total.labels(
|
||||
content_type=content_type,
|
||||
status="success"
|
||||
).inc()
|
||||
|
||||
self.file_download_duration_seconds.labels(
|
||||
content_type=content_type
|
||||
).observe(duration)
|
||||
|
||||
self.file_download_size_bytes.labels(
|
||||
content_type=content_type
|
||||
).observe(file_size)
|
||||
|
||||
def record_file_download_error(self, content_type: str, error_message: str):
|
||||
"""Record file download error metrics."""
|
||||
self.file_downloads_total.labels(
|
||||
content_type=content_type,
|
||||
status="error"
|
||||
).inc()
|
||||
|
||||
self.errors_total.labels(
|
||||
error_type="file_download_error",
|
||||
handler_type="media_processing",
|
||||
method_name="download_file"
|
||||
).inc()
|
||||
|
||||
def record_media_processing(self, content_type: str, duration: float, success: bool):
|
||||
"""Record media processing metrics."""
|
||||
status = "success" if success else "error"
|
||||
|
||||
self.media_processing_total.labels(
|
||||
content_type=content_type,
|
||||
status=status
|
||||
).inc()
|
||||
|
||||
self.media_processing_duration_seconds.labels(
|
||||
content_type=content_type
|
||||
).observe(duration)
|
||||
|
||||
if not success:
|
||||
self.errors_total.labels(
|
||||
error_type="media_processing_error",
|
||||
handler_type="media_processing",
|
||||
method_name="add_in_db_media"
|
||||
).inc()
|
||||
|
||||
def get_metrics(self) -> bytes:
|
||||
"""Generate metrics in Prometheus format."""
|
||||
return generate_latest(self.registry)
|
||||
|
||||
Reference in New Issue
Block a user