sonarr.py overhaul

This commit is contained in:
Nicholas St. Germain 2018-11-28 14:32:39 -06:00
parent 5b3e9d8649
commit 32b20c025c
2 changed files with 210 additions and 233 deletions

42
helpers.py Normal file
View file

@ -0,0 +1,42 @@
from typing import NamedTuple
class TVShow(NamedTuple):
seriesId: int
episodeFileId: int
seasonNumber: int
episodeNumber: int
title: str
airDate: str
airDateUtc: str
overview: str
episodeFile: dict
hasFile: bool
monitored: bool
unverifiedSceneNumbering: bool
absoluteEpisodeNumber: int
series: dict
id: int
class Queue(NamedTuple):
series: dict
episode: dict
quality: dict
size: float
title: str
sizeleft: float
timeleft: str
estimatedCompletionTime: str
status: str
trackedDownloadStatus: str
statusMessages: list
downloadId: str
protocol: str
id: int
class Server(NamedTuple):
url: str
api_key: str
id: int

401
sonarr.py
View file

@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Do not edit this script. Edit configuration.py # Do not edit this script. Edit configuration.py
import sys import sys
import requests import requests
@ -5,257 +6,183 @@ from datetime import datetime, timezone, date, timedelta
from influxdb import InfluxDBClient from influxdb import InfluxDBClient
import argparse import argparse
from argparse import RawTextHelpFormatter from argparse import RawTextHelpFormatter
import configuration import configuration as config
from helpers import Server, TVShow, Queue
def now_iso(): class SonarrAPI(object):
now = datetime.now(timezone.utc).astimezone().isoformat() TVShow.__new__.__defaults__ = (None,) * len(TVShow._fields)
return now
def __init__(self):
self.now = datetime.now(timezone.utc).astimezone().isoformat()
self.today = str(date.today())
self.influx = InfluxDBClient(config.influxdb_url, config.influxdb_port, config.influxdb_username,
config.influxdb_password, config.sonarr_influxdb_db_name)
self.influx_payload = []
self.servers = self.get_servers()
self.session = requests.Session()
self.session.params = {'pageSize': 1000}
def influx_sender(influx_payload): @staticmethod
influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username, def get_servers():
configuration.influxdb_password, configuration.sonarr_influxdb_db_name) if not config.sonarr_server_list:
influx.write_points(influx_payload) sys.exit("No Sonarr servers defined in config")
servers = []
for url, api_key, server_id in config.sonarr_server_list:
servers.append(Server(url=url, api_key=api_key, id=server_id))
def get_all_missing_shows(): return servers
# Set the time here so we have one timestamp to work with
now = now_iso()
missing, influx_payload = [], []
for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: def get_missing(self, days_past):
endpoint = '/api/calendar'
last_days = str(date.today() + timedelta(days=-days_past))
params = {'start': last_days, 'end': self.today}
headers = {'X-Api-Key': sonarr_api_key} for server in self.servers:
missing = []
headers = {'X-Api-Key': server.api_key}
get_tv_shows = requests.get('{}/api/wanted/missing/?pageSize=1000'.format(sonarr_url), get = self.session.get(server.url + endpoint, params=params, headers=headers).json()
headers=headers).json()['records'] tv_shows = [TVShow(**show) for show in get]
tv_shows = {d['id']: d for d in get_tv_shows} for show in tv_shows:
if not show.hasFile:
sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber)
missing.append((show.series['title'], sxe, show.airDate, show.title, show.id))
for show in tv_shows.keys(): for series_title, sxe, air_date, episode_title, sonarr_id in missing:
series_title = '{}'.format(tv_shows[show]['series']['title']) self.influx_payload.append(
sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) {
missing.append((series_title, sxe, tv_shows[show]['id'], tv_shows[show]['title'])) "measurement": "Sonarr",
"tags": {
for series_title, sxe, sonarr_id, episode_title in missing: "type": "Missing",
influx_payload.append( "sonarrId": sonarr_id,
{ "server": server.id
"measurement": "Sonarr", },
"tags": { "time": self.now,
"type": "Missing", "fields": {
"sonarrId": sonarr_id, "name": series_title,
"server": server_id "epname": episode_title,
}, "sxe": sxe,
"time": now, "airs": air_date
"fields": { }
"name": series_title,
"epname": episode_title,
"sxe": sxe
} }
} )
)
# Empty missing or else things get foo bared
missing = []
return influx_payload def get_upcoming(self):
endpoint = '/api/calendar/'
for server in self.servers:
upcoming = []
headers = {'X-Api-Key': server.api_key}
def get_missing_shows(days_past): get = self.session.get(server.url + endpoint, headers=headers).json()
# Set the time here so we have one timestamp to work with tv_shows = [TVShow(**show) for show in get]
now = now_iso()
last_days = str(date.today()+timedelta(days=-days_past))
today = str(date.today())
missing, influx_payload = [], []
for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: for show in tv_shows:
sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber)
upcoming.append((show.series['title'], sxe, show.id, show.title, show.airDate))
headers = {'X-Api-Key': sonarr_api_key} for series_title, sxe, sonarr_id, episode_title, air_date in upcoming:
self.influx_payload.append(
get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=1000'.format(sonarr_url, last_days, {
today), "measurement": "Sonarr",
headers=headers).json() "tags": {
"type": "Soon",
tv_shows = {d['id']: d for d in get_tv_shows} "sonarrId": sonarr_id,
"server": server.id
for show in tv_shows.keys(): },
if not (tv_shows[show]['hasFile']): "time": self.now,
series_title = '{}'.format(tv_shows[show]['series']['title']) "fields": {
sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) "name": series_title,
air_date = (tv_shows[show]['airDate']) "epname": episode_title,
missing.append((series_title, sxe, air_date, tv_shows[show]['id'])) "sxe": sxe,
"airs": air_date
for series_title, sxe, air_date, sonarr_id in missing: }
influx_payload.append(
{
"measurement": "Sonarr",
"tags": {
"type": "Missing_Days",
"sonarrId": sonarr_id,
"server": server_id
},
"time": now,
"fields": {
"name": series_title,
"sxe": sxe,
"airs": air_date
} }
} )
)
# Empty missing or else things get foo bared def get_future(self, future_days):
missing = [] endpoint = '/api/calendar/'
future = str(date.today() + timedelta(days=future_days))
return influx_payload for server in self.servers:
air_days = []
headers = {'X-Api-Key': server.api_key}
params = {'start': self.today, 'end': future}
get = self.session.get(server.url + endpoint, params=params, headers=headers).json()
tv_shows = [TVShow(**show) for show in get]
def get_upcoming_shows(): for show in tv_shows:
# Set the time here so we have one timestamp to work with sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber)
now = now_iso() air_days.append((show.series['title'], show.hasFile, sxe, show.title, show.airDate, show.id))
upcoming = []
influx_payload = []
for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days:
self.influx_payload.append(
headers = {'X-Api-Key': sonarr_api_key} {
"measurement": "Sonarr",
upcoming_shows_request = requests.get('{}/api/calendar/'.format(sonarr_url), headers=headers).json() "tags": {
"type": "Future",
upcoming_shows = {d['id']: d for d in upcoming_shows_request} "sonarrId": sonarr_id,
"server": server.id
for show in upcoming_shows.keys(): },
series_title = '{}'.format(upcoming_shows[show]['series']['title']) "time": self.now,
sxe = 'S{:0>2}E{:0>2}'.format(upcoming_shows[show]['seasonNumber'], upcoming_shows[show]['episodeNumber']) "fields": {
upcoming.append((series_title, sxe, upcoming_shows[show]['id'], upcoming_shows[show]['title'], "name": series_title,
upcoming_shows[show]['airDate'])) "epname": episode_title,
"sxe": sxe,
for series_title, sxe, sonarr_id, episode_title, air_date in upcoming: "airs": air_date,
influx_payload.append( "downloaded": dl_status
{ }
"measurement": "Sonarr",
"tags": {
"type": "Soon",
"sonarrId": sonarr_id,
"server": server_id
},
"time": now,
"fields": {
"name": series_title,
"epname": episode_title,
"sxe": sxe,
"airs": air_date
} }
} )
)
# Empty upcoming or else things get foo bared
upcoming = []
return influx_payload def get_queue(self):
endpoint = '/api/queue'
for server in self.servers:
queue = []
headers = {'X-Api-Key': server.api_key}
def get_future_shows(future_days): get = self.session.get(server.url + endpoint, headers=headers).json()
# Set the time here so we have one timestamp to work with download_queue = [Queue(**show) for show in get]
now = now_iso()
today = str(date.today()) for show in download_queue:
future = str(date.today()+timedelta(days=future_days)) sxe = 'S{:0>2}E{:0>2}'.format(show.episode['seasonNumber'], show.episode['episodeNumber'])
air_days = [] if show.protocol.upper() == 'USENET':
influx_payload = [] protocol_id = 1
else:
protocol_id = 0
for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: queue.append((show.series['title'], show.episode['title'], show.protocol.upper(),
protocol_id, sxe, show.id))
headers = {'X-Api-Key': sonarr_api_key} for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue:
self.influx_payload.append(
{
"measurement": "Sonarr",
"tags": {
"type": "Queue",
"sonarrId": sonarr_id,
"server": server.id
get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=200'.format(sonarr_url, today, future), },
headers=headers).json() "time": self.now,
"fields": {
tv_shows = {d['id']: d for d in get_tv_shows} "name": series_title,
"epname": episode_title,
for show in tv_shows.keys(): "sxe": sxe,
series_title = '{}'.format(tv_shows[show]['series']['title']) "protocol": protocol,
dl_status = int(tv_shows[show]['hasFile']) "protocol_id": protocol_id
sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) }
air_days.append((series_title, dl_status, sxe, tv_shows[show]['title'], tv_shows[show]['airDate'],
tv_shows[show]['id']))
for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days:
influx_payload.append(
{
"measurement": "Sonarr",
"tags": {
"type": "Future",
"sonarrId": sonarr_id,
"server": server_id
},
"time": now,
"fields": {
"name": series_title,
"epname": episode_title,
"sxe": sxe,
"airs": air_date,
"downloaded": dl_status
} }
} )
)
# Empty air_days or else things get foo bared
air_days = []
return influx_payload def influx_push(self):
# TODO: error handling for failed connection
self.influx.write_points(self.influx_payload)
def get_queue_shows():
# Set the time here so we have one timestamp to work with
now = now_iso()
queue = []
influx_payload = []
for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list:
headers = {'X-Api-Key': sonarr_api_key}
get_tv_shows = requests.get('{}/api/queue'.format(sonarr_url),
headers=headers).json()
tv_shows = {d['id']: d for d in get_tv_shows}
for show in tv_shows.keys():
series_title = '{}'.format(tv_shows[show]['series']['title'])
episode_title = '{}'.format(tv_shows[show]['episode']['title'])
protocol = tv_shows[show]['protocol'].upper()
sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['episode']['seasonNumber'],
tv_shows[show]['episode']['episodeNumber'])
if protocol == 'USENET':
protocol_id = 1
else:
protocol_id = 0
queue.append((series_title, episode_title, protocol, protocol_id, sxe, tv_shows[show]['id']))
for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue:
influx_payload.append(
{
"measurement": "Sonarr",
"tags": {
"type": "Queue",
"sonarrId": sonarr_id,
"server": server_id
},
"time": now,
"fields": {
"name": series_title,
"epname": episode_title,
"sxe": sxe,
"protocol": protocol,
"protocol_id": protocol_id
}
}
)
# Empty queue or else things get foo bared
queue = []
return influx_payload
if __name__ == "__main__": if __name__ == "__main__":
@ -263,25 +190,33 @@ if __name__ == "__main__":
description='Script to aid in data gathering from Sonarr', description='Script to aid in data gathering from Sonarr',
formatter_class=RawTextHelpFormatter) formatter_class=RawTextHelpFormatter)
parser.add_argument("--missing", action='store_true', help='Get all missing TV shows') parser.add_argument("--missing", metavar='$days', type=int, help='Get missing TV shows in past X days'
parser.add_argument("--missing_days", type=int, help='Get missing TV shows in past X days') '\ni.e. --missing 7 is in the last week')
parser.add_argument("--missing_days", metavar='$days', type=int, help='legacy command. Deprecated in favor of'
' --missing'
'\nfunctions identically to --missing'
'\nNote: Will be removed in a future release')
parser.add_argument("--upcoming", action='store_true', help='Get upcoming TV shows') parser.add_argument("--upcoming", action='store_true', help='Get upcoming TV shows')
parser.add_argument("--future", type=int, help='Get TV shows on X days into the future. Includes today.' parser.add_argument("--future", metavar='$days', type=int, help='Get TV shows on X days into the future. '
'\ni.e. --future 2 is Today and Tomorrow') 'Includes today.'
'\ni.e. --future 2 is Today and Tomorrow')
parser.add_argument("--queue", action='store_true', help='Get TV shows in queue') parser.add_argument("--queue", action='store_true', help='Get TV shows in queue')
opts = parser.parse_args() opts = parser.parse_args()
sonarr = SonarrAPI()
if opts.missing: if len(sys.argv) == 1:
influx_sender(get_all_missing_shows())
elif opts.missing_days:
influx_sender(get_missing_shows(opts.missing_days))
elif opts.upcoming:
influx_sender(get_upcoming_shows())
elif opts.future:
influx_sender(get_future_shows(opts.future))
elif opts.queue:
influx_sender(get_queue_shows())
elif len(sys.argv) == 1:
parser.print_help(sys.stderr) parser.print_help(sys.stderr)
sys.exit(1) sys.exit(1)
if any([opts.missing, opts.missing_days]):
days = opts.missing if opts.missing else opts.missing_days
sonarr.get_missing(days)
if opts.upcoming:
sonarr.get_upcoming()
if opts.future:
sonarr.get_future(opts.future)
if opts.queue:
sonarr.get_queue()
sonarr.influx_push()