import html from datetime import datetime, timedelta from typing import Dict, Any from aiogram import Bot from aiogram.types import CallbackQuery from helper_bot.utils.helper_func import ( send_text_message, send_photo_message, send_video_message, send_video_note_message, send_audio_message, send_voice_message, send_media_group_to_channel, delete_user_blacklist ) from helper_bot.keyboards.keyboards import create_keyboard_for_ban_reason from .exceptions import ( UserBlockedBotError, PostNotFoundError, UserNotFoundError, PublishError, BanError ) from .constants import ( CONTENT_TYPE_TEXT, CONTENT_TYPE_PHOTO, CONTENT_TYPE_VIDEO, CONTENT_TYPE_VIDEO_NOTE, CONTENT_TYPE_AUDIO, CONTENT_TYPE_VOICE, CONTENT_TYPE_MEDIA_GROUP, MESSAGE_POST_PUBLISHED, MESSAGE_POST_DECLINED, MESSAGE_USER_BANNED_SPAM, ERROR_BOT_BLOCKED ) from logs.custom_logger import logger class PostPublishService: def __init__(self, bot: Bot, db, settings: Dict[str, Any]): # bot может быть None - в этом случае используем бота из контекста сообщения self.bot = bot self.db = db self.settings = settings 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 async def publish_post(self, call: CallbackQuery) -> None: """Основной метод публикации поста""" content_type = call.message.content_type if content_type == CONTENT_TYPE_TEXT and call.message.text != CONTENT_TYPE_MEDIA_GROUP: 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) elif call.message.text == CONTENT_TYPE_MEDIA_GROUP: await self._publish_media_group(call) else: raise PublishError(f"Неподдерживаемый тип контента: {content_type}") async def _publish_text_post(self, call: CallbackQuery) -> None: """Публикация текстового поста""" text_post = html.escape(str(call.message.text)) author_id = await self._get_author_id(call.message.message_id) await send_text_message(self.main_public, call.message, text_post) await self._delete_post_and_notify_author(call, author_id) logger.info(f'Текст сообщения опубликован в канале {self.main_public}.') async def _publish_photo_post(self, call: CallbackQuery) -> None: """Публикация поста с фото""" text_post_with_photo = html.escape(str(call.message.caption)) author_id = await self._get_author_id(call.message.message_id) await send_photo_message(self.main_public, call.message, call.message.photo[-1].file_id, text_post_with_photo) await self._delete_post_and_notify_author(call, author_id) logger.info(f'Пост с фото опубликован в канале {self.main_public}.') async def _publish_video_post(self, call: CallbackQuery) -> None: """Публикация поста с видео""" text_post_with_photo = html.escape(str(call.message.caption)) author_id = await self._get_author_id(call.message.message_id) await send_video_message(self.main_public, call.message, call.message.video.file_id, text_post_with_photo) await self._delete_post_and_notify_author(call, author_id) logger.info(f'Пост с видео опубликован в канале {self.main_public}.') async def _publish_video_note_post(self, call: CallbackQuery) -> None: """Публикация поста с кружком""" author_id = await self._get_author_id(call.message.message_id) await send_video_note_message(self.main_public, call.message, call.message.video_note.file_id) await self._delete_post_and_notify_author(call, author_id) logger.info(f'Пост с кружком опубликован в канале {self.main_public}.') async def _publish_audio_post(self, call: CallbackQuery) -> None: """Публикация поста с аудио""" text_post_with_photo = html.escape(str(call.message.caption)) author_id = await self._get_author_id(call.message.message_id) await send_audio_message(self.main_public, call.message, call.message.audio.file_id, text_post_with_photo) await self._delete_post_and_notify_author(call, author_id) logger.info(f'Пост с аудио опубликован в канале {self.main_public}.') async def _publish_voice_post(self, call: CallbackQuery) -> None: """Публикация поста с войсом""" author_id = await self._get_author_id(call.message.message_id) await send_voice_message(self.main_public, call.message, call.message.voice.file_id) await self._delete_post_and_notify_author(call, author_id) logger.info(f'Пост с войсом опубликован в канале {self.main_public}.') async def _publish_media_group(self, call: CallbackQuery) -> None: """Публикация медиагруппы""" post_content = await self.db.get_post_content_from_telegram_by_last_id(call.message.message_id) pre_text = await self.db.get_post_text_from_telegram_by_last_id(call.message.message_id) post_text = html.escape(str(pre_text)) author_id = await self._get_author_id_for_media_group(call.message.message_id) await send_media_group_to_channel(bot=self._get_bot(call.message), chat_id=self.main_public, post_content=post_content, post_text=post_text) await self._delete_media_group_and_notify_author(call, author_id) async def decline_post(self, call: CallbackQuery) -> None: """Отклонение поста""" content_type = call.message.content_type if (content_type == CONTENT_TYPE_TEXT and call.message.text != CONTENT_TYPE_MEDIA_GROUP) or \ content_type in [CONTENT_TYPE_PHOTO, CONTENT_TYPE_AUDIO, CONTENT_TYPE_VOICE, CONTENT_TYPE_VIDEO, CONTENT_TYPE_VIDEO_NOTE]: await self._decline_single_post(call) elif call.message.text == CONTENT_TYPE_MEDIA_GROUP: await self._decline_media_group(call) else: raise PublishError(f"Неподдерживаемый тип контента для отклонения: {content_type}") async def _decline_single_post(self, call: CallbackQuery) -> None: """Отклонение одиночного поста""" author_id = await self._get_author_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: raise UserBlockedBotError("Пользователь заблокировал бота") raise logger.info(f'Сообщение отклонено админом {call.from_user.full_name} (ID: {call.from_user.id}).') async def _decline_media_group(self, call: CallbackQuery) -> None: """Отклонение медиагруппы""" post_ids = await self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id) message_ids = [row[0] for row in post_ids] message_ids.append(call.message.message_id) author_id = await self._get_author_id_for_media_group(call.message.message_id) await self._get_bot(call.message).delete_messages(chat_id=self.group_for_posts, message_ids=message_ids) try: await send_text_message(author_id, call.message, MESSAGE_POST_DECLINED) except Exception as e: if str(e) == ERROR_BOT_BLOCKED: raise UserBlockedBotError("Пользователь заблокировал бота") raise 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 async def _get_author_id_for_media_group(self, message_id: int) -> int: """Получение ID автора для медиагруппы""" author_id = await self.db.get_author_id_by_helper_message_id(message_id) if not author_id: raise PostNotFoundError(f"Автор не найден для медиагруппы {message_id}") return author_id 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 async def _delete_media_group_and_notify_author(self, call: CallbackQuery, author_id: int) -> None: """Удаление медиагруппы и уведомление автора""" post_ids = await self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id) message_ids = [row[0] for row in post_ids] message_ids.append(call.message.message_id) await self._get_bot(call.message).delete_messages(chat_id=self.group_for_posts, message_ids=message_ids) 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 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'] async def ban_user_from_post(self, call: CallbackQuery) -> None: """Бан пользователя за спам""" 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()) await self.db.set_user_blacklist( user_id=author_id, user_name=None, message_for_user="Спам", date_to_unban=date_to_unban ) 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}") 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 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