From eb9bfbf82efb04f27b74c3ad65003b5180a279ef Mon Sep 17 00:00:00 2001 From: sbcrumb Date: Sun, 19 Oct 2025 12:47:03 -0400 Subject: [PATCH] db:sonarr --- VERSION | 2 +- processors/tv_processor.py | 90 ++++++++++++++++++++------------------ static/js/app.js | 18 ++++++++ 3 files changed, 67 insertions(+), 43 deletions(-) diff --git a/VERSION b/VERSION index da6b0a8..e70b452 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.6 +2.6.0 diff --git a/processors/tv_processor.py b/processors/tv_processor.py index b4d5c24..6386e43 100644 --- a/processors/tv_processor.py +++ b/processors/tv_processor.py @@ -908,66 +908,72 @@ class TVProcessor: def _get_webhook_episode_date(self, imdb_id: str, season_num: int, episode_num: int, series_metadata: Optional[Dict[str, Any]] = None) -> Tuple[Optional[str], Optional[str], str]: """ - Get episode date for webhook processing with correct priority: - 1. Check existing NFOGuard database entry (preserve previous import dates) - 2. Check Sonarr import history - if old history exists, prefer air date - 3. If no history, use current time (webhook = new download) + Get episode date for webhook processing - avoid treating webhook as gospel. + + Logic: + 1. Check Sonarr import history FIRST (any history = show has existed for a while) + 2. If ANY import history exists → Use air date (webhook is likely upgrade/rename) + 3. If NO import history → Use webhook date (truly new show) + + This prevents upgrades from overriding dates for shows you've had for months/years. """ - # Check if we already have this episode in our database (preserve existing dates) - existing = self.db.get_episode_date(imdb_id, season_num, episode_num) - if existing and existing.get('dateadded'): - _log("DEBUG", f"Found existing database entry for S{season_num:02d}E{episode_num:02d}: {existing['dateadded']}") - return existing.get('aired'), existing.get('dateadded'), existing.get('source', 'nfoguard:database') - - # Get aired date from Sonarr if available + # Get aired date and episode ID from Sonarr aired = None - if series_metadata and 'episodes' in series_metadata: - episode_data = series_metadata['episodes'].get((season_num, episode_num)) - if episode_data: - aired = episode_data.get('airDate') - _log("DEBUG", f"Got aired date from Sonarr for S{season_num:02d}E{episode_num:02d}: {aired}") - - # NEW: Check Sonarr import history to detect if this is an existing show episode_id = None if series_metadata and 'episodes' in series_metadata: episode_data = series_metadata['episodes'].get((season_num, episode_num)) if episode_data: + aired = episode_data.get('airDate') episode_id = episode_data.get('id') - + _log("DEBUG", f"Got aired date from Sonarr for S{season_num:02d}E{episode_num:02d}: {aired}") + + # STEP 1: Check Sonarr import history FIRST (this is the key check) + _log("INFO", f"Checking Sonarr import history for S{season_num:02d}E{episode_num:02d} to detect import vs rename events") + if episode_id and hasattr(self, 'sonarr'): try: + _log("DEBUG", f"Calling get_episode_import_history for episode_id: {episode_id}") import_history = self.sonarr.get_episode_import_history(episode_id) + _log("DEBUG", f"Import history result: {import_history}") + if import_history: - from datetime import datetime as dt - from dateutil import parser + # Found actual import event - use this date + _log("INFO", f"Found real import event for S{season_num:02d}E{episode_num:02d}: {import_history}") + _log("INFO", f"Using first import date (not webhook): {import_history}") + return aired, import_history, "sonarr:import_history" + else: + # No import events found - this means only renames/moves exist in history + # The episode was already in Sonarr, just being managed/renamed + _log("INFO", f"No import events found for S{season_num:02d}E{episode_num:02d} - only renames/moves in history") - # Parse the import history date - history_date = parser.parse(import_history) - current_time = dt.now(history_date.tzinfo) - time_diff = current_time - history_date - - # If import history is more than 24 hours old, this is likely a re-download/upgrade - # In this case, prefer air date over webhook date - if time_diff.days > 0: - _log("INFO", f"Found old import history for S{season_num:02d}E{episode_num:02d} ({import_history}), using air date instead of webhook date") - if aired: - # Use air date for shows with existing history - return aired, aired + "T20:00:00", "airdate" - else: - # Fallback to import history if no air date - return aired, import_history, "sonarr:import_history" + if aired: + _log("INFO", f"Using air date for existing episode (rename-only history): {aired}") + return aired, aired + "T20:00:00", "airdate" else: - # Recent import, use the import date - _log("DEBUG", f"Found recent import history for S{season_num:02d}E{episode_num:02d}: {import_history}") - return aired, import_history, "sonarr:import_history" + _log("DEBUG", f"No air date available for rename-only episode S{season_num:02d}E{episode_num:02d}") + except Exception as e: - _log("DEBUG", f"Error checking Sonarr import history for S{season_num:02d}E{episode_num:02d}: {e}") + _log("ERROR", f"Error checking Sonarr import history for S{season_num:02d}E{episode_num:02d}: {e}") + import traceback + _log("ERROR", traceback.format_exc()) + else: + if not episode_id: + _log("DEBUG", f"No episode_id found for S{season_num:02d}E{episode_num:02d}") + if not hasattr(self, 'sonarr'): + _log("DEBUG", f"No sonarr client available") - # For webhook processing with no history, use current time as dateadded (new download) + # STEP 2: No import history found - this is likely a genuinely new show + # Check our database to avoid duplicates + existing = self.db.get_episode_date(imdb_id, season_num, episode_num) + if existing and existing.get('dateadded'): + _log("INFO", f"Episode S{season_num:02d}E{episode_num:02d} already exists in our database: {existing['dateadded']}") + return existing.get('aired'), existing.get('dateadded'), existing.get('source', 'nfoguard:database') + + # STEP 3: Truly new episode - use webhook date dateadded = datetime.now().isoformat() source = "sonarr:webhook" - _log("DEBUG", f"Using webhook date for S{season_num:02d}E{episode_num:02d}: {dateadded}") + _log("INFO", f"No import history and not in database - using webhook date for genuinely new episode S{season_num:02d}E{episode_num:02d}: {dateadded}") return aired, dateadded, source diff --git a/static/js/app.js b/static/js/app.js index 49f082f..7ce6a8f 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -245,6 +245,24 @@ function updateMoviesTable(data) { } else if (movie.source === 'no_valid_date_source') { dateType = 'No Valid Source'; dateTypeBadge = 'badge-danger'; + } else if (movie.source && movie.source.toLowerCase().includes('tmdb:theatrical')) { + dateType = 'TMDB Theatrical'; + dateTypeBadge = 'badge-primary'; + } else if (movie.source && movie.source.toLowerCase().includes('tmdb:digital')) { + dateType = 'TMDB Digital'; + dateTypeBadge = 'badge-primary'; + } else if (movie.source && movie.source.toLowerCase().includes('tmdb:physical')) { + dateType = 'TMDB Physical'; + dateTypeBadge = 'badge-primary'; + } else if (movie.source && movie.source.toLowerCase().includes('tmdb:')) { + dateType = 'TMDB Release'; + dateTypeBadge = 'badge-primary'; + } else if (movie.source && movie.source.toLowerCase().includes('omdb:')) { + dateType = 'OMDb Release'; + dateTypeBadge = 'badge-info'; + } else if (movie.source && movie.source.toLowerCase().includes('webhook:')) { + dateType = 'Webhook/API'; + dateTypeBadge = 'badge-warning'; } return `