From 4e4ddbf92a14d0afcb37f3adfbfc8a9b746e4ce8 Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Mon, 8 Sep 2025 15:04:20 -0400 Subject: [PATCH] updates to api and logging --- CHANGELOG.md | 11 ++++++ VERSION | 2 +- clients/radarr_client.py | 83 ++++++++++++++++++---------------------- core/logging.py | 7 ++++ 4 files changed, 57 insertions(+), 46 deletions(-) create mode 100644 core/logging.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f61e564..ed5bf6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.4.0] - 2025-09-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. + - Removed legacy string-based event type checks + - Improved event type parsing and validation +### Changed +- Added documentation for Radarr API event types (1=grabbed, 3=imported) +- Improved event type detection using Radarr's internal event type IDs +- Removed old string-based event type checks in favor of numeric IDs + ## [0.3.8] - 2025-09-08 ### Changed - Use Radarr's numeric EventType (3="Imported") for more accurate import date detection diff --git a/VERSION b/VERSION index 6678432..1d0ba9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.8 +0.4.0 diff --git a/clients/radarr_client.py b/clients/radarr_client.py index 4c6ce24..77dccd0 100644 --- a/clients/radarr_client.py +++ b/clients/radarr_client.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 -""" -Enhanced Radarr API client with improved history event detection and path mapping -""" +"""Enhanced Radarr API client with improved import date detection""" + import json import time from datetime import datetime, timezone @@ -11,7 +10,7 @@ from urllib.parse import urlencode, urljoin from urllib.request import Request as UrlRequest, urlopen from urllib.error import URLError, HTTPError -from .logging import _log +from ..core.logging import _log # Import path mapper for proper path handling try: @@ -31,32 +30,16 @@ except ImportError: class RadarrClient: """Enhanced Radarr API client with improved import date detection""" - # Radarr API numeric event types: - # 1 = "grabbed" (when release is grabbed/downloaded) - # 3 = "imported" (when file is imported to library) - # These come from Radarr's EventType enum in the API/database - - # Known Radarr event types that indicate actual imports - REAL_IMPORT_EVENTS = { - # Primary events to check first - "moviefileimported", - "importevent", - # Secondary events - "downloadfolderimported", - "imported", - "downloadimported", - "fileimported", - "movieimported", - # Legacy events - "downloadedepisode", - "downloadedmovie", - } - - # Events that should be checked first - PRIMARY_IMPORT_EVENTS = { - "moviefileimported", - "importevent" - } + # Radarr History API event types (HistoryEventType enum) + # From: https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/History/HistoryEventType.cs + EVENT_TYPE_GRABBED = 1 # Movie was grabbed from indexer + EVENT_TYPE_IMPORTED = 3 # Movie was imported to final library + EVENT_TYPE_FAILED = 4 # Download or import failed + EVENT_TYPE_RETAGGED = 6 # Files were tagged + EVENT_TYPE_RENAMED = 8 # Files were renamed + + # Event types that indicate real imports + REAL_IMPORT_EVENT_TYPES = [EVENT_TYPE_IMPORTED] # Only trust actual "imported" events # These are now handled by path_mapper, but keeping for backward compatibility DOWNLOAD_PATH_INDICATORS = [ @@ -156,7 +139,7 @@ class RadarrClient: Returns: (is_real_import, reason, date_iso) """ - event_type = (event.get("eventType") or event.get("type") or "").lower() + event_type = event.get("eventType") date_str = event.get("date") event_data = event.get("data", {}) @@ -171,8 +154,14 @@ class RadarrClient: if not date_iso: return False, "no_valid_date", None + # Convert event type to int if needed + try: + event_type_int = int(event_type) if isinstance(event_type, str) and event_type.isdigit() else event_type + except (ValueError, TypeError): + event_type_int = None + # Check if event type indicates import - if event_type not in self.REAL_IMPORT_EVENTS: + if event_type_int not in self.REAL_IMPORT_EVENT_TYPES: return False, f"event_type_not_import({event_type})", date_iso # Get all possible source paths/titles @@ -265,18 +254,27 @@ class RadarrClient: for event in items: total_processed += 1 - event_type = (event.get("eventType") or "").lower() + event_type = event.get("eventType") + if not event_type: + continue + + try: + event_type = int(event_type) + except (ValueError, TypeError): + continue - # Check for grab events (fallback) - if event_type == "grabbed" and not first_grab: + # Check for grab events (type 1) + 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 at {first_grab}") + _log("DEBUG", f"Found grab event (type {self.EVENT_TYPE_GRABBED}) at {first_grab}") except Exception: pass - # Get imported path from Data field + # Only process import events (type 3) + if event_type != self.EVENT_TYPE_IMPORTED: + continue imported_path = None try: data = json.loads(event.get("data", "{}")) @@ -306,19 +304,14 @@ class RadarrClient: # Then try title/year match with fuzzy path cleaning if movie_title and movie_year: - # Clean strings for comparison by replacing common separators + # Clean strings for comparison 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 potential title/year match for event type {event_type}: {clean_title} ({movie_year})") _log("INFO", f"✅ FOUND IMPORT at {date_iso}") earliest_real_import = date_iso break @@ -351,7 +344,7 @@ class RadarrClient: _log("INFO", f"✅ FOUND IMPORT: Title/year match at {date_iso}") earliest_real_import = date_iso break - elif event_type in self.REAL_IMPORT_EVENTS: + elif event_type == 3: _log("DEBUG", f"⚠️ Skipped import event: {reason}") # If we found a real import, no need to continue diff --git a/core/logging.py b/core/logging.py new file mode 100644 index 0000000..5b1cd04 --- /dev/null +++ b/core/logging.py @@ -0,0 +1,7 @@ +"""Logging utilities for NFOguard""" + +from datetime import datetime, timezone + +def _log(level: str, msg: str): + """Basic logging function that writes to console""" + print(f"[{datetime.now(timezone.utc).isoformat(timespec='seconds')}] {level}: {msg}") \ No newline at end of file