diff --git a/Writerside/topics/Indexer-Settings.md b/Writerside/topics/Indexer-Settings.md index 61ecba9..cd53672 100644 --- a/Writerside/topics/Indexer-Settings.md +++ b/Writerside/topics/Indexer-Settings.md @@ -36,6 +36,13 @@ DEBUG - media_manager.indexer.utils - Read timed out. (read timeout=10) ``` +- `follow_redirects` + +This is necessary for some indexers that use redirect links for torrent downloads. Especially useful if your download +client cannot access Prowlarr directly. This increases the time it takes to fetch torrent details, so only enable it if +you really need it. +Default is `false`. + ## Jackett (`[indexers.jackett]`) - `enabled` diff --git a/config.dev.toml b/config.dev.toml index 3ae7e43..c69f477 100644 --- a/config.dev.toml +++ b/config.dev.toml @@ -121,6 +121,7 @@ url = "http://localhost:9696" api_key = "" reject_torrents_on_url_error = true timeout_seconds = 60 +follow_redirects = false # Jackett settings [indexers.jackett] diff --git a/config.example.toml b/config.example.toml index ed5f1b6..9bda286 100644 --- a/config.example.toml +++ b/config.example.toml @@ -121,6 +121,7 @@ url = "http://localhost:9696" api_key = "" reject_torrents_on_url_error = true timeout_seconds = 60 +follow_redirects = false # Jackett settings [indexers.jackett] diff --git a/media_manager/indexer/config.py b/media_manager/indexer/config.py index dafa971..06be397 100644 --- a/media_manager/indexer/config.py +++ b/media_manager/indexer/config.py @@ -7,6 +7,7 @@ class ProwlarrConfig(BaseSettings): url: str = "http://localhost:9696" reject_torrents_on_url_error: bool = True timeout_seconds: int = 60 + follow_redirects: bool = False class JackettConfig(BaseSettings): diff --git a/media_manager/indexer/indexers/prowlarr.py b/media_manager/indexer/indexers/prowlarr.py index 66d90f4..c5918b6 100644 --- a/media_manager/indexer/indexers/prowlarr.py +++ b/media_manager/indexer/indexers/prowlarr.py @@ -27,6 +27,7 @@ class Prowlarr(GenericIndexer): self.url = config.url self.reject_torrents_on_url_error = config.reject_torrents_on_url_error self.timeout_seconds = config.timeout_seconds + self.follow_redirects = config.follow_redirects def search(self, query: str, is_tv: bool) -> list[IndexerQueryResult]: log.debug("Searching for " + query) @@ -94,7 +95,7 @@ class Prowlarr(GenericIndexer): log.error(f"No valid download URL found for result: {result}") return None - if not initial_url.startswith("magnet:"): + if not initial_url.startswith("magnet:") and self.follow_redirects: try: final_download_url = follow_redirects_to_final_torrent_url( initial_url=initial_url, diff --git a/media_manager/movies/router.py b/media_manager/movies/router.py index b0dc4e5..7d89fe0 100644 --- a/media_manager/movies/router.py +++ b/media_manager/movies/router.py @@ -87,7 +87,6 @@ def delete_a_movie( ) - # -------------------------------- # GET MOVIES # -------------------------------- diff --git a/media_manager/movies/service.py b/media_manager/movies/service.py index 7d8db9c..c473df4 100644 --- a/media_manager/movies/service.py +++ b/media_manager/movies/service.py @@ -143,12 +143,12 @@ class MovieService: if delete_torrents: # Get all torrents associated with this movie - torrents = self.movie_repository.get_torrents_by_movie_id(movie_id=movie_id) + torrents = self.movie_repository.get_torrents_by_movie_id( + movie_id=movie_id + ) for torrent in torrents: try: - self.torrent_service.cancel_download( - torrent, delete_files=True - ) + self.torrent_service.cancel_download(torrent, delete_files=True) log.info(f"Deleted torrent: {torrent.hash}") except Exception as e: log.warning(f"Failed to delete torrent {torrent.hash}: {e}") @@ -275,11 +275,14 @@ class MovieService: # Fetch the internal movie ID. try: movie = self.movie_repository.get_movie_by_external_id( - external_id=result.external_id, metadata_provider=metadata_provider.name + external_id=result.external_id, + metadata_provider=metadata_provider.name, ) result.id = movie.id except Exception: - log.error(f"Unable to find internal movie ID for {result.external_id} on {metadata_provider.name}") + log.error( + f"Unable to find internal movie ID for {result.external_id} on {metadata_provider.name}" + ) return results def get_popular_movies( diff --git a/media_manager/torrent/utils.py b/media_manager/torrent/utils.py index d14bb70..25b37f9 100644 --- a/media_manager/torrent/utils.py +++ b/media_manager/torrent/utils.py @@ -9,8 +9,11 @@ import bencoder import patoolib import requests import libtorrent +from requests.exceptions import InvalidSchema + 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 from media_manager.torrent.schemas import Torrent log = logging.getLogger(__name__) @@ -141,6 +144,15 @@ def get_torrent_hash(torrent: IndexerQueryResult) -> str: response = requests.get(str(torrent.download_url), timeout=30) response.raise_for_status() torrent_content = response.content + except InvalidSchema as e: + log.debug(f"Invalid schema for URL {torrent.download_url}: {e}") + final_url = follow_redirects_to_final_torrent_url( + initial_url=torrent.download_url, + session=requests.Session(), + timeout=AllEncompassingConfig().indexers.prowlarr.timeout_seconds, + ) + torrent_hash = str(libtorrent.parse_magnet_uri(final_url).info_hash) + return torrent_hash except Exception as e: log.error(f"Failed to download torrent file: {e}") raise diff --git a/media_manager/tv/service.py b/media_manager/tv/service.py index 8af7b5d..386bbaf 100644 --- a/media_manager/tv/service.py +++ b/media_manager/tv/service.py @@ -162,9 +162,7 @@ class TvService: torrents = self.tv_repository.get_torrents_by_show_id(show_id=show_id) for torrent in torrents: try: - self.torrent_service.cancel_download( - torrent, delete_files=True - ) + self.torrent_service.cancel_download(torrent, delete_files=True) log.info(f"Deleted torrent: {torrent.hash}") except Exception as e: log.warning(f"Failed to delete torrent {torrent.hash}: {e}") @@ -288,11 +286,14 @@ class TvService: # Fetch the internal show ID. try: show = self.tv_repository.get_show_by_external_id( - external_id=result.external_id, metadata_provider=metadata_provider.name + external_id=result.external_id, + metadata_provider=metadata_provider.name, ) result.id = show.id except Exception: - log.error(f"Unable to find internal show ID for {result.external_id} on {metadata_provider.name}") + log.error( + f"Unable to find internal show ID for {result.external_id} on {metadata_provider.name}" + ) return results def get_popular_shows(