From 6a6b25fecec50273f88a551ab7402e05a959b023 Mon Sep 17 00:00:00 2001 From: p0ps Date: Mon, 15 Feb 2021 03:02:15 +0100 Subject: [PATCH] Medusa apiv2 (#1812) * add fork Medusa-apiV2 * Added classes for sickbeard (base) and PyMedusa. * refactored part of the forks.py code -> InitSickBeard class. * Add .vscode to gitignore * Further refactor forks.py -> sickbeard.py * Working example for pyMedusa when fork is 'medusa' (no api key) * fix import for Py2 Co-authored-by: clinton-hall --- .gitignore | 1 + core/__init__.py | 2 + core/auto_process/managers/__init__.py | 0 core/auto_process/managers/pymedusa.py | 38 +++ core/auto_process/managers/sickbeard.py | 426 ++++++++++++++++++++++++ core/auto_process/tv.py | 37 +- core/forks.py | 244 -------------- 7 files changed, 492 insertions(+), 256 deletions(-) create mode 100644 core/auto_process/managers/__init__.py create mode 100644 core/auto_process/managers/pymedusa.py create mode 100644 core/auto_process/managers/sickbeard.py delete mode 100644 core/forks.py diff --git a/.gitignore b/.gitignore index 4b4e204e..9c80798b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ /venv/ *.dist-info *.egg-info +/.vscode diff --git a/core/__init__.py b/core/__init__.py index 439c9e82..98bc39bf 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -102,6 +102,7 @@ FORK_SICKCHILL_API = 'SickChill-api' FORK_SICKBEARD_API = 'SickBeard-api' FORK_MEDUSA = 'Medusa' FORK_MEDUSA_API = 'Medusa-api' +FORK_MEDUSA_APIV2 = 'Medusa-apiv2' FORK_SICKGEAR = 'SickGear' FORK_SICKGEAR_API = 'SickGear-api' FORK_STHENO = 'Stheno' @@ -115,6 +116,7 @@ FORKS = { FORK_SICKBEARD_API: {'path': None, 'failed': None, 'process_method': None, 'force_replace': None, 'return_data': None, 'type': None, 'delete': None, 'force_next': None, 'cmd': 'postprocess'}, FORK_MEDUSA: {'proc_dir': None, 'failed': None, 'process_method': None, 'force': None, 'delete_on': None, 'ignore_subs': None}, FORK_MEDUSA_API: {'path': None, 'failed': None, 'process_method': None, 'force_replace': None, 'return_data': None, 'type': None, 'delete_files': None, 'is_priority': None, 'cmd': 'postprocess'}, + FORK_MEDUSA_APIV2: {'proc_dir': None, 'resource': None, 'failed': None, 'process_method': None, 'force': None, 'delete_on': None, 'is_priority': None}, FORK_SICKGEAR: {'dir': None, 'failed': None, 'process_method': None, 'force': None}, FORK_SICKGEAR_API: {'path': None, 'process_method': None, 'force_replace': None, 'return_data': None, 'type': None, 'is priority': None, 'failed': None, 'cmd': 'sg.postprocess'}, FORK_STHENO: {'proc_dir': None, 'failed': None, 'process_method': None, 'force': None, 'delete_on': None, 'ignore_subs': None}, diff --git a/core/auto_process/managers/__init__.py b/core/auto_process/managers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/core/auto_process/managers/pymedusa.py b/core/auto_process/managers/pymedusa.py new file mode 100644 index 00000000..e08b6173 --- /dev/null +++ b/core/auto_process/managers/pymedusa.py @@ -0,0 +1,38 @@ +import requests + +from core import logger + +from .sickbeard import SickBeard + + +class PyMedusa(SickBeard): + """PyMedusa class.""" + + def __init__(self, sb_init): + super(PyMedusa, self).__init__(sb_init) + self.cfg = self.sb_init.config # in case we need something that's not already directly on self.sb_init. + + def _configure(): + """Configure pymedusa with config options.""" + + def _create_url(self): + if self.sb_init.apikey: + return '{0}{1}:{2}{3}/api/{4}/'.format(self.sb_init.protocol, self.sb_init.host, self.sb_init.port, self.sb_init.web_root, self.sb_init.apikey) + return '{0}{1}:{2}{3}/home/postprocess/processEpisode'.format(self.sb_init.protocol, self.sb_init.host, self.sb_init.port, self.sb_init.web_root) + + def api_call(self): + """Perform the api call with PyMedusa.""" + s = requests.Session() + + self._process_fork_prarams() + url = self._create_url() + + logger.debug('Opening URL: {0} with params: {1}'.format(url, self.sb_init.fork_params), self.sb_init.section) + if not self.sb_init.apikey and self.sb_init.username and self.sb_init.password: + login = '{0}{1}:{2}{3}/login'.format(self.sb_init.protocol, self.sb_init.host, self.sb_init.port, self.sb_init.web_root) + login_params = {'username': self.sb_init.username, 'password': self.sb_init.password} + r = s.get(login, verify=False, timeout=(30, 60)) + if r.status_code in [401, 403] and r.cookies.get('_xsrf'): + login_params['_xsrf'] = r.cookies.get('_xsrf') + s.post(login, data=login_params, stream=True, verify=False, timeout=(30, 60)) + return s.get(url, auth=(self.sb_init.username, self.sb_init.password), params=self.sb_init.fork_params, stream=True, verify=False, timeout=(30, 1800)) diff --git a/core/auto_process/managers/sickbeard.py b/core/auto_process/managers/sickbeard.py new file mode 100644 index 00000000..19f256b8 --- /dev/null +++ b/core/auto_process/managers/sickbeard.py @@ -0,0 +1,426 @@ +# coding=utf-8 + +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) + + +import copy + +import core +from core import logger +from core.utils import ( + convert_to_ascii, + flatten, + list_media_files, + remote_dir, + remove_dir, + server_responding, +) + +from oauthlib.oauth2 import LegacyApplicationClient + +import requests + +from requests_oauthlib import OAuth2Session + +import six +from six import iteritems + + +class InitSickBeard(object): + """Sickbeard init class. + + Used to determin which sickbeard fork object to initialize. + """ + + def __init__(self, cfg, section, input_category): + # As a bonus let's also put the config on self. + self.config = cfg + self.section = section + self.input_category = input_category + + self.host = cfg['host'] + self.port = cfg['port'] + self.ssl = int(cfg.get('ssl', 0)) + self.web_root = cfg.get('web_root', '') + self.protocol = 'https://' if self.ssl else 'http://' + self.username = cfg.get('username', '') + self.password = cfg.get('password', '') + self.apikey = cfg.get('apikey', '') + self.api_version = int(cfg.get('api_version', 2)) + self.sso_username = cfg.get('sso_username', '') + self.sso_password = cfg.get('sso_password', '') + + self.fork = '' + self.fork_params = None + self.fork_obj = None + + replace = { + 'medusa': 'Medusa', + 'medusa-api': 'Medusa-api', + 'sickbeard-api': 'SickBeard-api', + 'sickgear': 'SickGear', + 'sickchill': 'SickChill', + 'stheno': 'Stheno', + } + _val = cfg.get('fork', 'auto') + f1 = replace.get(_val, _val) + try: + self.fork = f1, core.FORKS[f1] + except KeyError: + self.fork = 'auto' + self.protocol = 'https://' if self.ssl else 'http://' + + def auto_fork(self): + # auto-detect correct section + # config settings + if core.FORK_SET: # keep using determined fork for multiple (manual) post-processing + logger.info('{section}:{category} fork already set to {fork}'.format + (section=self.section, category=self.input_category, fork=core.FORK_SET[0])) + return core.FORK_SET[0], core.FORK_SET[1] + + cfg = dict(core.CFG[self.section][self.input_category]) + + replace = { + 'medusa': 'Medusa', + 'medusa-api': 'Medusa-api', + 'sickbeard-api': 'SickBeard-api', + 'sickgear': 'SickGear', + 'sickchill': 'SickChill', + 'stheno': 'Stheno', + } + _val = cfg.get('fork', 'auto') + f1 = replace.get(_val, _val) + try: + fork = f1, core.FORKS[f1] + except KeyError: + fork = 'auto' + protocol = 'https://' if self.ssl else 'http://' + + if self.section == 'NzbDrone': + logger.info('Attempting to verify {category} fork'.format + (category=self.input_category)) + url = '{protocol}{host}:{port}{root}/api/rootfolder'.format( + protocol=protocol, host=self.host, port=self.port, root=self.web_root, + ) + headers = {'X-Api-Key': self.apikey} + try: + r = requests.get(url, headers=headers, stream=True, verify=False) + except requests.ConnectionError: + logger.warning('Could not connect to {0}:{1} to verify fork!'.format(self.section, self.input_category)) + + if not r.ok: + logger.warning('Connection to {section}:{category} failed! ' + 'Check your configuration'.format + (section=self.section, category=self.input_category)) + + fork = ['default', {}] + + elif self.section == 'SiCKRAGE': + logger.info('Attempting to verify {category} fork'.format + (category=self.input_category)) + + if self.api_version >= 2: + url = '{protocol}{host}:{port}{root}/api/v{api_version}/ping'.format( + protocol=protocol, host=self.host, port=self.port, root=self.web_root, api_version=self.api_version + ) + api_params = {} + else: + url = '{protocol}{host}:{port}{root}/api/v{api_version}/{apikey}/'.format( + protocol=protocol, host=self.host, port=self.port, root=self.web_root, api_version=self.api_version, apikey=self.apikey, + ) + api_params = {'cmd': 'postprocess', 'help': '1'} + + try: + if self.api_version >= 2 and self.sso_username and self.sso_password: + oauth = OAuth2Session(client=LegacyApplicationClient(client_id=core.SICKRAGE_OAUTH_CLIENT_ID)) + oauth_token = oauth.fetch_token(client_id=core.SICKRAGE_OAUTH_CLIENT_ID, + token_url=core.SICKRAGE_OAUTH_TOKEN_URL, + username=self.sso_username, + password=self.sso_password) + r = requests.get(url, headers={'Authorization': 'Bearer ' + oauth_token['access_token']}, stream=True, verify=False) + else: + r = requests.get(url, params=api_params, stream=True, verify=False) + + if not r.ok: + logger.warning('Connection to {section}:{category} failed! ' + 'Check your configuration'.format( + section=self.section, category=self.input_category + )) + except requests.ConnectionError: + logger.warning('Could not connect to {0}:{1} to verify API version!'.format(self.section, self.input_category)) + + params = { + 'path': None, + 'failed': None, + 'process_method': None, + 'force_replace': None, + 'return_data': None, + 'type': None, + 'delete': None, + 'force_next': None, + 'is_priority': None + } + + fork = ['default', params] + + elif fork == 'auto': + fork = self.detect_fork() + + logger.info('{section}:{category} fork set to {fork}'.format + (section=self.section, category=self.input_category, fork=fork[0])) + core.FORK_SET = fork + self.fork, self.fork_params = fork[0], fork[1] + # This will create the fork object, and attach to self.fork_obj. + self._init_fork() + return self.fork, self.fork_params + + @staticmethod + def _api_check(r, params, rem_params): + try: + json_data = r.json() + except ValueError: + logger.error('Failed to get JSON data from response') + logger.debug('Response received') + raise + + try: + json_data = json_data['data'] + except KeyError: + logger.error('Failed to get data from JSON') + logger.debug('Response received: {}'.format(json_data)) + raise + else: + if six.PY3: + str_type = (str) + else: + str_type = (str, unicode) + if isinstance(json_data, str_type): + return rem_params, False + json_data = json_data.get('data', json_data) + + try: + optional_parameters = json_data['optionalParameters'].keys() + # Find excess parameters + excess_parameters = set(params).difference(optional_parameters) + excess_parameters.remove('cmd') # Don't remove cmd from api params + logger.debug('Removing excess parameters: {}'.format(sorted(excess_parameters))) + rem_params.extend(excess_parameters) + return rem_params, True + except: + logger.error('Failed to identify optionalParameters') + return rem_params, False + + def detect_fork(self): + """Try to detect a specific fork.""" + detected = False + params = core.ALL_FORKS + rem_params = [] + logger.info('Attempting to auto-detect {category} fork'.format(category=self.input_category)) + # define the order to test. Default must be first since the default fork doesn't reject parameters. + # then in order of most unique parameters. + + if self.apikey: + url = '{protocol}{host}:{port}{root}/api/{apikey}/'.format( + protocol=self.protocol, host=self.host, port=self.port, root=self.web_root, apikey=self.apikey, + ) + api_params = {'cmd': 'sg.postprocess', 'help': '1'} + else: + url = '{protocol}{host}:{port}{root}/home/postprocess/'.format( + protocol=self.protocol, host=self.host, port=self.port, root=self.web_root, + ) + api_params = {} + + # attempting to auto-detect fork + try: + s = requests.Session() + + if not self.apikey and self.username and self.password: + login = '{protocol}{host}:{port}{root}/login'.format( + protocol=self.protocol, host=self.host, port=self.port, root=self.web_root) + login_params = {'username': self.username, 'password': self.password} + r = s.get(login, verify=False, timeout=(30, 60)) + if r.status_code in [401, 403] and r.cookies.get('_xsrf'): + login_params['_xsrf'] = r.cookies.get('_xsrf') + s.post(login, data=login_params, stream=True, verify=False) + r = s.get(url, auth=(self.username, self.password), params=api_params, verify=False) + except requests.ConnectionError: + logger.info('Could not connect to {section}:{category} to perform auto-fork detection!'.format + (section=self.section, category=self.input_category)) + r = [] + + if r and r.ok: + if self.apikey: + rem_params, found = self._api_check(r, params, rem_params) + if found: + params['cmd'] = 'sg.postprocess' + else: # try different api set for non-SickGear forks. + api_params = {'cmd': 'help', 'subject': 'postprocess'} + try: + if not self.apikey and self.username and self.password: + r = s.get(url, auth=(self.username, self.password), params=api_params, verify=False) + else: + r = s.get(url, params=api_params, verify=False) + except requests.ConnectionError: + logger.info('Could not connect to {section}:{category} to perform auto-fork detection!'.format + (section=self.section, category=self.input_category)) + rem_params, found = self._api_check(r, params, rem_params) + params['cmd'] = 'postprocess' + else: + # Find excess parameters + rem_params.extend( + param + for param in params + if 'name="{param}"'.format(param=param) not in r.text + ) + + # Remove excess params + for param in rem_params: + params.pop(param) + + for fork in sorted(iteritems(core.FORKS), reverse=False): + if params == fork[1]: + detected = True + break + + if detected: + self.fork = fork + logger.info('{section}:{category} fork auto-detection successful ...'.format + (section=self.section, category=self.input_category)) + elif rem_params: + logger.info('{section}:{category} fork auto-detection found custom params {params}'.format + (section=self.section, category=self.input_category, params=params)) + self.fork = ['custom', params] + else: + logger.info('{section}:{category} fork auto-detection failed'.format + (section=self.section, category=self.input_category)) + self.fork = list(core.FORKS.items())[list(core.FORKS.keys()).index(core.FORK_DEFAULT)] + + return fork + + def _init_fork(self): + from .pymedusa import PyMedusa + mapped_forks = { + 'Medusa': PyMedusa + } + logger.debug('Create object for fork {fork}'.format(fork=self.fork)) + if self.fork and mapped_forks.get(self.fork): + # Create the fork object and pass self (SickBeardInit) to it for all the data, like Config. + self.fork = mapped_forks[self.fork](self) + else: + logger.info('{section}:{category} Could not create a fork object for {fork}. Probaly class not added yet.'.format( + section=self.section, category=self.input_category, fork=self.fork) + ) + + +class SickBeard(object): + """Sickbeard base class.""" + + def __init__(self, sb_init): + """SB constructor.""" + self.sb_init = sb_init + self.failed = None + self.status = None + self.input_name = None + self.dir_name = None + + self.delete_failed = int(self.sb_init.config.get('delete_failed', 0)) + self.nzb_extraction_by = self.sb_init.config.get('nzbExtractionBy', 'Downloader') + self.process_method = self.sb_init.config.get('process_method') + self.remote_path = int(self.sb_init.config.get('remote_path', 0)) + self.wait_for = int(self.sb_init.config.get('wait_for', 2)) + self.force = int(self.sb_init.config.get('force', 0)) + self.delete_on = int(self.sb_init.config.get('delete_on', 0)) + self.ignore_subs = int(self.sb_init.config.get('ignore_subs', 0)) + + # get importmode, default to 'Move' for consistency with legacy + self.import_mode = self.sb_init.config.get('importMode', 'Move') + + def initialize(self, dir_name, input_name=None, failed=False, client_agent='manual'): + """We need to call this explicitely because we need some variables. + + We can't pass these directly through the constructor. + """ + self.dir_name = dir_name + self.input_name = input_name + self.failed = failed + self.status = int(self.failed) + if self.status > 0 and core.NOEXTRACTFAILED: + self.extract = 0 + else: + self.extract = int(self.sb_init.config.get('extract', 0)) + if client_agent == core.TORRENT_CLIENT_AGENT and core.USE_LINK == 'move-sym': + self.process_method = 'symlink' + + def _create_url(self): + if self.sb_init.apikey: + return '{0}{1}:{2}{3}/api/{4}/'.format(self.sb_init.protocol, self.sb_init.host, self.sb_init.port, self.sb_init.web_root, self.sb_init.apikey) + return '{0}{1}:{2}{3}/home/postprocess/processEpisode'.format(self.sb_init.protocol, self.sb_init.host, self.sb_init.port, self.sb_init.web_root) + + def _process_fork_prarams(self): + # configure SB params to pass + fork_params = self.sb_init.fork_params + fork_params['quiet'] = 1 + fork_params['proc_type'] = 'manual' + if self.input_name is not None: + fork_params['nzbName'] = self.input_name + + for param in copy.copy(fork_params): + if param == 'failed': + if self.failed > 1: + self.failed = 1 + fork_params[param] = self.failed + if 'proc_type' in fork_params: + del fork_params['proc_type'] + if 'type' in fork_params: + del fork_params['type'] + + if param == 'return_data': + fork_params[param] = 0 + if 'quiet' in fork_params: + del fork_params['quiet'] + + if param == 'type': + if 'type' in fork_params: # only set if we haven't already deleted for 'failed' above. + fork_params[param] = 'manual' + if 'proc_type' in fork_params: + del fork_params['proc_type'] + + if param in ['dir_name', 'dir', 'proc_dir', 'process_directory', 'path']: + fork_params[param] = self.dir_name + if self.remote_path: + fork_params[param] = remote_dir(self.dir_name) + + if param == 'process_method': + if self.process_method: + fork_params[param] = self.process_method + else: + del fork_params[param] + + if param in ['force', 'force_replace']: + if self.force: + fork_params[param] = self.force + else: + del fork_params[param] + + if param in ['delete_on', 'delete']: + if self.delete_on: + fork_params[param] = self.delete_on + else: + del fork_params[param] + + if param == 'ignore_subs': + if self.ignore_subs: + fork_params[param] = self.ignore_subs + else: + del fork_params[param] + + if param == 'force_next': + fork_params[param] = 1 + + # delete any unused params so we don't pass them to SB by mistake + [fork_params.pop(k) for k, v in list(fork_params.items()) if v is None] diff --git a/core/auto_process/tv.py b/core/auto_process/tv.py index ec1ffb75..41059a83 100644 --- a/core/auto_process/tv.py +++ b/core/auto_process/tv.py @@ -24,7 +24,7 @@ from core.auto_process.common import ( command_complete, completed_download_handling, ) -from core.forks import auto_fork +from core.auto_process.managers.sickbeard import InitSickBeard from core.plugins.downloaders.nzb.utils import report_nzb from core.plugins.subtitles import import_subs from core.scene_exceptions import process_all_exceptions @@ -37,6 +37,7 @@ from core.utils import ( server_responding, ) + requests.packages.urllib3.disable_warnings() @@ -55,9 +56,15 @@ def process(section, dir_name, input_name=None, failed=False, client_agent='manu sso_username = cfg.get('sso_username', '') sso_password = cfg.get('sso_password', '') + # Refactor into an OO structure. + # For now let's do botch the OO and the serialized code, until everything has been migrated. + init_sickbeard = InitSickBeard(cfg, section, input_category) + if server_responding('{0}{1}:{2}{3}'.format(protocol, host, port, web_root)): # auto-detect correct fork - fork, fork_params = auto_fork(section, input_category) + # During reactor we also return fork, fork_params. But these are also stored in the object. + # Should be changed after refactor. + fork, fork_params = init_sickbeard.auto_fork() elif not username and not apikey and not sso_username: logger.info('No SickBeard / SiCKRAGE username or Sonarr apikey entered. Performing transcoder functions only') fork, fork_params = 'None', {} @@ -184,6 +191,9 @@ def process(section, dir_name, input_name=None, failed=False, client_agent='manu status_code=1, ) + # Part of the refactor + init_sickbeard.fork.initialize(dir_name, input_name, failed, client_agent='manual') + # configure SB params to pass fork_params['quiet'] = 1 fork_params['proc_type'] = 'manual' @@ -311,17 +321,20 @@ def process(section, dir_name, input_name=None, failed=False, client_agent='manu try: if section == 'SickBeard': - s = requests.Session() + if init_sickbeard.fork: + r = init_sickbeard.fork.api_call() + else: + s = requests.Session() - logger.debug('Opening URL: {0} with params: {1}'.format(url, fork_params), section) - if not apikey and username and password: - login = '{0}{1}:{2}{3}/login'.format(protocol, host, port, web_root) - login_params = {'username': username, 'password': password} - r = s.get(login, verify=False, timeout=(30, 60)) - if r.status_code in [401, 403] and r.cookies.get('_xsrf'): - login_params['_xsrf'] = r.cookies.get('_xsrf') - s.post(login, data=login_params, stream=True, verify=False, timeout=(30, 60)) - r = s.get(url, auth=(username, password), params=fork_params, stream=True, verify=False, timeout=(30, 1800)) + logger.debug('Opening URL: {0} with params: {1}'.format(url, fork_params), section) + if not apikey and username and password: + login = '{0}{1}:{2}{3}/login'.format(protocol, host, port, web_root) + login_params = {'username': username, 'password': password} + r = s.get(login, verify=False, timeout=(30, 60)) + if r.status_code in [401, 403] and r.cookies.get('_xsrf'): + login_params['_xsrf'] = r.cookies.get('_xsrf') + s.post(login, data=login_params, stream=True, verify=False, timeout=(30, 60)) + r = s.get(url, auth=(username, password), params=fork_params, stream=True, verify=False, timeout=(30, 1800)) elif section == 'SiCKRAGE': s = requests.Session() diff --git a/core/forks.py b/core/forks.py deleted file mode 100644 index 0b11255a..00000000 --- a/core/forks.py +++ /dev/null @@ -1,244 +0,0 @@ -# coding=utf-8 - -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals, -) - -import requests -import six -from oauthlib.oauth2 import LegacyApplicationClient -from requests_oauthlib import OAuth2Session -from six import iteritems - -import core -from core import logger - - -def api_check(r, params, rem_params): - try: - json_data = r.json() - except ValueError: - logger.error('Failed to get JSON data from response') - logger.debug('Response received') - raise - - try: - json_data = json_data['data'] - except KeyError: - logger.error('Failed to get data from JSON') - logger.debug('Response received: {}'.format(json_data)) - raise - else: - if six.PY3: - str_type = (str) - else: - str_type = (str, unicode) - if isinstance(json_data, str_type): - return rem_params, False - json_data = json_data.get('data', json_data) - - try: - optional_parameters = json_data['optionalParameters'].keys() - # Find excess parameters - excess_parameters = set(params).difference(optional_parameters) - excess_parameters.remove('cmd') # Don't remove cmd from api params - logger.debug('Removing excess parameters: {}'.format(sorted(excess_parameters))) - rem_params.extend(excess_parameters) - return rem_params, True - except: - logger.error('Failed to identify optionalParameters') - return rem_params, False - - -def auto_fork(section, input_category): - # auto-detect correct section - # config settings - if core.FORK_SET: # keep using determined fork for multiple (manual) post-processing - logger.info('{section}:{category} fork already set to {fork}'.format - (section=section, category=input_category, fork=core.FORK_SET[0])) - return core.FORK_SET[0], core.FORK_SET[1] - - cfg = dict(core.CFG[section][input_category]) - - host = cfg.get('host') - port = cfg.get('port') - username = cfg.get('username', '') - password = cfg.get('password', '') - sso_username = cfg.get('sso_username', '') - sso_password = cfg.get('sso_password', '') - apikey = cfg.get('apikey', '') - api_version = int(cfg.get('api_version', 2)) - ssl = int(cfg.get('ssl', 0)) - web_root = cfg.get('web_root', '') - replace = { - 'medusa': 'Medusa', - 'medusa-api': 'Medusa-api', - 'sickbeard-api': 'SickBeard-api', - 'sickgear': 'SickGear', - 'sickchill': 'SickChill', - 'stheno': 'Stheno', - } - _val = cfg.get('fork', 'auto') - f1 = replace.get(_val, _val) - try: - fork = f1, core.FORKS[f1] - except KeyError: - fork = 'auto' - protocol = 'https://' if ssl else 'http://' - - detected = False - if section == 'NzbDrone': - logger.info('Attempting to verify {category} fork'.format - (category=input_category)) - url = '{protocol}{host}:{port}{root}/api/rootfolder'.format( - protocol=protocol, host=host, port=port, root=web_root, - ) - headers = {'X-Api-Key': apikey} - try: - r = requests.get(url, headers=headers, stream=True, verify=False) - except requests.ConnectionError: - logger.warning('Could not connect to {0}:{1} to verify fork!'.format(section, input_category)) - - if not r.ok: - logger.warning('Connection to {section}:{category} failed! ' - 'Check your configuration'.format - (section=section, category=input_category)) - - fork = ['default', {}] - - elif section == 'SiCKRAGE': - logger.info('Attempting to verify {category} fork'.format - (category=input_category)) - - if api_version >= 2: - url = '{protocol}{host}:{port}{root}/api/v{api_version}/ping'.format( - protocol=protocol, host=host, port=port, root=web_root, api_version=api_version - ) - api_params = {} - else: - url = '{protocol}{host}:{port}{root}/api/v{api_version}/{apikey}/'.format( - protocol=protocol, host=host, port=port, root=web_root, api_version=api_version, apikey=apikey, - ) - api_params = {'cmd': 'postprocess', 'help': '1'} - - try: - if api_version >= 2 and sso_username and sso_password: - oauth = OAuth2Session(client=LegacyApplicationClient(client_id=core.SICKRAGE_OAUTH_CLIENT_ID)) - oauth_token = oauth.fetch_token(client_id=core.SICKRAGE_OAUTH_CLIENT_ID, - token_url=core.SICKRAGE_OAUTH_TOKEN_URL, - username=sso_username, - password=sso_password) - r = requests.get(url, headers={'Authorization': 'Bearer ' + oauth_token['access_token']}, stream=True, verify=False) - else: - r = requests.get(url, params=api_params, stream=True, verify=False) - - if not r.ok: - logger.warning('Connection to {section}:{category} failed! ' - 'Check your configuration'.format - (section=section, category=input_category)) - except requests.ConnectionError: - logger.warning('Could not connect to {0}:{1} to verify API version!'.format(section, input_category)) - - params = { - 'path': None, - 'failed': None, - 'process_method': None, - 'force_replace': None, - 'return_data': None, - 'type': None, - 'delete': None, - 'force_next': None, - 'is_priority': None - } - - fork = ['default', params] - - elif fork == 'auto': - params = core.ALL_FORKS - rem_params = [] - logger.info('Attempting to auto-detect {category} fork'.format(category=input_category)) - # define the order to test. Default must be first since the default fork doesn't reject parameters. - # then in order of most unique parameters. - - if apikey: - url = '{protocol}{host}:{port}{root}/api/{apikey}/'.format( - protocol=protocol, host=host, port=port, root=web_root, apikey=apikey, - ) - api_params = {'cmd': 'sg.postprocess', 'help': '1'} - else: - url = '{protocol}{host}:{port}{root}/home/postprocess/'.format( - protocol=protocol, host=host, port=port, root=web_root, - ) - api_params = {} - - # attempting to auto-detect fork - try: - s = requests.Session() - - if not apikey and username and password: - login = '{protocol}{host}:{port}{root}/login'.format( - protocol=protocol, host=host, port=port, root=web_root) - login_params = {'username': username, 'password': password} - r = s.get(login, verify=False, timeout=(30, 60)) - if r.status_code in [401, 403] and r.cookies.get('_xsrf'): - login_params['_xsrf'] = r.cookies.get('_xsrf') - s.post(login, data=login_params, stream=True, verify=False) - r = s.get(url, auth=(username, password), params=api_params, verify=False) - except requests.ConnectionError: - logger.info('Could not connect to {section}:{category} to perform auto-fork detection!'.format - (section=section, category=input_category)) - r = [] - - if r and r.ok: - if apikey: - rem_params, found = api_check(r, params, rem_params) - if found: - params['cmd'] = 'sg.postprocess' - else: # try different api set for non-SickGear forks. - api_params = {'cmd': 'help', 'subject': 'postprocess'} - try: - if not apikey and username and password: - r = s.get(url, auth=(username, password), params=api_params, verify=False) - else: - r = s.get(url, params=api_params, verify=False) - except requests.ConnectionError: - logger.info('Could not connect to {section}:{category} to perform auto-fork detection!'.format - (section=section, category=input_category)) - rem_params, found = api_check(r, params, rem_params) - params['cmd'] = 'postprocess' - else: - # Find excess parameters - rem_params.extend( - param - for param in params - if 'name="{param}"'.format(param=param) not in r.text - ) - - # Remove excess params - for param in rem_params: - params.pop(param) - - for fork in sorted(iteritems(core.FORKS), reverse=False): - if params == fork[1]: - detected = True - break - - if detected: - logger.info('{section}:{category} fork auto-detection successful ...'.format - (section=section, category=input_category)) - elif rem_params: - logger.info('{section}:{category} fork auto-detection found custom params {params}'.format - (section=section, category=input_category, params=params)) - fork = ['custom', params] - else: - logger.info('{section}:{category} fork auto-detection failed'.format - (section=section, category=input_category)) - fork = list(core.FORKS.items())[list(core.FORKS.keys()).index(core.FORK_DEFAULT)] - - logger.info('{section}:{category} fork set to {fork}'.format - (section=section, category=input_category, fork=fork[0])) - core.FORK_SET = fork - return fork[0], fork[1]