более надежный поиск магнет ссылки

This commit is contained in:
vrubel 2025-10-31 11:43:43 +03:00
parent 05f6fe40f0
commit 368d57012d
2 changed files with 209 additions and 36 deletions

239
app.py
View file

@ -301,18 +301,40 @@ async def search_torrents(movie_title: str, year: str = None, original_title: st
# Обрабатываем все результаты и скорим их # Обрабатываем все результаты и скорим их
for result in results[:20]: # Берем больше результатов для скоринга for result in results[:20]: # Берем больше результатов для скоринга
print(f"Processing torrent: {result['Name'][:100]}...") print(f"Processing torrent: {result['Name'][:100]}...")
# Логируем доступные поля для отладки
torrent_id = result.get('Id') or result.get('id') or result.get('ID') or result.get('Hash', '')[:8] or ''
if torrent_id:
print(f" Found ID: {torrent_id[:20]}...")
torrent = parse_torrent_result(result, movie_title, year) torrent = parse_torrent_result(result, movie_title, year)
if torrent: if torrent:
# Если ID не был найден, пробуем использовать Hash как ID
if not torrent.get('id') or torrent.get('id') == '':
torrent_id_from_hash = result.get('Hash', '')
if torrent_id_from_hash:
torrent['id'] = torrent_id_from_hash
print(f" Using Hash as ID: {torrent_id_from_hash[:20]}...")
# Скорим торрент # Скорим торрент
score = score_torrent(torrent, movie_title, original_title, year) score = score_torrent(torrent, movie_title, original_title, year)
torrent['relevance_score'] = score torrent['relevance_score'] = score
size_mb = torrent.get('size_bytes', 0) / (1024 * 1024) size_mb = torrent.get('size_bytes', 0) / (1024 * 1024)
print(f"Torrent score: {score:.2f}, size: {size_mb:.1f}MB, seeds: {torrent.get('seeds', 0)} - {result['Name'][:100]}...") print(f"Torrent score: {score:.2f}, size: {size_mb:.1f}MB, seeds: {torrent.get('seeds', 0)}, ID: {torrent.get('id', 'None')[:20]}...")
# Добавляем только если скор больше 0.1 и размер больше 100MB # Добавляем только если скор больше 0.1 и размер больше 100MB
if score > 0.1 and torrent.get('size_bytes', 0) > 100 * 1024 * 1024: if score > 0.1 and torrent.get('size_bytes', 0) > 100 * 1024 * 1024:
# Проверяем, что ID есть, иначе используем Hash
if not torrent.get('id') or torrent.get('id') == '':
if result.get('Hash'):
torrent['id'] = result['Hash']
elif result.get('Url'):
# Пытаемся извлечь ID из URL
url_id_match = re.search(r'/(\d+)/?$', result.get('Url', ''))
if url_id_match:
torrent['id'] = url_id_match.group(1)
else:
torrent['id'] = result.get('Url', '')[-20:] # Используем последние 20 символов URL
torrents.append(torrent) torrents.append(torrent)
print(f" ✅ Added to results") print(f" ✅ Added to results with ID: {torrent.get('id', 'None')[:20]}...")
else: else:
print(f" ❌ Filtered out (score: {score:.2f}, size: {size_mb:.1f}MB)") print(f" ❌ Filtered out (score: {score:.2f}, size: {size_mb:.1f}MB)")
@ -355,15 +377,28 @@ async def search_torrent_by_id(torrent_id: str) -> dict:
try: try:
print(f"Searching ID {torrent_id} on {provider_name}") print(f"Searching ID {torrent_id} on {provider_name}")
response = await client.get( # Пробуем разные варианты API для поиска по ID
search_urls = [
f"{TORRENT_SEARCH_URL}/api/search/id/{provider_name}", f"{TORRENT_SEARCH_URL}/api/search/id/{provider_name}",
params={"query": torrent_id} f"{TORRENT_SEARCH_URL}/api/search/{provider_name}",
) ]
if response.status_code == 200: response = None
results = response.json() results = None
print(f"Response from {provider_name}: {type(results)} - {len(results) if isinstance(results, list) else 'not a list'}")
if results and isinstance(results, list) and len(results) > 0: for search_url in search_urls:
try:
response = await client.get(search_url, params={"query": torrent_id}, timeout=30.0)
if response.status_code == 200:
results = response.json()
print(f"Response from {provider_name} ({search_url}): {type(results)} - {len(results) if isinstance(results, list) else 'not a list'}")
break
except Exception as e:
print(f"Error trying {search_url}: {e}")
continue
if response and response.status_code == 200 and results:
if isinstance(results, list) and len(results) > 0:
# Берем первый результат # Берем первый результат
result = results[0] result = results[0]
# Используем Original_Name если Name пустое # Используем Original_Name если Name пустое
@ -374,6 +409,16 @@ async def search_torrent_by_id(torrent_id: str) -> dict:
hash_value = result.get('Hash', '') hash_value = result.get('Hash', '')
torrent_title = torrent_name torrent_title = torrent_name
# Если хэша нет в Hash, пытаемся извлечь из Magnet ссылки
if not hash_value:
original_magnet = result.get('Magnet', '')
if original_magnet:
# Извлекаем хэш из magnet ссылки
hash_match = re.search(r'urn:btih:([a-fA-F0-9]{40}|[a-zA-Z0-9]{32})', original_magnet)
if hash_match:
hash_value = hash_match.group(1)
print(f"Extracted hash from magnet link: {hash_value[:10]}...")
if hash_value: if hash_value:
# Генерируем чистую magnet-ссылку с публичными трекерами # Генерируем чистую magnet-ссылку с публичными трекерами
magnet = generate_clean_magnet(hash_value, torrent_title) magnet = generate_clean_magnet(hash_value, torrent_title)
@ -381,8 +426,9 @@ async def search_torrent_by_id(torrent_id: str) -> dict:
else: else:
# Fallback на оригинальную magnet-ссылку если нет хэша # Fallback на оригинальную magnet-ссылку если нет хэша
magnet = result.get('Magnet', '') magnet = result.get('Magnet', '')
if magnet and not magnet.startswith('magnet:'): if not magnet or not magnet.startswith('magnet:'):
magnet = f"magnet:?xt=urn:btih:{hash_value}" print(f"Warning: No hash found and no valid magnet link. Hash: {hash_value}, Magnet: {result.get('Magnet', 'None')[:50]}")
magnet = ""
# Парсим результат в стандартный формат # Парсим результат в стандартный формат
torrent = { torrent = {
@ -486,6 +532,25 @@ def parse_torrent_result(result: dict, movie_title: str, year: str = None) -> di
elif 'nnmclub.to' in torrent_url: elif 'nnmclub.to' in torrent_url:
provider = 'nonameclub' provider = 'nonameclub'
# Извлекаем ID из разных возможных полей
torrent_id = (
result.get('Id') or
result.get('id') or
result.get('ID') or
result.get('Hash', '')[:8] or # Используем первые 8 символов хэша как fallback
''
)
# Если ID не найден, пытаемся извлечь из URL
if not torrent_id and result.get('Url'):
url_id_match = re.search(r'/(\d+)/?$', result.get('Url', ''))
if url_id_match:
torrent_id = url_id_match.group(1)
# Если все еще нет ID, используем Hash полностью
if not torrent_id:
torrent_id = result.get('Hash', '')
return { return {
"title": result['Name'], "title": result['Name'],
"size_bytes": size_bytes, "size_bytes": size_bytes,
@ -499,7 +564,7 @@ def parse_torrent_result(result: dict, movie_title: str, year: str = None) -> di
"category": result.get('Category', ''), "category": result.get('Category', ''),
"date": result.get('Date', ''), "date": result.get('Date', ''),
"provider": provider, "provider": provider,
"id": result.get('Id', '') "id": torrent_id
} }
except Exception as e: except Exception as e:
print(f"Error parsing torrent result: {e}") print(f"Error parsing torrent result: {e}")
@ -911,6 +976,60 @@ async def api_search_torrent_by_id(torrent_id: str):
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Torrent ID search error: {str(e)}") raise HTTPException(status_code=500, detail=f"Torrent ID search error: {str(e)}")
@app.get("/api/check-qbittorrent")
async def check_qbittorrent_connection():
"""Проверка доступности qBittorrent"""
try:
qb_username = os.getenv("QBITTORRENT_USERNAME", "admin")
qb_password = os.getenv("QBITTORRENT_PASSWORD", "vrubel07")
qb_host = os.getenv("QBITTORRENT_HOST", "localhost")
qb_port = os.getenv("QBITTORRENT_PORT", "8082")
qb_url = f"http://{qb_host}:{qb_port}"
async with httpx.AsyncClient(timeout=10.0) as client:
try:
auth_response = await client.post(
f"{qb_url}/api/v2/auth/login",
data={"username": qb_username, "password": qb_password}
)
if auth_response.status_code == 200 and auth_response.text.strip() == "Ok.":
# Получаем версию qBittorrent
version_response = await client.get(f"{qb_url}/api/v2/app/version")
version = version_response.text if version_response.status_code == 200 else "Unknown"
# Получаем список торрентов
torrents_response = await client.get(f"{qb_url}/api/v2/torrents/info")
torrents_count = 0
if torrents_response.status_code == 200:
torrents_count = len(torrents_response.json())
return {
"status": "success",
"message": "qBittorrent доступен",
"url": qb_url,
"version": version,
"torrents_count": torrents_count
}
else:
return {
"status": "error",
"message": f"Ошибка аутентификации: {auth_response.text}",
"url": qb_url
}
except httpx.ConnectError as e:
return {
"status": "error",
"message": f"Не удалось подключиться к qBittorrent: {str(e)}",
"url": qb_url,
"hint": "Проверьте, что qBittorrent запущен и доступен по адресу выше. Если используется VPN, убедитесь, что он настроен правильно."
}
except Exception as e:
return {
"status": "error",
"message": f"Ошибка при проверке: {str(e)}"
}
@app.get("/torrents/{movie_title}", response_class=HTMLResponse) @app.get("/torrents/{movie_title}", response_class=HTMLResponse)
async def torrents_page(request: Request, movie_title: str, year: str = None): async def torrents_page(request: Request, movie_title: str, year: str = None):
"""Страница с результатами поиска торрентов""" """Страница с результатами поиска торрентов"""
@ -940,10 +1059,16 @@ async def add_torrent_to_client(torrent_id: str = Form(...)):
try: try:
print(f"Attempting to add torrent with ID: {torrent_id}") print(f"Attempting to add torrent with ID: {torrent_id}")
if not torrent_id or torrent_id.strip() == '':
return {"status": "error", "message": "ID торрента не указан"}
# Получаем информацию о торренте по ID # Получаем информацию о торренте по ID
torrent_info = await search_torrent_by_id(torrent_id) torrent_info = await search_torrent_by_id(torrent_id)
if not torrent_info: if not torrent_info:
return {"status": "error", "message": f"Торрент с ID {torrent_id} не найден"} print(f"Torrent info is None for ID: {torrent_id}")
return {"status": "error", "message": f"Торрент с ID {torrent_id} не найден. Проверьте логи для деталей."}
print(f"Found torrent info: title={torrent_info.get('title', 'Unknown')[:50]}, magnet={'present' if torrent_info.get('magnet') else 'missing'}, torrent_url={'present' if torrent_info.get('torrent_url') else 'missing'}")
# Получаем учетные данные из переменных окружения # Получаем учетные данные из переменных окружения
qb_username = os.getenv("QBITTORRENT_USERNAME", "admin") qb_username = os.getenv("QBITTORRENT_USERNAME", "admin")
@ -965,8 +1090,10 @@ async def add_torrent_to_client(torrent_id: str = Form(...)):
print("Successfully authenticated with qBittorrent") print("Successfully authenticated with qBittorrent")
# Пробуем сначала добавить через magnet-ссылку (более надежно) # Пробуем сначала добавить через magnet-ссылку (более надежно)
magnet = torrent_info.get('magnet') magnet = torrent_info.get('magnet', '').strip()
if magnet:
# Проверяем, что magnet ссылка валидна (содержит хэш)
if magnet and 'urn:btih:' in magnet and len(magnet) > 20:
print(f"Trying to add via magnet link: {magnet[:100]}...") print(f"Trying to add via magnet link: {magnet[:100]}...")
add_response = await client.post( add_response = await client.post(
f"{qb_url}/api/v2/torrents/add", f"{qb_url}/api/v2/torrents/add",
@ -979,20 +1106,29 @@ async def add_torrent_to_client(torrent_id: str = Form(...)):
if add_response.status_code == 200 and add_response.text.strip() == "Ok.": if add_response.status_code == 200 and add_response.text.strip() == "Ok.":
# Проверяем, что торрент действительно добавился # Проверяем, что торрент действительно добавился
await asyncio.sleep(2) # Ждем немного await asyncio.sleep(2) # Ждем немного
torrents_response = await client.get(f"{qb_url}/api/v2/torrents/info") torrent_hash = torrent_info.get('hash', '').upper()
if torrents_response.status_code == 200:
torrents = torrents_response.json()
# Ищем торрент по хэшу
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-ссылку!",
"torrent_hash": torrent.get('hash'),
"torrent_name": torrent_info.get('title', 'Unknown')
}
# Если хэш не был в torrent_info, извлекаем из magnet ссылки
if not torrent_hash and magnet:
hash_match = re.search(r'urn:btih:([a-fA-F0-9]{40}|[a-zA-Z0-9]{32})', magnet)
if hash_match:
torrent_hash = hash_match.group(1).upper()
if torrent_hash:
torrents_response = await client.get(f"{qb_url}/api/v2/torrents/info")
if torrents_response.status_code == 200:
torrents = torrents_response.json()
# Ищем торрент по хэшу
for torrent in torrents:
if torrent.get('hash', '').upper() == torrent_hash:
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')
}
# Если не нашли по хэшу, но ответ был Ok, считаем успешным
return { return {
"status": "success", "status": "success",
"message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через magnet-ссылку!", "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через magnet-ссылку!",
@ -1000,10 +1136,16 @@ async def add_torrent_to_client(torrent_id: str = Form(...)):
"torrent_name": torrent_info.get('title', 'Unknown') "torrent_name": torrent_info.get('title', 'Unknown')
} }
else: else:
print(f"Magnet link failed, trying .torrent file...") print(f"Magnet link failed (status: {add_response.status_code}, response: {add_response.text}), trying .torrent file...")
else:
print(f"Magnet link invalid or empty: '{magnet[:50] if magnet else 'None'}...', trying .torrent file...")
# Если magnet не сработал, пробуем .torrent файл # Если magnet не сработал, пробуем .torrent файл
torrent_url = torrent_info.get('torrent_url') torrent_url = torrent_info.get('torrent_url', '')
if not torrent_url:
# Пробуем также поле url
torrent_url = torrent_info.get('url', '')
if torrent_url: if torrent_url:
print(f"Trying to add via .torrent file: {torrent_url}") print(f"Trying to add via .torrent file: {torrent_url}")
add_response = await client.post( add_response = await client.post(
@ -1015,16 +1157,47 @@ 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.":
# Проверяем, что торрент действительно добавился
await asyncio.sleep(2) # Ждем немного
# Пытаемся найти торрент в списке по названию (так как хэш может быть неизвестен)
torrent_title = torrent_info.get('title', '').lower()
torrents_response = await client.get(f"{qb_url}/api/v2/torrents/info")
if torrents_response.status_code == 200:
torrents = torrents_response.json()
# Ищем торрент по названию (первые 20 символов для точного совпадения)
added_torrent = None
for torrent in torrents:
torrent_name = torrent.get('name', '').lower()
# Проверяем совпадение по началу названия
if torrent_title and torrent_name:
if torrent_title[:20] in torrent_name or torrent_name[:20] in torrent_title:
added_torrent = torrent
break
if added_torrent:
return {
"status": "success",
"message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' успешно добавлен в qBittorrent через .torrent файл!",
"torrent_hash": added_torrent.get('hash', ''),
"torrent_name": added_torrent.get('name', torrent_info.get('title', 'Unknown'))
}
# Если не нашли по названию, но ответ был Ok, считаем успешным
return { return {
"status": "success", "status": "success",
"message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через .torrent файл!", "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через .torrent файл (проверьте список торрентов в qBittorrent)!",
"torrent_hash": torrent_info.get('hash', ''), "torrent_hash": torrent_info.get('hash', ''),
"torrent_name": torrent_info.get('title', 'Unknown') "torrent_name": torrent_info.get('title', 'Unknown')
} }
else: else:
return {"status": "error", "message": f"Ошибка добавления торрента (HTTP {add_response.status_code}): {add_response.text}"} error_msg = add_response.text.strip()
if "Fails" in error_msg or "Bad Request" in error_msg:
return {"status": "error", "message": f"qBittorrent отклонил торрент. Возможно, проблема с VPN или файл недоступен. Ответ: {error_msg}"}
return {"status": "error", "message": f"Ошибка добавления торрента (HTTP {add_response.status_code}): {error_msg}"}
else: else:
return {"status": "error", "message": f"Не найдена ни magnet-ссылка, ни .torrent файл для торрента {torrent_id}"} return {"status": "error", "message": f"Не найдена ни валидная magnet-ссылка, ни .torrent файл для торрента {torrent_id}. Проверьте, что торрент доступен."}
except httpx.ConnectError as e: except httpx.ConnectError as e:
print(f"Connection error: {e}") print(f"Connection error: {e}")

View file

@ -9,7 +9,7 @@ services:
- TORAPI_URL=http://torrent-api:8000 - TORAPI_URL=http://torrent-api:8000
- TORRENT_SEARCH_URL=http://host.docker.internal:8443 - TORRENT_SEARCH_URL=http://host.docker.internal:8443
- TORRENT_ADD_URL=http://host.docker.internal:8088 - TORRENT_ADD_URL=http://host.docker.internal:8088
- QBITTORRENT_USERNAME=admin - QBITTORRENT_USERNAME=vrubelroman
- QBITTORRENT_PASSWORD=vrubel07 - QBITTORRENT_PASSWORD=vrubel07
- QBITTORRENT_HOST=host.docker.internal - QBITTORRENT_HOST=host.docker.internal
- QBITTORRENT_PORT=8082 - QBITTORRENT_PORT=8082
@ -39,7 +39,7 @@ services:
image: lifailon/torapi:latest image: lifailon/torapi:latest
container_name: TorAPI-qBittorrent container_name: TorAPI-qBittorrent
environment: environment:
- USERNAME=admin - USERNAME=vrubelroman
- PASSWORD=vrubel07 - PASSWORD=vrubel07
- PROXY_ADDRESS=host.docker.internal - PROXY_ADDRESS=host.docker.internal
- PROXY_PORT=8082 - PROXY_PORT=8082
@ -62,7 +62,7 @@ services:
- TELEGRAM_BOT_TOKEN=7662650066:AAFgsfYJNYgpcSHaSe6fspsjqmhMkOBT1s4 - TELEGRAM_BOT_TOKEN=7662650066:AAFgsfYJNYgpcSHaSe6fspsjqmhMkOBT1s4
- TORRENT_SEARCH_URL=http://host.docker.internal:8443 - TORRENT_SEARCH_URL=http://host.docker.internal:8443
- TORRENT_ADD_URL=http://host.docker.internal:8088 - TORRENT_ADD_URL=http://host.docker.internal:8088
- QBITTORRENT_USERNAME=admin - QBITTORRENT_USERNAME=vrubelroman
- QBITTORRENT_PASSWORD=vrubel07 - QBITTORRENT_PASSWORD=vrubel07
- QBITTORRENT_HOST=host.docker.internal - QBITTORRENT_HOST=host.docker.internal
- QBITTORRENT_PORT=8082 - QBITTORRENT_PORT=8082