Добавлен функционал для работы с медиагруппами и улучшена обработка сообщений
- Реализованы методы для добавления связи между постами и сообщениями в `PostRepository` и `AsyncBotDB`. - Обновлены обработчики публикации постов для корректной работы с медиагруппами, включая удаление и уведомление авторов. - Улучшена логика обработки сообщений в `AlbumMiddleware` для более эффективного сбора медиагрупп. - Обновлены тесты для проверки нового функционала и обработки ошибок.
This commit is contained in:
@@ -139,6 +139,10 @@ class AsyncBotDB:
|
||||
"""Добавление контента поста."""
|
||||
return await self.factory.posts.add_post_content(post_id, message_id, content_name, content_type)
|
||||
|
||||
async def add_message_link(self, post_id: int, message_id: int) -> bool:
|
||||
"""Добавляет связь между post_id и message_id в таблицу message_link_to_content."""
|
||||
return await self.factory.posts.add_message_link(post_id, message_id)
|
||||
|
||||
async def get_post_content_from_telegram_by_last_id(self, last_post_id: int) -> List[Tuple[str, str]]:
|
||||
"""Получает контент поста по helper_text_message_id."""
|
||||
return await self.factory.posts.get_post_content_by_helper_id(last_post_id)
|
||||
@@ -175,6 +179,10 @@ class AsyncBotDB:
|
||||
"""Получает ID сообщений по helper_text_message_id."""
|
||||
return await self.factory.posts.get_post_ids_by_helper_id(last_post_id)
|
||||
|
||||
async def get_post_ids_by_helper_id(self, helper_message_id: int) -> List[int]:
|
||||
"""Алиас для get_post_ids_from_telegram_by_last_id (используется callback-сервисом)."""
|
||||
return await self.get_post_ids_from_telegram_by_last_id(helper_message_id)
|
||||
|
||||
async def get_author_id_by_message_id(self, message_id: int) -> Optional[int]:
|
||||
"""Получает ID автора по message_id."""
|
||||
return await self.factory.posts.get_author_id_by_message_id(message_id)
|
||||
|
||||
@@ -27,10 +27,22 @@ class PostRepository(DatabaseConnection):
|
||||
|
||||
# Добавляем поле published_message_id если его нет (для существующих БД)
|
||||
try:
|
||||
await self._execute_query('ALTER TABLE post_from_telegram_suggest ADD COLUMN published_message_id INTEGER')
|
||||
except Exception:
|
||||
# Поле уже существует, игнорируем ошибку
|
||||
pass
|
||||
check_column_query = """
|
||||
SELECT name FROM pragma_table_info('post_from_telegram_suggest')
|
||||
WHERE name = 'published_message_id'
|
||||
"""
|
||||
existing_columns = await self._execute_query_with_result(check_column_query)
|
||||
if not existing_columns:
|
||||
await self._execute_query('ALTER TABLE post_from_telegram_suggest ADD COLUMN published_message_id INTEGER')
|
||||
self.logger.info("Столбец published_message_id добавлен в post_from_telegram_suggest")
|
||||
except Exception as e:
|
||||
# Если проверка не удалась, пытаемся добавить столбец (может быть уже существует)
|
||||
try:
|
||||
await self._execute_query('ALTER TABLE post_from_telegram_suggest ADD COLUMN published_message_id INTEGER')
|
||||
self.logger.info("Столбец published_message_id добавлен в post_from_telegram_suggest (fallback)")
|
||||
except Exception:
|
||||
# Столбец уже существует, игнорируем ошибку
|
||||
pass
|
||||
|
||||
# Таблица контента постов
|
||||
content_query = '''
|
||||
@@ -85,14 +97,15 @@ class PostRepository(DatabaseConnection):
|
||||
# Преобразуем bool в int для SQLite (True -> 1, False -> 0, None -> None)
|
||||
is_anonymous_int = None if post.is_anonymous is None else (1 if post.is_anonymous else 0)
|
||||
|
||||
# Используем INSERT OR IGNORE чтобы избежать ошибок при повторном создании
|
||||
query = """
|
||||
INSERT INTO post_from_telegram_suggest (message_id, text, author_id, created_at, status, is_anonymous)
|
||||
INSERT OR IGNORE INTO post_from_telegram_suggest (message_id, text, author_id, created_at, status, is_anonymous)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
params = (post.message_id, post.text, post.author_id, post.created_at, status, is_anonymous_int)
|
||||
|
||||
await self._execute_query(query, params)
|
||||
self.logger.info(f"Пост добавлен: message_id={post.message_id}")
|
||||
self.logger.info(f"Пост добавлен (или уже существует): message_id={post.message_id}, text длина={len(post.text) if post.text else 0}, is_anonymous={is_anonymous_int}")
|
||||
|
||||
async def update_helper_message(self, message_id: int, helper_message_id: int) -> None:
|
||||
"""Обновление helper сообщения."""
|
||||
@@ -188,6 +201,18 @@ class PostRepository(DatabaseConnection):
|
||||
self.logger.error(f"Ошибка при добавлении контента поста: {e}")
|
||||
return False
|
||||
|
||||
async def add_message_link(self, post_id: int, message_id: int) -> bool:
|
||||
"""Добавляет связь между post_id и message_id в таблицу message_link_to_content."""
|
||||
try:
|
||||
self.logger.info(f"Добавление связи: post_id={post_id}, message_id={message_id}")
|
||||
link_query = "INSERT OR IGNORE INTO message_link_to_content (post_id, message_id) VALUES (?, ?)"
|
||||
await self._execute_query(link_query, (post_id, message_id))
|
||||
self.logger.info(f"Связь успешно добавлена: post_id={post_id}, message_id={message_id}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Ошибка при добавлении связи post_id={post_id}, message_id={message_id}: {e}")
|
||||
return False
|
||||
|
||||
async def get_post_content_by_helper_id(self, helper_message_id: int) -> List[Tuple[str, str]]:
|
||||
"""Получает контент поста по helper_text_message_id."""
|
||||
query = """
|
||||
|
||||
@@ -54,7 +54,12 @@ class PostPublishService:
|
||||
@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
|
||||
@@ -280,44 +285,42 @@ class PostPublishService:
|
||||
@track_media_processing("media_group")
|
||||
async def _publish_media_group(self, call: CallbackQuery) -> None:
|
||||
"""Публикация медиагруппы"""
|
||||
logger.info(f"Начинаю публикацию медиагруппы. Helper message ID: {call.message.message_id}")
|
||||
try:
|
||||
# call.message.message_id - это ID helper сообщения
|
||||
helper_message_id = call.message.message_id
|
||||
|
||||
# Получаем контент медиагруппы по helper_message_id
|
||||
logger.debug(f"Получаю контент медиагруппы для helper_message_id: {helper_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"Контент медиагруппы не найден в базе данных для helper_message_id: {helper_message_id}")
|
||||
logger.error(f"_publish_media_group: Контент медиагруппы не найден в базе данных для helper_message_id={helper_message_id}")
|
||||
raise PublishError("Контент медиагруппы не найден в базе данных")
|
||||
|
||||
# Получаем сырой текст и is_anonymous по helper_message_id
|
||||
logger.debug(f"Получаю текст и is_anonymous поста для helper_message_id: {helper_message_id}")
|
||||
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 = ""
|
||||
logger.debug(f"Текст поста получен: {'пустой' if not raw_text else f'длина: {len(raw_text)} символов'}, is_anonymous={is_anonymous}")
|
||||
|
||||
# Получаем ID автора по helper_message_id
|
||||
logger.debug(f"Получаю ID автора для helper_message_id: {helper_message_id}")
|
||||
author_id = await self.db.get_author_id_by_helper_message_id(helper_message_id)
|
||||
if not author_id:
|
||||
logger.error(f"Автор не найден для медиагруппы {helper_message_id}")
|
||||
logger.error(f"_publish_media_group: Автор не найден для медиагруппы helper_message_id={helper_message_id}")
|
||||
raise PostNotFoundError(f"Автор не найден для медиагруппы {helper_message_id}")
|
||||
logger.debug(f"ID автора получен: {author_id}")
|
||||
|
||||
# Получаем данные автора
|
||||
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)
|
||||
logger.debug(f"Сформирован финальный текст: {'пустой' if not formatted_text else f'длина: {len(formatted_text)} символов'}")
|
||||
|
||||
# Отправляем медиагруппу в канал
|
||||
logger.info(f"Отправляю медиагруппу в канал {self.main_public}")
|
||||
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,
|
||||
@@ -326,31 +329,49 @@ class PostPublishService:
|
||||
s3_storage=self.s3_storage
|
||||
)
|
||||
|
||||
# Получаем оригинальные message_id из медиагруппы
|
||||
original_message_ids = await self.db.get_post_ids_from_telegram_by_last_id(helper_message_id)
|
||||
logger.debug(f"Получены оригинальные message_id медиагруппы: {original_message_ids}")
|
||||
|
||||
# Сохраняем published_message_id для каждого сообщения медиагруппы
|
||||
if len(sent_messages) == len(original_message_ids):
|
||||
for i, original_message_id in enumerate(original_message_ids):
|
||||
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
|
||||
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)
|
||||
logger.debug(f"Сохранен published_message_id: {original_message_id} -> {published_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"Количество опубликованных сообщений ({len(sent_messages)}) не совпадает с количеством оригинальных ({len(original_message_ids)})")
|
||||
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")
|
||||
logger.debug(f"Удаляю медиагруппу и уведомляю автора {author_id}")
|
||||
await self._delete_media_group_and_notify_author(call, author_id)
|
||||
logger.info(f'Медиагруппа опубликована в канале {self.main_public}, опубликовано сообщений: {len(sent_messages)}.')
|
||||
|
||||
# Удаляем 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"Ошибка при публикации медиагруппы: {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")
|
||||
@@ -399,27 +420,32 @@ class PostPublishService:
|
||||
@track_media_processing("media_group")
|
||||
async def _decline_media_group(self, call: CallbackQuery) -> None:
|
||||
"""Отклонение медиагруппы"""
|
||||
await self.db.update_status_for_media_group_by_helper_id(call.message.message_id, "declined")
|
||||
helper_message_id = call.message.message_id
|
||||
|
||||
await self.db.update_status_for_media_group_by_helper_id(helper_message_id, "declined")
|
||||
|
||||
post_ids = await self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id)
|
||||
message_ids = post_ids.copy()
|
||||
message_ids.append(call.message.message_id)
|
||||
logger.debug(f"Получены ID сообщений для удаления: {message_ids}")
|
||||
media_group_message_ids = await self.db.get_post_ids_by_helper_id(helper_message_id)
|
||||
|
||||
author_id = await self._get_author_id_for_media_group(call.message.message_id)
|
||||
logger.debug(f"ID автора медиагруппы получен: {author_id}")
|
||||
message_ids_to_delete = media_group_message_ids.copy()
|
||||
message_ids_to_delete.append(helper_message_id)
|
||||
|
||||
logger.debug(f"Удаляю {len(message_ids)} сообщений из группы {self.group_for_posts}")
|
||||
await self._get_bot(call.message).delete_messages(chat_id=self.group_for_posts, message_ids=message_ids)
|
||||
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:
|
||||
logger.debug(f"Отправляю уведомление об отклонении автору медиагруппы {author_id}")
|
||||
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} заблокировал бота")
|
||||
logger.warning(f"_decline_media_group: Пользователь {author_id} заблокировал бота")
|
||||
raise UserBlockedBotError("Пользователь заблокировал бота")
|
||||
logger.error(f"Ошибка при отправке уведомления автору медиагруппы {author_id}: {e}")
|
||||
logger.error(f"_decline_media_group: Ошибка при отправке уведомления автору {author_id}: {e}")
|
||||
raise
|
||||
|
||||
@track_time("_get_author_id", "post_publish_service")
|
||||
@@ -477,12 +503,15 @@ class PostPublishService:
|
||||
@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:
|
||||
"""Удаление медиагруппы и уведомление автора"""
|
||||
post_ids = await self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id)
|
||||
"""Удаление медиагруппы и уведомление автора (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 = post_ids.copy()
|
||||
post_ids.append(call.message.message_id)
|
||||
await self._get_bot(call.message).delete_messages(chat_id=self.group_for_posts, message_ids=post_ids)
|
||||
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:
|
||||
@@ -538,7 +567,12 @@ class BanService:
|
||||
@db_query_time("ban_user_from_post", "users", "mixed")
|
||||
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)
|
||||
# Если это 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}")
|
||||
|
||||
|
||||
@@ -51,9 +51,8 @@ class PrivateHandlers:
|
||||
self.post_service = PostService(db, settings, s3_storage)
|
||||
self.sticker_service = StickerService(settings)
|
||||
|
||||
# Create router
|
||||
self.router = Router()
|
||||
self.router.message.middleware(AlbumMiddleware())
|
||||
self.router.message.middleware(AlbumMiddleware(latency=5.0))
|
||||
self.router.message.middleware(BlacklistMiddleware())
|
||||
|
||||
# Register handlers
|
||||
@@ -158,12 +157,10 @@ class PrivateHandlers:
|
||||
@track_time("suggest_router", "private_handlers")
|
||||
async def suggest_router(self, message: types.Message, state: FSMContext, album: list = None, **kwargs):
|
||||
"""Handle post submission in suggest state"""
|
||||
# Post service operations with metrics
|
||||
await self.user_service.update_user_activity(message.from_user.id)
|
||||
await self.user_service.log_user_message(message)
|
||||
if message.media_group_id is None:
|
||||
await self.user_service.log_user_message(message)
|
||||
await self.post_service.process_post(message, album)
|
||||
|
||||
# Send success message and return to start state
|
||||
markup_for_user = await get_reply_keyboard(self.db, message.from_user.id)
|
||||
success_send_message = messages.get_message(get_first_name(message), 'SUCCESS_SEND_MESSAGE')
|
||||
await message.answer(success_send_message, reply_markup=markup_for_user)
|
||||
|
||||
@@ -323,7 +323,6 @@ class PostService:
|
||||
@track_media_processing("media_group")
|
||||
async def handle_media_group_post(self, message: types.Message, album: list, first_name: str) -> None:
|
||||
"""Handle media group post submission"""
|
||||
#TODO: Мне кажется тут какая-то дичь с одинаковыми переменными, в которых post_caption никуда не ведет
|
||||
post_caption = " "
|
||||
raw_caption = ""
|
||||
|
||||
@@ -331,12 +330,17 @@ class PostService:
|
||||
raw_caption = album[0].caption or ""
|
||||
post_caption = get_text_message(album[0].caption.lower(), first_name, message.from_user.username)
|
||||
|
||||
# Определяем анонимность на основе сырого caption
|
||||
is_anonymous = determine_anonymity(raw_caption)
|
||||
media_group = await prepare_media_group_from_middlewares(album, post_caption)
|
||||
|
||||
media_group_message_ids = await send_media_group_message_to_private_chat(
|
||||
self.settings.group_for_posts, message, media_group, self.db, None, self.s3_storage
|
||||
)
|
||||
|
||||
main_post_id = media_group_message_ids[-1]
|
||||
|
||||
# Создаем основной пост для медиагруппы
|
||||
main_post = TelegramPost(
|
||||
message_id=message.message_id, # ID основного сообщения медиагруппы
|
||||
message_id=main_post_id,
|
||||
text=raw_caption,
|
||||
author_id=message.from_user.id,
|
||||
created_at=int(datetime.now().timestamp()),
|
||||
@@ -344,32 +348,32 @@ class PostService:
|
||||
)
|
||||
await self.db.add_post(main_post)
|
||||
|
||||
# Отправляем медиагруппу в группу для модерации
|
||||
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, main_post.message_id, self.s3_storage
|
||||
)
|
||||
for msg_id in media_group_message_ids:
|
||||
await self.db.add_message_link(main_post_id, msg_id)
|
||||
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
# Создаем helper сообщение с кнопками
|
||||
markup = get_reply_keyboard_for_post()
|
||||
help_message = await send_text_message(self.settings.group_for_posts, message, "ВРУЧНУЮ ВЫКЛАДЫВАТЬ, ПОСЛЕ ВЫКЛАДКИ УДАЛИТЬ ОБА ПОСТА")
|
||||
helper_message = await send_text_message(
|
||||
self.settings.group_for_posts,
|
||||
message,
|
||||
"^",
|
||||
markup
|
||||
)
|
||||
helper_message_id = helper_message.message_id
|
||||
|
||||
# Создаем helper пост и связываем его с основным
|
||||
helper_post = TelegramPost(
|
||||
message_id=help_message.message_id, # ID helper сообщения
|
||||
text="^", # Специальный маркер для медиагруппы
|
||||
message_id=helper_message_id,
|
||||
text="^",
|
||||
author_id=message.from_user.id,
|
||||
helper_text_message_id=main_post.message_id, # Ссылка на основной пост
|
||||
helper_text_message_id=main_post_id,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(helper_post)
|
||||
|
||||
# Обновляем основной пост, чтобы он ссылался на helper
|
||||
await self.db.update_helper_message(
|
||||
message_id=main_post.message_id,
|
||||
helper_message_id=help_message.message_id
|
||||
message_id=main_post_id,
|
||||
helper_message_id=helper_message_id
|
||||
)
|
||||
|
||||
@track_time("process_post", "post_service")
|
||||
@@ -378,13 +382,8 @@ class PostService:
|
||||
async def process_post(self, message: types.Message, album: Union[list, None] = None) -> None:
|
||||
"""Process post based on content type"""
|
||||
first_name = get_first_name(message)
|
||||
# TODO: Бесит меня этот функционал
|
||||
|
||||
if message.media_group_id is not None:
|
||||
safe_username = html.escape(message.from_user.username) if message.from_user.username else "Без никнейма"
|
||||
await send_text_message(
|
||||
self.settings.group_for_logs, message,
|
||||
f'Закинул медиагруппу, пользователь: имя - {first_name}, ник - {safe_username}'
|
||||
)
|
||||
await self.handle_media_group_post(message, album, first_name)
|
||||
return
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ class AlbumMiddleware(BaseMiddleware):
|
||||
Собирает все сообщения одной медиа группы и передает их как album в data.
|
||||
"""
|
||||
|
||||
def __init__(self, latency: Union[int, float] = 0.01):
|
||||
def __init__(self, latency: Union[int, float] = 5.0):
|
||||
"""
|
||||
Инициализация middleware.
|
||||
|
||||
@@ -45,38 +45,43 @@ class AlbumMiddleware(BaseMiddleware):
|
||||
"""
|
||||
Основная логика middleware.
|
||||
|
||||
Собирает все сообщения медиагруппы и обрабатывает только последнее сообщение
|
||||
после завершения сбора всех сообщений.
|
||||
|
||||
Args:
|
||||
handler: Обработчик события
|
||||
event: Событие (сообщение)
|
||||
data: Данные для передачи в обработчик
|
||||
|
||||
|
||||
Returns:
|
||||
Результат выполнения обработчика
|
||||
"""
|
||||
# Если у события нет media_group_id, передаем его обработчику сразу
|
||||
if not event.media_group_id:
|
||||
return await handler(event, data)
|
||||
|
||||
# Собираем сообщения одной медиа группы
|
||||
total_before = self.collect_album_messages(event)
|
||||
media_group_id = event.media_group_id
|
||||
message_id = event.message_id
|
||||
|
||||
if media_group_id not in self.album_data:
|
||||
self.album_data[media_group_id] = {"messages": []}
|
||||
|
||||
self.album_data[media_group_id]["messages"].append(event)
|
||||
count_before = len(self.album_data[media_group_id]["messages"])
|
||||
|
||||
# Ждем указанный период для сбора всех сообщений
|
||||
await asyncio.sleep(self.latency)
|
||||
|
||||
# Проверяем количество сообщений после задержки
|
||||
total_after = len(self.album_data[event.media_group_id]["messages"])
|
||||
|
||||
# Если за время задержки добавились новые сообщения, выходим
|
||||
if total_before != total_after:
|
||||
count_after = len(self.album_data[media_group_id]["messages"])
|
||||
if count_before != count_after:
|
||||
return
|
||||
|
||||
# Сортируем сообщения по message_id и добавляем в data
|
||||
album_messages = self.album_data[event.media_group_id]["messages"]
|
||||
album_messages = self.album_data[media_group_id]["messages"]
|
||||
album_messages.sort(key=lambda x: x.message_id)
|
||||
last_message_id = album_messages[-1].message_id
|
||||
|
||||
if message_id != last_message_id:
|
||||
return
|
||||
|
||||
data["album"] = album_messages
|
||||
del self.album_data[media_group_id]
|
||||
|
||||
# Удаляем медиа группу из отслеживания для освобождения памяти
|
||||
del self.album_data[event.media_group_id]
|
||||
|
||||
# Вызываем оригинальный обработчик события
|
||||
return await handler(event, data)
|
||||
|
||||
@@ -369,9 +369,7 @@ async def add_in_db_media_mediagroup(sent_message: List[types.Message], bot_db:
|
||||
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
|
||||
@@ -381,7 +379,6 @@ async def add_in_db_media_mediagroup(sent_message: List[types.Message], bot_db:
|
||||
content_type = None
|
||||
file_id = None
|
||||
|
||||
# Определяем тип контента и file_id
|
||||
if message.photo:
|
||||
content_type = 'photo'
|
||||
file_id = message.photo[-1].file_id
|
||||
@@ -407,54 +404,40 @@ async def add_in_db_media_mediagroup(sent_message: List[types.Message], bot_db:
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
logger.debug(f"add_in_db_media_mediagroup: Обрабатываю {content_type} в сообщении {i+1}/{len(sent_message)}")
|
||||
|
||||
# Получаем s3_storage если не передан
|
||||
if s3_storage is None:
|
||||
bdf = get_global_instance()
|
||||
s3_storage = bdf.get_s3_storage()
|
||||
|
||||
# Скачиваем файл (в S3 или на локальный диск)
|
||||
file_path = await download_file(message, file_id=file_id, content_type=content_type, s3_storage=s3_storage)
|
||||
if not file_path:
|
||||
logger.error(f"add_in_db_media_mediagroup: Не удалось скачать файл {file_id} в сообщении {i+1}/{len(sent_message)}")
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
# Добавляем в базу данных
|
||||
# Для медиагруппы используем post_id (основной пост) как message_id для контента,
|
||||
# так как FOREIGN KEY требует существования message_id в post_from_telegram_suggest
|
||||
success = await bot_db.add_post_content(post_id, post_id, file_path, content_type)
|
||||
if not success:
|
||||
logger.error(f"add_in_db_media_mediagroup: Не удалось добавить контент в БД для сообщения {i+1}/{len(sent_message)}")
|
||||
# Удаляем скачанный файл при ошибке БД (только если это локальный файл, не S3)
|
||||
if file_path.startswith('files/'):
|
||||
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}")
|
||||
return False
|
||||
|
||||
if failed_count > 0:
|
||||
logger.warning(f"add_in_db_media_mediagroup: Обработано {processed_count}/{len(sent_message)} сообщений медиагруппы {post_id}, ошибок: {failed_count}")
|
||||
else:
|
||||
logger.info(f"add_in_db_media_mediagroup: Успешно обработана медиагруппа {post_id} - {processed_count} сообщений, время: {processing_time:.2f}с")
|
||||
|
||||
return failed_count == 0
|
||||
|
||||
@@ -556,22 +539,32 @@ async def add_in_db_media(sent_message: types.Message, bot_db: Any, s3_storage =
|
||||
@track_media_processing("media_group")
|
||||
@db_query_time("send_media_group_message_to_private_chat", "posts", "insert")
|
||||
async def send_media_group_message_to_private_chat(chat_id: int, message: types.Message,
|
||||
media_group: List, bot_db: Any, main_post_id: Optional[int] = None, s3_storage=None) -> int:
|
||||
sent_message = await message.bot.send_media_group(
|
||||
media_group: List, bot_db: Any, main_post_id: Optional[int] = None, s3_storage=None) -> List[int]:
|
||||
"""
|
||||
Отправляет медиагруппу в чат и возвращает все message_id отправленных сообщений.
|
||||
|
||||
Args:
|
||||
chat_id: ID чата для отправки
|
||||
message: Оригинальное сообщение от пользователя
|
||||
media_group: Список InputMedia объектов
|
||||
bot_db: Экземпляр базы данных
|
||||
main_post_id: ID основного поста в БД (опционально)
|
||||
s3_storage: S3StorageService для сохранения медиа
|
||||
|
||||
Returns:
|
||||
List[int]: Список всех message_id отправленных сообщений медиагруппы
|
||||
"""
|
||||
sent_messages = await message.bot.send_media_group(
|
||||
chat_id=chat_id,
|
||||
media=media_group,
|
||||
)
|
||||
post = TelegramPost(
|
||||
message_id=sent_message[-1].message_id,
|
||||
text=sent_message[-1].caption or "",
|
||||
author_id=message.from_user.id,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await bot_db.add_post(post)
|
||||
# Сохраняем медиа в фоне, чтобы не блокировать ответ пользователю
|
||||
asyncio.create_task(_save_media_group_background(sent_message, bot_db, main_post_id, s3_storage))
|
||||
message_id = sent_message[-1].message_id
|
||||
return message_id
|
||||
|
||||
sent_message_ids = [msg.message_id for msg in sent_messages]
|
||||
main_message_id = sent_message_ids[-1]
|
||||
|
||||
asyncio.create_task(_save_media_group_background(sent_messages, bot_db, main_message_id, s3_storage))
|
||||
|
||||
return sent_message_ids
|
||||
|
||||
@track_time("send_media_group_to_channel", "helper_func")
|
||||
@track_errors("helper_func", "send_media_group_to_channel")
|
||||
|
||||
Reference in New Issue
Block a user