fix(radarr): improve event type parsing and grab event validation

- Fix string-based event type parsing (grabbed, downloadFolderImported, etc.)
- Filter grab events to only count actual downloads with metadata
- Fix JSON parsing errors for dict-type event data
- Enhanced chronological processing for accurate import date detection
- Validate grab events require sourceTitle or indexer info

Fixes issue where library addition events were mistaken for download grabs,
ensuring only real download timestamps are used for
This commit is contained in:
2025-09-08 15:43:04 -04:00
parent 65794ae038
commit 0def0e33af
3 changed files with 52 additions and 22 deletions
+15 -1
View File
@@ -4,7 +4,21 @@ All notable changes to this project will be documented in this file.
## [Unreleased] ## [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 ### Changed
- Switched to using Radarr's numeric event types for more reliable import detection - Switched to using Radarr's numeric event types for more reliable import detection
- Added constants for EVENT_TYPE_GRABBED (1), IMPORTED (3), etc. - Added constants for EVENT_TYPE_GRABBED (1), IMPORTED (3), etc.
+1 -1
View File
@@ -1 +1 @@
0.4.0 0.4.1
+33 -17
View File
@@ -278,39 +278,55 @@ class RadarrClient:
_log("DEBUG", f"Unknown event type: {event_type}") _log("DEBUG", f"Unknown event type: {event_type}")
continue 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_type == self.EVENT_TYPE_GRABBED and not first_grab:
if event.get("date"): if event.get("date"):
try: try:
first_grab = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds") # Get event data to check if this is a real grab with download info
_log("DEBUG", f"Found grab event (type {self.EVENT_TYPE_GRABBED}) at {first_grab}") event_data = event.get("data", {})
except Exception: if isinstance(event_data, str):
pass try:
event_data = json.loads(event_data)
except (json.JSONDecodeError, AttributeError):
event_data = {}
# Track the first grab event we find # Check if this grab has actual download/indexer info
if event_type == self.EVENT_TYPE_GRABBED and not first_grab: source_title = event_data.get("sourceTitle", "")
if event.get("date"): indexer = event_data.get("indexer", "")
try:
first_grab = datetime.fromisoformat(event["date"].replace("Z", "+00:00")).astimezone(timezone.utc).isoformat(timespec="seconds") # Only count grabs that have actual download metadata
_log("DEBUG", f"Found first grab event at {first_grab}") 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: except Exception:
pass pass
continue
# Only process import events (type 3) # Only process import events (type 3)
if event_type != self.EVENT_TYPE_IMPORTED: if event_type != self.EVENT_TYPE_IMPORTED:
continue continue
# Get imported path from event data
imported_path = None imported_path = None
try: event_data = event.get("data", {})
data = json.loads(event.get("data", "{}"))
imported_path = data.get("importedPath", "").lower() # Handle both string and dict data
except (json.JSONDecodeError, AttributeError) as e: if isinstance(event_data, str):
_log("DEBUG", f"Failed to get imported path: {e}") 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 continue
imported_path = event_data.get("importedPath", "")
if not imported_path: if not imported_path:
continue continue
imported_path = imported_path.lower()
movie_imdb = (movie_info.get("imdbId", "") or "").lower() movie_imdb = (movie_info.get("imdbId", "") or "").lower()
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", ""))