Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d02e4d1
Merge pull request #492 from plexguide/dev
Admin9705 May 20, 2025
e8d2c7c
Merge pull request #493 from plexguide/dev
Admin9705 May 20, 2025
9fe7942
feat: expand SSL verification handling to API requests in Radarr, Rea…
jharder01 May 21, 2025
04dace7
feat: add optional SSL verification parameter to API request function…
jharder01 May 21, 2025
bcc80d8
Merge pull request #1 from jharder01/fix/ssl-verifiation/dev
jharder01 May 21, 2025
4eddbc0
'feat: add optional SSL verification parameter to arr_request functio…
jharder01 May 21, 2025
90f3bc4
feat: add optional SSL verification parameter to arr_request function…
jharder01 May 22, 2025
d8f767d
Merge pull request #2 from jharder01/fix/ssl-verifiation/dev
jharder01 May 22, 2025
4bb4b20
feat: refactor test_connection to use arr_request for API status check
jharder01 May 22, 2025
6939f66
Merge pull request #3 from jharder01/fix/ssl-verifiation/dev
jharder01 May 22, 2025
5dbda49
feat: add verify_ssl parameter to get_missing_episodes and update API…
jharder01 May 22, 2025
7fd5085
Merge pull request #4 from jharder01/fix/ssl-verifiation/dev
jharder01 May 22, 2025
51c7cae
feat: refactor API request handling to use arr_request for multiple e…
jharder01 May 22, 2025
240a0a7
Merge pull request #5 from jharder01/fix/ssl-verifiation/dev
jharder01 May 22, 2025
aa10d4b
feat: update arr_request to ignore verify_ssl parameter in favor of g…
jharder01 May 22, 2025
463bcaf
Merge pull request #6 from jharder01/fix/ssl-verifiation/dev
jharder01 May 22, 2025
f33fd9d
feat: update arr_request and check_connection to use global SSL verif…
jharder01 May 22, 2025
474aa81
Merge pull request #7 from jharder01/fix/ssl-verifiation/dev
jharder01 May 23, 2025
6ad7263
feat: enhance connection check and add get_system_status function for…
jharder01 May 23, 2025
29b3b96
Merge pull request #8 from jharder01/fix/ssl-verifiation/dev
jharder01 May 23, 2025
c124673
feat: add debug logging for missing episodes count request in get_mis…
jharder01 May 23, 2025
583ae08
Merge pull request #9 from jharder01/fix/ssl-verifiation/dev
jharder01 May 23, 2025
2f05780
feat: reintroduce retry logic and enhance SSL verification logging in…
jharder01 May 23, 2025
599b63f
Merge pull request #10 from jharder01/fix/ssl-verifiation/dev
jharder01 May 23, 2025
2fb2e56
feat: update arr_request to use global SSL verification setting and e…
jharder01 May 23, 2025
69fc86a
Merge pull request #11 from jharder01/fix/ssl-verifiation/dev
jharder01 May 23, 2025
559d15a
feat: unify SSL verification handling in arr_request and enhance logg…
jharder01 May 25, 2025
a3a85bc
feat: enhance logging in arr_request and get_ssl_verify_setting for b…
jharder01 May 25, 2025
bcbb0bd
feat: streamline SSL verification handling in arr_request and setting…
jharder01 May 25, 2025
817ce30
refactor: rename get_cutoff_unmet_episodes_random_page to get_missing…
jharder01 May 26, 2025
cb4a860
feat: add debug logging for random selection mode in process_missing_…
jharder01 May 26, 2025
019ac1d
feat: update logging level for random selection mode in process_missi…
jharder01 May 26, 2025
4f77e55
Merge branch 'main' of https://github.com/jharder01/Huntarr.io
jharder01 May 26, 2025
7ba36bb
feat: enhance random episode selection and streamline connection checks
jharder01 May 26, 2025
7cbb308
refactor: improve docstring formatting and enhance comments for clari…
jharder01 May 26, 2025
4d735a2
refactor: remove unused SSL verification parameter from API request f…
jharder01 May 27, 2025
f7ecf5f
fix: initialize series_id in get_cutoff_unmet_episodes_random_page fu…
jharder01 May 27, 2025
1f91a8a
fix: return raw response in get_system_status function for better err…
jharder01 May 27, 2025
c3e3134
fix: update function call to get_cutoff_unmet_episodes_random_page fo…
jharder01 May 27, 2025
753ef72
fix: improve logging in arr_request function for better request tracking
jharder01 May 27, 2025
846c1e3
Merge branch 'dev' into main
jharder01 May 29, 2025
ae37d60
fix: correct bug in obtaining queue size
jharder01 Jun 1, 2025
b468e48
Merge branch 'main' of https://github.com/jharder01/Huntarr.io
jharder01 Jun 1, 2025
08d63ae
fix: resolve merge conflict in arr_request function signature
jharder01 Jun 1, 2025
7ebf547
fix: update arr_request function to use Optional for data and params,…
jharder01 Jun 1, 2025
cd0e3aa
fix: update arr_request function to use Optional for params, streamli…
jharder01 Jun 1, 2025
3488cd6
Merge branch 'dev' into dev-ssl
jharder01 Jun 16, 2025
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
20 changes: 17 additions & 3 deletions src/primary/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import requests
import time
from typing import List, Dict, Any, Optional, Union
from primary.settings_manager import get_ssl_verify_setting
from primary.utils.logger import logger, debug_log
from primary.config import API_KEY, API_URL, API_TIMEOUT, COMMAND_WAIT_DELAY, COMMAND_WAIT_ATTEMPTS, APP_TYPE
from src.primary.stats_manager import get_stats, reset_stats
Expand Down Expand Up @@ -37,12 +38,18 @@ def arr_request(endpoint: str, method: str = "GET", data: Dict = None) -> Option
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
}

