mirror of
https://github.com/plexguide/Huntarr-Sonarr.git
synced 2025-12-21 06:10:09 -06:00
248 lines
9.4 KiB
Python
248 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Missing Episode Processing for Sonarr
|
|
Handles searching for missing episodes in Sonarr
|
|
"""
|
|
|
|
import random
|
|
import time
|
|
import datetime
|
|
import os
|
|
import json
|
|
from typing import List, Callable, Dict, Optional
|
|
from primary.utils.logger import get_logger, debug_log
|
|
from primary.config import (
|
|
MONITORED_ONLY,
|
|
SKIP_FUTURE_EPISODES,
|
|
SKIP_SERIES_REFRESH
|
|
)
|
|
from primary import settings_manager
|
|
from primary.api import (
|
|
get_episodes_for_series,
|
|
refresh_series,
|
|
episode_search_episodes,
|
|
get_series_with_missing_episodes,
|
|
arr_request,
|
|
get_missing_episodes
|
|
)
|
|
from primary.state import load_processed_ids, save_processed_id, truncate_processed_list, get_state_file_path
|
|
|
|
# Get app-specific logger
|
|
logger = get_logger("sonarr")
|
|
|
|
def get_missing_total_pages(pageSize: int = 200) -> int:
|
|
"""
|
|
Calculates the total number of pages for missing episodes.
|
|
Returns the number of pages, or 0 if no missing episodes.
|
|
Returns -1 if there was an API error.
|
|
"""
|
|
response = get_missing_episodes(pageSize=1) # Get just 1 to get total count
|
|
if not response:
|
|
logger.error("Failed to get missing episodes data from API")
|
|
return -1
|
|
|
|
if "totalRecords" not in response:
|
|
logger.error("Missing totalRecords in API response")
|
|
return -1
|
|
|
|
total_records = response.get("totalRecords", 0)
|
|
if not isinstance(total_records, int) or total_records < 1:
|
|
logger.info("No missing episodes found")
|
|
return 0
|
|
|
|
# Calculate total pages based on pageSize
|
|
total_pages = (total_records + pageSize - 1) // pageSize
|
|
logger.debug(f"Total missing episodes: {total_records}, pages: {total_pages}")
|
|
return max(total_pages, 1)
|
|
|
|
def get_missing(page: int) -> Optional[Dict]:
|
|
"""Get a page of missing episodes."""
|
|
return get_missing_episodes(pageSize=200)
|
|
|
|
def process_missing_episodes(restart_cycle_flag: Callable[[], bool] = lambda: False) -> bool:
|
|
"""
|
|
Process episodes that are missing from the library.
|
|
|
|
Args:
|
|
restart_cycle_flag: Function that returns whether to restart the cycle
|
|
|
|
Returns:
|
|
True if any processing was done, False otherwise
|
|
"""
|
|
# Reload settings to ensure the latest values are used
|
|
from primary.config import refresh_settings
|
|
refresh_settings("sonarr")
|
|
|
|
# Get the current value directly at the start of processing
|
|
HUNT_MISSING_SHOWS = settings_manager.get_setting("huntarr", "hunt_missing_shows", 1)
|
|
RANDOM_MISSING = settings_manager.get_setting("advanced", "random_missing", True)
|
|
|
|
# Get app-specific state file
|
|
PROCESSED_MISSING_FILE = get_state_file_path("sonarr", "processed_missing")
|
|
|
|
logger.info("=== Checking for Missing Episodes ===")
|
|
|
|
# Skip if HUNT_MISSING_SHOWS is set to 0
|
|
if HUNT_MISSING_SHOWS <= 0:
|
|
logger.info("HUNT_MISSING_SHOWS is set to 0, skipping missing episodes")
|
|
return False
|
|
|
|
# Check for restart signal
|
|
if restart_cycle_flag():
|
|
logger.info("🔄 Received restart signal before starting missing episodes. Aborting...")
|
|
return False
|
|
|
|
total_pages = get_missing_total_pages()
|
|
|
|
# If we got an error (-1) from the API request, return early
|
|
if total_pages < 0:
|
|
logger.error("Failed to get missing data due to API error. Skipping this cycle.")
|
|
return False
|
|
|
|
if total_pages == 0:
|
|
logger.info("No missing episodes found.")
|
|
return False
|
|
|
|
# Check for restart signal
|
|
if restart_cycle_flag():
|
|
logger.info("🔄 Received restart signal after getting total pages. Aborting...")
|
|
return False
|
|
|
|
logger.info(f"Found {total_pages} total pages of missing episodes.")
|
|
processed_missing_ids = load_processed_ids(PROCESSED_MISSING_FILE)
|
|
episodes_processed = 0
|
|
processing_done = False
|
|
|
|
# Use RANDOM_MISSING setting
|
|
should_use_random = RANDOM_MISSING
|
|
|
|
logger.info(f"Using {'random' if should_use_random else 'sequential'} selection for missing episodes (RANDOM_MISSING={should_use_random})")
|
|
|
|
# Initialize page variable for both modes
|
|
page = 1
|
|
|
|
while True:
|
|
# Check for restart signal at the beginning of each page processing
|
|
if restart_cycle_flag():
|
|
logger.info("🔄 Received restart signal at start of page loop. Aborting...")
|
|
break
|
|
|
|
# Check again to make sure we're using the current limit
|
|
# This ensures if settings changed during processing, we use the new value
|
|
current_limit = settings_manager.get_setting("huntarr", "hunt_missing_shows", 1)
|
|
|
|
if episodes_processed >= current_limit:
|
|
logger.info(f"Reached HUNT_MISSING_SHOWS={current_limit} for this cycle.")
|
|
break
|
|
|
|
# If random selection is enabled, pick a random page each iteration
|
|
if should_use_random and total_pages > 1:
|
|
page = random.randint(1, total_pages)
|
|
# If sequential and we've reached the end, we're done
|
|
elif not should_use_random and page > total_pages:
|
|
break
|
|
|
|
logger.info(f"Retrieving missing episodes (page={page} of {total_pages})...")
|
|
missing_data = get_missing(page)
|
|
|
|
# Check for restart signal after retrieving page
|
|
if restart_cycle_flag():
|
|
logger.info(f"🔄 Received restart signal after retrieving page {page}. Aborting...")
|
|
break
|
|
|
|
if not missing_data or "records" not in missing_data:
|
|
logger.error(f"ERROR: Unable to retrieve missing data from Sonarr on page {page}.")
|
|
|
|
# In sequential mode, try the next page
|
|
if not should_use_random:
|
|
page += 1
|
|
continue
|
|
else:
|
|
break
|
|
|
|
episodes = missing_data["records"]
|
|
total_eps = len(episodes)
|
|
logger.info(f"Found {total_eps} episodes on page {page} that are missing.")
|
|
|
|
# Randomize or sequential indices within the page
|
|
indices = list(range(total_eps))
|
|
if should_use_random:
|
|
random.shuffle(indices)
|
|
|
|
# Check for restart signal before processing episodes
|
|
if restart_cycle_flag():
|
|
logger.info(f"🔄 Received restart signal before processing episodes on page {page}. Aborting...")
|
|
break
|
|
|
|
for idx in indices:
|
|
# Check for restart signal before each episode
|
|
if restart_cycle_flag():
|
|
logger.info(f"🔄 Received restart signal during episode processing. Aborting...")
|
|
break
|
|
|
|
# Check again for the current limit in case it was changed during processing
|
|
current_limit = settings_manager.get_setting("huntarr", "hunt_missing_shows", 1)
|
|
|
|
if episodes_processed >= current_limit:
|
|
break
|
|
|
|
ep_obj = episodes[idx]
|
|
episode_id = ep_obj.get("id")
|
|
if not episode_id or episode_id in processed_missing_ids:
|
|
continue
|
|
|
|
series_id = ep_obj.get("seriesId")
|
|
season_num = ep_obj.get("seasonNumber")
|
|
ep_num = ep_obj.get("episodeNumber")
|
|
ep_title = ep_obj.get("title", "Unknown Episode Title")
|
|
|
|
series_title = ep_obj.get("seriesTitle", None)
|
|
if not series_title:
|
|
# fallback: request the series
|
|
series_data = arr_request(f"series/{series_id}", method="GET")
|
|
if series_data:
|
|
series_title = series_data.get("title", "Unknown Series")
|
|
else:
|
|
series_title = "Unknown Series"
|
|
|
|
logger.info(f"Processing missing episode for \"{series_title}\" - S{season_num}E{ep_num} - \"{ep_title}\" (Episode ID: {episode_id})")
|
|
|
|
# Search for the episode (missing)
|
|
logger.info(" - Searching for missing episode...")
|
|
search_res = episode_search_episodes([episode_id])
|
|
if search_res:
|
|
logger.info(f"Search command completed successfully.")
|
|
# Mark processed
|
|
save_processed_id(PROCESSED_MISSING_FILE, episode_id)
|
|
episodes_processed += 1
|
|
processing_done = True
|
|
|
|
# Log with the current limit, not the initial one
|
|
current_limit = settings_manager.get_setting("huntarr", "hunt_missing_shows", 1)
|
|
logger.info(f"Processed {episodes_processed}/{current_limit} missing episodes this cycle.")
|
|
else:
|
|
logger.warning(f"WARNING: Search command failed for episode ID {episode_id}.")
|
|
continue
|
|
|
|
# Check for restart signal after processing an episode
|
|
if restart_cycle_flag():
|
|
logger.info(f"🔄 Received restart signal after processing episode {ep_title}. Aborting...")
|
|
break
|
|
|
|
# Move to the next page if using sequential mode
|
|
if not should_use_random:
|
|
page += 1
|
|
# In random mode, we just handle one random page this iteration,
|
|
# then check if we've processed enough episodes or continue to another random page
|
|
|
|
# Check for restart signal after processing a page
|
|
if restart_cycle_flag():
|
|
logger.info(f"🔄 Received restart signal after processing page {page}. Aborting...")
|
|
break
|
|
|
|
# Log with the current limit, not the initial one
|
|
current_limit = settings_manager.get_setting("huntarr", "hunt_missing_shows", 1)
|
|
logger.info(f"Completed processing {episodes_processed} missing episodes for this cycle.")
|
|
truncate_processed_list(PROCESSED_MISSING_FILE)
|
|
|
|
return processing_done |