new event ID lookup
This commit is contained in:
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.3.8] - 2025-09-08
|
||||||
|
### Changed
|
||||||
|
- Use Radarr's numeric EventType (3="Imported") for more accurate import date detection
|
||||||
|
- Simplified history processing logic
|
||||||
|
|
||||||
## [0.3.7] - 2025-09-08
|
## [0.3.7] - 2025-09-08
|
||||||
### Changed
|
### Changed
|
||||||
- Improved import date detection by tracking grab dates as fallback
|
- Improved import date detection by tracking grab dates as fallback
|
||||||
|
|||||||
+64
-111
@@ -11,6 +11,8 @@ from urllib.parse import urlencode, urljoin
|
|||||||
from urllib.request import Request as UrlRequest, urlopen
|
from urllib.request import Request as UrlRequest, urlopen
|
||||||
from urllib.error import URLError, HTTPError
|
from urllib.error import URLError, HTTPError
|
||||||
|
|
||||||
|
from .logging import _log
|
||||||
|
|
||||||
# Import path mapper for proper path handling
|
# Import path mapper for proper path handling
|
||||||
try:
|
try:
|
||||||
from ..core.path_mapper import path_mapper
|
from ..core.path_mapper import path_mapper
|
||||||
@@ -26,11 +28,6 @@ except ImportError:
|
|||||||
path_mapper = DummyPathMapper()
|
path_mapper = DummyPathMapper()
|
||||||
|
|
||||||
|
|
||||||
def _log(level: str, msg: str):
|
|
||||||
"""Placeholder logging function - replace with your actual logger"""
|
|
||||||
print(f"[{datetime.now().isoformat()}] {level}: {msg}")
|
|
||||||
|
|
||||||
|
|
||||||
class RadarrClient:
|
class RadarrClient:
|
||||||
"""Enhanced Radarr API client with improved import date detection"""
|
"""Enhanced Radarr API client with improved import date detection"""
|
||||||
|
|
||||||
@@ -274,26 +271,13 @@ class RadarrClient:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# For import/download events, handle specially
|
# Get imported path from Data field
|
||||||
# Grab date of first download/grab event as fallback
|
|
||||||
if event_type == "grabbed" and not earliest_grab_date:
|
|
||||||
earliest_grab_date = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
|
||||||
_log("DEBUG", f"Found first grab event at {earliest_grab_date}")
|
|
||||||
|
|
||||||
if event_type not in ["moviefileimported", "downloadFolderImported"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
imported_path = None
|
imported_path = None
|
||||||
# Try different sources for the path
|
try:
|
||||||
if event_type == "moviefileimported":
|
data = json.loads(event.get("data", "{}"))
|
||||||
imported_path = (event.get("data", {}).get("importedPath", "") or
|
imported_path = data.get("importedPath", "").lower()
|
||||||
event.get("data", {}).get("path", "") or
|
except (json.JSONDecodeError, AttributeError):
|
||||||
event.get("importedPath", "") or
|
pass
|
||||||
event.get("sourcePath", "")).lower()
|
|
||||||
elif event_type == "downloadFolderImported":
|
|
||||||
imported_path = (event.get("data", {}).get("path", "") or
|
|
||||||
event.get("sourcePath", "") or
|
|
||||||
event.get("downloadPath", "")).lower()
|
|
||||||
|
|
||||||
if not imported_path:
|
if not imported_path:
|
||||||
continue
|
continue
|
||||||
@@ -302,6 +286,7 @@ class RadarrClient:
|
|||||||
movie_title = (movie_info.get("title", "") or "").lower()
|
movie_title = (movie_info.get("title", "") or "").lower()
|
||||||
movie_year = str(movie_info.get("year", ""))
|
movie_year = str(movie_info.get("year", ""))
|
||||||
|
|
||||||
|
# First try IMDb ID match
|
||||||
# First try IMDb ID match
|
# First try IMDb ID match
|
||||||
if movie_imdb and (
|
if movie_imdb and (
|
||||||
f"[imdb-{movie_imdb}]" in imported_path or
|
f"[imdb-{movie_imdb}]" in imported_path or
|
||||||
@@ -309,30 +294,30 @@ class RadarrClient:
|
|||||||
movie_imdb in imported_path
|
movie_imdb in imported_path
|
||||||
):
|
):
|
||||||
_log("INFO", f"Found potential IMDb match in {event_type} event: {imported_path}")
|
_log("INFO", f"Found potential IMDb match in {event_type} event: {imported_path}")
|
||||||
|
date_iso = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
||||||
|
_log("INFO", f"✅ FOUND IMPORT: exact IMDb match at {date_iso}")
|
||||||
|
earliest_real_import = date_iso
|
||||||
|
break
|
||||||
|
|
||||||
|
# Then try title/year match with fuzzy path cleaning
|
||||||
|
if movie_title and movie_year:
|
||||||
|
# Clean strings for comparison by replacing common separators
|
||||||
|
clean_title = movie_title.replace(" ", ".").replace(":", ".").replace("-", ".").replace("_", ".").lower()
|
||||||
|
clean_path = imported_path.replace(" ", ".").replace("-", ".").replace("_", ".").replace("[", "").replace("]", "").lower()
|
||||||
|
|
||||||
|
# Remove common words for better matching
|
||||||
|
for word in ["the", "a", "an"]:
|
||||||
|
clean_title = clean_title.replace(f".{word}.", ".").replace(f"{word}.", "").replace(f".{word}", "")
|
||||||
|
clean_path = clean_path.replace(f".{word}.", ".").replace(f"{word}.", "").replace(f".{word}", "")
|
||||||
|
|
||||||
|
# Look for both title and year in the path
|
||||||
|
if clean_title in clean_path and movie_year in clean_path:
|
||||||
date_iso = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
date_iso = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
||||||
_log("INFO", f"✅ FOUND IMPORT: exact IMDb match at {date_iso}")
|
_log("INFO", f"Found potential title/year match in {event_type} event: {clean_title} ({movie_year})")
|
||||||
|
_log("INFO", f"✅ FOUND IMPORT at {date_iso}")
|
||||||
earliest_real_import = date_iso
|
earliest_real_import = date_iso
|
||||||
break
|
break
|
||||||
|
|
||||||
# Then try title/year match with fuzzy path cleaning
|
|
||||||
if movie_title and movie_year:
|
|
||||||
# Clean strings for comparison by replacing common separators
|
|
||||||
clean_title = movie_title.replace(" ", ".").replace(":", ".").replace("-", ".").replace("_", ".").lower()
|
|
||||||
clean_path = imported_path.replace(" ", ".").replace("-", ".").replace("_", ".").replace("[", "").replace("]", "").lower()
|
|
||||||
|
|
||||||
# Remove common words for better matching
|
|
||||||
for word in ["the", "a", "an"]:
|
|
||||||
clean_title = clean_title.replace(f".{word}.", ".").replace(f"{word}.", "").replace(f".{word}", "")
|
|
||||||
clean_path = clean_path.replace(f".{word}.", ".").replace(f"{word}.", "").replace(f".{word}", "")
|
|
||||||
|
|
||||||
# Look for both title and year in the path
|
|
||||||
if clean_title in clean_path and movie_year in clean_path:
|
|
||||||
date_iso = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
|
||||||
_log("INFO", f"Found potential title/year match in {event_type} event: {clean_title} ({movie_year})")
|
|
||||||
_log("INFO", f"✅ FOUND IMPORT at {date_iso}")
|
|
||||||
earliest_real_import = date_iso
|
|
||||||
break
|
|
||||||
|
|
||||||
# Fallback to normal import analysis
|
# Fallback to normal import analysis
|
||||||
is_real, reason, date_iso = self._analyze_event_for_import(event, movie_info)
|
is_real, reason, date_iso = self._analyze_event_for_import(event, movie_info)
|
||||||
if is_real and date_iso:
|
if is_real and date_iso:
|
||||||
@@ -440,83 +425,44 @@ class RadarrClient:
|
|||||||
"""Get the earliest import date from Radarr history."""
|
"""Get the earliest import date from Radarr history."""
|
||||||
_log("INFO", f"Finding earliest import for movie_id {movie_id}")
|
_log("INFO", f"Finding earliest import for movie_id {movie_id}")
|
||||||
|
|
||||||
# Get full movie history
|
|
||||||
earliest_real_import = None
|
earliest_real_import = None
|
||||||
earliest_grab_date = None
|
earliest_grab_date = None
|
||||||
page = 1
|
page = 1
|
||||||
page_size = 50
|
page_size = 50
|
||||||
total_events = 0
|
total_events = 0
|
||||||
|
|
||||||
|
# Get full movie history
|
||||||
while True:
|
while True:
|
||||||
# Get page of history
|
# Get page of history
|
||||||
history_data = self._get_movie_history_page(movie_id, page, page_size)
|
history_data = self._get_movie_history_page(movie_id, page, page_size)
|
||||||
|
if not history_data:
|
||||||
|
break
|
||||||
|
|
||||||
# Process events on this page
|
# Process events on this page
|
||||||
for event in history_data:
|
for event in history_data:
|
||||||
event_type = event.get("eventType", "").lower()
|
event_type = event.get("eventType")
|
||||||
|
if not event_type:
|
||||||
|
continue
|
||||||
|
|
||||||
# Track earliest grab date as fallback
|
# Parse event date
|
||||||
if event_type == "grabbed" and not earliest_grab_date:
|
event_date = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
||||||
earliest_grab_date = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
|
||||||
|
# Track earliest grab date as fallback (EventType 1)
|
||||||
|
if event_type == 1 and not earliest_grab_date:
|
||||||
|
earliest_grab_date = event_date
|
||||||
_log("DEBUG", f"Found first grab event at {earliest_grab_date}")
|
_log("DEBUG", f"Found first grab event at {earliest_grab_date}")
|
||||||
|
|
||||||
# Only process import events
|
|
||||||
if event_type not in ["moviefileimported", "downloadFolderImported"]:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Try different sources for the path
|
# Look for import events (EventType 3)
|
||||||
imported_path = None
|
if event_type == 3:
|
||||||
if event_type == "moviefileimported":
|
try:
|
||||||
imported_path = (event.get("data", {}).get("importedPath", "") or
|
data = json.loads(event.get("data", "{}"))
|
||||||
event.get("data", {}).get("path", "") or
|
if data.get("importedPath"):
|
||||||
event.get("importedPath", "") or
|
_log("INFO", f"✅ FOUND IMPORT at {event_date}")
|
||||||
event.get("sourcePath", "")).lower()
|
earliest_real_import = event_date
|
||||||
elif event_type == "downloadFolderImported":
|
break
|
||||||
imported_path = (event.get("data", {}).get("path", "") or
|
except (json.JSONDecodeError, AttributeError):
|
||||||
event.get("sourcePath", "") or
|
continue
|
||||||
event.get("downloadPath", "")).lower()
|
|
||||||
|
|
||||||
if not imported_path:
|
|
||||||
continue
|
|
||||||
|
|
||||||
movie_imdb = (movie_info.get("imdbId", "") or "").lower()
|
|
||||||
movie_title = (movie_info.get("title", "") or "").lower()
|
|
||||||
movie_year = str(movie_info.get("year", ""))
|
|
||||||
|
|
||||||
# First try IMDb ID match
|
|
||||||
if movie_imdb and (
|
|
||||||
f"[imdb-{movie_imdb}]" in imported_path or
|
|
||||||
f"[{movie_imdb}]" in imported_path or
|
|
||||||
movie_imdb in imported_path
|
|
||||||
):
|
|
||||||
_log("INFO", f"Found potential IMDb match in {event_type} event: {imported_path}")
|
|
||||||
date_iso = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
|
||||||
_log("INFO", f"✅ FOUND IMPORT at {date_iso}")
|
|
||||||
earliest_real_import = date_iso
|
|
||||||
break
|
|
||||||
|
|
||||||
# Then try title/year match with fuzzy path cleaning
|
|
||||||
if movie_title and movie_year:
|
|
||||||
# Clean strings for comparison by replacing common separators
|
|
||||||
clean_title = movie_title.replace(" ", ".").replace(":", ".").replace("-", ".").replace("_", ".").lower()
|
|
||||||
clean_path = imported_path.replace(" ", ".").replace("-", ".").replace("_", ".").replace("[", "").replace("]", "").lower()
|
|
||||||
|
|
||||||
# Remove common words for better matching
|
|
||||||
for word in ["the", "a", "an"]:
|
|
||||||
clean_title = clean_title.replace(f".{word}.", ".").replace(f"{word}.", "").replace(f".{word}", "")
|
|
||||||
clean_path = clean_path.replace(f".{word}.", ".").replace(f"{word}.", "").replace(f".{word}", "")
|
|
||||||
|
|
||||||
# Look for both title and year in the path
|
|
||||||
if clean_title in clean_path and movie_year in clean_path:
|
|
||||||
date_iso = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds")
|
|
||||||
_log("INFO", f"Found potential title/year match in {event_type} event: {clean_title} ({movie_year})")
|
|
||||||
_log("INFO", f"✅ FOUND IMPORT at {date_iso}")
|
|
||||||
earliest_real_import = date_iso
|
|
||||||
break
|
|
||||||
|
|
||||||
# Break if no more pages
|
|
||||||
if not history_data:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Break if we found an import
|
# Break if we found an import
|
||||||
if earliest_real_import:
|
if earliest_real_import:
|
||||||
@@ -525,15 +471,22 @@ class RadarrClient:
|
|||||||
total_events += len(history_data)
|
total_events += len(history_data)
|
||||||
page += 1
|
page += 1
|
||||||
|
|
||||||
_log("INFO", f"Processed {total_events} events across {page-1} pages")
|
_log("INFO", f"Processed {total_events} events across {page}")
|
||||||
|
|
||||||
# Return earliest real import if found
|
|
||||||
if earliest_real_import:
|
if earliest_real_import:
|
||||||
return earliest_real_import
|
return earliest_real_import
|
||||||
|
|
||||||
# Fall back to earliest grab date if available
|
|
||||||
if earliest_grab_date:
|
if earliest_grab_date:
|
||||||
_log("WARNING", f"⚠️ No real imports found, using grab date: {earliest_grab_date}")
|
_log("WARNING", f"⚠️ No EventType 3 (import) found, using grab date: {earliest_grab_date}")
|
||||||
return earliest_grab_date
|
return earliest_grab_date
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_movie_history_page(self, movie_id: int, page: int, page_size: int) -> List[Dict[str, Any]]:
|
||||||
|
"""Get a page of movie history."""
|
||||||
|
data = self._get("/api/v3/history", {
|
||||||
|
"movieId": str(movie_id),
|
||||||
|
"page": page,
|
||||||
|
"pageSize": page_size,
|
||||||
|
"sortKey": "date",
|
||||||
|
"sortDirection": "ascending"
|
||||||
|
})
|
||||||
|
return data if isinstance(data, list) else data.get("records", [])
|
||||||
Reference in New Issue
Block a user