Добавлена система уведомлений о завершении загрузки в Telegram

- Реализован класс 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
- Реализована система отслеживания активных загрузок
- Автоматическое удаление из мониторинга после уведомления

Теперь пользователи получают уведомления:
🎉 'Фильм скачался!' - при успешном завершении
 'Ошибка загрузки' - при проблемах с загрузкой
This commit is contained in:
vrubelroman 2025-10-09 12:53:06 +03:00
parent bc461d36a6
commit 215b471a5e
2 changed files with 183 additions and 3 deletions

21
app.py
View file

@ -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:

View file

@ -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"🎉 <b>Загрузка завершена!</b>\n\n"
f"🎬 <b>Фильм:</b> {movie_title}\n"
f"📁 <b>Торрент:</b> {torrent_name}\n"
f"✅ <b>Статус:</b> Готов к просмотру!",
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"❌ <b>Ошибка загрузки</b>\n\n"
f"🎬 <b>Фильм:</b> {movie_title}\n"
f"📁 <b>Торрент:</b> {torrent_name}\n"
f"⚠️ <b>Статус:</b> {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()