Добавлена система уведомлений о завершении загрузки в 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() torrent_hash = torrent_info.get('hash', '').upper()
for torrent in torrents: for torrent in torrents:
if torrent.get('hash', '').upper() == torrent_hash: 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: else:
print(f"Magnet link failed, trying .torrent file...") 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}") print(f"Add via .torrent response text: {add_response.text}")
if add_response.status_code == 200 and add_response.text.strip() == "Ok.": 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: else:
return {"status": "error", "message": f"Ошибка добавления торрента (HTTP {add_response.status_code}): {add_response.text}"} return {"status": "error", "message": f"Ошибка добавления торрента (HTTP {add_response.status_code}): {add_response.text}"}
else: else:

View file

@ -8,6 +8,7 @@ import os
import asyncio import asyncio
import httpx import httpx
import logging import logging
import json
from typing import Dict, List, Optional from typing import Dict, List, Optional
from dataclasses import dataclass from dataclasses import dataclass
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto
@ -65,6 +66,7 @@ class MovieSearchBot:
def __init__(self): def __init__(self):
self.application = Application.builder().token(TELEGRAM_BOT_TOKEN).build() self.application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
self.user_states = {} # Состояния пользователей self.user_states = {} # Состояния пользователей
self.download_monitor = None # Мониторинг загрузок
self.setup_handlers() self.setup_handlers()
def setup_handlers(self): def setup_handlers(self):
@ -587,6 +589,20 @@ class MovieSearchBot:
data = response.json() data = response.json()
if data.get("status") == "success": if data.get("status") == "success":
message = f"{data.get('message', 'Торрент успешно добавлен!')}" 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: else:
message = f"{data.get('message', 'Ошибка при добавлении торрента')}" message = f"{data.get('message', 'Ошибка при добавлении торрента')}"
else: else:
@ -613,8 +629,157 @@ class MovieSearchBot:
def run(self): def run(self):
"""Запуск бота""" """Запуск бота"""
logger.info("Starting Movie Search Bot...") 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() 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(): def main():
"""Главная функция""" """Главная функция"""
bot = MovieSearchBot() bot = MovieSearchBot()