1+ #!/usr/bin/env python3
2+ """
3+ Radarr API Helper Functions
4+ Handles all communication with the Radarr API
5+ """
6+
7+ import requests
8+ import time
9+ import datetime
10+ from typing import List , Dict , Any , Optional , Union
11+ from utils .logger import logger , debug_log
12+ from config import API_KEY , API_URL , API_TIMEOUT , MONITORED_ONLY , SKIP_FUTURE_RELEASES
13+
14+ # Create a session for reuse
15+ session = requests .Session ()
16+
17+ def radarr_request (endpoint : str , method : str = "GET" , data : Dict = None ) -> Optional [Union [Dict , List ]]:
18+ """
19+ Make a request to the Radarr API (v3).
20+ `endpoint` should be something like 'movie', 'command', etc.
21+ """
22+ url = f"{ API_URL } /api/v3/{ endpoint } "
23+ headers = {
24+ "X-Api-Key" : API_KEY ,
25+ "Content-Type" : "application/json"
26+ }
27+
28+ try :
29+ if method .upper () == "GET" :
30+ response = session .get (url , headers = headers , timeout = API_TIMEOUT )
31+ elif method .upper () == "POST" :
32+ response = session .post (url , headers = headers , json = data , timeout = API_TIMEOUT )
33+ else :
34+ logger .error (f"Unsupported HTTP method: { method } " )
35+ return None
36+
37+ response .raise_for_status ()
38+ return response .json ()
39+ except requests .exceptions .RequestException as e :
40+ logger .error (f"API request error: { e } " )
41+ return None
42+
43+ def get_movies () -> List [Dict ]:
44+ """Get all movies from Radarr (full list)"""
45+ result = radarr_request ("movie" )
46+ if result :
47+ debug_log ("Raw movies API response sample:" , result [:2 ] if len (result ) > 2 else result )
48+ return result or []
49+
50+ def get_cutoff_unmet () -> List [Dict ]:
51+ """
52+ Directly query Radarr for only those movies where the quality cutoff is not met.
53+ This is the most reliable way for big libraries. Optionally filter by monitored.
54+ """
55+ query = "movie?qualityCutoffNotMet=true"
56+ if MONITORED_ONLY :
57+ # Append &monitored=true to the querystring
58+ query += "&monitored=true"
59+
60+ # Perform the request
61+ result = radarr_request (query , method = "GET" )
62+ return result or []
63+
64+ def get_missing_movies () -> List [Dict ]:
65+ """
66+ Get a list of movies that are missing files.
67+ Filters based on MONITORED_ONLY setting and optionally
68+ excludes future releases.
69+ """
70+ movies = get_movies ()
71+
72+ if not movies :
73+ return []
74+
75+ missing_movies = []
76+
77+ # Get current date in ISO format (YYYY-MM-DD) for date comparison
78+ current_date = datetime .datetime .now ().strftime ("%Y-%m-%d" )
79+
80+ for movie in movies :
81+ # Skip if not missing a file
82+ if movie .get ('hasFile' ):
83+ continue
84+
85+ # Apply monitored filter if needed
86+ if MONITORED_ONLY and not movie .get ('monitored' ):
87+ continue
88+
89+ # Skip future releases if enabled
90+ if SKIP_FUTURE_RELEASES :
91+ # Check physical, digital, and cinema release dates
92+ physical_release = movie .get ('physicalRelease' )
93+ digital_release = movie .get ('digitalRelease' )
94+ in_cinemas = movie .get ('inCinemas' )
95+
96+ # Use the earliest available release date for comparison
97+ release_date = None
98+ if physical_release :
99+ release_date = physical_release
100+ elif digital_release :
101+ release_date = digital_release
102+ elif in_cinemas :
103+ release_date = in_cinemas
104+
105+ # Skip if release date exists and is in the future
106+ if release_date and release_date > current_date :
107+ logger .debug (f"Skipping future release '{ movie .get ('title' )} ' with date { release_date } " )
108+ continue
109+
110+ missing_movies .append (movie )
111+
112+ return missing_movies
113+
114+ def refresh_movie (movie_id : int ) -> Optional [Dict ]:
115+ """Refresh a movie by ID"""
116+ data = {
117+ "name" : "RefreshMovie" ,
118+ "movieIds" : [movie_id ]
119+ }
120+ return radarr_request ("command" , method = "POST" , data = data )
121+
122+ def movie_search (movie_id : int ) -> Optional [Dict ]:
123+ """Search for a movie by ID"""
124+ data = {
125+ "name" : "MoviesSearch" ,
126+ "movieIds" : [movie_id ]
127+ }
128+ return radarr_request ("command" , method = "POST" , data = data )
129+
130+ def rescan_movie (movie_id : int ) -> Optional [Dict ]:
131+ """Rescan movie files"""
132+ data = {
133+ "name" : "RescanMovie" ,
134+ "movieIds" : [movie_id ]
135+ }
136+ return radarr_request ("command" , method = "POST" , data = data )
0 commit comments