Skip to content
Merged

Ui2 #44

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,21 @@ 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 *.py ./
COPY utils/ ./utils/
COPY web_server.py ./
# Create templates directory and copy index.html
RUN mkdir -p templates
RUN mkdir -p templates static/css static/js
COPY templates/ ./templates/
COPY static/ ./static/
# Create required directories
RUN mkdir -p /tmp/huntarr-state
RUN mkdir -p /tmp/huntarr-logs
RUN mkdir -p /config/stateful /config/settings
# 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=0 \
HUNT_UPGRADE_EPISODES=5 \
SLEEP_DURATION=900 \
STATE_RESET_INTERVAL_HOURS=168 \
RANDOM_SELECTION="true" \
Expand All @@ -30,6 +29,8 @@ DEBUG_MODE="false" \
ENABLE_WEB_UI="true" \
SKIP_FUTURE_EPISODES="true" \
SKIP_SERIES_REFRESH="false"
# Create volume mount points
VOLUME ["/config"]
# Expose web interface port
EXPOSE 8988
# Add startup script that conditionally starts the web UI
Expand Down
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
</tr>
</table>


**NOTE**: This utilizes Sonarr API Version - `5`. Legacy name of this program: Sonarr Hunter.

---
Expand Down Expand Up @@ -198,10 +197,18 @@ The following environment variables can be configured:
- The minimum number of items in the download queue before a new hunt is initiated. For example if set to `5` then a new hunt will only start when there are 5 or less items marked as `downloading` in the queue.
- This helps prevent overwhelming the queue with too many download requests at once and avoids creating a massive backlog of downloads.
- Set to `-1` to disable this check.

## Web Interface

Huntarr-Sonarr includes a real-time log viewer web interface that allows you to monitor its operation directly from your browser.
<table>
<tr>
<td colspan="2">
<img src="https://github.com/user-attachments/assets/37c052cb-df00-4d61-aaa2-be8c0dd3c10e" width="100%"/>
<p align="center"><em>Demo Logger UI</em></p>
</td>
</tr>
</table>

### Features

