diff --git a/database/async_db.py b/database/async_db.py index 6174e78..e9494f3 100644 --- a/database/async_db.py +++ b/database/async_db.py @@ -158,7 +158,19 @@ class AsyncBotDB: async def get_author_id_by_helper_message_id(self, helper_text_message_id: int) -> Optional[int]: """Получает ID автора по helper_text_message_id.""" return await self.factory.posts.get_author_id_by_helper_message_id(helper_text_message_id) - + + async def update_status_by_message_id(self, message_id: int, status: str) -> None: + """Обновление статуса поста по message_id (одиночные посты).""" + await self.factory.posts.update_status_by_message_id(message_id, status) + + async def update_status_for_media_group_by_helper_id( + self, helper_message_id: int, status: str + ) -> None: + """Обновление статуса постов медиагруппы по helper_message_id.""" + await self.factory.posts.update_status_for_media_group_by_helper_id( + helper_message_id, status + ) + # Методы для работы с черным списком async def set_user_blacklist(self, user_id: int, user_name: str = None, message_for_user: str = None, date_to_unban: int = None): diff --git a/database/models.py b/database/models.py index a2b5b90..751e653 100644 --- a/database/models.py +++ b/database/models.py @@ -45,6 +45,7 @@ class TelegramPost: author_id: int helper_text_message_id: Optional[int] = None created_at: Optional[int] = None + status: str = "suggest" @dataclass diff --git a/database/repositories/post_repository.py b/database/repositories/post_repository.py index ca90077..4c730f6 100644 --- a/database/repositories/post_repository.py +++ b/database/repositories/post_repository.py @@ -17,6 +17,7 @@ class PostRepository(DatabaseConnection): helper_text_message_id INTEGER, author_id INTEGER, created_at INTEGER NOT NULL, + status TEXT NOT NULL DEFAULT 'suggest', FOREIGN KEY (author_id) REFERENCES our_users (user_id) ON DELETE CASCADE ) ''' @@ -51,13 +52,14 @@ class PostRepository(DatabaseConnection): """Добавление поста.""" if not post.created_at: post.created_at = int(datetime.now().timestamp()) - + status = post.status if post.status else "suggest" + query = """ - INSERT INTO post_from_telegram_suggest (message_id, text, author_id, created_at) - VALUES (?, ?, ?, ?) + INSERT INTO post_from_telegram_suggest (message_id, text, author_id, created_at, status) + VALUES (?, ?, ?, ?, ?) """ - params = (post.message_id, post.text, post.author_id, post.created_at) - + params = (post.message_id, post.text, post.author_id, post.created_at, status) + await self._execute_query(query, params) self.logger.info(f"Пост добавлен: message_id={post.message_id}") @@ -65,7 +67,27 @@ class PostRepository(DatabaseConnection): """Обновление helper сообщения.""" query = "UPDATE post_from_telegram_suggest SET helper_text_message_id = ? WHERE message_id = ?" await self._execute_query(query, (helper_message_id, message_id)) - + + async def update_status_by_message_id(self, message_id: int, status: str) -> None: + """Обновление статуса поста по message_id (одиночные посты).""" + query = "UPDATE post_from_telegram_suggest SET status = ? WHERE message_id = ?" + await self._execute_query(query, (status, message_id)) + self.logger.info(f"Статус поста message_id={message_id} обновлён на {status}") + + async def update_status_for_media_group_by_helper_id( + self, helper_message_id: int, status: str + ) -> None: + """Обновление статуса постов медиагруппы по helper_text_message_id.""" + query = """ + UPDATE post_from_telegram_suggest + SET status = ? + WHERE message_id = ? OR helper_text_message_id = ? + """ + await self._execute_query(query, (status, helper_message_id, helper_message_id)) + self.logger.info( + f"Статус медиагруппы helper_message_id={helper_message_id} обновлён на {status}" + ) + async def add_post_content(self, post_id: int, message_id: int, content_name: str, content_type: str) -> bool: """Добавление контента поста.""" try: diff --git a/database/schema.sql b/database/schema.sql index 7cb4afd..acb4353 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -57,6 +57,7 @@ CREATE TABLE IF NOT EXISTS post_from_telegram_suggest ( helper_text_message_id INTEGER, author_id INTEGER, created_at INTEGER NOT NULL, + status TEXT NOT NULL DEFAULT 'suggest', FOREIGN KEY (author_id) REFERENCES our_users(user_id) ON DELETE CASCADE ); diff --git a/helper_bot/handlers/callback/services.py b/helper_bot/handlers/callback/services.py index 229eea3..2338ca9 100644 --- a/helper_bot/handlers/callback/services.py +++ b/helper_bot/handlers/callback/services.py @@ -80,7 +80,8 @@ class PostPublishService: """Публикация текстового поста""" text_post = html.escape(str(call.message.text)) author_id = await self._get_author_id(call.message.message_id) - + + await self.db.update_status_by_message_id(call.message.message_id, "approved") 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}.') @@ -91,7 +92,8 @@ class PostPublishService: """Публикация поста с фото""" text_post_with_photo = html.escape(str(call.message.caption)) author_id = await self._get_author_id(call.message.message_id) - + + await self.db.update_status_by_message_id(call.message.message_id, "approved") 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}.') @@ -102,7 +104,8 @@ class PostPublishService: """Публикация поста с видео""" text_post_with_photo = html.escape(str(call.message.caption)) author_id = await self._get_author_id(call.message.message_id) - + + await self.db.update_status_by_message_id(call.message.message_id, "approved") 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}.') @@ -112,7 +115,8 @@ class PostPublishService: async def _publish_video_note_post(self, call: CallbackQuery) -> None: """Публикация поста с кружком""" author_id = await self._get_author_id(call.message.message_id) - + + await self.db.update_status_by_message_id(call.message.message_id, "approved") 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}.') @@ -123,7 +127,8 @@ class PostPublishService: """Публикация поста с аудио""" text_post_with_photo = html.escape(str(call.message.caption)) author_id = await self._get_author_id(call.message.message_id) - + + await self.db.update_status_by_message_id(call.message.message_id, "approved") 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}.') @@ -133,7 +138,8 @@ class PostPublishService: async def _publish_voice_post(self, call: CallbackQuery) -> None: """Публикация поста с войсом""" author_id = await self._get_author_id(call.message.message_id) - + + await self.db.update_status_by_message_id(call.message.message_id, "approved") 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}.') @@ -177,7 +183,8 @@ class PostPublishService: post_content=post_content, post_text=post_text ) - + + 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}.') @@ -214,7 +221,8 @@ class PostPublishService: logger.debug(f"Отклоняю одиночный пост. Message ID: {call.message.message_id}") author_id = await self._get_author_id(call.message.message_id) logger.debug(f"ID автора получен: {author_id}") - + + await self.db.update_status_by_message_id(call.message.message_id, "declined") logger.debug(f"Удаляю сообщение из группы {self.group_for_posts}") await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id) diff --git a/requirements.txt b/requirements.txt index b4721a1..505c65c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ aiohttp==3.9.1 # Network stability improvements aiohttp[speedups]>=3.9.1 aiodns>=3.0.0 -cchardet>=2.1.7 +charset-normalizer>=3.0.0 # Development tools pluggy==1.5.0 diff --git a/scripts/backfill_post_status_legacy.py b/scripts/backfill_post_status_legacy.py new file mode 100644 index 0000000..1b7580a --- /dev/null +++ b/scripts/backfill_post_status_legacy.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +Скрипт для проставления status='legacy' всем существующим записям в post_from_telegram_suggest. +Добавляет колонку status, если её нет, затем обновляет все строки. +""" +import argparse +import asyncio +import os +import sys +from pathlib import Path + +project_root = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(project_root)) + +import aiosqlite + +from logs.custom_logger import logger + +DEFAULT_DB_PATH = "database/tg-bot-database.db" + + +def _column_exists(rows: list, name: str) -> bool: + """PRAGMA table_info returns (cid, name, type, notnull, dflt_value, pk).""" + for row in rows: + if row[1] == name: + return True + return False + + +async def main(db_path: str) -> None: + db_path = os.path.abspath(db_path) + if not os.path.exists(db_path): + logger.error("База данных не найдена: %s", db_path) + print(f"Ошибка: база данных не найдена: {db_path}") + return + + async with aiosqlite.connect(db_path) as conn: + await conn.execute("PRAGMA foreign_keys = ON") + + # Проверяем наличие колонки status + cursor = await conn.execute( + "PRAGMA table_info(post_from_telegram_suggest)" + ) + rows = await cursor.fetchall() + await cursor.close() + + if not _column_exists(rows, "status"): + logger.info("Добавление колонки status в post_from_telegram_suggest") + await conn.execute( + "ALTER TABLE post_from_telegram_suggest " + "ADD COLUMN status TEXT NOT NULL DEFAULT 'suggest'" + ) + await conn.commit() + print("Колонка status добавлена.") + else: + print("Колонка status уже существует.") + + # Обновляем все существующие записи на legacy + await conn.execute( + "UPDATE post_from_telegram_suggest SET status = 'legacy'" + ) + await conn.commit() + cursor = await conn.execute("SELECT changes()") + row = await cursor.fetchone() + updated = row[0] if row else 0 + await cursor.close() + + logger.info("Обновлено записей в post_from_telegram_suggest: %d", updated) + print(f"Обновлено записей: {updated}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Backfill status='legacy' для post_from_telegram_suggest" + ) + parser.add_argument( + "--db", + default=os.environ.get("DB_PATH", DEFAULT_DB_PATH), + help="Путь к БД (или DB_PATH)", + ) + args = parser.parse_args() + asyncio.run(main(args.db))