diff --git a/Varken.py b/Varken.py index b03bc9ea..8734e122 100644 --- a/Varken.py +++ b/Varken.py @@ -21,6 +21,7 @@ from varken.tautulli import TautulliAPI from varken.radarr import RadarrAPI from varken.ombi import OmbiAPI +from varken.sickchill import SickChillAPI from varken.cisco import CiscoAPI from varken.dbmanager import DBManager from varken.varkenlogger import VarkenLogger @@ -110,6 +111,12 @@ def threaded(job): if server.request_total_counts: schedule.every(server.request_total_run_seconds).seconds.do(threaded, OMBI.get_all_requests) + if CONFIG.sickchill_enabled: + for server in CONFIG.sickchill_servers: + SICKCHILL = SickChillAPI(server, DBMANAGER) + if server.get_missing: + schedule.every(server.get_missing_run_seconds).seconds.do(threaded, SICKCHILL.get_missing) + if CONFIG.ciscoasa_enabled: for firewall in CONFIG.ciscoasa_firewalls: ASA = CiscoAPI(firewall, DBMANAGER) @@ -117,7 +124,7 @@ def threaded(job): # Run all on startup SERVICES_ENABLED = [CONFIG.ombi_enabled, CONFIG.radarr_enabled, CONFIG.tautulli_enabled, - CONFIG.sonarr_enabled, CONFIG.ciscoasa_enabled] + CONFIG.sonarr_enabled, CONFIG.ciscoasa_enabled, CONFIG.sickchill_enabled] if not [enabled for enabled in SERVICES_ENABLED if enabled]: exit("All services disabled. Exiting") schedule.run_all() diff --git a/data/varken.example.ini b/data/varken.example.ini index a8e4b800..51675ada 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -11,6 +11,7 @@ radarr_server_ids = 1,2 tautulli_server_ids = 1 ombi_server_ids = 1 ciscoasa_firewall_ids = false +sickchill_server_ids = false [influxdb] url = influxdb.domain.tld @@ -23,7 +24,7 @@ url = tautulli.domain.tld:8181 fallback_ip = 0.0.0.0 apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = true +verify_ssl = false get_activity = true get_activity_run_seconds = 30 @@ -31,7 +32,7 @@ get_activity_run_seconds = 30 url = sonarr1.domain.tld:8989 apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = true +verify_ssl = false missing_days = 7 missing_days_run_seconds = 300 future_days = 1 @@ -43,7 +44,7 @@ queue_run_seconds = 300 url = sonarr2.domain.tld:8989 apikey = yyyyyyyyyyyyyyyy ssl = false -verify_ssl = true +verify_ssl = false missing_days = 7 missing_days_run_seconds = 300 future_days = 1 @@ -55,7 +56,7 @@ queue_run_seconds = 300 url = radarr1.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = true +verify_ssl = false queue = true queue_run_seconds = 300 get_missing = true @@ -65,7 +66,7 @@ get_missing_run_seconds = 300 url = radarr2.domain.tld apikey = yyyyyyyyyyyyyyyy ssl = false -verify_ssl = true +verify_ssl = false queue = true queue_run_seconds = 300 get_missing = true @@ -75,17 +76,27 @@ get_missing_run_seconds = 300 url = ombi.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = true +verify_ssl = false get_request_type_counts = true request_type_run_seconds = 300 get_request_total_counts = true request_total_run_seconds = 300 +[sickchill-1] +url = sickchill.domain.tld:8081 +apikey = xxxxxxxxxxxxxxxx +ssl = false +verify_ssl = false +get_missing = true +get_missing_run_seconds = 300 + + + [ciscoasa-1] url = firewall.domain.tld username = cisco password = cisco outside_interface = WAN ssl = false -verify_ssl = true +verify_ssl = false get_bandwidth_run_seconds = 300 diff --git a/varken/iniparser.py b/varken/iniparser.py index cbd03df0..846de523 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -8,6 +8,7 @@ from varken.helpers import clean_sid_check from varken.varkenlogger import BlacklistFilter from varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer, CiscoASAFirewall +from varken.structures import SickChillServer class INIParser(object): @@ -31,6 +32,9 @@ def __init__(self, data_folder): self.tautulli_enabled = False self.tautulli_servers = [] + self.sickchill_enabled = False + self.sickchill_servers = [] + self.ciscoasa_enabled = False self.ciscoasa_firewalls = [] @@ -40,7 +44,7 @@ def __init__(self, data_folder): def config_blacklist(self): filtered_strings = [section.get(k) for key, section in self.config.items() - for k in section if k in BlacklistFilter.blacklisted_strings] + for k in section if k in BlacklistFilter.blacklisted_strings] self.filtered_strings = list(filter(None, filtered_strings)) for handler in self.logger.handlers: @@ -72,9 +76,9 @@ def url_check(self, url=None, include_port=True): url_check = url inc_port = include_port - search = (r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain... - r'localhost|' #localhost... - r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip + search = (r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... + r'localhost|' # localhost... + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip ) if inc_port: @@ -89,13 +93,14 @@ def url_check(self, url=None, include_port=True): valid = re.match(regex, url_check) is not None if not valid: if inc_port: - self.logger.error('%s is invalid! URL must host/IP and port if not 80 or 443. ie. localhost:8080', url_check) + self.logger.error('%s is invalid! URL must host/IP and port if not 80 or 443. ie. localhost:8080', + url_check) exit(1) else: self.logger.error('%s is invalid! URL must host/IP. ie. localhost', url_check) exit(1) else: - self.logger.debug('%s is a vlaid URL in the config.', url_check) + self.logger.debug('%s is a valid URL in the config.', url_check) return url_check def parse_opts(self): @@ -116,37 +121,30 @@ def parse_opts(self): if self.sonarr_enabled: for server_id in self.sonarr_enabled: - sonarr_section = 'sonarr-' + str(server_id) + section = 'sonarr-' + str(server_id) try: - url = self.url_check(self.config.get(sonarr_section, 'url')) + url = self.url_check(self.config.get(section, 'url')) - apikey = self.config.get(sonarr_section, 'apikey') + apikey = self.config.get(section, 'apikey') - scheme = 'https://' if self.config.getboolean( - sonarr_section, 'ssl') else 'http://' + scheme = 'https://' if self.config.getboolean(section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean( - sonarr_section, 'verify_ssl') + verify_ssl = self.config.getboolean(section, 'verify_ssl') if scheme != 'https://': verify_ssl = False - queue = self.config.getboolean(sonarr_section, 'queue') + queue = self.config.getboolean(section, 'queue') - missing_days = self.config.getint( - sonarr_section, 'missing_days') + missing_days = self.config.getint(section, 'missing_days') - future_days = self.config.getint( - sonarr_section, 'future_days') + future_days = self.config.getint(section, 'future_days') - missing_days_run_seconds = self.config.getint( - sonarr_section, 'missing_days_run_seconds') + missing_days_run_seconds = self.config.getint(section, 'missing_days_run_seconds') - future_days_run_seconds = self.config.getint( - sonarr_section, 'future_days_run_seconds') + future_days_run_seconds = self.config.getint(section, 'future_days_run_seconds') - queue_run_seconds = self.config.getint( - sonarr_section, 'queue_run_seconds') + queue_run_seconds = self.config.getint(section, 'queue_run_seconds') server = SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days, missing_days_run_seconds, future_days, future_days_run_seconds, @@ -154,40 +152,35 @@ def parse_opts(self): self.sonarr_servers.append(server) except configparser.NoOptionError as e: - self.radarr_enabled = False + self.sonarr_enabled = False self.logger.error( - '%s disabled. Error: %s', sonarr_section, e) + '%s disabled. Error: %s', section, e) # Parse Radarr options self.radarr_enabled = self.enable_check('radarr_server_ids') if self.radarr_enabled: for server_id in self.radarr_enabled: - radarr_section = 'radarr-' + str(server_id) + section = 'radarr-' + str(server_id) try: - url = self.url_check(self.config.get(radarr_section, 'url')) + url = self.url_check(self.config.get(section, 'url')) - apikey = self.config.get(radarr_section, 'apikey') + apikey = self.config.get(section, 'apikey') - scheme = 'https://' if self.config.getboolean( - radarr_section, 'ssl') else 'http://' + scheme = 'https://' if self.config.getboolean(section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean( - radarr_section, 'verify_ssl') + verify_ssl = self.config.getboolean(section, 'verify_ssl') if scheme != 'https://': verify_ssl = False - queue = self.config.getboolean(radarr_section, 'queue') + queue = self.config.getboolean(section, 'queue') - queue_run_seconds = self.config.getint( - radarr_section, 'queue_run_seconds') + queue_run_seconds = self.config.getint(section, 'queue_run_seconds') - get_missing = self.config.getboolean( - radarr_section, 'get_missing') + get_missing = self.config.getboolean(section, 'get_missing') - get_missing_run_seconds = self.config.getint( - radarr_section, 'get_missing_run_seconds') + get_missing_run_seconds = self.config.getint(section, 'get_missing_run_seconds') server = RadarrServer(server_id, scheme + url, apikey, verify_ssl, queue, queue_run_seconds, get_missing, get_missing_run_seconds) @@ -195,36 +188,31 @@ def parse_opts(self): except configparser.NoOptionError as e: self.radarr_enabled = False self.logger.error( - '%s disabled. Error: %s', radarr_section, e) + '%s disabled. Error: %s', section, e) # Parse Tautulli options self.tautulli_enabled = self.enable_check('tautulli_server_ids') if self.tautulli_enabled: for server_id in self.tautulli_enabled: - tautulli_section = 'tautulli-' + str(server_id) + section = 'tautulli-' + str(server_id) try: - url = self.url_check(self.config.get(tautulli_section, 'url')) + url = self.url_check(self.config.get(section, 'url')) - fallback_ip = self.config.get( - tautulli_section, 'fallback_ip') + fallback_ip = self.config.get(section, 'fallback_ip') - apikey = self.config.get(tautulli_section, 'apikey') + apikey = self.config.get(section, 'apikey') - scheme = 'https://' if self.config.getboolean( - tautulli_section, 'ssl') else 'http://' + scheme = 'https://' if self.config.getboolean(section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean( - tautulli_section, 'verify_ssl') + verify_ssl = self.config.getboolean(section, 'verify_ssl') if scheme != 'https://': verify_ssl = False - get_activity = self.config.getboolean( - tautulli_section, 'get_activity') + get_activity = self.config.getboolean(section, 'get_activity') - get_activity_run_seconds = self.config.getint( - tautulli_section, 'get_activity_run_seconds') + get_activity_run_seconds = self.config.getint(section, 'get_activity_run_seconds') server = TautulliServer(server_id, scheme + url, fallback_ip, apikey, verify_ssl, get_activity, get_activity_run_seconds) @@ -232,39 +220,33 @@ def parse_opts(self): except configparser.NoOptionError as e: self.tautulli_enabled = False self.logger.error( - '%s disabled. Error: %s', tautulli_section, e) + '%s disabled. Error: %s', section, e) # Parse Ombi options self.ombi_enabled = self.enable_check('ombi_server_ids') if self.ombi_enabled: for server_id in self.ombi_enabled: - ombi_section = 'ombi-' + str(server_id) + section = 'ombi-' + str(server_id) try: - url = self.url_check(self.config.get(ombi_section, 'url')) + url = self.url_check(self.config.get(section, 'url')) - apikey = self.config.get(ombi_section, 'apikey') + apikey = self.config.get(section, 'apikey') - scheme = 'https://' if self.config.getboolean( - ombi_section, 'ssl') else 'http://' + scheme = 'https://' if self.config.getboolean(section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean( - ombi_section, 'verify_ssl') + verify_ssl = self.config.getboolean(section, 'verify_ssl') if scheme != 'https://': verify_ssl = False - request_type_counts = self.config.getboolean( - ombi_section, 'get_request_type_counts') + request_type_counts = self.config.getboolean(section, 'get_request_type_counts') - request_type_run_seconds = self.config.getint( - ombi_section, 'request_type_run_seconds') + request_type_run_seconds = self.config.getint(section, 'request_type_run_seconds') - request_total_counts = self.config.getboolean( - ombi_section, 'get_request_total_counts') + request_total_counts = self.config.getboolean(section, 'get_request_total_counts') - request_total_run_seconds = self.config.getint( - ombi_section, 'request_total_run_seconds') + request_total_run_seconds = self.config.getint(section, 'request_total_run_seconds') server = OmbiServer(server_id, scheme + url, apikey, verify_ssl, request_type_counts, request_type_run_seconds, request_total_counts, request_total_run_seconds) @@ -272,35 +254,61 @@ def parse_opts(self): except configparser.NoOptionError as e: self.ombi_enabled = False self.logger.error( - '%s disabled. Error: %s', ombi_section, e) + '%s disabled. Error: %s', section, e) + + # Parse SickChill options + self.sickchill_enabled = self.enable_check('sickchill_server_ids') + + if self.sickchill_enabled: + for server_id in self.sickchill_enabled: + section = 'sickchill-' + str(server_id) + try: + url = self.url_check(self.config.get(section, 'url')) + + apikey = self.config.get(section, 'apikey') + + scheme = 'https://' if self.config.getboolean(section, 'ssl') else 'http://' + + verify_ssl = self.config.getboolean(section, 'verify_ssl') + + if scheme != 'https://': + verify_ssl = False + + get_missing = self.config.getboolean(section, 'get_missing') + + get_missing_run_seconds = self.config.getint(section, 'get_missing_run_seconds') + + server = SickChillServer(server_id, scheme + url, apikey, verify_ssl, + get_missing, get_missing_run_seconds) + self.sickchill_servers.append(server) + except configparser.NoOptionError as e: + self.sickchill_enabled = False + self.logger.error( + '%s disabled. Error: %s', section, e) # Parse ASA opts self.ciscoasa_enabled = self.enable_check('ciscoasa_firewall_ids') if self.ciscoasa_enabled: for firewall_id in self.ciscoasa_enabled: - ciscoasa_section = 'ciscoasa-' + str(firewall_id) + section = 'ciscoasa-' + str(firewall_id) try: - url = self.url_check(self.config.get(ciscoasa_section, 'url')) + url = self.url_check(self.config.get(section, 'url')) - username = self.config.get(ciscoasa_section, 'username') + username = self.config.get(section, 'username') - password = self.config.get(ciscoasa_section, 'password') + password = self.config.get(section, 'password') - scheme = 'https://' if self.config.getboolean( - ciscoasa_section, 'ssl') else 'http://' + scheme = 'https://' if self.config.getboolean(section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean( - ciscoasa_section, 'verify_ssl') + verify_ssl = self.config.getboolean(section, 'verify_ssl') if scheme != 'https://': verify_ssl = False - outside_interface = self.config.get( - ciscoasa_section, 'outside_interface') + outside_interface = self.config.get(section, 'outside_interface') - get_bandwidth_run_seconds = self.config.getint( - ciscoasa_section, 'get_bandwidth_run_seconds') + get_bandwidth_run_seconds = self.config.getint(section, 'get_bandwidth_run_seconds') firewall = CiscoASAFirewall(firewall_id, scheme + url, username, password, outside_interface, verify_ssl, get_bandwidth_run_seconds) @@ -308,4 +316,4 @@ def parse_opts(self): except configparser.NoOptionError as e: self.ciscoasa_enabled = False self.logger.error( - '%s disabled. Error: %s', ciscoasa_section, e) + '%s disabled. Error: %s', section, e) diff --git a/varken/sickchill.py b/varken/sickchill.py new file mode 100644 index 00000000..3af36844 --- /dev/null +++ b/varken/sickchill.py @@ -0,0 +1,64 @@ +import logging +from requests import Session, Request +from datetime import datetime, timezone, date, timedelta + +from varken.helpers import hashit, connection_handler +from varken.structures import SickChillTVShow + + +class SickChillAPI(object): + def __init__(self, server, dbmanager): + self.dbmanager = dbmanager + self.server = server + # Create session to reduce server web thread load, and globally define pageSize for all requests + self.session = Session() + self.session.params = {'limit': 1000} + self.endpoint = f"/api/{self.server.api_key}" + self.logger = logging.getLogger() + + def __repr__(self): + return "".format(self.server.id) + + def get_missing(self): + now = datetime.now(timezone.utc).astimezone().isoformat() + influx_payload = [] + params = {'cmd': 'future', 'paused': 1, 'type': 'missed|today|soon|later|snatched'} + + req = self.session.prepare_request(Request('GET', self.server.url + self.endpoint, params=params)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + + try: + for key, section in get['data'].items(): + get['data'][key] = [SickChillTVShow(**show) for show in section] + except TypeError as e: + self.logger.error('TypeError has occurred : %s while creating SickChillTVShow structure', e) + return + + for key, section in get['data'].items(): + for show in section: + sxe = 'S{:0>2}E{:0>2}'.format(show.season, show.episode) + hash_id = hashit('{}{}{}'.format(self.server.id, show.show_name, sxe)) + missing_types = [(0, 'future'), (1, 'later'), (2, 'soon'), (3, 'today'), (4, 'missed')] + influx_payload.append( + { + "measurement": "SickChill", + "tags": { + "type": [item[0] for item in missing_types if key in item][0], + "indexerid": show.indexerid, + "server": self.server.id, + "name": show.show_name, + "epname": show.ep_name, + "sxe": sxe, + "airdate": show.airdate, + }, + "time": now, + "fields": { + "hash": hash_id + } + } + ) + + self.dbmanager.write_points(influx_payload) diff --git a/varken/structures.py b/varken/structures.py index 3e1a5831..6140770d 100644 --- a/varken/structures.py +++ b/varken/structures.py @@ -70,6 +70,16 @@ class InfluxServer(NamedTuple): username: str = 'root' password: str = 'root' + +class SickChillServer(NamedTuple): + id: int = None + url: str = None + api_key: str = None + verify_ssl: bool = False + get_missing: bool = False + get_missing_run_seconds: int = 30 + + class CiscoASAFirewall(NamedTuple): id: int = None url: str = '192.168.1.1' @@ -384,3 +394,19 @@ class OmbiTVRequest(NamedTuple): totalSeasons: int = None childRequests: list = None id: int = None + +class SickChillTVShow(NamedTuple): + airdate: str = None + airs: str = None + ep_name: str = None + ep_plot: str = None + episode: int = None + indexerid: int = None + network: str = None + paused: int = None + quality: str = None + season: int = None + show_name: str = None + show_status: str = None + tvdbid: int = None + weekday: int = None \ No newline at end of file