diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5bf6d..de893c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,21 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -## [0.4.0] - 2025-09-08 +# Changelog + +## [0.4.1] - 2025-01-09 + +### Fixed +- **Radarr Client**: Fixed event type parsing for string-based event types (grabbed, downloadFolderImported, etc.) +- **Radarr Client**: Improved grab event detection to filter out library addition events and only count actual download grabs +- **Radarr Client**: Fixed JSON parsing errors when handling event data that's already a dictionary +- **Radarr Client**: Enhanced validation of grab events to require download metadata (sourceTitle or indexer) + +### Improved +- **Radarr Client**: Better chronological event processing to find earliest actual download dates +- **Radarr Client**: More accurate import date detection by validating grab events have real download information + +## [0.4.0] - 2025-01-08 ### Changed - Switched to using Radarr's numeric event types for more reliable import detection - Added constants for EVENT_TYPE_GRABBED (1), IMPORTED (3), etc. diff --git a/VERSION b/VERSION index 1d0ba9e..267577d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.0 +0.4.1 diff --git a/clients/radarr_client.py b/clients/radarr_client.py index 92f6893..517c27b 100644 --- a/clients/radarr_client.py +++ b/clients/radarr_client.py @@ -278,39 +278,55 @@ class RadarrClient: _log("DEBUG", f"Unknown event type: {event_type}") continue - # Check for grab events (type 1) + # Check for grab events (type 1) - but validate it's a real download if event_type == self.EVENT_TYPE_GRABBED and not first_grab: if event.get("date"): try: - first_grab = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds") - _log("DEBUG", f"Found grab event (type {self.EVENT_TYPE_GRABBED}) at {first_grab}") + # Get event data to check if this is a real grab with download info + event_data = event.get("data", {}) + if isinstance(event_data, str): + try: + event_data = json.loads(event_data) + except (json.JSONDecodeError, AttributeError): + event_data = {} + + # Check if this grab has actual download/indexer info + source_title = event_data.get("sourceTitle", "") + indexer = event_data.get("indexer", "") + + # Only count grabs that have actual download metadata + if source_title or indexer: + first_grab = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds") + _log("DEBUG", f"Found real grab event with source '{source_title}' from '{indexer}' at {first_grab}") + else: + _log("DEBUG", f"Skipping grab event without download info at {event.get('date')}") except Exception: pass - # Track the first grab event we find - if event_type == self.EVENT_TYPE_GRABBED and not first_grab: - if event.get("date"): - try: - first_grab = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds") - _log("DEBUG", f"Found first grab event at {first_grab}") - except Exception: - pass - continue - # Only process import events (type 3) if event_type != self.EVENT_TYPE_IMPORTED: continue - imported_path = None - try: - data = json.loads(event.get("data", "{}")) - imported_path = data.get("importedPath", "").lower() - except (json.JSONDecodeError, AttributeError) as e: - _log("DEBUG", f"Failed to get imported path: {e}") - continue + # Get imported path from event data + imported_path = None + event_data = event.get("data", {}) + + # Handle both string and dict data + if isinstance(event_data, str): + try: + event_data = json.loads(event_data) + except (json.JSONDecodeError, AttributeError) as e: + _log("DEBUG", f"Failed to parse event data JSON: {e}") + continue + elif not isinstance(event_data, dict): + continue + + imported_path = event_data.get("importedPath", "") if not imported_path: continue + imported_path = imported_path.lower() + movie_imdb = (movie_info.get("imdbId", "") or "").lower() movie_title = (movie_info.get("title", "") or "").lower() movie_year = str(movie_info.get("year", ""))