- 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.
257 lines
13 KiB
Python
257 lines
13 KiB
Python
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
|