From 8024eea8689319e7f81a05b70a5927fcf1106b4d Mon Sep 17 00:00:00 2001 From: vrubelroman Date: Wed, 10 Dec 2025 21:05:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BC=D1=83=D0=BB=D1=8C=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA.=20=D0=BE=D1=87=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2.=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D0=BB=20=D0=BE=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=D0=B8=D0=B5=20/start=20=D0=BF=D1=80=D0=BE=20?= =?UTF-8?q?=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 146 ++++++++++++++++++++++++++++++++++-------- instagram_cookies.txt | 6 +- 2 files changed, 121 insertions(+), 31 deletions(-) diff --git a/bot.py b/bot.py index 9f3296c..0bf9199 100644 --- a/bot.py +++ b/bot.py @@ -9,6 +9,7 @@ import subprocess from pathlib import Path from urllib.parse import urlparse from datetime import datetime +from concurrent.futures import ThreadPoolExecutor import yt_dlp import httpx @@ -40,6 +41,10 @@ DATA_DIR = BASE_DIR / 'data' DATA_DIR.mkdir(parents=True, exist_ok=True) DB_FILE = DATA_DIR / 'bot.db' +# ThreadPoolExecutor для выполнения блокирующих операций (скачивание видео) +# Позволяет обрабатывать несколько запросов параллельно +DOWNLOAD_EXECUTOR = ThreadPoolExecutor(max_workers=5, thread_name_prefix="download") + def init_database(): """Инициализирует базу данных и создает таблицы если их нет""" try: @@ -169,6 +174,37 @@ def extract_urls_from_text(text: str) -> list[str]: return urls +def cleanup_old_files(max_age_hours: int = 24): + """Удаляет старые файлы и .part файлы из папки загрузок""" + try: + current_time = time.time() + max_age_seconds = max_age_hours * 3600 + + for file_path in DOWNLOADS_DIR.glob('*'): + if not file_path.is_file(): + continue + + # Удаляем все .part файлы (недокачанные) + if file_path.suffix == '.part': + try: + file_path.unlink() + logger.info(f"Удален .part файл: {file_path.name}") + except Exception as e: + logger.warning(f"Не удалось удалить .part файл {file_path.name}: {e}") + continue + + # Удаляем старые файлы (старше max_age_hours) + try: + file_age = current_time - file_path.stat().st_mtime + if file_age > max_age_seconds: + file_path.unlink() + logger.info(f"Удален старый файл: {file_path.name} (возраст: {file_age/3600:.1f} часов)") + except Exception as e: + logger.warning(f"Не удалось проверить/удалить файл {file_path.name}: {e}") + except Exception as e: + logger.error(f"Ошибка при очистке старых файлов: {e}") + + def _safe_filename(title: str, chat_id: int) -> str: """Создает безопасное имя файла""" safe_title = re.sub(r'[<>:"/\\|?*]', '', title)[:100] @@ -203,10 +239,15 @@ async def download_youtube_video(url: str, chat_id: int, max_retries: int = 3) - }, } - with yt_dlp.YoutubeDL(ydl_opts_info) as ydl: - info = ydl.extract_info(url, download=False) - video_title = info.get('title', 'video') - logger.info(f"YouTube: получена информация о видео: {video_title}") + # Получаем информацию о видео в executor (неблокирующе) + def extract_info_sync(): + with yt_dlp.YoutubeDL(ydl_opts_info) as ydl: + return ydl.extract_info(url, download=False) + + loop = asyncio.get_event_loop() + info = await loop.run_in_executor(DOWNLOAD_EXECUTOR, extract_info_sync) + video_title = info.get('title', 'video') + logger.info(f"YouTube: получена информация о видео: {video_title}") # Скачиваем видео ydl_opts_download = { @@ -232,15 +273,25 @@ async def download_youtube_video(url: str, chat_id: int, max_retries: int = 3) - } logger.info(f"YouTube: начинаем скачивание (попытка {attempt + 1}/{max_retries})") - with yt_dlp.YoutubeDL(ydl_opts_download) as ydl: - loop = asyncio.get_event_loop() - await loop.run_in_executor(None, lambda: ydl.download([url])) - # Находим скачанный файл - downloaded_files = list(DOWNLOADS_DIR.glob(f'{chat_id}_*')) - if downloaded_files: - downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True) - return str(downloaded_files[0]) + # Скачиваем в executor (неблокирующе) + def download_sync(): + with yt_dlp.YoutubeDL(ydl_opts_download) as ydl: + ydl.download([url]) + + await loop.run_in_executor(DOWNLOAD_EXECUTOR, download_sync) + + # Находим скачанный файл (тоже в executor для консистентности) + def find_downloaded_file(): + downloaded_files = list(DOWNLOADS_DIR.glob(f'{chat_id}_*')) + if downloaded_files: + downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True) + return str(downloaded_files[0]) + return None + + video_path = await loop.run_in_executor(DOWNLOAD_EXECUTOR, find_downloaded_file) + if video_path: + return video_path else: raise Exception("Файл не был найден после скачивания") @@ -327,15 +378,25 @@ async def download_instagram_video(url: str, chat_id: int, max_retries: int = 3) logger.info(f"Instagram: начинаем скачивание (попытка {attempt + 1}/{max_retries})") - with yt_dlp.YoutubeDL(ydl_opts) as ydl: - loop = asyncio.get_event_loop() - await loop.run_in_executor(None, lambda: ydl.download([url])) + # Скачиваем в executor (неблокирующе) + def download_sync(): + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + ydl.download([url]) - # Находим скачанный файл - downloaded_files = list(DOWNLOADS_DIR.glob(f'{chat_id}_*')) - if downloaded_files: - downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True) - return str(downloaded_files[0]) + loop = asyncio.get_event_loop() + await loop.run_in_executor(DOWNLOAD_EXECUTOR, download_sync) + + # Находим скачанный файл (тоже в executor для консистентности) + def find_downloaded_file(): + downloaded_files = list(DOWNLOADS_DIR.glob(f'{chat_id}_*')) + if downloaded_files: + downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True) + return str(downloaded_files[0]) + return None + + video_path = await loop.run_in_executor(DOWNLOAD_EXECUTOR, find_downloaded_file) + if video_path: + return video_path else: raise Exception("Файл не был найден после скачивания") @@ -546,14 +607,12 @@ async def keep_instagram_session_alive(): 'socket_timeout': 10, } + def extract_info_sync(): + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + return ydl.extract_info('https://www.instagram.com/', download=False) + loop = asyncio.get_event_loop() - await loop.run_in_executor( - None, - lambda: yt_dlp.YoutubeDL(ydl_opts).extract_info( - 'https://www.instagram.com/', - download=False - ) - ) + await loop.run_in_executor(DOWNLOAD_EXECUTOR, extract_info_sync) logger.info(f"Сессия Instagram успешно обновлена. Cookies действительны еще {days_left} дней") except Exception as e: @@ -731,7 +790,20 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): except Exception as e: logger.error(f"Ошибка: {e}") error_msg = f"❌ Произошла ошибка при обработке видео:\n{str(e)}" - await status_message.edit_text(error_msg) + try: + await status_message.edit_text(error_msg) + except: + # Если status_message не существует, создаем новое сообщение + await update.message.reply_text(error_msg) + + # При ошибке тоже пытаемся удалить временные файлы + try: + # Удаляем все .part файлы для этого chat_id + for part_file in DOWNLOADS_DIR.glob(f'{chat_id}_*.part'): + part_file.unlink() + logger.info(f"Удален .part файл после ошибки: {part_file.name}") + except Exception as cleanup_error: + logger.warning(f"Не удалось удалить .part файлы после ошибки: {cleanup_error}") async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): @@ -749,6 +821,10 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): "• YouTube (youtube.com, youtu.be)\n" "• Instagram (instagram.com)\n" "• VK (vk.com)\n\n" + "👥 Работа в группах:\n" + "Добавь меня в группу и дай права администратора (нужно право на удаление сообщений). " + "После этого я буду автоматически находить ссылки на видео в сообщениях участников, " + "скачивать их и отправлять прямо в группу, заменяя исходное сообщение со ссылкой.\n\n" "Команды:\n" "/start - Начать работу\n" "/stat - Статистика скачанных видео\n\n" @@ -777,6 +853,10 @@ def main(): # Инициализируем базу данных init_database() + # Очищаем старые файлы при старте + logger.info("Очистка старых файлов при старте...") + cleanup_old_files(max_age_hours=1) # Удаляем файлы старше 1 часа + # Создаем приложение application = Application.builder().token(TELEGRAM_BOT_TOKEN).build() @@ -791,6 +871,16 @@ def main(): # Запускаем задачу поддержания сессии Instagram в фоне asyncio.create_task(keep_instagram_session_alive()) logger.info("Фоновая задача поддержания сессии Instagram запущена") + + # Запускаем периодическую очистку файлов (каждые 6 часов) + async def periodic_cleanup(): + while True: + await asyncio.sleep(6 * 3600) # 6 часов + cleanup_old_files(max_age_hours=1) + logger.info("Периодическая очистка старых файлов выполнена") + + asyncio.create_task(periodic_cleanup()) + logger.info("Фоновая задача периодической очистки файлов запущена") application.post_init = post_init diff --git a/instagram_cookies.txt b/instagram_cookies.txt index 45ed93b..721cba7 100644 --- a/instagram_cookies.txt +++ b/instagram_cookies.txt @@ -39,13 +39,13 @@ rusoska.com FALSE / FALSE 1799795493 userToken a01e24c3-c94f-4a4e-b11b-751b72046 .mozilla.org TRUE / FALSE 1799925684 _ga_B9CY1C9VBC GS2.1.s1765365263$o1$g1$t1765365684$j60$l0$h0 .mozilla.org TRUE / FALSE 1799925263 _ga GA1.2.1451822324.1765365263 .mozilla.org TRUE / FALSE 1765451663 _gid GA1.2.878207985.1765365263 -.instagram.com TRUE / TRUE 1799947196 csrftoken CnChQ6nTz8cfm_U7q2ur9w +.instagram.com TRUE / TRUE 1799949717 csrftoken CnChQ6nTz8cfm_U7q2ur9w .instagram.com TRUE / TRUE 1799925292 datr LFY5aVDEvvzQRTypNm_NZ0d3 .instagram.com TRUE / TRUE 1796901312 ig_did B0879634-89D6-4098-9B3E-958B6BC00183 .instagram.com TRUE / TRUE 1765970112 dpr 2 .instagram.com TRUE / TRUE 1799925293 mid aTlWLAAEAAEBRoS_PfrA_i5UP0w1 .instagram.com TRUE / TRUE 1765980504 wd 1920x944 .instagram.com TRUE / TRUE 1796911697 sessionid 42059678244%3AD0GdfKmaFZWqXp%3A10%3AAYieDJrvoWIE9WW--tzjgv-3EyrgI9XT6seopSdHFw -.instagram.com TRUE / TRUE 1773163196 ds_user_id 42059678244 -.instagram.com TRUE / TRUE 0 rur "LDC\05442059678244\0541796923196:01fea3f1fb8a6a9f2cc556e3847d913f9a142263fa428807624c02963fddee2a5d36b728" +.instagram.com TRUE / TRUE 1773165717 ds_user_id 42059678244 +.instagram.com TRUE / TRUE 0 rur "LDC\05442059678244\0541796925717:01fef99bf0a6a2eec6207d44260971856a393246d5f7590afa05e8b7183b7b873f243693" addons.mozilla.org FALSE / TRUE 0 taarId 4dffa50e49cca797bb48f2f4f11803c251746ad45af1fef3ba1ad37379a24fea