Добавлена система уведомлений о завершении загрузки в 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()
|
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:
|
||||||
|
|
|
||||||
165
telegram_bot.py
165
telegram_bot.py
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue