diff --git a/couchpotato/core/downloaders/nzbvortex/__init__.py b/couchpotato/core/downloaders/nzbvortex/__init__.py new file mode 100644 index 00000000..d79983f4 --- /dev/null +++ b/couchpotato/core/downloaders/nzbvortex/__init__.py @@ -0,0 +1,40 @@ +from .main import NZBVortex + +def start(): + return NZBVortex() + +config = [{ + 'name': 'nzbvortex', + 'groups': [ + { + 'tab': 'downloaders', + 'name': 'nzbvortex', + 'label': 'NZBVortex', + 'description': 'Send NZBs to your NZBVortex app.', + 'wizard': True, + 'options': [ + { + 'name': 'enabled', + 'default': 0, + 'type': 'enabler', + 'radio_group': 'nzb', + }, + { + 'name': 'host', + 'default': 'https://localhost:4321', + }, + { + 'name': 'api_key', + 'label': 'Api Key', + }, + { + 'name': 'manual', + 'default': False, + 'type': 'bool', + 'advanced': True, + 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', + }, + ], + } + ], +}] diff --git a/couchpotato/core/downloaders/nzbvortex/main.py b/couchpotato/core/downloaders/nzbvortex/main.py new file mode 100644 index 00000000..d9078f8d --- /dev/null +++ b/couchpotato/core/downloaders/nzbvortex/main.py @@ -0,0 +1,161 @@ +from base64 import b64encode +from couchpotato.core.downloaders.base import Downloader +from couchpotato.core.helpers.encoding import tryUrlencode, ss +from couchpotato.core.helpers.variable import cleanHost +from couchpotato.core.logger import CPLog +from urllib2 import URLError +from uuid import uuid4 +import hashlib +import httplib +import json +import socket +import ssl +import sys +import traceback +import urllib2 + +log = CPLog(__name__) + +class NZBVortex(Downloader): + + type = ['nzb'] + api_level = None + session_id = None + + def download(self, data = {}, movie = {}, manual = False, filedata = None): + + if self.isDisabled(manual) or not self.isCorrectType(data.get('type')) or not self.getApiLevel(): + return + + # Send the nzb + try: + nzb_filename = self.createFileName(data, filedata, movie) + self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True) + + return True + except: + log.error('Something went wrong sending the NZB file: %s', traceback.format_exc()) + return False + + def getAllDownloadStatus(self): + + if self.isDisabled(manual = False): + return False + + raw_statuses = self.call('nzb') + + statuses = [] + for item in raw_statuses.get('nzbs', []): + + # Check status + status = 'busy' + if item['state'] == 20: + status = 'completed' + elif item['state'] in [21, 22, 24]: + status = 'failed' + + statuses.append({ + 'id': item['id'], + 'name': item['uiTitle'], + 'status': status, + 'original_status': item['state'], + 'timeleft':-1, + }) + + return statuses + + 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 = {}, repeat = False, auth = True, *args, **kwargs): + + # Login first + 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 + url_opener = urllib2.build_opener(HTTPSHandler()) + + try: + data = self.urlopen('%s?%s' % (url, params), opener = url_opener, *args, **kwargs) + + if data: + return json.loads(data) + except URLError, e: + if hasattr(e, 'code') and e.code == 403: + # Try login and do again + if not repeat: + self.login() + return self.call(call, parameters = parameters, repeat = True, *args, **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: + + url = cleanHost(self.conf('host')) + 'api/app/apilevel' + url_opener = urllib2.build_opener(HTTPSHandler()) + + try: + data = self.urlopen(url, opener = url_opener, show_error = False) + self.api_level = float(json.loads(data).get('apilevel')) + except URLError, e: + if hasattr(e, 'code') and e.code == 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 + + +class HTTPSConnection(httplib.HTTPSConnection): + def __init__(self, *args, **kwargs): + httplib.HTTPSConnection.__init__(self, *args, **kwargs) + + def connect(self): + sock = socket.create_connection((self.host, self.port), self.timeout) + if sys.version_info < (2, 6, 7): + if hasattr(self, '_tunnel_host'): + self.sock = sock + self._tunnel() + else: + if self._tunnel_host: + self.sock = sock + self._tunnel() + + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version = ssl.PROTOCOL_TLSv1) + +class HTTPSHandler(urllib2.HTTPSHandler): + def https_open(self, req): + return self.do_open(HTTPSConnection, req)