From bd358e90b507876211bac5920b8db4838aceecaf Mon Sep 17 00:00:00 2001 From: Jared Ledvina Date: Sun, 7 Dec 2025 15:03:21 -0500 Subject: [PATCH 1/2] Add Radarr clickable links support to Hunt Manager Enable Radarr entries in Hunt Manager to be clickable and link directly to movies in the Radarr web interface. Changes: - Enable Radarr entries to be clickable in Hunt Manager - Fix Radarr URL generation to use numeric TMDb IDs (/movie/{tmdbId}) - Store TMDb ID in hunt_history for Radarr entries instead of internal ID - Update click handler to process both Sonarr and Radarr - Remove incorrect slug-based URL generation for Radarr - Apply TMDb ID fix to both missing and upgrade processing Note: Existing hunt history entries will still have internal IDs. Users can clear hunt history to regenerate with correct TMDb IDs. --- frontend/static/js/hunt_manager.js | 45 ++++++++++-------------------- src/primary/apps/radarr/missing.py | 4 ++- src/primary/apps/radarr/upgrade.py | 4 ++- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/frontend/static/js/hunt_manager.js b/frontend/static/js/hunt_manager.js index ebffc620..5fec98f9 100644 --- a/frontend/static/js/hunt_manager.js +++ b/frontend/static/js/hunt_manager.js @@ -92,18 +92,18 @@ const huntManagerModule = { const title = link.textContent; // Use the text content as the title console.log('Hunt item clicked:', { appType, instanceName, itemId, title }); - - // Only process clicks for Sonarr (other apps have URL issues) - if (appType === 'sonarr' && instanceName) { + + // Process clicks for Sonarr and Radarr + if ((appType === 'sonarr' || appType === 'radarr') && instanceName) { huntManagerModule.openAppInstance(appType, instanceName, itemId, title); - } else if (appType === 'sonarr' && window.huntarrUI) { - // Fallback to Apps section for Sonarr if no instance name + } else if ((appType === 'sonarr' || appType === 'radarr') && window.huntarrUI) { + // Fallback to Apps section if no instance name window.huntarrUI.switchSection('apps'); window.location.hash = '#apps'; console.log(`Navigated to apps section for ${appType}`); } else { - // For non-Sonarr apps, show a helpful message - console.log(`Clicking disabled for ${appType} - only Sonarr links work properly`); + // For other apps, show a helpful message + console.log(`Clicking disabled for ${appType} - only Sonarr and Radarr links work currently`); } } }); @@ -248,10 +248,10 @@ const huntManagerModule = { return row; }, - // Format processed info + // Format processed info formatProcessedInfo: function(entry) { - // Only Sonarr entries are clickable with external linking (other apps have URL issues) - const isClickable = entry.app_type === 'sonarr' && entry.instance_name; + // Sonarr and Radarr entries are clickable with external linking + const isClickable = (entry.app_type === 'sonarr' || entry.app_type === 'radarr') && entry.instance_name; const dataAttributes = isClickable ? `data-app="${entry.app_type}" data-instance="${entry.instance_name}" data-item-id="${entry.media_id || ''}"` : `data-app="${entry.app_type}"`; @@ -399,23 +399,8 @@ const huntManagerModule = { } break; case 'radarr': - // Radarr also uses title-based slugs - if (title) { - // Extract movie title (remove year and other info) - let movieTitle = title.replace(/\s*\(\d{4}\).*$/, ''); // Remove (2023) and anything after - - const slug = movieTitle - .toLowerCase() - .trim() - .replace(/[^\w\s-]/g, '') - .replace(/\s+/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, ''); - - path = `/movie/${slug}`; - } else { - path = `/movie/${itemId}`; - } + // Radarr uses numeric IDs + path = `/movie/${itemId}`; break; case 'lidarr': path = `/artist/${itemId}`; @@ -490,9 +475,9 @@ const huntManagerModule = { if (instanceSettings && instanceSettings.api_url) { let targetUrl; - - // If we have item details, try to create a direct link for all supported apps - if (itemId && title && ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros'].includes(appType.toLowerCase())) { + + // If we have item details, try to create a direct link for supported apps + if (itemId && ['sonarr', 'radarr'].includes(appType.toLowerCase())) { targetUrl = this.generateDirectLink(appType, instanceSettings.api_url, itemId, title); console.log('Generated direct link:', targetUrl); } diff --git a/src/primary/apps/radarr/missing.py b/src/primary/apps/radarr/missing.py index a5898df9..2b4e0bb2 100644 --- a/src/primary/apps/radarr/missing.py +++ b/src/primary/apps/radarr/missing.py @@ -308,7 +308,9 @@ def process_missing_movies( # Log to history system year = movie.get("year", "Unknown Year") media_name = f"{movie_title} ({year})" - log_processed_media("radarr", media_name, movie_id, instance_name, "missing") + # Use TMDb ID for Radarr URLs (falls back to internal ID if TMDb ID not available) + tmdb_id = movie.get("tmdbId", movie_id) + log_processed_media("radarr", media_name, tmdb_id, instance_name, "missing") radarr_logger.debug(f"Logged history entry for movie: {media_name}") increment_stat_only("radarr", "hunted") diff --git a/src/primary/apps/radarr/upgrade.py b/src/primary/apps/radarr/upgrade.py index 792ce5d5..aa996122 100644 --- a/src/primary/apps/radarr/upgrade.py +++ b/src/primary/apps/radarr/upgrade.py @@ -244,7 +244,9 @@ def process_cutoff_upgrades( # Log to history so the upgrade appears in the history UI media_name = f"{movie_title} ({movie_year})" - log_processed_media("radarr", media_name, movie_id, instance_name, "upgrade") + # Use TMDb ID for Radarr URLs (falls back to internal ID if TMDb ID not available) + tmdb_id = movie.get("tmdbId", movie_id) + log_processed_media("radarr", media_name, tmdb_id, instance_name, "upgrade") radarr_logger.debug(f"Logged quality upgrade to history for movie ID {movie_id}") processed_count += 1 From 0a394f0c7f791a43eee1a5832ebf260689f1026b Mon Sep 17 00:00:00 2001 From: Jared Ledvina Date: Sun, 7 Dec 2025 21:25:56 -0500 Subject: [PATCH 2/2] Add Lidarr clickable links support to Hunt Manager Enable Lidarr entries in Hunt Manager to be clickable and link directly to albums in the Lidarr web interface. Changes: - Enable Lidarr entries to be clickable in Hunt Manager - Use foreignAlbumId (MusicBrainz UUID) for Lidarr URLs - Store foreignAlbumId in hunt_history for Lidarr entries - Update click handler to process Sonarr, Radarr, and Lidarr - Apply foreignAlbumId fix to both missing and upgrade processing Note: Existing hunt history entries will still have internal IDs. Users can clear hunt history to regenerate with correct UUIDs. --- frontend/static/js/hunt_manager.js | 17 +++++++++-------- src/primary/apps/lidarr/missing.py | 4 +++- src/primary/apps/lidarr/upgrade.py | 4 +++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/frontend/static/js/hunt_manager.js b/frontend/static/js/hunt_manager.js index 5fec98f9..241f23b0 100644 --- a/frontend/static/js/hunt_manager.js +++ b/frontend/static/js/hunt_manager.js @@ -93,17 +93,17 @@ const huntManagerModule = { console.log('Hunt item clicked:', { appType, instanceName, itemId, title }); - // Process clicks for Sonarr and Radarr - if ((appType === 'sonarr' || appType === 'radarr') && instanceName) { + // Process clicks for Sonarr, Radarr, and Lidarr + if ((appType === 'sonarr' || appType === 'radarr' || appType === 'lidarr') && instanceName) { huntManagerModule.openAppInstance(appType, instanceName, itemId, title); - } else if ((appType === 'sonarr' || appType === 'radarr') && window.huntarrUI) { + } else if ((appType === 'sonarr' || appType === 'radarr' || appType === 'lidarr') && window.huntarrUI) { // Fallback to Apps section if no instance name window.huntarrUI.switchSection('apps'); window.location.hash = '#apps'; console.log(`Navigated to apps section for ${appType}`); } else { // For other apps, show a helpful message - console.log(`Clicking disabled for ${appType} - only Sonarr and Radarr links work currently`); + console.log(`Clicking disabled for ${appType} - only Sonarr, Radarr, and Lidarr links work currently`); } } }); @@ -250,8 +250,8 @@ const huntManagerModule = { // Format processed info formatProcessedInfo: function(entry) { - // Sonarr and Radarr entries are clickable with external linking - const isClickable = (entry.app_type === 'sonarr' || entry.app_type === 'radarr') && entry.instance_name; + // Sonarr, Radarr, and Lidarr entries are clickable with external linking + const isClickable = (entry.app_type === 'sonarr' || entry.app_type === 'radarr' || entry.app_type === 'lidarr') && entry.instance_name; const dataAttributes = isClickable ? `data-app="${entry.app_type}" data-instance="${entry.instance_name}" data-item-id="${entry.media_id || ''}"` : `data-app="${entry.app_type}"`; @@ -403,7 +403,8 @@ const huntManagerModule = { path = `/movie/${itemId}`; break; case 'lidarr': - path = `/artist/${itemId}`; + // Lidarr uses foreignAlbumId (MusicBrainz UUID) + path = `/album/${itemId}`; break; case 'readarr': path = `/author/${itemId}`; @@ -477,7 +478,7 @@ const huntManagerModule = { let targetUrl; // If we have item details, try to create a direct link for supported apps - if (itemId && ['sonarr', 'radarr'].includes(appType.toLowerCase())) { + if (itemId && ['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'eros'].includes(appType.toLowerCase())) { targetUrl = this.generateDirectLink(appType, instanceSettings.api_url, itemId, title); console.log('Generated direct link:', targetUrl); } diff --git a/src/primary/apps/lidarr/missing.py b/src/primary/apps/lidarr/missing.py index a2a4dc3b..5e0ead29 100644 --- a/src/primary/apps/lidarr/missing.py +++ b/src/primary/apps/lidarr/missing.py @@ -350,7 +350,9 @@ def process_missing_albums( title = album_info.get('title', f'Album ID {album_id}') artist_name = album_info.get('artist', {}).get('artistName', 'Unknown Artist') media_name = f"{artist_name} - {title}" - log_processed_media("lidarr", media_name, album_id, instance_name, "missing") + # Use foreignAlbumId for Lidarr URLs (falls back to internal ID if not available) + foreign_album_id = album_info.get('foreignAlbumId', album_id) + log_processed_media("lidarr", media_name, foreign_album_id, instance_name, "missing") lidarr_logger.debug(f"Logged history entry for album: {media_name}") time.sleep(command_wait_delay) # Basic delay after the single command diff --git a/src/primary/apps/lidarr/upgrade.py b/src/primary/apps/lidarr/upgrade.py index a0d4358b..6da51b73 100644 --- a/src/primary/apps/lidarr/upgrade.py +++ b/src/primary/apps/lidarr/upgrade.py @@ -192,7 +192,9 @@ def process_cutoff_upgrades( album_title = album.get('title', f'Album ID {album_id}') artist_name = album.get('artist', {}).get('artistName', 'Unknown Artist') media_name = f"{artist_name} - {album_title}" - log_processed_media("lidarr", media_name, album_id, instance_name, "upgrade") + # Use foreignAlbumId for Lidarr URLs (falls back to internal ID if not available) + foreign_album_id = album.get('foreignAlbumId', album_id) + log_processed_media("lidarr", media_name, foreign_album_id, instance_name, "upgrade") lidarr_logger.debug(f"Logged quality upgrade to history for album ID {album_id}") break