From c23e8870c3f89944ea2692018a6c66190b0260f0 Mon Sep 17 00:00:00 2001 From: Admin9705 <9705@duck.com> Date: Mon, 7 Apr 2025 16:53:17 -0400 Subject: [PATCH 1/4] update --- README.md | 47 +++++++++++++++++++++++++++++++++------ config.py | 9 ++++++++ missing.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++-------- upgrade.py | 44 +++++++++++++++++++++++++++++-------- 4 files changed, 139 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index a640ba77..18fadbc3 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,9 @@ My 12-year-old daughter is passionate about singing, dancing, and exploring STEM - 🔁 **State Tracking**: Remembers which shows and episodes have been processed to avoid duplicate searches - ⚙️ **Configurable Reset Timer**: Automatically resets search history after a configurable period - 📦 **Modular Design**: Modern codebase with separated concerns for easier maintenance -- 🌐 **Web Interface**: Real-time log viewer with day/night mode (new!) +- 🌐 **Web Interface**: Real-time log viewer with day/night mode +- 🔮 **Future Episode Skipping**: Skip processing episodes with future air dates +- 💾 **Reduced Disk Activity**: Option to skip series refresh before processing ## Indexers Approving of Huntarr: * https://ninjacentral.co.za @@ -72,11 +74,13 @@ My 12-year-old daughter is passionate about singing, dancing, and exploring STEM 2. **Missing Episodes**: - Identifies shows with missing episodes - Randomly selects shows to process (up to configurable limit) - - Refreshes metadata and triggers searches + - Refreshes metadata (optional) and triggers searches + - Skips episodes with future air dates (configurable) 3. **Quality Upgrades**: - Finds episodes that don't meet your quality cutoff settings - Processes them in configurable batches - Uses smart pagination to handle large libraries + - Skips episodes with future air dates (configurable) 4. **State Management**: - Tracks which shows and episodes have been processed - Automatically resets this tracking after a configurable time period @@ -112,12 +116,14 @@ The following environment variables can be configured: | `API_TIMEOUT` | Timeout in seconds for API requests to Sonarr | 60 | | `MONITORED_ONLY` | Only process monitored shows/episodes | true | | `HUNT_MISSING_SHOWS` | Maximum missing shows to process per cycle | 1 | -| `HUNT_UPGRADE_EPISODES` | Maximum upgrade episodes to process per cycle | 0 | +| `HUNT_UPGRADE_EPISODES` | Maximum upgrade episodes to process per cycle | 5 | | `SLEEP_DURATION` | Seconds to wait after completing a cycle (900 = 15 minutes) | 900 | | `RANDOM_SELECTION` | Use random selection (`true`) or sequential (`false`) | true | | `STATE_RESET_INTERVAL_HOURS` | Hours which the processed state files reset (168=1 week, 0=never reset) | 168 | | `DEBUG_MODE` | Enable detailed debug logging (`true` or `false`) | false | | `ENABLE_WEB_UI` | Enable or disable the web interface (`true` or `false`) | true | +| `SKIP_FUTURE_EPISODES` | Skip processing episodes with future air dates (`true` or `false`) | true | +| `SKIP_SERIES_REFRESH` | Skip refreshing series metadata before processing (`true` or `false`) | false | ### Advanced Options (Optional) @@ -167,6 +173,18 @@ The following environment variables can be configured: - When set to `false`, the web interface will not start, saving resources. - Default is `true` for convenient monitoring. +- **SKIP_FUTURE_EPISODES** + - When set to `true`, the script will skip processing episodes with future air dates. + - This helps avoid unnecessary searches for content that isn't available yet. + - Works for both missing episodes and quality upgrade processing. + - Default is `true` to optimize search efficiency. + +- **SKIP_SERIES_REFRESH** + - When set to `true`, the script will skip refreshing series metadata before searching. + - This can significantly reduce disk activity on your Sonarr server. + - Default is `false` to maintain compatibility with previous behavior. + - Set to `true` if you notice excessive disk activity during Huntarr cycles. + - **COMMAND_WAIT_DELAY** - Certain operations like refreshing and searching happen asynchronously. - This is the delay in seconds between checking the status of these operations for completion. @@ -242,14 +260,17 @@ docker run -d --name huntarr-sonarr \ -p 8988:8988 \ # Can be removed if ENABLE_WEB_UI=false -e API_KEY="your-api-key" \ -e API_URL="http://your-sonarr-address:8989" \ + -e API_TIMEOUT="60" \ -e MONITORED_ONLY="true" \ -e HUNT_MISSING_SHOWS="1" \ - -e HUNT_UPGRADE_EPISODES="0" \ + -e HUNT_UPGRADE_EPISODES="5" \ -e SLEEP_DURATION="900" \ -e RANDOM_SELECTION="true" \ -e STATE_RESET_INTERVAL_HOURS="168" \ -e DEBUG_MODE="false" \ -e ENABLE_WEB_UI="true" \ + -e SKIP_FUTURE_EPISODES="true" \ + -e SKIP_SERIES_REFRESH="false" \ huntarr/4sonarr:latest # Optional advanced settings @@ -282,12 +303,14 @@ services: API_TIMEOUT: "60" MONITORED_ONLY: "true" HUNT_MISSING_SHOWS: "1" - HUNT_UPGRADE_EPISODES: "0" + HUNT_UPGRADE_EPISODES: "5" SLEEP_DURATION: "900" RANDOM_SELECTION: "true" STATE_RESET_INTERVAL_HOURS: "168" DEBUG_MODE: "false" ENABLE_WEB_UI: "true" + SKIP_FUTURE_EPISODES: "true" + SKIP_SERIES_REFRESH: "false" # Optional advanced settings # COMMAND_WAIT_DELAY: "1" @@ -314,12 +337,14 @@ docker run -d --name huntarr-sonarr \ -e API_TIMEOUT="60" \ -e MONITORED_ONLY="true" \ -e HUNT_MISSING_SHOWS="1" \ - -e HUNT_UPGRADE_EPISODES="0" \ + -e HUNT_UPGRADE_EPISODES="5" \ -e SLEEP_DURATION="900" \ -e RANDOM_SELECTION="true" \ -e STATE_RESET_INTERVAL_HOURS="168" \ -e DEBUG_MODE="false" \ -e ENABLE_WEB_UI="true" \ + -e SKIP_FUTURE_EPISODES="true" \ + -e SKIP_SERIES_REFRESH="false" \ huntarr/4sonarr:latest # Optional advanced settings @@ -349,11 +374,14 @@ Environment="API_URL=http://localhost:8989" Environment="API_TIMEOUT=60" Environment="MONITORED_ONLY=true" Environment="HUNT_MISSING_SHOWS=1" -Environment="HUNT_UPGRADE_EPISODES=0" +Environment="HUNT_UPGRADE_EPISODES=5" Environment="SLEEP_DURATION=900" Environment="RANDOM_SELECTION=true" Environment="STATE_RESET_INTERVAL_HOURS=168" Environment="DEBUG_MODE=false" +Environment="ENABLE_WEB_UI=true" +Environment="SKIP_FUTURE_EPISODES=true" +Environment="SKIP_SERIES_REFRESH=false" ExecStart=/usr/local/bin/huntarr.sh Restart=on-failure RestartSec=10 @@ -377,6 +405,8 @@ sudo systemctl start huntarr - **Background Service**: Run it in the background to continuously maintain your library - **Smart Rotation**: With state tracking, ensures all content gets attention over time - **Real-time Monitoring**: Use the web interface to see what's happening at any time +- **Disk Usage Optimization**: Skip refreshing metadata to reduce disk wear and tear +- **Efficient Searching**: Skip processing episodes with future air dates to save resources ## Tips @@ -389,6 +419,8 @@ sudo systemctl start huntarr - **Port Conflicts**: If port 8988 is already in use, map to a different host port (e.g., `-p 9000:8988`) - **Disable Web UI**: Set `ENABLE_WEB_UI=false` if you don't need the interface to save resources - **Debugging Issues**: Enable `DEBUG_MODE=true` temporarily to see detailed logs when troubleshooting +- **Hard Drive Saving**: Enable `SKIP_SERIES_REFRESH=true` to reduce disk activity +- **Search Efficiency**: Keep `SKIP_FUTURE_EPISODES=true` to avoid searching for unavailable content ## Troubleshooting @@ -399,6 +431,7 @@ sudo systemctl start huntarr - **Logs**: Check the container logs with `docker logs huntarr-sonarr` if running in Docker - **Debug Mode**: Enable `DEBUG_MODE=true` to see detailed API responses and process flow - **State Files**: The script stores state in `/tmp/huntarr-state/` - if something seems stuck, you can try deleting these files +- **Excessive Disk Activity**: If you notice high disk usage, try enabling `SKIP_SERIES_REFRESH=true` --- diff --git a/config.py b/config.py index 2cf8e5f1..e597e4ad 100644 --- a/config.py +++ b/config.py @@ -70,6 +70,14 @@ MINIMUM_DOWNLOAD_QUEUE_SIZE = -1 print(f"Warning: Invalid MINIMUM_DOWNLOAD_QUEUE_SIZE value, using default: {MINIMUM_DOWNLOAD_QUEUE_SIZE}") +# New Options + +# Skip processing episodes with air dates in the future (default true) +SKIP_FUTURE_EPISODES = os.environ.get("SKIP_FUTURE_EPISODES", "true").lower() == "true" + +# Skip refreshing series metadata before processing (default false) +SKIP_SERIES_REFRESH = os.environ.get("SKIP_SERIES_REFRESH", "false").lower() == "true" + # Selection Settings RANDOM_SELECTION = os.environ.get("RANDOM_SELECTION", "true").lower() == "true" MONITORED_ONLY = os.environ.get("MONITORED_ONLY", "true").lower() == "true" @@ -92,5 +100,6 @@ def log_configuration(logger): logger.info(f"MONITORED_ONLY={MONITORED_ONLY}, RANDOM_SELECTION={RANDOM_SELECTION}") logger.info(f"HUNT_MODE={HUNT_MODE}, SLEEP_DURATION={SLEEP_DURATION}s") logger.info(f"COMMAND_WAIT_DELAY={COMMAND_WAIT_DELAY}, COMMAND_WAIT_ATTEMPTS={COMMAND_WAIT_ATTEMPTS}") + logger.info(f"SKIP_FUTURE_EPISODES={SKIP_FUTURE_EPISODES}, SKIP_SERIES_REFRESH={SKIP_SERIES_REFRESH}") logger.info(f"ENABLE_WEB_UI={ENABLE_WEB_UI}") logger.debug(f"API_KEY={API_KEY}") \ No newline at end of file diff --git a/missing.py b/missing.py index b9af8572..86505352 100644 --- a/missing.py +++ b/missing.py @@ -6,9 +6,16 @@ import random import time +import datetime from typing import List from utils.logger import logger -from config import HUNT_MISSING_SHOWS, MONITORED_ONLY, RANDOM_SELECTION +from config import ( + HUNT_MISSING_SHOWS, + MONITORED_ONLY, + RANDOM_SELECTION, + SKIP_FUTURE_EPISODES, + SKIP_SERIES_REFRESH +) from api import ( get_episodes_for_series, refresh_series, @@ -60,6 +67,9 @@ def process_missing_episodes() -> bool: if RANDOM_SELECTION: random.shuffle(shows_with_missing) + # Get current date for future episode filtering + current_date = datetime.datetime.now().date() + for show in shows_with_missing: if shows_processed >= HUNT_MISSING_SHOWS: break @@ -88,16 +98,52 @@ def process_missing_episodes() -> bool: logger.info(f"No missing monitored episodes found for '{show_title}' — skipping.") continue - logger.info(f"Found {len(monitored_missing_episodes)} missing monitored episode(s) for '{show_title}'.") + # Skip future episodes if SKIP_FUTURE_EPISODES is enabled + if SKIP_FUTURE_EPISODES: + # Get episodes that don't have a future air date + current_or_past_episodes = [] + future_episode_count = 0 + + for ep in monitored_missing_episodes: + air_date_str = ep.get("airDateUtc") + + # If no air date, include it (can't determine if it's future) + if not air_date_str: + current_or_past_episodes.append(ep) + continue + + try: + # Parse the UTC date string + air_date = datetime.datetime.fromisoformat(air_date_str.replace('Z', '+00:00')).date() + if air_date <= current_date: + current_or_past_episodes.append(ep) + else: + future_episode_count += 1 + except (ValueError, TypeError): + # If date parsing fails, include it anyway + current_or_past_episodes.append(ep) + + if future_episode_count > 0: + logger.info(f"Skipped {future_episode_count} future episodes for '{show_title}'") + + monitored_missing_episodes = current_or_past_episodes + + if not monitored_missing_episodes: + logger.info(f"All missing episodes for '{show_title}' are future episodes - skipping.") + continue - # Refresh the series - logger.info(f" - Refreshing series (ID: {series_id})...") - refresh_res = refresh_series(series_id) - if not refresh_res: - logger.warning(f"WARNING: Refresh command failed for {show_title}. Skipping.") - continue + logger.info(f"Found {len(monitored_missing_episodes)} missing monitored episode(s) for '{show_title}'.") - logger.info(f"Refresh command completed successfully.") + # Refresh the series only if SKIP_SERIES_REFRESH is not enabled + if not SKIP_SERIES_REFRESH: + logger.info(f" - Refreshing series (ID: {series_id})...") + refresh_res = refresh_series(series_id) + if not refresh_res: + logger.warning(f"WARNING: Refresh command failed for {show_title}. Skipping.") + continue + logger.info(f"Refresh command completed successfully.") + else: + logger.info(f" - Skipping series refresh (SKIP_SERIES_REFRESH=true)") # Search specifically for these missing + monitored episodes episode_ids = [ep["id"] for ep in monitored_missing_episodes] diff --git a/upgrade.py b/upgrade.py index 79e68c79..d702bdfb 100644 --- a/upgrade.py +++ b/upgrade.py @@ -6,8 +6,15 @@ import random import time +import datetime from utils.logger import logger -from config import HUNT_UPGRADE_EPISODES, MONITORED_ONLY, RANDOM_SELECTION +from config import ( + HUNT_UPGRADE_EPISODES, + MONITORED_ONLY, + RANDOM_SELECTION, + SKIP_FUTURE_EPISODES, + SKIP_SERIES_REFRESH +) from api import get_cutoff_unmet, get_cutoff_unmet_total_pages, refresh_series, episode_search_episodes, sonarr_request from state import load_processed_ids, save_processed_id, truncate_processed_list, PROCESSED_UPGRADE_FILE @@ -35,6 +42,9 @@ def process_cutoff_upgrades() -> bool: episodes_processed = 0 processing_done = False + # Get current date for future episode filtering + current_date = datetime.datetime.now().date() + page = 1 while True: if episodes_processed >= HUNT_UPGRADE_EPISODES: @@ -83,6 +93,20 @@ def process_cutoff_upgrades() -> bool: else: series_title = "Unknown Series" + # Skip future episodes if SKIP_FUTURE_EPISODES is enabled + if SKIP_FUTURE_EPISODES: + air_date_str = ep_obj.get("airDateUtc") + if air_date_str: + try: + # Parse the UTC date string + air_date = datetime.datetime.fromisoformat(air_date_str.replace('Z', '+00:00')).date() + if air_date > current_date: + logger.info(f"Skipping future episode '{series_title}' - S{season_num}E{ep_num} - '{ep_title}' (airs on {air_date})") + continue + except (ValueError, TypeError): + # If date parsing fails, proceed with the episode + pass + logger.info(f"Processing upgrade for \"{series_title}\" - S{season_num}E{ep_num} - \"{ep_title}\" (Episode ID: {episode_id})") # If MONITORED_ONLY, ensure both series & episode are monitored @@ -100,14 +124,16 @@ def process_cutoff_upgrades() -> bool: logger.info("Skipping unmonitored episode or series.") continue - # Refresh the series - logger.info(" - Refreshing series information...") - refresh_res = refresh_series(series_id) - if not refresh_res: - logger.warning("WARNING: Refresh command failed. Skipping this episode.") - continue - - logger.info(f"Refresh command completed successfully.") + # Refresh the series only if SKIP_SERIES_REFRESH is not enabled + if not SKIP_SERIES_REFRESH: + logger.info(" - Refreshing series information...") + refresh_res = refresh_series(series_id) + if not refresh_res: + logger.warning("WARNING: Refresh command failed. Skipping this episode.") + continue + logger.info(f"Refresh command completed successfully.") + else: + logger.info(" - Skipping series refresh (SKIP_SERIES_REFRESH=true)") # Search for the episode (upgrade) logger.info(" - Searching for quality upgrade...") From 253e645d2fe1eacf1b650488d016e937db48547e Mon Sep 17 00:00:00 2001 From: Admin9705 <9705@duck.com> Date: Mon, 7 Apr 2025 16:55:51 -0400 Subject: [PATCH 2/4] update --- Dockerfile | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/Dockerfile b/Dockerfile index e98dcd03..9f7e08ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,39 @@ FROM python:3.9-slim WORKDIR /app - # Install dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt - # Install Flask for the web interface RUN pip install --no-cache-dir flask - # Copy application files COPY main.py config.py api.py state.py ./ COPY missing.py upgrade.py ./ COPY web_server.py ./ COPY utils/ ./utils/ - # Create templates directory and copy index.html RUN mkdir -p templates COPY templates/ ./templates/ - # Create required directories RUN mkdir -p /tmp/huntarr-state RUN mkdir -p /tmp/huntarr-logs - # Default environment variables ENV API_KEY="your-api-key" \ - API_URL="http://your-sonarr-address:8989" \ - API_TIMEOUT="60" \ - HUNT_MISSING_SHOWS=1 \ - HUNT_UPGRADE_EPISODES=5 \ - SLEEP_DURATION=900 \ - STATE_RESET_INTERVAL_HOURS=168 \ - RANDOM_SELECTION="true" \ - MONITORED_ONLY="true" \ - DEBUG_MODE="false" \ - ENABLE_WEB_UI="true" - +API_URL="http://your-sonarr-address:8989" \ +API_TIMEOUT="60" \ +HUNT_MISSING_SHOWS=1 \ +HUNT_UPGRADE_EPISODES=5 \ +SLEEP_DURATION=900 \ +STATE_RESET_INTERVAL_HOURS=168 \ +RANDOM_SELECTION="true" \ +MONITORED_ONLY="true" \ +DEBUG_MODE="false" \ +ENABLE_WEB_UI="true" \ +SKIP_FUTURE_EPISODES="true" \ +SKIP_SERIES_REFRESH="false" # Expose web interface port EXPOSE 8988 - # Add startup script that conditionally starts the web UI COPY start.sh . RUN chmod +x start.sh - # Run the startup script which will decide what to launch CMD ["./start.sh"] \ No newline at end of file From 76b7190738c5445e3c34f0bc7263c4b734cf5dfe Mon Sep 17 00:00:00 2001 From: Admin9705 <9705@duck.com> Date: Mon, 7 Apr 2025 17:05:45 -0400 Subject: [PATCH 3/4] update --- Dockerfile | 2 +- README.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9f7e08ed..674fb254 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ ENV API_KEY="your-api-key" \ API_URL="http://your-sonarr-address:8989" \ API_TIMEOUT="60" \ HUNT_MISSING_SHOWS=1 \ -HUNT_UPGRADE_EPISODES=5 \ +HUNT_UPGRADE_EPISODES=0 \ SLEEP_DURATION=900 \ STATE_RESET_INTERVAL_HOURS=168 \ RANDOM_SELECTION="true" \ diff --git a/README.md b/README.md index 18fadbc3..0c06abe3 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,7 @@ docker run -d --name huntarr-sonarr \ -e API_TIMEOUT="60" \ -e MONITORED_ONLY="true" \ -e HUNT_MISSING_SHOWS="1" \ - -e HUNT_UPGRADE_EPISODES="5" \ + -e HUNT_UPGRADE_EPISODES="0" \ -e SLEEP_DURATION="900" \ -e RANDOM_SELECTION="true" \ -e STATE_RESET_INTERVAL_HOURS="168" \ @@ -303,7 +303,7 @@ services: API_TIMEOUT: "60" MONITORED_ONLY: "true" HUNT_MISSING_SHOWS: "1" - HUNT_UPGRADE_EPISODES: "5" + HUNT_UPGRADE_EPISODES: "0" SLEEP_DURATION: "900" RANDOM_SELECTION: "true" STATE_RESET_INTERVAL_HOURS: "168" @@ -337,7 +337,7 @@ docker run -d --name huntarr-sonarr \ -e API_TIMEOUT="60" \ -e MONITORED_ONLY="true" \ -e HUNT_MISSING_SHOWS="1" \ - -e HUNT_UPGRADE_EPISODES="5" \ + -e HUNT_UPGRADE_EPISODES="0" \ -e SLEEP_DURATION="900" \ -e RANDOM_SELECTION="true" \ -e STATE_RESET_INTERVAL_HOURS="168" \ @@ -374,7 +374,7 @@ Environment="API_URL=http://localhost:8989" Environment="API_TIMEOUT=60" Environment="MONITORED_ONLY=true" Environment="HUNT_MISSING_SHOWS=1" -Environment="HUNT_UPGRADE_EPISODES=5" +Environment="HUNT_UPGRADE_EPISODES=0" Environment="SLEEP_DURATION=900" Environment="RANDOM_SELECTION=true" Environment="STATE_RESET_INTERVAL_HOURS=168" From bc0fce1b15fb8b2d61179ac1f02c02a05e7e93f6 Mon Sep 17 00:00:00 2001 From: Admin9705 <9705@duck.com> Date: Mon, 7 Apr 2025 17:09:17 -0400 Subject: [PATCH 4/4] update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c06abe3..ecbd2164 100644 --- a/README.md +++ b/README.md @@ -441,4 +441,5 @@ This script helps automate the tedious process of finding missing episodes and q Thanks to: -[IntensiveCareCub](https://www.reddit.com/user/IntensiveCareCub/) for the Hunter to Huntarr idea! \ No newline at end of file +[IntensiveCareCub](https://www.reddit.com/user/IntensiveCareCub/) for the Hunter to Huntarr idea! +[ZPatten](https://github.com/zpatten) for adding the Queue Size and Delay Commands! \ No newline at end of file