Добавлена система уведомлений о завершении загрузки в 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:
parent
bc461d36a6
commit
215b471a5e
2 changed files with 183 additions and 3 deletions
21
app.py
21
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:
|
||||
|
|
|
|||
165
telegram_bot.py
165
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"🎉 <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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue