update to TVDB
This commit is contained in:
+58
-11
@@ -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"})
|
req = UrlRequest(url, headers=headers or {"Accept": "application/json"})
|
||||||
with urlopen(req, timeout=timeout) as resp:
|
with urlopen(req, timeout=timeout) as resp:
|
||||||
return json.loads(resp.read().decode("utf-8"))
|
return json.loads(resp.read().decode("utf-8"))
|
||||||
except Exception as e:
|
except HTTPError as e:
|
||||||
# Handle HTTP 400/404 errors more gracefully for APIs where missing data is normal
|
# Handle specific HTTP errors more gracefully
|
||||||
if suppress_404 and ("400" in str(e) or "404" in str(e)):
|
if suppress_404 and e.code in [400, 404]:
|
||||||
_log("DEBUG", f"GET {url} - item not found (expected): {e}")
|
_log("DEBUG", f"TVDB API: {url} - item not found (HTTP {e.code}) - this is expected")
|
||||||
|
return None
|
||||||
else:
|
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
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -55,12 +59,14 @@ class TVDBClient:
|
|||||||
def _get_token(self) -> Optional[str]:
|
def _get_token(self) -> Optional[str]:
|
||||||
"""Get TVDB auth token (cached)"""
|
"""Get TVDB auth token (cached)"""
|
||||||
if not self.api_key:
|
if not self.api_key:
|
||||||
|
_log("DEBUG", "TVDB: No API key provided")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if time.time() < self._token_expires and self._token:
|
if time.time() < self._token_expires and self._token:
|
||||||
return self._token
|
return self._token
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
_log("DEBUG", f"TVDB: Authenticating with API key: {self.api_key[:8]}...")
|
||||||
req = UrlRequest(
|
req = UrlRequest(
|
||||||
f"{self.base_url}/login",
|
f"{self.base_url}/login",
|
||||||
data=json.dumps({"apikey": self.api_key}).encode('utf-8'),
|
data=json.dumps({"apikey": self.api_key}).encode('utf-8'),
|
||||||
@@ -68,35 +74,71 @@ class TVDBClient:
|
|||||||
)
|
)
|
||||||
with urlopen(req, timeout=10) as resp:
|
with urlopen(req, timeout=10) as resp:
|
||||||
data = json.loads(resp.read().decode("utf-8"))
|
data = json.loads(resp.read().decode("utf-8"))
|
||||||
|
_log("DEBUG", f"TVDB login response: {data}")
|
||||||
if data.get("status") == "success":
|
if data.get("status") == "success":
|
||||||
self._token = data["data"]["token"]
|
self._token = data["data"]["token"]
|
||||||
self._token_expires = time.time() + 3600 # 1 hour
|
self._token_expires = time.time() + 3600 # 1 hour
|
||||||
|
_log("INFO", f"✅ TVDB: Authentication successful")
|
||||||
return self._token
|
return self._token
|
||||||
|
else:
|
||||||
|
_log("WARNING", f"TVDB login failed: {data}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log("WARNING", f"TVDB login failed: {e}")
|
_log("WARNING", f"TVDB login failed: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def imdb_to_tvdb_series_id(self, imdb_id: str) -> Optional[str]:
|
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()
|
token = self._get_token()
|
||||||
if not token:
|
if not token:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Search by remote ID (IMDB)
|
# Try the official v4 search endpoint first
|
||||||
url = f"{self.base_url}/search/remoteid?remoteId={imdb_id}&type=series"
|
# 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}"}
|
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)
|
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"):
|
if data and data.get("status") == "success" and data.get("data"):
|
||||||
series_list = data["data"]
|
series_list = data["data"]
|
||||||
if series_list and len(series_list) > 0:
|
if series_list and len(series_list) > 0:
|
||||||
tvdb_id = series_list[0].get("id")
|
tvdb_id = series_list[0].get("id")
|
||||||
if tvdb_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)
|
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)")
|
_log("DEBUG", f"TVDB: No series found for IMDb {imdb_id} (not available in TVDB)")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -528,8 +570,13 @@ class ExternalClientManager:
|
|||||||
|
|
||||||
def get_tvdb_series_id(self, imdb_id: str) -> Optional[str]:
|
def get_tvdb_series_id(self, imdb_id: str) -> Optional[str]:
|
||||||
"""Get TVDB series ID from IMDB ID"""
|
"""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:
|
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 None
|
||||||
|
|
||||||
return self.tvdb.imdb_to_tvdb_series_id(imdb_id)
|
return self.tvdb.imdb_to_tvdb_series_id(imdb_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user