# Get SSL verification setting
verify_ssl = get_ssl_verify_setting()

if not verify_ssl:
logger.debug("SSL verification disabled by global user setting")

try:
if method.upper() == "GET":
response = session.get(url, headers=headers, timeout=API_TIMEOUT)
response = session.get(url, headers=headers, timeout=API_TIMEOUT, verify=verify_ssl)
elif method.upper() == "POST":
response = session.post(url, headers=headers, json=data, timeout=API_TIMEOUT)
response = session.post(url, headers=headers, json=data, timeout=API_TIMEOUT, verify=verify_ssl)
else:
logger.error(f"Unsupported HTTP method: {method}")
return None
Expand Down Expand Up @@ -115,7 +122,14 @@ def check_connection(app_type: str = None) -> bool:
}

logger.debug(f"Testing connection with URL: {url}")
response = session.get(url, headers=headers, timeout=API_TIMEOUT)

# Get SSL verification setting
verify_ssl = get_ssl_verify_setting()

if not verify_ssl:
logger.debug("SSL verification disabled by global user setting")

response = session.get(url, headers=headers, timeout=API_TIMEOUT, verify=verify_ssl)

if response.status_code == 401:
logger.error(f"Connection test failed: 401 Client Error: Unauthorized - Invalid API key for {current_app_type.title()}")
Expand Down
7 changes: 4 additions & 3 deletions src/primary/apps/eros/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# Use a session for better performance
session = requests.Session()

def arr_request(api_url: str, api_key: str, api_timeout: int, endpoint: str, method: str = "GET", data: Dict = None, count_api: bool = True) -> Any:
def arr_request(api_url: str, api_key: str, api_timeout: int, endpoint: str, method: str = "GET", count_api: bool = True, data: Optional[Dict] = None, params: Optional[Dict] = None) -> Any:
"""
Make a request to the Eros API.

Expand Down Expand Up @@ -344,7 +344,7 @@ def refresh_item(api_url: str, api_key: str, api_timeout: int, item_id: int) ->
# Return a placeholder command ID to simulate success without actually refreshing
return 123

def item_search(api_url: str, api_key: str, api_timeout: int, item_ids: List[int]) -> int:
def item_search(api_url: str, api_key: str, api_timeout: int, item_ids: List[int], verify_ssl: Optional[bool] = None) -> int:
"""
Trigger a search for one or more movies in Whisparr V3.

