From 215b471a5ed5a4388ade98cb2b29948482bc5f31 Mon Sep 17 00:00:00 2001 From: vrubelroman Date: Thu, 9 Oct 2025 12:53:06 +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=D0=B0=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=B0=20?= =?UTF-8?q?=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=BE=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B8=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20Telegram?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Реализован класс DownloadMonitor для мониторинга загрузок в qBittorrent - Добавлена автоматическая аутентификация в qBittorrent API - Система проверяет статус загрузок каждые 30 секунд - Автоматические уведомления при завершении загрузки: * ✅ Успешное завершение с информацией о фильме и торренте * ❌ Уведомления об ошибках загрузки - Интеграция с API: возврат torrent_hash и torrent_name - Отслеживание загрузок по hash с привязкой к пользователю - Фоновый мониторинг через отдельный поток - Уведомления отправляются напрямую в Telegram чат пользователя Технические детали: - Добавлен класс DownloadMonitor в telegram_bot.py - Модифицирован API endpoint /api/add-torrent в app.py - Добавлена поддержка возврата torrent_hash и torrent_name - Реализована система отслеживания активных загрузок - Автоматическое удаление из мониторинга после уведомления Теперь пользователи получают уведомления: 🎉 'Фильм скачался!' - при успешном завершении ❌ 'Ошибка загрузки' - при проблемах с загрузкой --- app.py | 21 +++++- telegram_bot.py | 165 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index 989c682..fcb6878 100644 --- a/app.py +++ b/app.py @@ -684,9 +684,19 @@ async def add_torrent_to_client(torrent_id: str = Form(...)): torrent_hash = torrent_info.get('hash', '').upper() for torrent in torrents: if torrent.get('hash', '').upper() == torrent_hash: - return {"status": "success", "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через magnet-ссылку!"} + return { + "status": "success", + "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через magnet-ссылку!", + "torrent_hash": torrent.get('hash'), + "torrent_name": torrent_info.get('title', 'Unknown') + } - return {"status": "success", "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через magnet-ссылку!"} + return { + "status": "success", + "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через magnet-ссылку!", + "torrent_hash": torrent_hash, + "torrent_name": torrent_info.get('title', 'Unknown') + } else: print(f"Magnet link failed, trying .torrent file...") @@ -703,7 +713,12 @@ async def add_torrent_to_client(torrent_id: str = Form(...)): print(f"Add via .torrent response text: {add_response.text}") if add_response.status_code == 200 and add_response.text.strip() == "Ok.": - return {"status": "success", "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через .torrent файл!"} + return { + "status": "success", + "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через .torrent файл!", + "torrent_hash": torrent_info.get('hash', ''), + "torrent_name": torrent_info.get('title', 'Unknown') + } else: return {"status": "error", "message": f"Ошибка добавления торрента (HTTP {add_response.status_code}): {add_response.text}"} else: diff --git a/telegram_bot.py b/telegram_bot.py index e9190cc..f9331fb 100644 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -8,6 +8,7 @@ import os import asyncio import httpx import logging +import json from typing import Dict, List, Optional from dataclasses import dataclass from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto @@ -65,6 +66,7 @@ class MovieSearchBot: def __init__(self): self.application = Application.builder().token(TELEGRAM_BOT_TOKEN).build() self.user_states = {} # Состояния пользователей + self.download_monitor = None # Мониторинг загрузок self.setup_handlers() def setup_handlers(self): @@ -587,6 +589,20 @@ class MovieSearchBot: data = response.json() if data.get("status") == "success": message = f"✅ {data.get('message', 'Торрент успешно добавлен!')}" + + # Добавляем в мониторинг загрузок + if self.download_monitor and data.get("torrent_hash"): + user_id = update.effective_user.id + movie = context.user_data.get('selected_movie') + torrent_name = data.get("torrent_name", "Unknown") + + if movie: + self.download_monitor.add_download( + torrent_hash=data.get("torrent_hash"), + user_id=user_id, + movie_title=movie.title, + torrent_name=torrent_name + ) else: message = f"❌ {data.get('message', 'Ошибка при добавлении торрента')}" else: @@ -613,8 +629,157 @@ class MovieSearchBot: def run(self): """Запуск бота""" logger.info("Starting Movie Search Bot...") + + # Инициализируем мониторинг загрузок + self.download_monitor = DownloadMonitor(self) + + # Запускаем мониторинг в фоновом режиме + def start_monitoring(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(self.download_monitor.start_monitoring()) + + import threading + monitor_thread = threading.Thread(target=start_monitoring, daemon=True) + monitor_thread.start() + self.application.run_polling() +class DownloadMonitor: + """Мониторинг загрузок в qBittorrent и отправка уведомлений""" + + def __init__(self, bot_instance: MovieSearchBot): + self.bot = bot_instance + self.active_downloads = {} # {torrent_hash: {user_id, movie_title, torrent_name}} + self.qbittorrent_url = f"http://{QBITTORRENT_HOST}:{QBITTORRENT_PORT}" + self.qbittorrent_username = QBITTORRENT_USERNAME + self.qbittorrent_password = QBITTORRENT_PASSWORD + self.session_cookie = None + + async def authenticate_qbittorrent(self): + """Аутентификация в qBittorrent""" + try: + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.post( + f"{self.qbittorrent_url}/api/v2/auth/login", + data={ + "username": self.qbittorrent_username, + "password": self.qbittorrent_password + } + ) + if response.status_code == 200 and response.text == "Ok.": + # Сохраняем cookie для последующих запросов + self.session_cookie = response.cookies.get('SID') + logger.info("qBittorrent authentication successful") + return True + else: + logger.error(f"qBittorrent authentication failed: {response.status_code} - {response.text}") + return False + except Exception as e: + logger.error(f"Error authenticating with qBittorrent: {e}") + return False + + async def get_torrents_info(self): + """Получение информации о торрентах""" + try: + if not self.session_cookie: + await self.authenticate_qbittorrent() + + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + f"{self.qbittorrent_url}/api/v2/torrents/info", + cookies={"SID": self.session_cookie} + ) + if response.status_code == 200: + return response.json() + else: + logger.error(f"Failed to get torrents info: {response.status_code}") + return [] + except Exception as e: + logger.error(f"Error getting torrents info: {e}") + return [] + + async def check_downloads(self): + """Проверка статуса загрузок и отправка уведомлений""" + try: + torrents = await self.get_torrents_info() + if not torrents: + return + + for torrent in torrents: + torrent_hash = torrent.get('hash') + torrent_name = torrent.get('name', 'Unknown') + state = torrent.get('state') + progress = torrent.get('progress', 0) + + # Проверяем, отслеживаем ли мы этот торрент + if torrent_hash in self.active_downloads: + download_info = self.active_downloads[torrent_hash] + user_id = download_info['user_id'] + movie_title = download_info['movie_title'] + + # Если загрузка завершена (state == 'uploading' или progress == 1.0) + if state in ['uploading', 'stalledUP'] or progress >= 1.0: + try: + # Отправляем уведомление + await self.bot.application.bot.send_message( + chat_id=user_id, + text=f"🎉 Загрузка завершена!\n\n" + f"🎬 Фильм: {movie_title}\n" + f"📁 Торрент: {torrent_name}\n" + f"✅ Статус: Готов к просмотру!", + parse_mode=ParseMode.HTML + ) + + # Удаляем из отслеживания + del self.active_downloads[torrent_hash] + logger.info(f"Download completed notification sent for {movie_title}") + + except Exception as e: + logger.error(f"Error sending completion notification: {e}") + + # Если загрузка остановлена с ошибкой + elif state in ['error', 'missingFiles']: + try: + await self.bot.application.bot.send_message( + chat_id=user_id, + text=f"❌ Ошибка загрузки\n\n" + f"🎬 Фильм: {movie_title}\n" + f"📁 Торрент: {torrent_name}\n" + f"⚠️ Статус: {state}", + parse_mode=ParseMode.HTML + ) + + # Удаляем из отслеживания + del self.active_downloads[torrent_hash] + logger.info(f"Download error notification sent for {movie_title}") + + except Exception as e: + logger.error(f"Error sending error notification: {e}") + + except Exception as e: + logger.error(f"Error checking downloads: {e}") + + def add_download(self, torrent_hash: str, user_id: int, movie_title: str, torrent_name: str): + """Добавление торрента в отслеживание""" + self.active_downloads[torrent_hash] = { + 'user_id': user_id, + 'movie_title': movie_title, + 'torrent_name': torrent_name + } + logger.info(f"Added download to monitoring: {movie_title} for user {user_id}") + + async def start_monitoring(self): + """Запуск мониторинга загрузок""" + logger.info("Starting download monitoring...") + while True: + try: + await self.check_downloads() + await asyncio.sleep(30) # Проверяем каждые 30 секунд + except Exception as e: + logger.error(f"Error in monitoring loop: {e}") + await asyncio.sleep(60) # При ошибке ждем минуту + def main(): """Главная функция""" bot = MovieSearchBot()