from base64 import b64encode import os from uuid import uuid4 import hashlib import traceback from requests import HTTPError from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList from couchpotato.core.helpers.encoding import tryUrlencode, sp from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog log = CPLog(__name__) autoload = 'NZBVortex' class NZBVortex(DownloaderBase): protocol = ['nzb'] api_level = None session_id = None def download(self, data = None, media = None, filedata = None): if not media: media = {} if not data: data = {} # Send the nzb try: nzb_filename = self.createFileName(data, filedata, media, unique_tag = True) response = self.call('nzb/add', files = {'file': (nzb_filename, filedata, 'application/octet-stream')}, parameters = { 'name': nzb_filename, 'groupname': self.conf('group') }) if response and response.get('result', '').lower() == 'ok': return self.downloadReturnId(nzb_filename) log.error('Something went wrong sending the NZB file. Response: %s', response) return False except: log.error('Something went wrong sending the NZB file: %s', traceback.format_exc()) return False def test(self): try: login_result = self.login() except: return False return login_result def getAllDownloadStatus(self, ids): raw_statuses = self.call('nzb') release_downloads = ReleaseDownloadList(self) for nzb in raw_statuses.get('nzbs', []): nzb_id = os.path.basename(nzb['nzbFileName']) if nzb_id in ids: # Check status status = 'busy' if nzb['state'] == 20: status = 'completed' elif nzb['state'] in [21, 22, 24]: status = 'failed' release_downloads.append({ 'temp_id': nzb['id'], 'id': nzb_id, 'name': nzb['uiTitle'], 'status': status, 'original_status': nzb['state'], 'timeleft': -1, 'folder': sp(nzb['destinationPath']), }) return release_downloads def removeFailed(self, release_download): log.info('%s failed downloading, deleting...', release_download['name']) try: self.call('nzb/%s/cancel' % release_download['temp_id']) except: log.error('Failed deleting: %s', traceback.format_exc(0)) return False return True def login(self): nonce = self.call('auth/nonce', auth = False).get('authNonce') cnonce = uuid4().hex hashed = b64encode(hashlib.sha256('%s:%s:%s' % (nonce, cnonce, self.conf('api_key'))).digest()) params = { 'nonce': nonce, 'cnonce': cnonce, 'hash': hashed } login_data = self.call('auth/login', parameters = params, auth = False) # Save for later if login_data.get('loginResult') == 'successful': self.session_id = login_data.get('sessionID') return True log.error('Login failed, please check you api-key') return False def call(self, call, parameters = None, is_repeat = False, auth = True, *args, **kwargs): # Login first if not parameters: parameters = {} if not self.session_id and auth: self.login() # Always add session id to request if self.session_id: parameters['sessionid'] = self.session_id params = tryUrlencode(parameters) url = cleanHost(self.conf('host')) + 'api/' + call try: data = self.getJsonData('%s%s' % (url, '?' + params if params else ''), *args, cache_timeout = 0, show_error = False, **kwargs) if data: return data except HTTPError as e: sc = e.response.status_code if sc == 403: # Try login and do again if not is_repeat: self.login() return self.call(call, parameters = parameters, is_repeat = True, **kwargs) log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc())) except: log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc())) return {} def getApiLevel(self): if not self.api_level: try: data = self.call('app/apilevel', auth = False) self.api_level = float(data.get('apilevel')) except HTTPError as e: sc = e.response.status_code if sc == 403: log.error('This version of NZBVortex isn\'t supported. Please update to 2.8.6 or higher') else: log.error('NZBVortex doesn\'t seem to be running or maybe the remote option isn\'t enabled yet: %s', traceback.format_exc(1)) return self.api_level def isEnabled(self, manual = False, data = None): if not data: data = {} return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel() config = [{ 'name': 'nzbvortex', 'groups': [ { 'tab': 'downloaders', 'list': 'download_providers', 'name': 'nzbvortex', 'label': 'NZBVortex', 'description': 'Use NZBVortex to download NZBs.', 'wizard': True, 'options': [ { 'name': 'enabled', 'default': 0, 'type': 'enabler', 'radio_group': 'nzb', }, { 'name': 'host', 'default': 'https://localhost:4321', 'description': 'Hostname with port. Usually https://localhost:4321', }, { 'name': 'api_key', 'label': 'Api Key', }, { 'name': 'group', 'label': 'Group', 'description': 'The group CP places the nzb in. Make sure to create it in NZBVortex.', }, { 'name': 'manual', 'default': False, 'type': 'bool', 'advanced': True, 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', }, { 'name': 'delete_failed', 'default': True, 'advanced': True, 'type': 'bool', 'description': 'Delete a release after the download has failed.', }, ], } ], }]