Expand Down Expand Up @@ -389,7 +389,7 @@ def item_search(api_url: str, api_key: str, api_timeout: int, item_ids: List[int
eros_logger.debug(f"Trying search command format {i+1}: {payload}")

# Make the API request
response = arr_request(api_url, api_key, api_timeout, command_endpoint, "POST", payload)
response = arr_request(api_url, api_key, api_timeout, command_endpoint, "POST", payload, verify_ssl=verify_ssl)

if response and "id" in response:
command_id = response["id"]
Expand Down Expand Up @@ -455,6 +455,7 @@ def check_connection(api_url: str, api_key: str, api_timeout: int) -> bool:
api_url: The base URL of the Whisparr V3 API
api_key: The API key for authentication
api_timeout: Timeout for the API request
verify_ssl: Whether to verify SSL certificates (overrides global setting if provided)

Returns:
True if the connection is successful, False otherwise
Expand Down
63 changes: 24 additions & 39 deletions src/primary/apps/lidarr/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,28 @@ def arr_request(api_url: str, api_key: str, api_timeout: int, endpoint: str, met
clean_endpoint = endpoint.lstrip('/')

# Construct full URL with V1 API prefix for Lidarr
full_url = f"{base_url}/api/v1/{clean_endpoint}"
# Setup headers
full_url = f"{base_url}/api/v1/{clean_endpoint}"

# Set up headers with User-Agent to identify Huntarr
headers = {
"X-Api-Key": api_key,
"Content-Type": "application/json"
"Content-Type": "application/json",
"User-Agent": "Huntarr/1.0 (https://github.com/plexguide/Huntarr.io)"
}


lidarr_logger.debug(f"Using User-Agent: {headers['User-Agent']}")

# Get SSL verification setting
verify_ssl = get_ssl_verify_setting()

# Log the request details
lidarr_logger.debug(f"Making {method} request to Lidarr: {full_url}")
if params:
lidarr_logger.debug(f"Request params: {params}")
if data:
debug_log("Lidarr API request payload", data, "lidarr")

# Make the request
response = requests.request(
method.upper(),
full_url,

if not verify_ssl:
lidarr_logger.debug("SSL verification disabled by user setting")

lidarr_logger.debug(f"Lidarr API Request: {method} {full_url} Params: {params} Data: {data}")

response = session.request(
method=method.upper(),
url=full_url,
headers=headers,
json=data if data else None,
params=params if method.upper() == "GET" else None,
Expand Down Expand Up @@ -134,38 +134,27 @@ def arr_request(api_url: str, api_key: str, api_timeout: int, endpoint: str, met

# --- Specific API Functions ---

def get_system_status(api_url: str, api_key: str, api_timeout: int, verify_ssl: Optional[bool] = None) -> Dict:
def get_system_status(api_url: str, api_key: str, api_timeout: int) -> Dict:
"""
Get Lidarr system status.

Args:
api_url: The base URL of the Lidarr API
api_key: The API key for authentication
api_timeout: Timeout for the API request
verify_ssl: Optional override for SSL verification

Returns:
System status information or empty dict if request failed
"""
# If verify_ssl is not provided, get it from settings
if verify_ssl is None:
verify_ssl = get_ssl_verify_setting()

# Log whether SSL verification is being used
if not verify_ssl:
lidarr_logger.debug("SSL verification disabled for system status check")


try:
# For Lidarr, use V1 API
endpoint = f"{api_url.rstrip('/')}/api/v1/system/status"
headers = {"X-Api-Key": api_key}

# Execute the request with SSL verification setting
response = requests.get(endpoint, headers=headers, timeout=api_timeout, verify=verify_ssl)
response.raise_for_status()
response = arr_request(api_url, api_key, api_timeout, "system/status", method="GET")

# Parse and return the result
return response.json()
return response
except Exception as e:
lidarr_logger.error(f"Error getting system status: {str(e)}")
return {}
Expand All @@ -182,11 +171,8 @@ def check_connection(api_url: str, api_key: str, api_timeout: int) -> bool:
try:
# Use a shorter timeout for a quick connection check
quick_timeout = min(api_timeout, 15)

# Get SSL verification setting
verify_ssl = get_ssl_verify_setting()

status = get_system_status(api_url, api_key, quick_timeout, verify_ssl)

status = get_system_status(api_url, api_key, quick_timeout)
if status and isinstance(status, dict) and 'version' in status:
# Log success only if debug is enabled to avoid clutter
lidarr_logger.debug(f"Connection check successful for {api_url}. Version: {status.get('version')}")
Expand All @@ -196,8 +182,7 @@ def check_connection(api_url: str, api_key: str, api_timeout: int) -> bool:
lidarr_logger.warning(f"Connection check for {api_url} returned unexpected status: {str(status)[:200]}")
return False
except Exception as e:
# Error should have been logged by arr_request, just indicate failure
lidarr_logger.error(f"Connection check failed for {api_url}: {str(e)}")
lidarr_logger.error(f"Connection check failed for {api_url}")
return False

def get_artists(api_url: str, api_key: str, api_timeout: int, artist_id: Optional[int] = None) -> Union[List, Dict, None]:
Expand Down
80 changes: 49 additions & 31 deletions src/primary/apps/radarr/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# Use a session for better performance
session = requests.Session()

def arr_request(api_url: str, api_key: str, api_timeout: int, endpoint: str, method: str = "GET", data: Dict = None, params: Dict = None, count_api: bool = True) -> Any:
def arr_request(api_url: str, api_key: str, api_timeout: int, endpoint: str, method: str = "GET", data: Optional[Dict] = None, params: Optional[Dict] = None, count_api: bool = True) -> Any:
"""
Make a request to the Radarr API.

Expand Down Expand Up @@ -64,7 +64,7 @@ def arr_request(api_url: str, api_key: str, api_timeout: int, endpoint: str, met
verify_ssl = get_ssl_verify_setting()

if not verify_ssl:
radarr_logger.debug("SSL verification disabled by user setting")
radarr_logger.debug("SSL verification disabled by global user setting")

# Make the request based on the method
if method.upper() == "GET":
Expand Down Expand Up @@ -112,11 +112,12 @@ def get_download_queue_size(api_url: str, api_key: str, api_timeout: int) -> int
return -1
try:
# Radarr uses /api/v3/queue
endpoint = f"{api_url.rstrip('/')}/api/v3/queue?page=1&pageSize=1000" # Fetch a large page size
headers = {"X-Api-Key": api_key}
response = session.get(endpoint, headers=headers, timeout=api_timeout)
response.raise_for_status()
queue_data = response.json()
params = {
"page": 1,
"pageSize": 1000 # Fetch a large page size to get all items
}

queue_data = arr_request(api_url, api_key, api_timeout, "queue", params=params)
queue_size = queue_data.get('totalRecords', 0)
radarr_logger.debug(f"Radarr download queue size: {queue_size}")
return queue_size
Expand Down Expand Up @@ -171,6 +172,7 @@ def get_cutoff_unmet_movies(api_url: str, api_key: str, api_timeout: int, monito
Returns:
A list of movie objects that need quality upgrades, or None if the request failed.
"""

# Use Radarr's dedicated cutoff endpoint
radarr_logger.debug(f"Fetching cutoff unmet movies (monitored_only={monitored_only})...")

Expand Down Expand Up @@ -344,36 +346,52 @@ def movie_search(api_url: str, api_key: str, api_timeout: int, movie_ids: List[i
return command_id
else:
radarr_logger.error(f"Failed to trigger search command for movie IDs {movie_ids}. Response: {response}")
return None
return None

def check_connection(api_url: str, api_key: str, api_timeout: int) -> bool:
"""Check the connection to Radarr API."""
try:
# Ensure api_url is properly formatted
if not api_url:
radarr_logger.error("API URL is empty or not set")
return False

# Make sure api_url has a scheme
if not (api_url.startswith('http://') or api_url.startswith('https://')):
radarr_logger.error(f"Invalid URL format: {api_url} - URL must start with http:// or https://")
return False

# Ensure URL doesn't end with a slash before adding the endpoint
base_url = api_url.rstrip('/')
full_url = f"{base_url}/api/v3/system/status"

response = requests.get(full_url, headers={"X-Api-Key": api_key}, timeout=api_timeout)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
radarr_logger.debug("Successfully connected to Radarr.")
return True
except requests.exceptions.RequestException as e:
radarr_logger.error(f"Error connecting to Radarr: {e}")
"""Checks connection by fetching system status."""
if not api_url:
radarr_logger.error("API URL is empty or not set")
return False
if not api_key:
radarr_logger.error("API Key is empty or not set")
return False

try:
# Use a shorter timeout for a quick connection check
quick_timeout = min(api_timeout, 15)
status = get_system_status(api_url, api_key, quick_timeout)
if status and isinstance(status, dict) and 'version' in status:
# Log success only if debug is enabled to avoid clutter
radarr_logger.debug(f"Connection check successful for {api_url}. Version: {status.get('version')}")
return True
else:
# Log details if the status response was unexpected
radarr_logger.warning(f"Connection check for {api_url} returned unexpected status: {str(status)[:200]}")
return False
except Exception as e:
radarr_logger.error(f"An unexpected error occurred during Radarr connection check: {e}")
# Error should have been logged by arr_request, just indicate failure
radarr_logger.error(f"Connection check failed for {api_url}")
return False


def get_system_status(api_url: str, api_key: str, api_timeout: int) -> Dict:
"""
Get Radarr system status.

Args:
api_url: The base URL of the Radarr API
api_key: The API key for authentication
api_timeout: Timeout for the API request

Returns:
System status information or empty dict if request failed
"""
response = arr_request(api_url, api_key, api_timeout, "system/status")
if response:
return response
return {}

def wait_for_command(api_url: str, api_key: str, api_timeout: int, command_id: int,
delay_seconds: int = 1, max_attempts: int = 600) -> bool:
"""
Expand Down
13 changes: 6 additions & 7 deletions src/primary/apps/readarr/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def check_connection(api_url: str, api_key: str, api_timeout: int) -> bool:
"User-Agent": "Huntarr/1.0 (https://github.com/plexguide/Huntarr.io)"
}

response = requests.get(full_url, headers=headers, timeout=api_timeout)
response = requests.get(full_url, headers=headers, timeout=api_timeout, verify=get_ssl_verify_setting())
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
logger.debug("Successfully connected to Readarr.")
return True
Expand Down Expand Up @@ -83,9 +83,9 @@ def get_download_queue_size(api_url: str = None, api_key: str = None, timeout: i
"X-Api-Key": api_key,
"Content-Type": "application/json"
}

verify_ssl = get_ssl_verify_setting()
# Make the request
response = session.get(url, headers=headers, timeout=timeout)
response = session.get(url, headers=headers, timeout=timeout, verify=verify_ssl)
response.raise_for_status()

# Parse JSON response
Expand Down Expand Up @@ -192,7 +192,6 @@ def arr_request(endpoint: str, method: str = "GET", data: Dict = None, app_type:

# Get SSL verification setting
verify_ssl = get_ssl_verify_setting()

if not verify_ssl:
logger.debug("SSL verification disabled by user setting")

Expand Down Expand Up @@ -317,7 +316,7 @@ def get_wanted_missing_books(api_url: str, api_key: str, api_timeout: int, monit
# 'monitored': monitored_only # Note: Check if Readarr API supports this directly for wanted/missing
}
try:
response = requests.get(url, headers=headers, params=params, timeout=api_timeout)
response = requests.get(url, headers=headers, params=params, timeout=api_timeout, verify=get_ssl_verify_setting())
response.raise_for_status()
data = response.json()

Expand Down Expand Up @@ -536,7 +535,7 @@ def get_author_details(api_url: str, api_key: str, author_id: int, api_timeout:
endpoint = f"{api_url}/api/v1/author/{author_id}"
headers = {'X-Api-Key': api_key}
try:
response = requests.get(endpoint, headers=headers, timeout=api_timeout)
response = requests.get(endpoint, headers=headers, timeout=api_timeout, verify=get_ssl_verify_setting())
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
author_data = response.json()
logger.debug(f"Successfully fetched details for author ID {author_id}.")
Expand Down Expand Up @@ -569,7 +568,7 @@ def search_books(api_url: str, api_key: str, book_ids: List[int], api_timeout: i
}
try:
# This uses requests.post directly, not arr_request. It's already correct.
response = requests.post(endpoint, headers=headers, json=payload, timeout=api_timeout)
response = requests.post(endpoint, headers=headers, json=payload, timeout=api_timeout, verify=get_ssl_verify_setting())
response.raise_for_status()
command_data = response.json()
command_id = command_data.get('id')
Expand Down
Loading