fix(app): исправление скачивания торрентов

- generate_clean_magnet: убраны мёртвые трекеры (coppersurfer.tk, leechers-paradise.org),
  добавлены рабочие (tamersunion.org, exodus.desync.com, moeking.me),
  включено &dn= с URL-кодированием кириллицы
- extract_hash_from_result: новая единая функция извлечения хэша из 5 источников
  (Hash, InfoHash, Magnet, btih: в URL, Id)
- /api/add-torrent: убран ложный success — после Ok. от qBittorrent идёт реальная
  верификация (торрент появился в списке по хэшу или названию). Если не появился — error.
- /api/proxy-torrent-download: новый endpoint для скачивания .torrent файлов
  через NL-прокси (обходит DPI-блокировку)
- torrents.html: кнопка Копировать magnet (Clipboard API + fallback),
  proxy-ссылки для .torrent, disabled-состояния для пустых magnet/torrent_url
- tmdb-proxy: добавлен /proxy-torrent endpoint
- urlencode filter для Jinja2
- test_app.py: 47 тестов на чистые функции
This commit is contained in:
vrubelroman 2026-06-03 19:27:14 +00:00
parent fb2aa5a60a
commit a5497eef26
4 changed files with 598 additions and 130 deletions

View file

@ -8,6 +8,7 @@ import os
import logging
import httpx
from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import Response
from fastapi.middleware.cors import CORSMiddleware
# Настройка логирования
@ -130,6 +131,34 @@ async def get_movie_details(
raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
@app.get("/proxy-torrent")
async def proxy_torrent(url: str = Query(..., description="URL .torrent файла")):
"""Скачивает .torrent файл через NL-сервер (обходит DPI-блокировки)"""
async with httpx.AsyncClient(timeout=30.0) as client:
try:
logger.info(f"Proxying .torrent download: {url}")
response = await client.get(url, timeout=30.0)
response.raise_for_status()
return Response(
content=response.content,
media_type=response.headers.get("content-type", "application/x-bittorrent"),
headers={
"Content-Disposition": f'attachment; filename="torrent.torrent"',
"Content-Length": str(len(response.content))
}
)
except httpx.HTTPStatusError as e:
logger.error(f".torrent proxy returned {e.response.status_code} for {url}")
raise HTTPException(
status_code=e.response.status_code,
detail=f"Failed to download .torrent (status {e.response.status_code})"
)
except httpx.RequestError as e:
logger.error(f".torrent proxy request failed for {url}: {e}")
raise HTTPException(
status_code=502,
detail=f"Cannot download .torrent: {str(e)}"
)
@app.get("/health")
async def health_check():
"""Проверка работоспособности сервиса"""