621 lines
36 KiB
Python
621 lines
36 KiB
Python
import html
|
||
from datetime import datetime, timedelta
|
||
from typing import Any, Dict
|
||
|
||
from aiogram import Bot, types
|
||
from aiogram.types import CallbackQuery
|
||
from helper_bot.keyboards.keyboards import create_keyboard_for_ban_reason
|
||
from helper_bot.utils.helper_func import (delete_user_blacklist,
|
||
get_text_message, send_audio_message,
|
||
send_media_group_to_channel,
|
||
send_photo_message,
|
||
send_text_message,
|
||
send_video_message,
|
||
send_video_note_message,
|
||
send_voice_message)
|
||
# Local imports - metrics
|
||
from helper_bot.utils.metrics import (db_query_time, track_errors,
|
||
track_media_processing, track_time)
|
||
from logs.custom_logger import logger
|
||
|
||
from .constants import (CONTENT_TYPE_AUDIO, CONTENT_TYPE_MEDIA_GROUP,
|
||
CONTENT_TYPE_PHOTO, CONTENT_TYPE_TEXT,
|
||
CONTENT_TYPE_VIDEO, CONTENT_TYPE_VIDEO_NOTE,
|
||
CONTENT_TYPE_VOICE, ERROR_BOT_BLOCKED,
|
||
MESSAGE_POST_DECLINED, MESSAGE_POST_PUBLISHED,
|
||
MESSAGE_USER_BANNED_SPAM)
|
||
from .exceptions import (BanError, PostNotFoundError, PublishError,
|
||
UserBlockedBotError, UserNotFoundError)
|
||
|
||
|
||
class PostPublishService:
|
||
def __init__(self, bot: Bot, db, settings: Dict[str, Any], s3_storage=None):
|
||
# bot может быть None - в этом случае используем бота из контекста сообщения
|
||
self.bot = bot
|
||
self.db = db
|
||
self.settings = settings
|
||
self.s3_storage = s3_storage
|
||
self.group_for_posts = settings['Telegram']['group_for_posts']
|
||
self.main_public = settings['Telegram']['main_public']
|
||
self.important_logs = settings['Telegram']['important_logs']
|
||
|
||
def _get_bot(self, message) -> Bot:
|
||
"""Получает бота из контекста сообщения или использует переданного"""
|
||
if self.bot:
|
||
return self.bot
|
||
return message.bot
|
||
|
||
@track_time("publish_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "publish_post")
|
||
async def publish_post(self, call: CallbackQuery) -> None:
|
||
"""Основной метод публикации поста"""
|
||
# Проверяем, является ли сообщение helper-сообщением медиагруппы
|
||
if call.message.text == CONTENT_TYPE_MEDIA_GROUP:
|
||
await self._publish_media_group(call)
|
||
return
|
||
|
||
# Проверяем, является ли сообщение частью медиагруппы (для обратной совместимости)
|
||
if call.message.media_group_id:
|
||
await self._publish_media_group(call)
|
||
return
|
||
|
||
content_type = call.message.content_type
|
||
|
||
if content_type == CONTENT_TYPE_TEXT:
|
||
await self._publish_text_post(call)
|
||
elif content_type == CONTENT_TYPE_PHOTO:
|
||
await self._publish_photo_post(call)
|
||
elif content_type == CONTENT_TYPE_VIDEO:
|
||
await self._publish_video_post(call)
|
||
elif content_type == CONTENT_TYPE_VIDEO_NOTE:
|
||
await self._publish_video_note_post(call)
|
||
elif content_type == CONTENT_TYPE_AUDIO:
|
||
await self._publish_audio_post(call)
|
||
elif content_type == CONTENT_TYPE_VOICE:
|
||
await self._publish_voice_post(call)
|
||
else:
|
||
raise PublishError(f"Неподдерживаемый тип контента: {content_type}")
|
||
|
||
@track_time("_publish_text_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "_publish_text_post")
|
||
async def _publish_text_post(self, call: CallbackQuery) -> None:
|
||
"""Публикация текстового поста"""
|
||
author_id = await self._get_author_id(call.message.message_id)
|
||
|
||
updated_rows = await self.db.update_status_by_message_id(call.message.message_id, "approved")
|
||
if updated_rows == 0:
|
||
logger.error(f"Не удалось обновить статус поста message_id={call.message.message_id} на 'approved'")
|
||
raise PostNotFoundError(f"Пост с message_id={call.message.message_id} не найден в базе данных")
|
||
|
||
# Получаем сырой текст и is_anonymous из базы
|
||
raw_text, is_anonymous = await self.db.get_post_text_and_anonymity_by_message_id(call.message.message_id)
|
||
if raw_text is None:
|
||
raw_text = ""
|
||
|
||
# Получаем данные автора
|
||
user = await self.db.get_user_by_id(author_id)
|
||
if not user:
|
||
raise PostNotFoundError(f"Пользователь {author_id} не найден в базе данных")
|
||
|
||
# Формируем финальный текст с учетом is_anonymous
|
||
formatted_text = get_text_message(raw_text, user.first_name, user.username, is_anonymous)
|
||
|
||
sent_message = await send_text_message(self.main_public, call.message, formatted_text)
|
||
|
||
# Сохраняем published_message_id
|
||
await self.db.update_published_message_id(
|
||
original_message_id=call.message.message_id,
|
||
published_message_id=sent_message.message_id
|
||
)
|
||
|
||
await self._delete_post_and_notify_author(call, author_id)
|
||
logger.info(f'Текст сообщение опубликован в канале {self.main_public}, published_message_id={sent_message.message_id}.')
|
||
|
||
@track_time("_publish_photo_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "_publish_photo_post")
|
||
async def _publish_photo_post(self, call: CallbackQuery) -> None:
|
||
"""Публикация поста с фото"""
|
||
author_id = await self._get_author_id(call.message.message_id)
|
||
|
||
updated_rows = await self.db.update_status_by_message_id(call.message.message_id, "approved")
|
||
if updated_rows == 0:
|
||
logger.error(f"Не удалось обновить статус поста message_id={call.message.message_id} на 'approved'")
|
||
raise PostNotFoundError(f"Пост с message_id={call.message.message_id} не найден в базе данных")
|
||
|
||
# Получаем сырой текст и is_anonymous из базы
|
||
raw_text, is_anonymous = await self.db.get_post_text_and_anonymity_by_message_id(call.message.message_id)
|
||
if raw_text is None:
|
||
raw_text = ""
|
||
|
||
# Получаем данные автора
|
||
user = await self.db.get_user_by_id(author_id)
|
||
if not user:
|
||
raise PostNotFoundError(f"Пользователь {author_id} не найден в базе данных")
|
||
|
||
# Формируем финальный текст с учетом is_anonymous
|
||
formatted_text = get_text_message(raw_text, user.first_name, user.username, is_anonymous)
|
||
|
||
sent_message = await send_photo_message(self.main_public, call.message, call.message.photo[-1].file_id, formatted_text)
|
||
|
||
# Сохраняем published_message_id
|
||
await self.db.update_published_message_id(
|
||
original_message_id=call.message.message_id,
|
||
published_message_id=sent_message.message_id
|
||
)
|
||
|
||
# Сохраняем медиафайл из опубликованного поста (используем уже сохраненный файл)
|
||
await self._save_published_post_content(sent_message, sent_message.message_id, call.message.message_id)
|
||
|
||
await self._delete_post_and_notify_author(call, author_id)
|
||
logger.info(f'Пост с фото опубликован в канале {self.main_public}, published_message_id={sent_message.message_id}.')
|
||
|
||
@track_time("_publish_video_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "_publish_video_post")
|
||
async def _publish_video_post(self, call: CallbackQuery) -> None:
|
||
"""Публикация поста с видео"""
|
||
author_id = await self._get_author_id(call.message.message_id)
|
||
|
||
updated_rows = await self.db.update_status_by_message_id(call.message.message_id, "approved")
|
||
if updated_rows == 0:
|
||
logger.error(f"Не удалось обновить статус поста message_id={call.message.message_id} на 'approved'")
|
||
raise PostNotFoundError(f"Пост с message_id={call.message.message_id} не найден в базе данных")
|
||
|
||
# Получаем сырой текст и is_anonymous из базы
|
||
raw_text, is_anonymous = await self.db.get_post_text_and_anonymity_by_message_id(call.message.message_id)
|
||
if raw_text is None:
|
||
raw_text = ""
|
||
|
||
# Получаем данные автора
|
||
user = await self.db.get_user_by_id(author_id)
|
||
if not user:
|
||
raise PostNotFoundError(f"Пользователь {author_id} не найден в базе данных")
|
||
|
||
# Формируем финальный текст с учетом is_anonymous
|
||
formatted_text = get_text_message(raw_text, user.first_name, user.username, is_anonymous)
|
||
|
||
sent_message = await send_video_message(self.main_public, call.message, call.message.video.file_id, formatted_text)
|
||
|
||
# Сохраняем published_message_id
|
||
await self.db.update_published_message_id(
|
||
original_message_id=call.message.message_id,
|
||
published_message_id=sent_message.message_id
|
||
)
|
||
|
||
# Сохраняем медиафайл из опубликованного поста (используем уже сохраненный файл)
|
||
await self._save_published_post_content(sent_message, sent_message.message_id, call.message.message_id)
|
||
|
||
await self._delete_post_and_notify_author(call, author_id)
|
||
logger.info(f'Пост с видео опубликован в канале {self.main_public}, published_message_id={sent_message.message_id}.')
|
||
|
||
@track_time("_publish_video_note_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "_publish_video_note_post")
|
||
async def _publish_video_note_post(self, call: CallbackQuery) -> None:
|
||
"""Публикация поста с кружком"""
|
||
author_id = await self._get_author_id(call.message.message_id)
|
||
|
||
updated_rows = await self.db.update_status_by_message_id(call.message.message_id, "approved")
|
||
if updated_rows == 0:
|
||
logger.error(f"Не удалось обновить статус поста message_id={call.message.message_id} на 'approved'")
|
||
raise PostNotFoundError(f"Пост с message_id={call.message.message_id} не найден в базе данных")
|
||
|
||
sent_message = await send_video_note_message(self.main_public, call.message, call.message.video_note.file_id)
|
||
|
||
# Сохраняем published_message_id
|
||
await self.db.update_published_message_id(
|
||
original_message_id=call.message.message_id,
|
||
published_message_id=sent_message.message_id
|
||
)
|
||
|
||
# Сохраняем медиафайл из опубликованного поста (используем уже сохраненный файл)
|
||
await self._save_published_post_content(sent_message, sent_message.message_id, call.message.message_id)
|
||
|
||
await self._delete_post_and_notify_author(call, author_id)
|
||
logger.info(f'Пост с кружком опубликован в канале {self.main_public}, published_message_id={sent_message.message_id}.')
|
||
|
||
@track_time("_publish_audio_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "_publish_audio_post")
|
||
async def _publish_audio_post(self, call: CallbackQuery) -> None:
|
||
"""Публикация поста с аудио"""
|
||
author_id = await self._get_author_id(call.message.message_id)
|
||
|
||
updated_rows = await self.db.update_status_by_message_id(call.message.message_id, "approved")
|
||
if updated_rows == 0:
|
||
logger.error(f"Не удалось обновить статус поста message_id={call.message.message_id} на 'approved'")
|
||
raise PostNotFoundError(f"Пост с message_id={call.message.message_id} не найден в базе данных")
|
||
|
||
# Получаем сырой текст и is_anonymous из базы
|
||
raw_text, is_anonymous = await self.db.get_post_text_and_anonymity_by_message_id(call.message.message_id)
|
||
if raw_text is None:
|
||
raw_text = ""
|
||
|
||
# Получаем данные автора
|
||
user = await self.db.get_user_by_id(author_id)
|
||
if not user:
|
||
raise PostNotFoundError(f"Пользователь {author_id} не найден в базе данных")
|
||
|
||
# Формируем финальный текст с учетом is_anonymous
|
||
formatted_text = get_text_message(raw_text, user.first_name, user.username, is_anonymous)
|
||
|
||
sent_message = await send_audio_message(self.main_public, call.message, call.message.audio.file_id, formatted_text)
|
||
|
||
# Сохраняем published_message_id
|
||
await self.db.update_published_message_id(
|
||
original_message_id=call.message.message_id,
|
||
published_message_id=sent_message.message_id
|
||
)
|
||
|
||
# Сохраняем медиафайл из опубликованного поста (используем уже сохраненный файл)
|
||
await self._save_published_post_content(sent_message, sent_message.message_id, call.message.message_id)
|
||
|
||
await self._delete_post_and_notify_author(call, author_id)
|
||
logger.info(f'Пост с аудио опубликован в канале {self.main_public}, published_message_id={sent_message.message_id}.')
|
||
|
||
@track_time("_publish_voice_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "_publish_voice_post")
|
||
async def _publish_voice_post(self, call: CallbackQuery) -> None:
|
||
"""Публикация поста с войсом"""
|
||
author_id = await self._get_author_id(call.message.message_id)
|
||
|
||
updated_rows = await self.db.update_status_by_message_id(call.message.message_id, "approved")
|
||
if updated_rows == 0:
|
||
logger.error(f"Не удалось обновить статус поста message_id={call.message.message_id} на 'approved'")
|
||
raise PostNotFoundError(f"Пост с message_id={call.message.message_id} не найден в базе данных")
|
||
|
||
sent_message = await send_voice_message(self.main_public, call.message, call.message.voice.file_id)
|
||
|
||
# Сохраняем published_message_id
|
||
await self.db.update_published_message_id(
|
||
original_message_id=call.message.message_id,
|
||
published_message_id=sent_message.message_id
|
||
)
|
||
|
||
# Сохраняем медиафайл из опубликованного поста (используем уже сохраненный файл)
|
||
await self._save_published_post_content(sent_message, sent_message.message_id, call.message.message_id)
|
||
|
||
await self._delete_post_and_notify_author(call, author_id)
|
||
logger.info(f'Пост с войсом опубликован в канале {self.main_public}, published_message_id={sent_message.message_id}.')
|
||
|
||
@track_time("_publish_media_group", "post_publish_service")
|
||
@track_errors("post_publish_service", "_publish_media_group")
|
||
@track_media_processing("media_group")
|
||
async def _publish_media_group(self, call: CallbackQuery) -> None:
|
||
"""Публикация медиагруппы"""
|
||
try:
|
||
helper_message_id = call.message.message_id
|
||
|
||
media_group_message_ids = await self.db.get_post_ids_by_helper_id(helper_message_id)
|
||
if not media_group_message_ids:
|
||
logger.error(f"_publish_media_group: Не найдены message_id медиагруппы для helper_message_id={helper_message_id}")
|
||
raise PublishError("Не найдены message_id медиагруппы в базе данных")
|
||
|
||
post_content = await self.db.get_post_content_by_helper_id(helper_message_id)
|
||
if not post_content:
|
||
logger.error(f"_publish_media_group: Контент медиагруппы не найден в базе данных для helper_message_id={helper_message_id}")
|
||
raise PublishError("Контент медиагруппы не найден в базе данных")
|
||
|
||
raw_text, is_anonymous = await self.db.get_post_text_and_anonymity_by_helper_id(helper_message_id)
|
||
if raw_text is None:
|
||
raw_text = ""
|
||
|
||
author_id = await self.db.get_author_id_by_helper_message_id(helper_message_id)
|
||
if not author_id:
|
||
logger.error(f"_publish_media_group: Автор не найден для медиагруппы helper_message_id={helper_message_id}")
|
||
raise PostNotFoundError(f"Автор не найден для медиагруппы {helper_message_id}")
|
||
|
||
user = await self.db.get_user_by_id(author_id)
|
||
if not user:
|
||
raise PostNotFoundError(f"Пользователь {author_id} не найден в базе данных")
|
||
|
||
formatted_text = get_text_message(raw_text, user.first_name, user.username, is_anonymous)
|
||
|
||
try:
|
||
await self._get_bot(call.message).delete_messages(
|
||
chat_id=self.group_for_posts,
|
||
message_ids=media_group_message_ids
|
||
)
|
||
except Exception as e:
|
||
logger.warning(f"_publish_media_group: Ошибка при удалении медиагруппы из чата модерации: {e}")
|
||
|
||
sent_messages = await send_media_group_to_channel(
|
||
bot=self._get_bot(call.message),
|
||
chat_id=self.main_public,
|
||
post_content=post_content,
|
||
post_text=formatted_text,
|
||
s3_storage=self.s3_storage
|
||
)
|
||
|
||
if len(sent_messages) == len(media_group_message_ids):
|
||
for i, original_message_id in enumerate(media_group_message_ids):
|
||
published_message_id = sent_messages[i].message_id
|
||
try:
|
||
await self.db.update_published_message_id(
|
||
original_message_id=original_message_id,
|
||
published_message_id=published_message_id
|
||
)
|
||
await self._save_published_post_content(sent_messages[i], published_message_id, original_message_id)
|
||
except Exception as e:
|
||
logger.warning(f"_publish_media_group: Ошибка при сохранении published_message_id для {original_message_id}: {e}")
|
||
else:
|
||
logger.warning(f"_publish_media_group: Количество опубликованных сообщений ({len(sent_messages)}) не совпадает с количеством оригинальных ({len(media_group_message_ids)})")
|
||
|
||
await self.db.update_status_for_media_group_by_helper_id(helper_message_id, "approved")
|
||
|
||
# Удаляем helper сообщение - это критично, делаем это всегда
|
||
try:
|
||
await self._get_bot(call.message).delete_message(
|
||
chat_id=self.group_for_posts,
|
||
message_id=helper_message_id
|
||
)
|
||
except Exception as e:
|
||
logger.warning(f"_publish_media_group: Ошибка при удалении helper сообщения: {e}")
|
||
|
||
try:
|
||
await send_text_message(author_id, call.message, MESSAGE_POST_PUBLISHED)
|
||
except Exception as e:
|
||
if str(e) == ERROR_BOT_BLOCKED:
|
||
logger.warning(f"_publish_media_group: Пользователь {author_id} заблокировал бота")
|
||
raise UserBlockedBotError("Пользователь заблокировал бота")
|
||
logger.error(f"_publish_media_group: Ошибка при отправке уведомления автору: {e}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"_publish_media_group: Ошибка при публикации медиагруппы: {e}")
|
||
# Пытаемся удалить helper сообщение даже при ошибке
|
||
try:
|
||
await self._get_bot(call.message).delete_message(
|
||
chat_id=self.group_for_posts,
|
||
message_id=call.message.message_id
|
||
)
|
||
except Exception as delete_error:
|
||
logger.warning(f"_publish_media_group: Не удалось удалить helper сообщение при ошибке: {delete_error}")
|
||
raise PublishError(f"Не удалось опубликовать медиагруппу: {str(e)}")
|
||
|
||
@track_time("decline_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "decline_post")
|
||
async def decline_post(self, call: CallbackQuery) -> None:
|
||
"""Отклонение поста"""
|
||
# Проверяем, является ли сообщение частью медиагруппы (осознанный костыль, т.к. сообщение к которому прикреплен коллбек без медиагруппы)
|
||
if call.message.text == CONTENT_TYPE_MEDIA_GROUP:
|
||
await self._decline_media_group(call)
|
||
return
|
||
|
||
content_type = call.message.content_type
|
||
|
||
if content_type in [CONTENT_TYPE_TEXT, CONTENT_TYPE_PHOTO, CONTENT_TYPE_AUDIO,
|
||
CONTENT_TYPE_VOICE, CONTENT_TYPE_VIDEO, CONTENT_TYPE_VIDEO_NOTE]:
|
||
await self._decline_single_post(call)
|
||
else:
|
||
logger.error(f"Неподдерживаемый тип контента для отклонения: {content_type}")
|
||
raise PublishError(f"Неподдерживаемый тип контента для отклонения: {content_type}")
|
||
|
||
@track_time("_decline_single_post", "post_publish_service")
|
||
@track_errors("post_publish_service", "_decline_single_post")
|
||
async def _decline_single_post(self, call: CallbackQuery) -> None:
|
||
"""Отклонение одиночного поста"""
|
||
author_id = await self._get_author_id(call.message.message_id)
|
||
|
||
updated_rows = await self.db.update_status_by_message_id(call.message.message_id, "declined")
|
||
if updated_rows == 0:
|
||
logger.error(f"Не удалось обновить статус поста message_id={call.message.message_id} на 'declined'")
|
||
raise PostNotFoundError(f"Пост с message_id={call.message.message_id} не найден в базе данных")
|
||
|
||
await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||
|
||
try:
|
||
await send_text_message(author_id, call.message, MESSAGE_POST_DECLINED)
|
||
except Exception as e:
|
||
if str(e) == ERROR_BOT_BLOCKED:
|
||
logger.warning(f"Пользователь {author_id} заблокировал бота")
|
||
raise UserBlockedBotError("Пользователь заблокировал бота")
|
||
logger.error(f"Ошибка при отправке уведомления автору {author_id}: {e}")
|
||
raise
|
||
logger.info(f'Сообщение отклонено админом {call.from_user.full_name} (ID: {call.from_user.id}).')
|
||
|
||
@track_time("_decline_media_group", "post_publish_service")
|
||
@track_errors("post_publish_service", "_decline_media_group")
|
||
@track_media_processing("media_group")
|
||
async def _decline_media_group(self, call: CallbackQuery) -> None:
|
||
"""Отклонение медиагруппы"""
|
||
helper_message_id = call.message.message_id
|
||
|
||
await self.db.update_status_for_media_group_by_helper_id(helper_message_id, "declined")
|
||
|
||
media_group_message_ids = await self.db.get_post_ids_by_helper_id(helper_message_id)
|
||
|
||
message_ids_to_delete = media_group_message_ids.copy()
|
||
message_ids_to_delete.append(helper_message_id)
|
||
|
||
author_id = await self._get_author_id_for_media_group(helper_message_id)
|
||
|
||
try:
|
||
await self._get_bot(call.message).delete_messages(
|
||
chat_id=self.group_for_posts,
|
||
message_ids=message_ids_to_delete
|
||
)
|
||
except Exception as e:
|
||
logger.warning(f"_decline_media_group: Ошибка при удалении сообщений: {e}")
|
||
|
||
try:
|
||
await send_text_message(author_id, call.message, MESSAGE_POST_DECLINED)
|
||
except Exception as e:
|
||
if str(e) == ERROR_BOT_BLOCKED:
|
||
logger.warning(f"_decline_media_group: Пользователь {author_id} заблокировал бота")
|
||
raise UserBlockedBotError("Пользователь заблокировал бота")
|
||
logger.error(f"_decline_media_group: Ошибка при отправке уведомления автору {author_id}: {e}")
|
||
raise
|
||
|
||
@track_time("_get_author_id", "post_publish_service")
|
||
@track_errors("post_publish_service", "_get_author_id")
|
||
async def _get_author_id(self, message_id: int) -> int:
|
||
"""Получение ID автора по ID сообщения"""
|
||
author_id = await self.db.get_author_id_by_message_id(message_id)
|
||
if not author_id:
|
||
raise PostNotFoundError(f"Автор не найден для сообщения {message_id}")
|
||
return author_id
|
||
|
||
@track_time("_get_author_id_for_media_group", "post_publish_service")
|
||
@track_errors("post_publish_service", "_get_author_id_for_media_group")
|
||
async def _get_author_id_for_media_group(self, message_id: int) -> int:
|
||
"""Получение ID автора для медиагруппы"""
|
||
# Сначала пытаемся найти автора по helper_message_id
|
||
author_id = await self.db.get_author_id_by_helper_message_id(message_id)
|
||
if author_id:
|
||
return author_id
|
||
|
||
# Если не найден, ищем по основному message_id медиагруппы
|
||
# Для этого нужно найти связанные сообщения медиагруппы
|
||
try:
|
||
# Получаем все ID сообщений медиагруппы
|
||
post_ids = await self.db.get_post_ids_from_telegram_by_last_id(message_id)
|
||
if post_ids:
|
||
# Берем первый ID (основное сообщение медиагруппы)
|
||
main_message_id = post_ids[0]
|
||
author_id = await self.db.get_author_id_by_message_id(main_message_id)
|
||
if author_id:
|
||
return author_id
|
||
except Exception as e:
|
||
logger.warning(f"Не удалось найти автора через связанные сообщения: {e}")
|
||
|
||
# Если все способы не сработали, ищем напрямую
|
||
author_id = await self.db.get_author_id_by_message_id(message_id)
|
||
if not author_id:
|
||
raise PostNotFoundError(f"Автор не найден для медиагруппы {message_id}")
|
||
return author_id
|
||
|
||
@track_time("_delete_post_and_notify_author", "post_publish_service")
|
||
@track_errors("post_publish_service", "_delete_post_and_notify_author")
|
||
async def _delete_post_and_notify_author(self, call: CallbackQuery, author_id: int) -> None:
|
||
"""Удаление поста и уведомление автора"""
|
||
await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||
|
||
try:
|
||
await send_text_message(author_id, call.message, MESSAGE_POST_PUBLISHED)
|
||
except Exception as e:
|
||
if str(e) == ERROR_BOT_BLOCKED:
|
||
raise UserBlockedBotError("Пользователь заблокировал бота")
|
||
raise
|
||
|
||
@track_time("_delete_media_group_and_notify_author", "post_publish_service")
|
||
@track_errors("post_publish_service", "_delete_media_group_and_notify_author")
|
||
@track_media_processing("media_group")
|
||
async def _delete_media_group_and_notify_author(self, call: CallbackQuery, author_id: int) -> None:
|
||
"""Удаление медиагруппы и уведомление автора (legacy метод, используется для обратной совместимости)"""
|
||
helper_message_id = call.message.message_id
|
||
|
||
media_group_message_ids = await self.db.get_post_ids_by_helper_id(helper_message_id)
|
||
|
||
message_ids_to_delete = media_group_message_ids.copy()
|
||
message_ids_to_delete.append(helper_message_id)
|
||
|
||
await self._get_bot(call.message).delete_messages(chat_id=self.group_for_posts, message_ids=message_ids_to_delete)
|
||
try:
|
||
await send_text_message(author_id, call.message, MESSAGE_POST_PUBLISHED)
|
||
except Exception as e:
|
||
if str(e) == ERROR_BOT_BLOCKED:
|
||
raise UserBlockedBotError("Пользователь заблокировал бота")
|
||
raise
|
||
|
||
@track_time("_save_published_post_content", "post_publish_service")
|
||
@track_errors("post_publish_service", "_save_published_post_content")
|
||
async def _save_published_post_content(self, published_message: types.Message, published_message_id: int, original_message_id: int) -> None:
|
||
"""Сохраняет ссылку на медиафайл из опубликованного поста (файл уже в S3 или на диске)."""
|
||
try:
|
||
# Получаем уже сохраненный путь/S3 ключ из оригинального поста
|
||
saved_content = await self.db.get_post_content_by_message_id(original_message_id)
|
||
|
||
if saved_content and len(saved_content) > 0:
|
||
# Копируем тот же путь/S3 ключ
|
||
file_path, content_type = saved_content[0]
|
||
logger.debug(f"Копируем путь/S3 ключ для опубликованного поста: {file_path}")
|
||
|
||
success = await self.db.add_published_post_content(
|
||
published_message_id=published_message_id,
|
||
content_path=file_path, # Тот же путь/S3 ключ
|
||
content_type=content_type
|
||
)
|
||
if success:
|
||
logger.info(f"Ссылка на файл сохранена для опубликованного поста: published_message_id={published_message_id}, path={file_path}")
|
||
else:
|
||
logger.warning(f"Не удалось сохранить ссылку на файл: published_message_id={published_message_id}")
|
||
else:
|
||
logger.warning(f"Контент не найден для оригинального поста message_id={original_message_id}")
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при сохранении ссылки на контент опубликованного поста {published_message_id}: {e}")
|
||
# Не прерываем публикацию, если сохранение контента не удалось
|
||
|
||
|
||
class BanService:
|
||
def __init__(self, bot: Bot, db, settings: Dict[str, Any]):
|
||
self.bot = bot
|
||
self.db = db
|
||
self.settings = settings
|
||
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")
|
||
@db_query_time("ban_user_from_post", "users", "mixed")
|
||
async def ban_user_from_post(self, call: CallbackQuery) -> None:
|
||
"""Бан пользователя за спам"""
|
||
# Если это helper-сообщение медиагруппы, используем специальный метод
|
||
if call.message.text == CONTENT_TYPE_MEDIA_GROUP:
|
||
author_id = await self.db.get_author_id_by_helper_message_id(call.message.message_id)
|
||
else:
|
||
author_id = await self.db.get_author_id_by_message_id(call.message.message_id)
|
||
|
||
if not author_id:
|
||
raise UserNotFoundError(f"Автор не найден для сообщения {call.message.message_id}")
|
||
|
||
current_date = datetime.now()
|
||
date_to_unban = int((current_date + timedelta(days=7)).timestamp())
|
||
|
||
ban_author_id = call.from_user.id
|
||
|
||
await self.db.set_user_blacklist(
|
||
user_id=author_id,
|
||
user_name=None,
|
||
message_for_user="Спам",
|
||
date_to_unban=date_to_unban,
|
||
ban_author=ban_author_id,
|
||
)
|
||
|
||
await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||
|
||
date_str = (current_date + timedelta(days=7)).strftime("%d.%m.%Y %H:%M")
|
||
try:
|
||
await send_text_message(author_id, call.message, MESSAGE_USER_BANNED_SPAM.format(date=date_str))
|
||
except Exception as e:
|
||
if str(e) == ERROR_BOT_BLOCKED:
|
||
raise UserBlockedBotError("Пользователь заблокировал бота")
|
||
raise
|
||
|
||
logger.info(f"Пользователь {author_id} заблокирован за спам до {date_str}")
|
||
|
||
@track_time("ban_user", "ban_service")
|
||
@track_errors("ban_service", "ban_user")
|
||
async def ban_user(self, user_id: str, user_name: str) -> str:
|
||
"""Бан пользователя по ID"""
|
||
user_name = await self.db.get_username(int(user_id))
|
||
if not user_name:
|
||
raise UserNotFoundError(f"Пользователь с ID {user_id} не найден в базе")
|
||
|
||
return user_name
|
||
|
||
@track_time("unlock_user", "ban_service")
|
||
@track_errors("ban_service", "unlock_user")
|
||
@db_query_time("unlock_user", "users", "delete")
|
||
async def unlock_user(self, user_id: str) -> str:
|
||
"""Разблокировка пользователя"""
|
||
user_name = await self.db.get_username(int(user_id))
|
||
if not user_name:
|
||
raise UserNotFoundError(f"Пользователь с ID {user_id} не найден в базе")
|
||
|
||
await delete_user_blacklist(int(user_id), self.db)
|
||
logger.info(f"Разблокирован пользователь с ID: {user_id} username:{user_name}")
|
||
return user_name
|