Expand Down Expand Up @@ -257,7 +264,7 @@ The simplest way to run Huntarr is via Docker:
```bash
docker run -d --name huntarr-sonarr \
--restart always \
-p 8988:8988 \ # Can be removed if ENABLE_WEB_UI=false
-p 8988:8988 \
-e API_KEY="your-api-key" \
-e API_URL="http://your-sonarr-address:8989" \
-e API_TIMEOUT="60" \
Expand Down Expand Up @@ -296,7 +303,7 @@ services:
container_name: huntarr-sonarr
restart: always
ports:
- "8988:8988" # Can be removed if ENABLE_WEB_UI=false
- "8988:8988"
environment:
API_KEY: "your-api-key"
API_URL: "http://your-sonarr-address:8989"
Expand Down Expand Up @@ -331,7 +338,7 @@ Run this from Command Line in Unraid:
```bash
docker run -d --name huntarr-sonarr \
--restart always \
-p 8988:8988 \ # Can be removed if ENABLE_WEB_UI=false
-p 8988:8988 \
-e API_KEY="your-api-key" \
-e API_URL="http://your-sonarr-address:8989" \
-e API_TIMEOUT="60" \
Expand Down Expand Up @@ -382,6 +389,7 @@ Environment="DEBUG_MODE=false"
Environment="ENABLE_WEB_UI=true"
Environment="SKIP_FUTURE_EPISODES=true"
Environment="SKIP_SERIES_REFRESH=false"
ExecStartPre=/bin/sleep 30
ExecStart=/usr/local/bin/huntarr.sh
Restart=on-failure
RestartSec=10
Expand Down Expand Up @@ -441,5 +449,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!
[ZPatten](https://github.com/zpatten) for adding the Queue Size and Delay Commands!
* [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!
49 changes: 36 additions & 13 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import os
import logging
import settings_manager

# Web UI Configuration
ENABLE_WEB_UI = os.environ.get("ENABLE_WEB_UI", "true").lower() == "true"
Expand All @@ -21,6 +22,9 @@
API_TIMEOUT = 60
print(f"Warning: Invalid API_TIMEOUT value, using default: {API_TIMEOUT}")

# Settings that can be overridden by the settings manager
# Load from environment first, will be overridden by settings if they exist

# Missing Content Settings
try:
HUNT_MISSING_SHOWS = int(os.environ.get("HUNT_MISSING_SHOWS", "1"))
Expand Down Expand Up @@ -49,6 +53,14 @@
STATE_RESET_INTERVAL_HOURS = 168
print(f"Warning: Invalid STATE_RESET_INTERVAL_HOURS value, using default: {STATE_RESET_INTERVAL_HOURS}")

# Selection Settings
RANDOM_SELECTION = os.environ.get("RANDOM_SELECTION", "true").lower() == "true"
MONITORED_ONLY = os.environ.get("MONITORED_ONLY", "true").lower() == "true"

# New Options
SKIP_FUTURE_EPISODES = os.environ.get("SKIP_FUTURE_EPISODES", "true").lower() == "true"
SKIP_SERIES_REFRESH = os.environ.get("SKIP_SERIES_REFRESH", "false").lower() == "true"

# Delay in seconds between checking the status of a command (default 1 second)
try:
COMMAND_WAIT_DELAY = int(os.environ.get("COMMAND_WAIT_DELAY", "1"))
Expand All @@ -70,26 +82,34 @@
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"

# Hunt mode: "missing", "upgrade", or "both"
HUNT_MODE = os.environ.get("HUNT_MODE", "both")

# Debug Settings
DEBUG_MODE = os.environ.get("DEBUG_MODE", "false").lower() == "true"

# Override settings from settings manager if they exist
def refresh_settings():
"""Refresh configuration settings from the settings manager."""
global HUNT_MISSING_SHOWS, HUNT_UPGRADE_EPISODES, SLEEP_DURATION
global STATE_RESET_INTERVAL_HOURS, MONITORED_ONLY, RANDOM_SELECTION
global SKIP_FUTURE_EPISODES, SKIP_SERIES_REFRESH

# Load settings from settings manager
HUNT_MISSING_SHOWS = settings_manager.get_setting("huntarr", "hunt_missing_shows", HUNT_MISSING_SHOWS)
HUNT_UPGRADE_EPISODES = settings_manager.get_setting("huntarr", "hunt_upgrade_episodes", HUNT_UPGRADE_EPISODES)
SLEEP_DURATION = settings_manager.get_setting("huntarr", "sleep_duration", SLEEP_DURATION)
STATE_RESET_INTERVAL_HOURS = settings_manager.get_setting("huntarr", "state_reset_interval_hours", STATE_RESET_INTERVAL_HOURS)
MONITORED_ONLY = settings_manager.get_setting("huntarr", "monitored_only", MONITORED_ONLY)
RANDOM_SELECTION = settings_manager.get_setting("huntarr", "random_selection", RANDOM_SELECTION)
SKIP_FUTURE_EPISODES = settings_manager.get_setting("huntarr", "skip_future_episodes", SKIP_FUTURE_EPISODES)
SKIP_SERIES_REFRESH = settings_manager.get_setting("huntarr", "skip_series_refresh", SKIP_SERIES_REFRESH)

def log_configuration(logger):
"""Log the current configuration settings"""
# Refresh settings from the settings manager
refresh_settings()

logger.info("=== Huntarr [Sonarr Edition] Starting ===")
logger.info(f"API URL: {API_URL}")
logger.info(f"API Timeout: {API_TIMEOUT}s")
Expand All @@ -102,4 +122,7 @@ def log_configuration(logger):
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}")
logger.debug(f"API_KEY={API_KEY}")

# Initial refresh of settings
refresh_settings()
5 changes: 4 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import os
import socket
from utils.logger import logger
from config import HUNT_MODE, SLEEP_DURATION, MINIMUM_DOWNLOAD_QUEUE_SIZE, ENABLE_WEB_UI, log_configuration
from config import HUNT_MODE, SLEEP_DURATION, MINIMUM_DOWNLOAD_QUEUE_SIZE, ENABLE_WEB_UI, log_configuration, refresh_settings
from missing import process_missing_episodes
from upgrade import process_cutoff_upgrades
from state import check_state_reset, calculate_reset_time
Expand Down Expand Up @@ -40,6 +40,9 @@ def main_loop() -> None:
logger.info("GitHub: https://github.com/plexguide/huntarr-sonarr")

while True:
# Refresh settings from the settings manager before each cycle
refresh_settings()

# Check if state files need to be reset
check_state_reset()

Expand Down
101 changes: 101 additions & 0 deletions settings_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""
Settings manager for Huntarr-Sonarr
Handles loading, saving, and providing settings from a JSON file
"""

import os
import json
import pathlib
import logging
from typing import Dict, Any, Optional

# Create a simple logger for settings_manager
logging.basicConfig(level=logging.INFO)
settings_logger = logging.getLogger("settings_manager")

# Settings directory setup
SETTINGS_DIR = pathlib.Path("/config/settings")
SETTINGS_DIR.mkdir(parents=True, exist_ok=True)

SETTINGS_FILE = SETTINGS_DIR / "huntarr.json"

# Default settings
DEFAULT_SETTINGS = {
"ui": {
"dark_mode": True
},
"huntarr": {
"sleep_duration": 900, # 15 minutes in seconds
"hunt_missing_shows": 1,
"hunt_upgrade_episodes": 5,
"state_reset_interval_hours": 168, # 1 week in hours
"monitored_only": True,
"random_selection": True,
"skip_future_episodes": True,
"skip_series_refresh": False
}
}

def load_settings() -> Dict[str, Any]:
"""Load settings from the settings file, or return defaults if not available."""
try:
if SETTINGS_FILE.exists():
with open(SETTINGS_FILE, 'r') as f:
settings = json.load(f)
settings_logger.info("Settings loaded from configuration file")
return settings
else:
settings_logger.info("No settings file found, creating with default values")
save_settings(DEFAULT_SETTINGS)
return DEFAULT_SETTINGS
except Exception as e:
settings_logger.error(f"Error loading settings: {e}")
settings_logger.info("Using default settings due to error")
return DEFAULT_SETTINGS

def save_settings(settings: Dict[str, Any]) -> bool:
"""Save settings to the settings file."""
try:
with open(SETTINGS_FILE, 'w') as f:
json.dump(settings, f, indent=2)
settings_logger.info("Settings saved successfully")
return True
except Exception as e:
settings_logger.error(f"Error saving settings: {e}")
return False

def update_setting(category: str, key: str, value: Any) -> bool:
"""Update a specific setting value."""
try:
settings = load_settings()

# Ensure category exists
if category not in settings:
settings[category] = {}

# Update the value
settings[category][key] = value

# Save the updated settings
return save_settings(settings)
except Exception as e:
settings_logger.error(f"Error updating setting {category}.{key}: {e}")
return False

def get_setting(category: str, key: str, default: Any = None) -> Any:
"""Get a specific setting value."""
try:
settings = load_settings()
return settings.get(category, {}).get(key, default)
except Exception as e:
settings_logger.error(f"Error getting setting {category}.{key}: {e}")
return default

def get_all_settings() -> Dict[str, Any]:
"""Get all settings."""
return load_settings()

# Initialize settings file if it doesn't exist
if not SETTINGS_FILE.exists():
save_settings(DEFAULT_SETTINGS)
4 changes: 4 additions & 0 deletions start.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/bin/sh
# Startup script for Huntarr-Sonarr that conditionally starts the web UI

# Ensure the configuration directories exist and have proper permissions
mkdir -p /config/settings /config/stateful
chmod -R 755 /config

# Convert to lowercase
ENABLE_WEB_UI=$(echo "${ENABLE_WEB_UI:-true}" | tr '[:upper:]' '[:lower:]')

Expand Down
2 changes: 1 addition & 1 deletion state.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from config import STATE_RESET_INTERVAL_HOURS

# State directory setup
STATE_DIR = pathlib.Path("/tmp/huntarr-state")
STATE_DIR = pathlib.Path("/config/stateful")
STATE_DIR.mkdir(parents=True, exist_ok=True)

PROCESSED_MISSING_FILE = STATE_DIR / "processed_missing_ids.txt"
Expand Down
Loading