2014-03-10 05:18:05 +00:00
|
|
|
import re
|
|
|
|
import time
|
|
|
|
from hashlib import sha1
|
|
|
|
from base64 import b16encode, b32decode
|
|
|
|
|
|
|
|
import sickbeard
|
|
|
|
from sickbeard import logger
|
|
|
|
from sickbeard.exceptions import ex
|
|
|
|
from sickbeard.clients import http_error_code
|
|
|
|
from lib.bencode import bencode, bdecode
|
|
|
|
from lib import requests
|
2015-05-06 17:25:12 +00:00
|
|
|
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
class GenericClient(object):
|
|
|
|
def __init__(self, name, host=None, username=None, password=None):
|
|
|
|
|
|
|
|
self.name = name
|
|
|
|
self.username = sickbeard.TORRENT_USERNAME if username is None else username
|
|
|
|
self.password = sickbeard.TORRENT_PASSWORD if password is None else password
|
|
|
|
self.host = sickbeard.TORRENT_HOST if host is None else host
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
self.url = None
|
|
|
|
self.auth = None
|
|
|
|
self.last_time = time.time()
|
|
|
|
self.session = requests.session()
|
|
|
|
self.session.auth = (self.username, self.password)
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
def _request(self, method='get', params=None, data=None, files=None, **kwargs):
|
|
|
|
|
|
|
|
params = params or {}
|
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if time.time() > self.last_time + 1800 or not self.auth:
|
|
|
|
self.last_time = time.time()
|
|
|
|
self._get_auth()
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2016-09-05 03:59:13 +00:00
|
|
|
logger.log('%s: sending %s request to %s with ...' % (self.name, method.upper(), self.url), logger.DEBUG)
|
|
|
|
lines = [('params', (str(params), '')[not params]),
|
|
|
|
('data', (str(data), '')[not data]),
|
|
|
|
('files', (str(files), '')[not files]),
|
|
|
|
('json', (str(kwargs.get('json')), '')[not kwargs.get('json')])]
|
|
|
|
m, c = 300, 100
|
|
|
|
type_chunks = [(linetype, [ln[i:i + c] for i in range(0, min(len(ln), m), c)]) for linetype, ln in lines if ln]
|
|
|
|
for (arg, chunks) in type_chunks:
|
|
|
|
output = []
|
|
|
|
nch = len(chunks) - 1
|
|
|
|
for i, seg in enumerate(chunks):
|
|
|
|
if nch == i and 'files' == arg:
|
|
|
|
sample = ' ..excerpt(%s/%s)' % (m, len(lines[2][1]))
|
|
|
|
seg = seg[0:c - (len(sample) - 2)] + sample
|
|
|
|
output += ['%s: request %s= %s%s%s' % (self.name, arg, ('', '..')[bool(i)], seg, ('', '..')[i != nch])]
|
|
|
|
for out in output:
|
|
|
|
logger.log(out, logger.DEBUG)
|
2014-04-26 07:09:00 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if not self.auth:
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Authentication Failed' % self.name, logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
|
|
|
try:
|
2015-05-06 17:25:12 +00:00
|
|
|
response = self.session.__getattribute__(method)(self.url, params=params, data=data, files=files,
|
2016-03-09 11:16:02 +00:00
|
|
|
timeout=120, verify=False, **kwargs)
|
2015-06-08 12:47:01 +00:00
|
|
|
except requests.exceptions.ConnectionError as e:
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Unable to connect %s' % (self.name, ex(e)), logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
|
|
|
except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL):
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Invalid Host' % self.name, logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
2015-06-08 12:47:01 +00:00
|
|
|
except requests.exceptions.HTTPError as e:
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Invalid HTTP Request %s' % (self.name, ex(e)), logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
2015-06-08 12:47:01 +00:00
|
|
|
except requests.exceptions.Timeout as e:
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Connection Timeout %s' % (self.name, ex(e)), logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
2015-06-08 12:47:01 +00:00
|
|
|
except Exception as e:
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Unknown exception raised when sending torrent to %s: %s' % (self.name, self.name, ex(e)),
|
2014-03-25 05:57:24 +00:00
|
|
|
logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
if 401 == response.status_code:
|
|
|
|
logger.log('%s: Invalid Username or Password, check your config' % self.name, logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2015-05-06 17:25:12 +00:00
|
|
|
if response.status_code in http_error_code.keys():
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: %s' % (self.name, http_error_code[response.status_code]), logger.DEBUG)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Response to %s request is %s' % (self.name, method.upper(), response.text), logger.DEBUG)
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2015-05-06 17:25:12 +00:00
|
|
|
return response
|
2014-03-10 05:18:05 +00:00
|
|
|
|
|
|
|
def _get_auth(self):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-10 05:18:05 +00:00
|
|
|
This should be overridden and should return the auth_id needed for the client
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-10 05:18:05 +00:00
|
|
|
return None
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
def _add_torrent_uri(self, result):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-04-26 07:09:00 +00:00
|
|
|
This should be overridden should return the True/False from the client
|
2014-03-10 05:18:05 +00:00
|
|
|
when a torrent is added via url (magnet or .torrent link)
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-25 05:57:24 +00:00
|
|
|
return False
|
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
def _add_torrent_file(self, result):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-04-26 07:09:00 +00:00
|
|
|
This should be overridden should return the True/False from the client
|
2014-03-10 05:18:05 +00:00
|
|
|
when a torrent is added via result.content (only .torrent file)
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-25 05:57:24 +00:00
|
|
|
return False
|
2014-03-10 05:18:05 +00:00
|
|
|
|
|
|
|
def _set_torrent_label(self, result):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-04-26 07:09:00 +00:00
|
|
|
This should be overridden should return the True/False from the client
|
2014-03-10 05:18:05 +00:00
|
|
|
when a torrent is set with label
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-10 05:18:05 +00:00
|
|
|
return True
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
def _set_torrent_ratio(self, result):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-04-26 07:09:00 +00:00
|
|
|
This should be overridden should return the True/False from the client
|
2014-03-10 05:18:05 +00:00
|
|
|
when a torrent is set with ratio
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-10 05:18:05 +00:00
|
|
|
return True
|
|
|
|
|
2014-04-28 14:34:27 +00:00
|
|
|
def _set_torrent_seed_time(self, result):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-04-28 14:34:27 +00:00
|
|
|
This should be overridden should return the True/False from the client
|
|
|
|
when a torrent is set with a seed time
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-04-28 14:34:27 +00:00
|
|
|
return True
|
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
def _set_torrent_priority(self, result):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-10 05:18:05 +00:00
|
|
|
This should be overriden should return the True/False from the client
|
|
|
|
when a torrent is set with result.priority (-1 = low, 0 = normal, 1 = high)
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-10 05:18:05 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
def _set_torrent_path(self, torrent_path):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-04-26 07:09:00 +00:00
|
|
|
This should be overridden should return the True/False from the client
|
2014-03-10 05:18:05 +00:00
|
|
|
when a torrent is set with path
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-10 05:18:05 +00:00
|
|
|
return True
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
def _set_torrent_pause(self, result):
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-04-26 07:09:00 +00:00
|
|
|
This should be overridden should return the True/False from the client
|
2014-03-10 05:18:05 +00:00
|
|
|
when a torrent is set with pause
|
2016-03-09 11:16:02 +00:00
|
|
|
"""
|
2014-03-10 05:18:05 +00:00
|
|
|
return True
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
@staticmethod
|
|
|
|
def _get_torrent_hash(result):
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if result.url.startswith('magnet'):
|
2014-08-24 18:46:26 +00:00
|
|
|
result.hash = re.findall('urn:btih:([\w]{32,40})', result.url)[0]
|
2016-03-09 11:16:02 +00:00
|
|
|
if 32 == len(result.hash):
|
2014-08-25 03:32:41 +00:00
|
|
|
result.hash = b16encode(b32decode(result.hash)).lower()
|
2014-07-28 19:19:41 +00:00
|
|
|
else:
|
2015-05-06 17:25:12 +00:00
|
|
|
info = bdecode(result.content)['info']
|
2014-08-24 18:46:26 +00:00
|
|
|
result.hash = sha1(bencode(info)).hexdigest()
|
2014-03-10 05:18:05 +00:00
|
|
|
|
2014-08-24 18:46:26 +00:00
|
|
|
return result
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
def send_torrent(self, result):
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
r_code = False
|
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('Calling %s Client' % self.name, logger.DEBUG)
|
2014-03-10 05:18:05 +00:00
|
|
|
|
|
|
|
if not self._get_auth():
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Authentication Failed' % self.name, logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return r_code
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
try:
|
2014-07-28 19:19:41 +00:00
|
|
|
# Sets per provider seed ratio
|
2015-07-13 09:39:20 +00:00
|
|
|
result.ratio = result.provider.seed_ratio()
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-08-25 03:49:00 +00:00
|
|
|
result = self._get_torrent_hash(result)
|
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if result.url.startswith('magnet'):
|
|
|
|
r_code = self._add_torrent_uri(result)
|
|
|
|
else:
|
|
|
|
r_code = self._add_torrent_file(result)
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if not r_code:
|
2016-11-01 18:13:51 +00:00
|
|
|
logger.log('%s: Unable to send Torrent: Return code undefined (already exists in client?)' % self.name, logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
return False
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if not self._set_torrent_pause(result):
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Unable to set the pause for Torrent' % self.name, logger.ERROR)
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if not self._set_torrent_label(result):
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Unable to set the label for Torrent' % self.name, logger.ERROR)
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if not self._set_torrent_ratio(result):
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Unable to set the ratio for Torrent' % self.name, logger.ERROR)
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-04-28 14:34:27 +00:00
|
|
|
if not self._set_torrent_seed_time(result):
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Unable to set the seed time for Torrent' % self.name, logger.ERROR)
|
2014-04-28 14:34:27 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
if not self._set_torrent_path(result):
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Unable to set the path for Torrent' % self.name, logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
if 0 != result.priority and not self._set_torrent_priority(result):
|
|
|
|
logger.log('%s: Unable to set priority for Torrent' % self.name, logger.ERROR)
|
2014-03-10 05:18:05 +00:00
|
|
|
|
2015-06-08 12:47:01 +00:00
|
|
|
except Exception as e:
|
2016-03-09 11:16:02 +00:00
|
|
|
logger.log('%s: Failed sending torrent: %s - %s' % (self.name, result.name, result.hash), logger.ERROR)
|
|
|
|
logger.log('%s: Exception raised when sending torrent: %s' % (self.name, ex(e)), logger.DEBUG)
|
2014-03-10 05:18:05 +00:00
|
|
|
return r_code
|
2014-03-25 05:57:24 +00:00
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
return r_code
|
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
def test_authentication(self):
|
|
|
|
|
2014-03-10 05:18:05 +00:00
|
|
|
try:
|
2015-05-06 17:25:12 +00:00
|
|
|
response = self.session.get(self.url, timeout=120, verify=False)
|
|
|
|
|
2016-03-09 11:16:02 +00:00
|
|
|
if 401 == response.status_code:
|
|
|
|
return False, 'Error: Invalid %s Username or Password, check your config!' % self.name
|
2015-05-06 17:25:12 +00:00
|
|
|
except requests.exceptions.ConnectionError:
|
2016-03-09 11:16:02 +00:00
|
|
|
return False, 'Error: %s Connection Error' % self.name
|
2014-03-10 05:18:05 +00:00
|
|
|
except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL):
|
2016-03-09 11:16:02 +00:00
|
|
|
return False, 'Error: Invalid %s host' % self.name
|
2014-03-25 05:57:24 +00:00
|
|
|
|
|
|
|
try:
|
2015-05-06 17:25:12 +00:00
|
|
|
authenticated = self._get_auth()
|
|
|
|
# FIXME: This test is redundant
|
|
|
|
if authenticated and self.auth:
|
2014-03-25 05:57:24 +00:00
|
|
|
return True, 'Success: Connected and Authenticated'
|
2016-11-13 00:39:23 +00:00
|
|
|
return False, 'Error: Unable to get %s authentication, check your config!' % self.name
|
2016-09-05 03:59:13 +00:00
|
|
|
except (StandardError, Exception):
|
2016-03-09 11:16:02 +00:00
|
|
|
return False, 'Error: Unable to connect to %s' % self.name
|