From 0e2aef8c03a0c6f80f9767563794931b6fb85bc2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 24 Jan 2026 01:23:35 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B=20=D1=81=20=D0=BC=D0=B5=D0=B4=D0=B8=D0=B0=D0=B3=D1=80?= =?UTF-8?q?=D1=83=D0=BF=D0=BF=D0=B0=D0=BC=D0=B8=20=D0=B8=20=D1=83=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Реализованы методы для добавления связи между постами и сообщениями в `PostRepository` и `AsyncBotDB`. - Обновлены обработчики публикации постов для корректной работы с медиагруппами, включая удаление и уведомление авторов. - Улучшена логика обработки сообщений в `AlbumMiddleware` для более эффективного сбора медиагрупп. - Обновлены тесты для проверки нового функционала и обработки ошибок. --- database/async_db.py | 8 + database/repositories/post_repository.py | 37 ++++- helper_bot/handlers/callback/services.py | 144 +++++++++++------- .../handlers/private/private_handlers.py | 9 +- helper_bot/handlers/private/services.py | 47 +++--- helper_bot/middlewares/album_middleware.py | 39 ++--- helper_bot/utils/helper_func.py | 53 +++---- 7 files changed, 199 insertions(+), 138 deletions(-) diff --git a/database/async_db.py b/database/async_db.py index b48f689..035b6e1 100644 --- a/database/async_db.py +++ b/database/async_db.py @@ -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) diff --git a/database/repositories/post_repository.py b/database/repositories/post_repository.py index fe183ae..79627a2 100644 --- a/database/repositories/post_repository.py +++ b/database/repositories/post_repository.py @@ -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 = """ diff --git a/helper_bot/handlers/callback/services.py b/helper_bot/handlers/callback/services.py index 1184ab9..671c1b5 100644 --- a/helper_bot/handlers/callback/services.py +++ b/helper_bot/handlers/callback/services.py @@ -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}") diff --git a/helper_bot/handlers/private/private_handlers.py b/helper_bot/handlers/private/private_handlers.py index 05a21b9..1dace7d 100644 --- a/helper_bot/handlers/private/private_handlers.py +++ b/helper_bot/handlers/private/private_handlers.py @@ -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) diff --git a/helper_bot/handlers/private/services.py b/helper_bot/handlers/private/services.py index 4341f13..ec233c3 100644 --- a/helper_bot/handlers/private/services.py +++ b/helper_bot/handlers/private/services.py @@ -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 diff --git a/helper_bot/middlewares/album_middleware.py b/helper_bot/middlewares/album_middleware.py index 65f1743..a94022a 100644 --- a/helper_bot/middlewares/album_middleware.py +++ b/helper_bot/middlewares/album_middleware.py @@ -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) diff --git a/helper_bot/utils/helper_func.py b/helper_bot/utils/helper_func.py index 360c1b7..8159de9 100644 --- a/helper_bot/utils/helper_func.py +++ b/helper_bot/utils/helper_func.py @@ -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")