From 9c84e04de2fbc0e1d644a580b858910df1141dc7 Mon Sep 17 00:00:00 2001 From: maxDorninger <97409287+maxDorninger@users.noreply.github.com> Date: Wed, 30 Jul 2025 22:29:30 +0200 Subject: [PATCH 1/4] make prowlarr follow redirects for every url --- media_manager/indexer/indexers/prowlarr.py | 38 +++++++++++++-- media_manager/indexer/utils.py | 56 ++++++++++++++++++++++ 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/media_manager/indexer/indexers/prowlarr.py b/media_manager/indexer/indexers/prowlarr.py index 2c17c47..7d5bd3b 100644 --- a/media_manager/indexer/indexers/prowlarr.py +++ b/media_manager/indexer/indexers/prowlarr.py @@ -5,6 +5,7 @@ import requests from media_manager.indexer.indexers.generic import GenericIndexer from media_manager.config import AllEncompassingConfig from media_manager.indexer.schemas import IndexerQueryResult +from media_manager.indexer.utils import follow_redirects_to_final_torrent_url log = logging.getLogger(__name__) @@ -38,13 +39,40 @@ class Prowlarr(GenericIndexer): if response.status_code == 200: result_list: list[IndexerQueryResult] = [] for result in response.json(): - is_torrent = result["protocol"] == "torrent" - if is_torrent: + if result["protocol"] == "torrent": + initial_url = None + if "downloadUrl" in result: + log.info(f"Using download URL: {result['downloadUrl']}") + initial_url = result["downloadUrl"] + elif "magnetUrl" in result: + log.info( + f"Using magnet URL as fallback for download URL: {result['magnetUrl']}" + ) + initial_url = result["magnetUrl"] + elif "guid" in result: + log.warning( + f"Using guid as fallback for download URL: {result['guid']}" + ) + initial_url = result["guid"] + else: + log.error(f"No valid download URL found for result: {result}") + continue + + if not initial_url.startswith("magnet:"): + try: + final_download_url = follow_redirects_to_final_torrent_url( + initial_url=initial_url + ) + except RuntimeError as e: + log.error( + f"Failed to follow redirects for {initial_url}, falling back to the initial url as download url, error: {e}" + ) + final_download_url = initial_url + else: + final_download_url = initial_url result_list.append( IndexerQueryResult( - download_url=result["downloadUrl"] - if "downloadUrl" in result - else result["guid"], + download_url=final_download_url, title=result["sortTitle"], seeders=result["seeders"], flags=result["indexerFlags"], diff --git a/media_manager/indexer/utils.py b/media_manager/indexer/utils.py index fb733a9..9f591b6 100644 --- a/media_manager/indexer/utils.py +++ b/media_manager/indexer/utils.py @@ -1,5 +1,7 @@ import logging +import requests + from media_manager.config import AllEncompassingConfig from media_manager.indexer.config import ScoringRuleSet from media_manager.indexer.schemas import IndexerQueryResult @@ -107,3 +109,57 @@ def evaluate_indexer_query_results( query_results = [result for result in query_results if result.score >= 0] query_results.sort(reverse=True) return query_results + + +def follow_redirects_to_final_torrent_url(initial_url: str) -> str | None: + """ + Follows redirects to get the final torrent URL. + :param initial_url: The initial URL to follow. + :return: The final torrent URL or None if it fails. + """ + current_url = initial_url + final_url = None + try: + while True: + response = requests.get(current_url, allow_redirects=False) + + if 300 <= response.status_code < 400: + redirect_url = response.headers.get("Location") + if redirect_url.startswith("http://") or redirect_url.startswith( + "https://" + ): + # It's an HTTP/HTTPS redirect, continue following + current_url = redirect_url + log.info(f"Following HTTP/HTTPS redirect to: {current_url}") + elif redirect_url.startswith("magnet:"): + # It's a Magnet URL, this is our final destination + final_url = redirect_url + log.info(f"Reached Magnet URL: {final_url}") + break + else: + log.error( + f"Reached unexpected non-HTTP/HTTPS/magnet URL: {final_url}" + ) + raise RuntimeError( + f"Reached unexpected non-HTTP/HTTPS/magnet URL: {final_url}" + ) + else: + # Not a redirect, so the current URL is the final one + final_url = current_url + log.info(f"Reached final (non-redirect) URL: {final_url}") + break + except requests.exceptions.RequestException as e: + log.error(f"An error occurred during the request: {e}") + raise RuntimeError(f"An error occurred during the request: {e}") + + if final_url.startswith("http://") or final_url.startswith("https://"): + log.info("Final URL protocol: HTTP/HTTPS") + elif final_url.startswith("magnet:"): + log.info("Final URL protocol: Magnet") + else: + log.error(f"Final URL is not a valid HTTP/HTTPS or Magnet URL: {final_url}") + raise RuntimeError( + f"Final URL is not a valid HTTP/HTTPS or Magnet URL: {final_url}" + ) + + return final_url From e2c65c9231b67a67fe336d63cae4ef6dfc55b0fb Mon Sep 17 00:00:00 2001 From: Maximilian Dorninger <97409287+maxdorninger@users.noreply.github.com> Date: Wed, 30 Jul 2025 22:34:37 +0200 Subject: [PATCH 2/4] Update media_manager/indexer/utils.py use redirect_url instead of final_url Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- media_manager/indexer/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/media_manager/indexer/utils.py b/media_manager/indexer/utils.py index 9f591b6..697ee26 100644 --- a/media_manager/indexer/utils.py +++ b/media_manager/indexer/utils.py @@ -138,10 +138,10 @@ def follow_redirects_to_final_torrent_url(initial_url: str) -> str | None: break else: log.error( - f"Reached unexpected non-HTTP/HTTPS/magnet URL: {final_url}" + f"Reached unexpected non-HTTP/HTTPS/magnet URL: {redirect_url}" ) raise RuntimeError( - f"Reached unexpected non-HTTP/HTTPS/magnet URL: {final_url}" + f"Reached unexpected non-HTTP/HTTPS/magnet URL: {redirect_url}" ) else: # Not a redirect, so the current URL is the final one From 6c20f7f026d6e33741f2587369fe0dcf3a5e31ff Mon Sep 17 00:00:00 2001 From: Maximilian Dorninger <97409287+maxdorninger@users.noreply.github.com> Date: Wed, 30 Jul 2025 22:34:45 +0200 Subject: [PATCH 3/4] Update media_manager/indexer/utils.py use redirect_url instead of final_url Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- media_manager/indexer/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/media_manager/indexer/utils.py b/media_manager/indexer/utils.py index 697ee26..6caad9b 100644 --- a/media_manager/indexer/utils.py +++ b/media_manager/indexer/utils.py @@ -152,6 +152,9 @@ def follow_redirects_to_final_torrent_url(initial_url: str) -> str | None: log.error(f"An error occurred during the request: {e}") raise RuntimeError(f"An error occurred during the request: {e}") + if final_url is None: + log.error("Final URL could not be determined. Returning None.") + return None if final_url.startswith("http://") or final_url.startswith("https://"): log.info("Final URL protocol: HTTP/HTTPS") elif final_url.startswith("magnet:"): From c8d0ec2a5f08d2816f97765cef6597ff6e813f52 Mon Sep 17 00:00:00 2001 From: maxDorninger <97409287+maxDorninger@users.noreply.github.com> Date: Wed, 30 Jul 2025 22:37:57 +0200 Subject: [PATCH 4/4] raise Runtime error instead of returning None when final url could not be determined --- media_manager/indexer/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/media_manager/indexer/utils.py b/media_manager/indexer/utils.py index 6caad9b..f933be7 100644 --- a/media_manager/indexer/utils.py +++ b/media_manager/indexer/utils.py @@ -151,10 +151,9 @@ def follow_redirects_to_final_torrent_url(initial_url: str) -> str | None: except requests.exceptions.RequestException as e: log.error(f"An error occurred during the request: {e}") raise RuntimeError(f"An error occurred during the request: {e}") - - if final_url is None: - log.error("Final URL could not be determined. Returning None.") - return None + if not final_url: + log.error("Final URL could not be determined.") + raise RuntimeError("Final URL could not be determined.") if final_url.startswith("http://") or final_url.startswith("https://"): log.info("Final URL protocol: HTTP/HTTPS") elif final_url.startswith("magnet:"):