From 539b09fc5b970f5b47a1bab4199510192fbaae7c Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Wed, 17 Sep 2025 15:32:34 -0400 Subject: [PATCH] update to TVDB --- VERSION | 2 +- clients/external_clients.py | 69 +++++++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/VERSION b/VERSION index 266146b..9f05f9f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.3 +1.6.5 diff --git a/clients/external_clients.py b/clients/external_clients.py index 97d42c2..1d4c16c 100644 --- a/clients/external_clients.py +++ b/clients/external_clients.py @@ -20,12 +20,16 @@ def _get_json(url: str, timeout: int = 20, headers: Dict[str, str] = None, suppr req = UrlRequest(url, headers=headers or {"Accept": "application/json"}) with urlopen(req, timeout=timeout) as resp: return json.loads(resp.read().decode("utf-8")) - except Exception as e: - # Handle HTTP 400/404 errors more gracefully for APIs where missing data is normal - if suppress_404 and ("400" in str(e) or "404" in str(e)): - _log("DEBUG", f"GET {url} - item not found (expected): {e}") + except HTTPError as e: + # Handle specific HTTP errors more gracefully + if suppress_404 and e.code in [400, 404]: + _log("DEBUG", f"TVDB API: {url} - item not found (HTTP {e.code}) - this is expected") + return None else: - _log("WARNING", f"GET {url} failed: {e}") + _log("WARNING", f"GET {url} failed: HTTP Error {e.code}: {e.reason}") + return None + except Exception as e: + _log("WARNING", f"GET {url} failed: {e}") return None @@ -55,12 +59,14 @@ class TVDBClient: def _get_token(self) -> Optional[str]: """Get TVDB auth token (cached)""" if not self.api_key: + _log("DEBUG", "TVDB: No API key provided") return None if time.time() < self._token_expires and self._token: return self._token try: + _log("DEBUG", f"TVDB: Authenticating with API key: {self.api_key[:8]}...") req = UrlRequest( f"{self.base_url}/login", data=json.dumps({"apikey": self.api_key}).encode('utf-8'), @@ -68,35 +74,71 @@ class TVDBClient: ) with urlopen(req, timeout=10) as resp: data = json.loads(resp.read().decode("utf-8")) + _log("DEBUG", f"TVDB login response: {data}") if data.get("status") == "success": self._token = data["data"]["token"] self._token_expires = time.time() + 3600 # 1 hour + _log("INFO", f"✅ TVDB: Authentication successful") return self._token + else: + _log("WARNING", f"TVDB login failed: {data}") except Exception as e: _log("WARNING", f"TVDB login failed: {e}") return None def imdb_to_tvdb_series_id(self, imdb_id: str) -> Optional[str]: - """Convert IMDB ID to TVDB series ID""" + """Convert IMDB ID to TVDB series ID using TVDB v4 API""" token = self._get_token() if not token: return None try: - # Search by remote ID (IMDB) - url = f"{self.base_url}/search/remoteid?remoteId={imdb_id}&type=series" + # Try the official v4 search endpoint first + # According to docs: /search?query=imdb_id&type=series + url = f"{self.base_url}/search?query={imdb_id}&type=series&meta=translations" headers = {"Authorization": f"Bearer {token}"} + _log("DEBUG", f"TVDB: Searching for {imdb_id} using /search endpoint") data = _get_json(url, headers=headers, suppress_404=True) + + if data and data.get("status") == "success" and data.get("data"): + series_list = data["data"] + _log("DEBUG", f"TVDB search response: found {len(series_list)} results") + + # Look for exact IMDB match in results + for series in series_list: + # Check if this series has the IMDB ID we're looking for + remote_ids = series.get("remote_ids", []) + for remote in remote_ids: + if (remote.get("source_name") == "IMDB" and + remote.get("remote_id") == imdb_id): + tvdb_id = series.get("tvdb_id") or series.get("id") + if tvdb_id: + _log("INFO", f"✅ TVDB: Found series {imdb_id} → {tvdb_id}") + return str(tvdb_id) + + # If no exact match, try the first result if it looks promising + if series_list: + first_result = series_list[0] + tvdb_id = first_result.get("tvdb_id") or first_result.get("id") + if tvdb_id: + _log("INFO", f"✅ TVDB: Found series {imdb_id} → {tvdb_id} (first result)") + return str(tvdb_id) + + # If search didn't work, try the legacy remoteid endpoint + _log("DEBUG", f"TVDB: Trying legacy remoteid endpoint for {imdb_id}") + url = f"{self.base_url}/search/remoteid?remoteId={imdb_id}&type=series" + data = _get_json(url, headers=headers, suppress_404=True) + if data and data.get("status") == "success" and data.get("data"): series_list = data["data"] if series_list and len(series_list) > 0: tvdb_id = series_list[0].get("id") if tvdb_id: - _log("INFO", f"✅ TVDB: Found series {imdb_id} → {tvdb_id}") + _log("INFO", f"✅ TVDB: Found series {imdb_id} → {tvdb_id} (legacy endpoint)") return str(tvdb_id) - # If we get here, the series wasn't found in TVDB (common for newer shows) + # If we get here, the series wasn't found in TVDB _log("DEBUG", f"TVDB: No series found for IMDb {imdb_id} (not available in TVDB)") except Exception as e: @@ -528,8 +570,13 @@ class ExternalClientManager: def get_tvdb_series_id(self, imdb_id: str) -> Optional[str]: """Get TVDB series ID from IMDB ID""" + # Check if TVDB lookups are disabled + if os.environ.get("DISABLE_TVDB", "false").lower() in ["true", "1", "yes"]: + _log("DEBUG", "TVDB lookups disabled via DISABLE_TVDB environment variable") + return None + if not self.tvdb.api_key: - _log("WARNING", "TVDB API key not configured, skipping TVDB ID lookup") + _log("INFO", "TVDB API key not configured, skipping TVDB ID lookup (set TVDB_API_KEY to enable)") return None return self.tvdb.imdb_to_tvdb_series_id(imdb_id)