Skip to content

Commit 7e73470

Browse files
authored
Add files via upload
1 parent 47e43e3 commit 7e73470

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""Radarr2 integration for Mediarr."""
2+
from ..common.sensor import MediarrSensor
3+
from datetime import datetime, timedelta
4+
import async_timeout
5+
import logging
6+
7+
8+
_LOGGER = logging.getLogger(__name__)
9+
10+
class Radarr2MediarrSensor(MediarrSensor):
11+
def __init__(self, session, api_key, url, max_items, days_to_check):
12+
"""Initialize the sensor."""
13+
self._session = session
14+
self._radarr2_api_key = api_key
15+
self._url = url.rstrip('/')
16+
self._max_items = max_items
17+
self._days_to_check = days_to_check
18+
self._name = "Radarr2 Mediarr"
19+
self._state = 0
20+
21+
22+
23+
@property
24+
def name(self):
25+
"""Return the name of the sensor."""
26+
return self._name
27+
28+
@property
29+
def unique_id(self):
30+
"""Return a unique ID."""
31+
return f"radarr2_mediarr_{self._url}"
32+
33+
async def async_update(self):
34+
"""Update the sensor."""
35+
try:
36+
headers = {'X-Api-Key': self._radarr2_api_key}
37+
now = datetime.now().astimezone()
38+
max_date = now + timedelta(days=self._days_to_check)
39+
40+
async with async_timeout.timeout(10):
41+
async with self._session.get(
42+
f"{self._url}/api/v3/movie",
43+
headers=headers
44+
) as response:
45+
if response.status != 200:
46+
raise Exception(f"Failed to connect to Radarr2. Status: {response.status}")
47+
48+
movies = await response.json()
49+
card_json = []
50+
upcoming_movies = []
51+
52+
for movie in movies:
53+
release_dates = []
54+
for date_field, date_type in [
55+
('digitalRelease', 'Digital'),
56+
('physicalRelease', 'Physical'),
57+
('inCinemas', 'Theaters')
58+
]:
59+
if movie.get(date_field):
60+
try:
61+
release_date = datetime.fromisoformat(
62+
movie[date_field].replace('Z', '+00:00')
63+
)
64+
if not release_date.tzinfo:
65+
release_date = release_date.replace(tzinfo=now.tzinfo)
66+
if now < release_date <= max_date:
67+
release_dates.append((date_type, release_date))
68+
except ValueError:
69+
continue
70+
71+
if release_dates:
72+
release_dates.sort(key=lambda x: x[1])
73+
release_type, release_date = release_dates[0]
74+
75+
images = {img['coverType']: img['remoteUrl'] for img in movie.get('images', [])}
76+
77+
movie_data = {
78+
"title": str(movie["title"]),
79+
"release": f"{release_type} - {release_date.strftime('%Y-%m-%d')}",
80+
"aired": release_date.strftime("%Y-%m-%d"),
81+
"year": str(movie["year"]),
82+
"poster": images.get('poster', ''),
83+
"fanart": images.get('fanart', ''),
84+
"banner": images.get('banner', ''),
85+
"genres": ", ".join(str(g) for g in movie.get("genres", [])[:3]),
86+
"runtime": str(movie.get("runtime", 0)),
87+
"rating": str(movie.get("ratings", {}).get("value", "")),
88+
"studio": str(movie.get("studio", "N/A")),
89+
"flag": 1
90+
}
91+
upcoming_movies.append(movie_data)
92+
93+
upcoming_movies.sort(key=lambda x: x['aired'])
94+
card_json.extend(upcoming_movies[:self._max_items])
95+
96+
if not card_json:
97+
card_json.append({
98+
'title_default': '$title',
99+
'line1_default': '$release',
100+
'line2_default': '$genres',
101+
'line3_default': '$rating - $runtime',
102+
'line4_default': '$studio',
103+
'icon': 'mdi:arrow-down-circle'
104+
})
105+
106+
self._state = len(upcoming_movies)
107+
self._attributes = {'data': card_json}
108+
self._available = True
109+
110+
except Exception as err:
111+
_LOGGER.error("Error updating Radarr2 sensor: %s", err)
112+
self._state = 0
113+
self._attributes = {'data': []}
114+
self._available = False
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""Sonarr2 integration for Mediarr."""
2+
from ..common.sensor import MediarrSensor
3+
from datetime import datetime, timedelta
4+
import async_timeout
5+
from zoneinfo import ZoneInfo
6+
import logging
7+
8+
9+
_LOGGER = logging.getLogger(__name__)
10+
11+
class Sonarr2MediarrSensor(MediarrSensor):
12+
def __init__(self, session, api_key, url, max_items, days_to_check):
13+
"""Initialize the sensor."""
14+
self._session = session
15+
self._sonarr2_api_key = api_key
16+
self._url = url.rstrip('/')
17+
self._max_items = max_items
18+
self._days_to_check = days_to_check
19+
self._name = "Sonarr2 Mediarr"
20+
self._state = 0
21+
22+
23+
24+
@property
25+
def name(self):
26+
"""Return the name of the sensor."""
27+
return self._name
28+
29+
@property
30+
def unique_id(self):
31+
"""Return a unique ID."""
32+
return f"sonarr2_mediarr_{self._url}"
33+
34+
def _format_date(self, date_str: str) -> str:
35+
"""Format date string."""
36+
try:
37+
return datetime.strptime(date_str, '%Y-%m-%d').strftime('%Y-%m-%d')
38+
except ValueError:
39+
return 'Unknown'
40+
41+
async def async_update(self):
42+
"""Update the sensor."""
43+
try:
44+
headers = {'X-Api-Key': self._sonarr2_api_key}
45+
now = datetime.now(ZoneInfo('UTC'))
46+
params = {
47+
'start': now.strftime('%Y-%m-%d'),
48+
'end': (now + timedelta(days=self._days_to_check)).strftime('%Y-%m-%d'),
49+
'includeSeries': 'true'
50+
}
51+
52+
async with async_timeout.timeout(10):
53+
async with self._session.get(
54+
f"{self._url}/api/v3/calendar",
55+
headers=headers,
56+
params=params
57+
) as response:
58+
if response.status == 200:
59+
upcoming_episodes = await response.json()
60+
card_json = []
61+
shows_dict = {}
62+
63+
for episode in upcoming_episodes:
64+
series = episode.get('series', {})
65+
air_date = self._format_date(episode['airDate'])
66+
67+
if air_date == 'Unknown':
68+
continue
69+
70+
series_id = series['id']
71+
images = {img['coverType']: img['remoteUrl'] for img in series.get('images', [])}
72+
73+
show_data = {
74+
'title': f"{series['title']} - {episode.get('seasonNumber', 0):02d}x{episode.get('episodeNumber', 0):02d}",
75+
'episode': str(episode.get('title', 'Unknown')),
76+
'release': air_date,
77+
'number': f"S{episode.get('seasonNumber', 0):02d}E{episode.get('episodeNumber', 0):02d}",
78+
'runtime': str(series.get('runtime', 0)),
79+
'network': str(series.get('network', 'N/A')),
80+
'poster': images.get('poster', ''),
81+
'fanart': images.get('fanart', ''),
82+
'banner': images.get('banner', ''),
83+
'season': str(episode.get('seasonNumber', 0)),
84+
'details': f"{series['title']}\n{episode.get('title', 'Unknown')}\nS{episode.get('seasonNumber', 0):02d}E{episode.get('episodeNumber', 0):02d}",
85+
'flag': 1
86+
}
87+
88+
# Take earliest episode air date for each series
89+
if series_id not in shows_dict or air_date < shows_dict[series_id]['release']:
90+
shows_dict[series_id] = show_data
91+
92+
upcoming_shows = list(shows_dict.values())
93+
upcoming_shows.sort(key=lambda x: x['release'])
94+
card_json.extend(upcoming_shows[:self._max_items])
95+
96+
if not card_json:
97+
card_json.append({
98+
'title_default': '$title',
99+
'line1_default': '$episode',
100+
'line2_default': '$release',
101+
'line3_default': '$number',
102+
'line4_default': '$runtime - $network',
103+
'icon': 'mdi:arrow-down-circle'
104+
})
105+
106+
self._state = len(upcoming_shows)
107+
self._attributes = {'data': card_json}
108+
self._available = True
109+
else:
110+
raise Exception(f"Failed to connect to Sonarr2. Status: {response.status}")
111+
112+
except Exception as err:
113+
_LOGGER.error("Error updating Sonarr2 sensor: %s", err)
114+
self._state = 0
115+
self._attributes = {'data': []}
116+
self._available = False

0 commit comments

Comments
 (0)