Merge branch 'refs/heads/develop' into tv
Conflicts: couchpotato/core/media/movie/_base/main.py couchpotato/core/providers/torrent/bitsoup/main.py couchpotato/core/providers/torrent/iptorrents/main.py couchpotato/core/providers/torrent/sceneaccess/main.py couchpotato/core/providers/torrent/torrentleech/main.py couchpotato/core/providers/torrent/torrentshack/main.py
This commit is contained in:
@@ -55,6 +55,10 @@ class Core(Plugin):
|
||||
if not Env.get('desktop'):
|
||||
self.signalHandler()
|
||||
|
||||
# Set default urlopen timeout
|
||||
import socket
|
||||
socket.setdefaulttimeout(30)
|
||||
|
||||
def md5Password(self, value):
|
||||
return md5(value) if value else ''
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class Downloader(Provider):
|
||||
|
||||
protocol = []
|
||||
http_time_between_calls = 0
|
||||
status_support = True
|
||||
|
||||
torrent_sources = [
|
||||
'http://torrage.com/torrent/%s.torrent',
|
||||
@@ -69,7 +70,7 @@ class Downloader(Provider):
|
||||
return
|
||||
|
||||
def getAllDownloadStatus(self, ids):
|
||||
return
|
||||
return []
|
||||
|
||||
def _removeFailed(self, release_download):
|
||||
if self.isDisabled(manual = True, data = {}):
|
||||
@@ -133,6 +134,7 @@ class Downloader(Provider):
|
||||
def downloadReturnId(self, download_id):
|
||||
return {
|
||||
'downloader': self.getName(),
|
||||
'status_support': self.status_support,
|
||||
'id': download_id
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ log = CPLog(__name__)
|
||||
class Blackhole(Downloader):
|
||||
|
||||
protocol = ['nzb', 'torrent', 'torrent_magnet']
|
||||
status_support = False
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
|
||||
@@ -91,51 +91,50 @@ class Deluge(Downloader):
|
||||
log.debug('Checking Deluge download status.')
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
return []
|
||||
|
||||
release_downloads = ReleaseDownloadList(self)
|
||||
|
||||
queue = self.drpc.get_alltorrents()
|
||||
queue = self.drpc.get_alltorrents(ids)
|
||||
|
||||
if not queue:
|
||||
log.debug('Nothing in queue or error')
|
||||
return False
|
||||
return []
|
||||
|
||||
for torrent_id in queue:
|
||||
torrent = queue[torrent_id]
|
||||
if torrent['hash'] in ids:
|
||||
log.debug('name=%s / id=%s / save_path=%s / move_completed_path=%s / hash=%s / progress=%s / state=%s / eta=%s / ratio=%s / stop_ratio=%s / is_seed=%s / is_finished=%s / paused=%s', (torrent['name'], torrent['hash'], torrent['save_path'], torrent['move_completed_path'], torrent['hash'], torrent['progress'], torrent['state'], torrent['eta'], torrent['ratio'], torrent['stop_ratio'], torrent['is_seed'], torrent['is_finished'], torrent['paused']))
|
||||
log.debug('name=%s / id=%s / save_path=%s / move_completed_path=%s / hash=%s / progress=%s / state=%s / eta=%s / ratio=%s / stop_ratio=%s / is_seed=%s / is_finished=%s / paused=%s', (torrent['name'], torrent['hash'], torrent['save_path'], torrent['move_completed_path'], torrent['hash'], torrent['progress'], torrent['state'], torrent['eta'], torrent['ratio'], torrent['stop_ratio'], torrent['is_seed'], torrent['is_finished'], torrent['paused']))
|
||||
|
||||
# Deluge has no easy way to work out if a torrent is stalled or failing.
|
||||
#status = 'failed'
|
||||
status = 'busy'
|
||||
if torrent['is_seed'] and tryFloat(torrent['ratio']) < tryFloat(torrent['stop_ratio']):
|
||||
# We have torrent['seeding_time'] to work out what the seeding time is, but we do not
|
||||
# have access to the downloader seed_time, as with deluge we have no way to pass it
|
||||
# when the torrent is added. So Deluge will only look at the ratio.
|
||||
# See above comment in download().
|
||||
status = 'seeding'
|
||||
elif torrent['is_seed'] and torrent['is_finished'] and torrent['paused'] and torrent['state'] == 'Paused':
|
||||
status = 'completed'
|
||||
# Deluge has no easy way to work out if a torrent is stalled or failing.
|
||||
#status = 'failed'
|
||||
status = 'busy'
|
||||
if torrent['is_seed'] and tryFloat(torrent['ratio']) < tryFloat(torrent['stop_ratio']):
|
||||
# We have torrent['seeding_time'] to work out what the seeding time is, but we do not
|
||||
# have access to the downloader seed_time, as with deluge we have no way to pass it
|
||||
# when the torrent is added. So Deluge will only look at the ratio.
|
||||
# See above comment in download().
|
||||
status = 'seeding'
|
||||
elif torrent['is_seed'] and torrent['is_finished'] and torrent['paused'] and torrent['state'] == 'Paused':
|
||||
status = 'completed'
|
||||
|
||||
download_dir = sp(torrent['save_path'])
|
||||
if torrent['move_on_completed']:
|
||||
download_dir = torrent['move_completed_path']
|
||||
|
||||
download_dir = sp(torrent['save_path'])
|
||||
if torrent['move_on_completed']:
|
||||
download_dir = torrent['move_completed_path']
|
||||
torrent_files = []
|
||||
for file_item in torrent['files']:
|
||||
torrent_files.append(sp(os.path.join(download_dir, file_item['path'])))
|
||||
|
||||
torrent_files = []
|
||||
for file_item in torrent['files']:
|
||||
torrent_files.append(sp(os.path.join(download_dir, file_item['path'])))
|
||||
|
||||
release_downloads.append({
|
||||
'id': torrent['hash'],
|
||||
'name': torrent['name'],
|
||||
'status': status,
|
||||
'original_status': torrent['state'],
|
||||
'seed_ratio': torrent['ratio'],
|
||||
'timeleft': str(timedelta(seconds = torrent['eta'])),
|
||||
'folder': sp(download_dir if len(torrent_files) == 1 else os.path.join(download_dir, torrent['name'])),
|
||||
'files': '|'.join(torrent_files),
|
||||
})
|
||||
release_downloads.append({
|
||||
'id': torrent['hash'],
|
||||
'name': torrent['name'],
|
||||
'status': status,
|
||||
'original_status': torrent['state'],
|
||||
'seed_ratio': torrent['ratio'],
|
||||
'timeleft': str(timedelta(seconds = torrent['eta'])),
|
||||
'folder': sp(download_dir if len(torrent_files) == 1 else os.path.join(download_dir, torrent['name'])),
|
||||
'files': '|'.join(torrent_files),
|
||||
})
|
||||
|
||||
return release_downloads
|
||||
|
||||
@@ -209,11 +208,11 @@ class DelugeRPC(object):
|
||||
|
||||
return torrent_id
|
||||
|
||||
def get_alltorrents(self):
|
||||
def get_alltorrents(self, ids):
|
||||
ret = False
|
||||
try:
|
||||
self.connect()
|
||||
ret = self.client.core.get_torrents_status({}, {}).get()
|
||||
ret = self.client.core.get_torrents_status({'id': ids}, {}).get()
|
||||
except Exception, err:
|
||||
log.error('Failed to get all torrents: %s %s', (err, traceback.format_exc()))
|
||||
finally:
|
||||
|
||||
@@ -25,6 +25,13 @@ config = [{
|
||||
'default': 'localhost:6789',
|
||||
'description': 'Hostname with port. Usually <strong>localhost:6789</strong>',
|
||||
},
|
||||
{
|
||||
'name': 'ssl',
|
||||
'default': 0,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'description': 'Use HyperText Transfer Protocol Secure, or <strong>https</strong>',
|
||||
},
|
||||
{
|
||||
'name': 'username',
|
||||
'default': 'nzbget',
|
||||
|
||||
@@ -17,7 +17,7 @@ class NZBGet(Downloader):
|
||||
|
||||
protocol = ['nzb']
|
||||
|
||||
url = 'http://%(username)s:%(password)s@%(host)s/xmlrpc'
|
||||
url = '%(protocol)s://%(username)s:%(password)s@%(host)s/xmlrpc'
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
@@ -29,7 +29,7 @@ class NZBGet(Downloader):
|
||||
|
||||
log.info('Sending "%s" to NZBGet.', data.get('name'))
|
||||
|
||||
url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
|
||||
url = self.url % {'protocol': 'https' if self.conf('ssl') else 'http', 'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
|
||||
nzb_name = ss('%s.nzb' % self.createNzbName(data, media))
|
||||
|
||||
rpc = xmlrpclib.ServerProxy(url)
|
||||
@@ -71,7 +71,7 @@ class NZBGet(Downloader):
|
||||
|
||||
log.debug('Checking NZBGet download status.')
|
||||
|
||||
url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
|
||||
url = self.url % {'protocol': 'https' if self.conf('ssl') else 'http', 'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
|
||||
|
||||
rpc = xmlrpclib.ServerProxy(url)
|
||||
try:
|
||||
@@ -81,13 +81,13 @@ class NZBGet(Downloader):
|
||||
log.info('Successfully connected to NZBGet, but unable to send a message')
|
||||
except socket.error:
|
||||
log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.')
|
||||
return False
|
||||
return []
|
||||
except xmlrpclib.ProtocolError, e:
|
||||
if e.errcode == 401:
|
||||
log.error('Password is incorrect.')
|
||||
else:
|
||||
log.error('Protocol Error: %s', e)
|
||||
return False
|
||||
return []
|
||||
|
||||
# Get NZBGet data
|
||||
try:
|
||||
@@ -97,7 +97,7 @@ class NZBGet(Downloader):
|
||||
history = rpc.history()
|
||||
except:
|
||||
log.error('Failed getting data: %s', traceback.format_exc(1))
|
||||
return False
|
||||
return []
|
||||
|
||||
release_downloads = ReleaseDownloadList(self)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class NZBVortex(Downloader):
|
||||
# Send the nzb
|
||||
try:
|
||||
nzb_filename = self.createFileName(data, filedata, media)
|
||||
self.call('nzb/add', params = {'file': (nzb_filename, filedata)}, multipart = True)
|
||||
self.call('nzb/add', files = {'file': (nzb_filename, filedata)})
|
||||
|
||||
time.sleep(10)
|
||||
raw_statuses = self.call('nzb')
|
||||
@@ -117,10 +117,9 @@ class NZBVortex(Downloader):
|
||||
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)
|
||||
data = self.urlopen('%s?%s' % (url, params), *args, **kwargs)
|
||||
|
||||
if data:
|
||||
return json.loads(data)
|
||||
@@ -142,10 +141,9 @@ class NZBVortex(Downloader):
|
||||
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)
|
||||
data = self.urlopen(url, show_error = False)
|
||||
self.api_level = float(json.loads(data).get('apilevel'))
|
||||
except URLError, e:
|
||||
if hasattr(e, 'code') and e.code == 403:
|
||||
|
||||
@@ -11,6 +11,7 @@ class Pneumatic(Downloader):
|
||||
|
||||
protocol = ['nzb']
|
||||
strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s'
|
||||
status_support = False
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
|
||||
@@ -147,7 +147,7 @@ class rTorrent(Downloader):
|
||||
log.debug('Checking rTorrent download status.')
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
return []
|
||||
|
||||
try:
|
||||
torrents = self.rt.get_torrents()
|
||||
@@ -182,7 +182,7 @@ class rTorrent(Downloader):
|
||||
|
||||
except Exception, err:
|
||||
log.error('Failed to get status from rTorrent: %s', err)
|
||||
return False
|
||||
return []
|
||||
|
||||
def pause(self, release_download, pause = True):
|
||||
if not self.connect():
|
||||
|
||||
@@ -43,7 +43,7 @@ class Sabnzbd(Downloader):
|
||||
|
||||
try:
|
||||
if nzb_filename and req_params.get('mode') is 'addfile':
|
||||
sab_data = self.call(req_params, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True)
|
||||
sab_data = self.call(req_params, files = {'nzbfile': (ss(nzb_filename), filedata)})
|
||||
else:
|
||||
sab_data = self.call(req_params)
|
||||
except URLError:
|
||||
@@ -75,7 +75,7 @@ class Sabnzbd(Downloader):
|
||||
})
|
||||
except:
|
||||
log.error('Failed getting queue: %s', traceback.format_exc(1))
|
||||
return False
|
||||
return []
|
||||
|
||||
# Go through history items
|
||||
try:
|
||||
@@ -85,7 +85,7 @@ class Sabnzbd(Downloader):
|
||||
})
|
||||
except:
|
||||
log.error('Failed getting history json: %s', traceback.format_exc(1))
|
||||
return False
|
||||
return []
|
||||
|
||||
release_downloads = ReleaseDownloadList(self)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ log = CPLog(__name__)
|
||||
class Synology(Downloader):
|
||||
|
||||
protocol = ['nzb', 'torrent', 'torrent_magnet']
|
||||
log = CPLog(__name__)
|
||||
status_support = False
|
||||
|
||||
def download(self, data = None, media = None, filedata = None):
|
||||
if not media: media = {}
|
||||
|
||||
@@ -88,7 +88,7 @@ class Transmission(Downloader):
|
||||
log.debug('Checking Transmission download status.')
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
return []
|
||||
|
||||
release_downloads = ReleaseDownloadList(self)
|
||||
|
||||
@@ -99,7 +99,7 @@ class Transmission(Downloader):
|
||||
queue = self.trpc.get_alltorrents(return_params)
|
||||
if not (queue and queue.get('torrents')):
|
||||
log.debug('Nothing in queue or error')
|
||||
return False
|
||||
return []
|
||||
|
||||
for torrent in queue['torrents']:
|
||||
if torrent['hashString'] in ids:
|
||||
|
||||
@@ -24,6 +24,16 @@ class uTorrent(Downloader):
|
||||
|
||||
protocol = ['torrent', 'torrent_magnet']
|
||||
utorrent_api = None
|
||||
status_flags = {
|
||||
'STARTED' : 1,
|
||||
'CHECKING' : 2,
|
||||
'CHECK-START' : 4,
|
||||
'CHECKED' : 8,
|
||||
'ERROR' : 16,
|
||||
'PAUSED' : 32,
|
||||
'QUEUED' : 64,
|
||||
'LOADED' : 128
|
||||
}
|
||||
|
||||
def connect(self):
|
||||
# Load host from config and split out port.
|
||||
@@ -40,7 +50,7 @@ class uTorrent(Downloader):
|
||||
if not media: media = {}
|
||||
if not data: data = {}
|
||||
|
||||
log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('protocol')))
|
||||
log.debug("Sending '%s' (%s) to uTorrent.", (data.get('name'), data.get('protocol')))
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
@@ -75,7 +85,7 @@ class uTorrent(Downloader):
|
||||
torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
|
||||
torrent_params['trackers'] = '%0D%0A%0D%0A'.join(self.torrent_trackers)
|
||||
else:
|
||||
info = bdecode(filedata)["info"]
|
||||
info = bdecode(filedata)['info']
|
||||
torrent_hash = sha1(benc(info)).hexdigest().upper()
|
||||
|
||||
torrent_filename = self.createFileName(data, filedata, media)
|
||||
@@ -110,23 +120,23 @@ class uTorrent(Downloader):
|
||||
log.debug('Checking uTorrent download status.')
|
||||
|
||||
if not self.connect():
|
||||
return False
|
||||
return []
|
||||
|
||||
release_downloads = ReleaseDownloadList(self)
|
||||
|
||||
data = self.utorrent_api.get_status()
|
||||
if not data:
|
||||
log.error('Error getting data from uTorrent')
|
||||
return False
|
||||
return []
|
||||
|
||||
queue = json.loads(data)
|
||||
if queue.get('error'):
|
||||
log.error('Error getting data from uTorrent: %s', queue.get('error'))
|
||||
return False
|
||||
return []
|
||||
|
||||
if not queue.get('torrents'):
|
||||
log.debug('Nothing in queue')
|
||||
return False
|
||||
return []
|
||||
|
||||
# Get torrents
|
||||
for torrent in queue['torrents']:
|
||||
@@ -140,21 +150,10 @@ class uTorrent(Downloader):
|
||||
except:
|
||||
log.debug('Failed getting files from torrent: %s', torrent[2])
|
||||
|
||||
status_flags = {
|
||||
"STARTED" : 1,
|
||||
"CHECKING" : 2,
|
||||
"CHECK-START" : 4,
|
||||
"CHECKED" : 8,
|
||||
"ERROR" : 16,
|
||||
"PAUSED" : 32,
|
||||
"QUEUED" : 64,
|
||||
"LOADED" : 128
|
||||
}
|
||||
|
||||
status = 'busy'
|
||||
if (torrent[1] & status_flags["STARTED"] or torrent[1] & status_flags["QUEUED"]) and torrent[4] == 1000:
|
||||
if (torrent[1] & self.status_flags['STARTED'] or torrent[1] & self.status_flags['QUEUED']) and torrent[4] == 1000:
|
||||
status = 'seeding'
|
||||
elif (torrent[1] & status_flags["ERROR"]):
|
||||
elif (torrent[1] & self.status_flags['ERROR']):
|
||||
status = 'failed'
|
||||
elif torrent[4] == 1000:
|
||||
status = 'completed'
|
||||
@@ -224,7 +223,7 @@ class uTorrentAPI(object):
|
||||
if time.time() > self.last_time + 1800:
|
||||
self.last_time = time.time()
|
||||
self.token = self.get_token()
|
||||
request = urllib2.Request(self.url + "?token=" + self.token + "&" + action, data)
|
||||
request = urllib2.Request(self.url + '?token=' + self.token + '&' + action, data)
|
||||
try:
|
||||
open_request = self.opener.open(request)
|
||||
response = open_request.read()
|
||||
@@ -244,52 +243,52 @@ class uTorrentAPI(object):
|
||||
return False
|
||||
|
||||
def get_token(self):
|
||||
request = self.opener.open(self.url + "token.html")
|
||||
token = re.findall("<div.*?>(.*?)</", request.read())[0]
|
||||
request = self.opener.open(self.url + 'token.html')
|
||||
token = re.findall('<div.*?>(.*?)</', request.read())[0]
|
||||
return token
|
||||
|
||||
def add_torrent_uri(self, filename, torrent, add_folder = False):
|
||||
action = "action=add-url&s=%s" % urllib.quote(torrent)
|
||||
action = 'action=add-url&s=%s' % urllib.quote(torrent)
|
||||
if add_folder:
|
||||
action += "&path=%s" % urllib.quote(filename)
|
||||
action += '&path=%s' % urllib.quote(filename)
|
||||
return self._request(action)
|
||||
|
||||
def add_torrent_file(self, filename, filedata, add_folder = False):
|
||||
action = "action=add-file"
|
||||
action = 'action=add-file'
|
||||
if add_folder:
|
||||
action += "&path=%s" % urllib.quote(filename)
|
||||
return self._request(action, {"torrent_file": (ss(filename), filedata)})
|
||||
action += '&path=%s' % urllib.quote(filename)
|
||||
return self._request(action, {'torrent_file': (ss(filename), filedata)})
|
||||
|
||||
def set_torrent(self, hash, params):
|
||||
action = "action=setprops&hash=%s" % hash
|
||||
action = 'action=setprops&hash=%s' % hash
|
||||
for k, v in params.iteritems():
|
||||
action += "&s=%s&v=%s" % (k, v)
|
||||
action += '&s=%s&v=%s' % (k, v)
|
||||
return self._request(action)
|
||||
|
||||
def pause_torrent(self, hash, pause = True):
|
||||
if pause:
|
||||
action = "action=pause&hash=%s" % hash
|
||||
action = 'action=pause&hash=%s' % hash
|
||||
else:
|
||||
action = "action=unpause&hash=%s" % hash
|
||||
action = 'action=unpause&hash=%s' % hash
|
||||
return self._request(action)
|
||||
|
||||
def stop_torrent(self, hash):
|
||||
action = "action=stop&hash=%s" % hash
|
||||
action = 'action=stop&hash=%s' % hash
|
||||
return self._request(action)
|
||||
|
||||
def remove_torrent(self, hash, remove_data = False):
|
||||
if remove_data:
|
||||
action = "action=removedata&hash=%s" % hash
|
||||
action = 'action=removedata&hash=%s' % hash
|
||||
else:
|
||||
action = "action=remove&hash=%s" % hash
|
||||
action = 'action=remove&hash=%s' % hash
|
||||
return self._request(action)
|
||||
|
||||
def get_status(self):
|
||||
action = "list=1"
|
||||
action = 'list=1'
|
||||
return self._request(action)
|
||||
|
||||
def get_settings(self):
|
||||
action = "action=getsettings"
|
||||
action = 'action=getsettings'
|
||||
settings_dict = {}
|
||||
try:
|
||||
utorrent_settings = json.loads(self._request(action))
|
||||
@@ -321,5 +320,5 @@ class uTorrentAPI(object):
|
||||
return self._request(action)
|
||||
|
||||
def get_files(self, hash):
|
||||
action = "action=getfiles&hash=%s" % hash
|
||||
action = 'action=getfiles&hash=%s' % hash
|
||||
return self._request(action)
|
||||
|
||||
@@ -60,9 +60,17 @@ def sp(path, *args):
|
||||
|
||||
path = os.path.normcase(os.path.normpath(ss(path, *args)))
|
||||
|
||||
# Remove any trailing path separators
|
||||
if path != os.path.sep:
|
||||
path = path.rstrip(os.path.sep)
|
||||
|
||||
# Add a trailing separator in case it is a root folder on windows (crashes guessit)
|
||||
if len(path) == 2 and path[1] == ':':
|
||||
path = path + os.path.sep
|
||||
|
||||
# Replace *NIX ambiguous '//' at the beginning of a path with '/' (crashes guessit)
|
||||
path = re.sub('^//', '/', path)
|
||||
|
||||
return path
|
||||
|
||||
def ek(original, *args):
|
||||
|
||||
@@ -2,7 +2,7 @@ from couchpotato.core.helpers.encoding import simplifyString, toSafeString, ss
|
||||
from couchpotato.core.logger import CPLog
|
||||
import collections
|
||||
import hashlib
|
||||
import os.path
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import re
|
||||
@@ -219,3 +219,7 @@ def splitString(str, split_on = ',', clean = True):
|
||||
|
||||
def dictIsSubset(a, b):
|
||||
return all([k in b and b[k] == v for k, v in a.items()])
|
||||
|
||||
def isSubFolder(sub_folder, base_folder):
|
||||
# Returns True is sub_folder is the same as or in base_folder
|
||||
return base_folder.rstrip(os.path.sep) + os.path.sep in sub_folder.rstrip(os.path.sep) + os.path.sep
|
||||
|
||||
@@ -35,6 +35,7 @@ class MovieBase(MovieTypeBase):
|
||||
'params': {
|
||||
'id': {'desc': 'Movie ID(s) you want to edit.', 'type': 'int (comma separated)'},
|
||||
'profile_id': {'desc': 'ID of quality profile you want the edit the movie to.'},
|
||||
'category_id': {'desc': 'ID of category you want the add the movie in. If empty will use no category.'},
|
||||
'default_title': {'desc': 'Movie title to use for searches. Has to be one of the titles returned by movie.search.'},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -283,6 +283,10 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
||||
return True
|
||||
else:
|
||||
|
||||
# Don't allow movies with years to far in the future
|
||||
if year is not None and year > now_year + 1:
|
||||
return False
|
||||
|
||||
# For movies before 1972
|
||||
if not dates or dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0:
|
||||
return True
|
||||
|
||||
@@ -16,14 +16,14 @@ class Boxcar(Notification):
|
||||
try:
|
||||
message = message.strip()
|
||||
|
||||
params = {
|
||||
data = {
|
||||
'email': self.conf('email'),
|
||||
'notification[from_screen_name]': self.default_title,
|
||||
'notification[message]': toUnicode(message),
|
||||
'notification[from_remote_service_id]': int(time.time()),
|
||||
}
|
||||
|
||||
self.urlopen(self.url, params = params)
|
||||
self.urlopen(self.url, data = data)
|
||||
except:
|
||||
log.error('Check your email and added services on boxcar.io')
|
||||
return False
|
||||
|
||||
@@ -26,7 +26,7 @@ class Prowl(Notification):
|
||||
}
|
||||
|
||||
try:
|
||||
self.urlopen(self.urls['api'], headers = headers, params = data, multipart = True, show_error = False)
|
||||
self.urlopen(self.urls['api'], headers = headers, data = data, show_error = False)
|
||||
log.info('Prowl notifications sent.')
|
||||
return True
|
||||
except:
|
||||
|
||||
@@ -29,7 +29,7 @@ class Pushalot(Notification):
|
||||
}
|
||||
|
||||
try:
|
||||
self.urlopen(self.urls['api'], headers = headers, params = data, multipart = True, show_error = False)
|
||||
self.urlopen(self.urls['api'], headers = headers, data = data, show_error = False)
|
||||
return True
|
||||
except:
|
||||
log.error('PushAlot failed: %s', traceback.format_exc())
|
||||
|
||||
@@ -74,9 +74,9 @@ class Pushbullet(Notification):
|
||||
}
|
||||
|
||||
if cache:
|
||||
return self.getJsonData(self.url % method, headers = headers, params = kwargs)
|
||||
return self.getJsonData(self.url % method, headers = headers, data = kwargs)
|
||||
else:
|
||||
data = self.urlopen(self.url % method, headers = headers, params = kwargs)
|
||||
data = self.urlopen(self.url % method, headers = headers, data = kwargs)
|
||||
return json.loads(data)
|
||||
|
||||
except Exception, ex:
|
||||
|
||||
@@ -35,7 +35,7 @@ class Trakt(Notification):
|
||||
def call(self, method_url, post_data):
|
||||
|
||||
try:
|
||||
response = self.getJsonData(self.urls['base'] % method_url, params = post_data, cache_timeout = 1)
|
||||
response = self.getJsonData(self.urls['base'] % method_url, data = post_data, cache_timeout = 1)
|
||||
if response:
|
||||
if response.get('status') == "success":
|
||||
log.info('Successfully called Trakt')
|
||||
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
import socket
|
||||
import traceback
|
||||
import urllib
|
||||
import requests
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -167,22 +168,18 @@ class XBMC(Notification):
|
||||
# manually fake expected response array
|
||||
return [{'result': 'Error'}]
|
||||
|
||||
except URLError, e:
|
||||
if isinstance(e.reason, socket.timeout):
|
||||
log.info('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
||||
return [{'result': 'Error'}]
|
||||
else:
|
||||
log.error('Failed sending non-JSON-type request to XBMC: %s', traceback.format_exc())
|
||||
return [{'result': 'Error'}]
|
||||
except requests.exceptions.Timeout:
|
||||
log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
||||
return [{'result': 'Error'}]
|
||||
except:
|
||||
log.error('Failed sending non-JSON-type request to XBMC: %s', traceback.format_exc())
|
||||
return [{'result': 'Error'}]
|
||||
|
||||
def request(self, host, requests):
|
||||
def request(self, host, do_requests):
|
||||
server = 'http://%s/jsonrpc' % host
|
||||
|
||||
data = []
|
||||
for req in requests:
|
||||
for req in do_requests:
|
||||
method, kwargs = req
|
||||
data.append({
|
||||
'method': method,
|
||||
@@ -202,17 +199,13 @@ class XBMC(Notification):
|
||||
|
||||
try:
|
||||
log.debug('Sending request to %s: %s', (host, data))
|
||||
response = self.getJsonData(server, headers = headers, params = data, timeout = 3, show_error = False)
|
||||
response = self.getJsonData(server, headers = headers, data = data, timeout = 3, show_error = False)
|
||||
log.debug('Returned from request %s: %s', (host, response))
|
||||
|
||||
return response
|
||||
except URLError, e:
|
||||
if isinstance(e.reason, socket.timeout):
|
||||
log.info('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
||||
return []
|
||||
else:
|
||||
log.error('Failed sending request to XBMC: %s', traceback.format_exc())
|
||||
return []
|
||||
except requests.exceptions.Timeout:
|
||||
log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
||||
return []
|
||||
except:
|
||||
log.error('Failed sending request to XBMC: %s', traceback.format_exc())
|
||||
return []
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
from StringIO import StringIO
|
||||
from couchpotato.core.event import fireEvent, addEvent
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode, ss, toSafeString, \
|
||||
from couchpotato.core.helpers.encoding import ss, toSafeString, \
|
||||
toUnicode, sp
|
||||
from couchpotato.core.helpers.variable import getExt, md5, isLocalIP
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.environment import Env
|
||||
from multipartpost import MultipartPostHandler
|
||||
import requests
|
||||
from tornado import template
|
||||
from tornado.web import StaticFileHandler
|
||||
from urlparse import urlparse
|
||||
import cookielib
|
||||
import glob
|
||||
import gzip
|
||||
import inspect
|
||||
import math
|
||||
import os.path
|
||||
import re
|
||||
import time
|
||||
@@ -39,6 +35,7 @@ class Plugin(object):
|
||||
http_time_between_calls = 0
|
||||
http_failed_request = {}
|
||||
http_failed_disabled = {}
|
||||
http_opener = requests.Session()
|
||||
|
||||
def __new__(typ, *args, **kwargs):
|
||||
new_plugin = super(Plugin, typ).__new__(typ)
|
||||
@@ -106,7 +103,9 @@ class Plugin(object):
|
||||
f.close()
|
||||
os.chmod(path, Env.getPermission('file'))
|
||||
except Exception, e:
|
||||
log.error('Unable writing to file "%s": %s', (path, e))
|
||||
log.error('Unable writing to file "%s": %s', (path, traceback.format_exc()))
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
|
||||
def makeDir(self, path):
|
||||
path = ss(path)
|
||||
@@ -120,11 +119,11 @@ class Plugin(object):
|
||||
return False
|
||||
|
||||
# http request
|
||||
def urlopen(self, url, timeout = 30, params = None, headers = None, opener = None, multipart = False, show_error = True):
|
||||
def urlopen(self, url, timeout = 30, data = None, headers = None, files = None, show_error = True, return_raw = False):
|
||||
url = urllib2.quote(ss(url), safe = "%/:=&?~#+!$,;'@()*[]")
|
||||
|
||||
if not headers: headers = {}
|
||||
if not params: params = {}
|
||||
if not data: data = {}
|
||||
|
||||
# Fill in some headers
|
||||
parsed_url = urlparse(url)
|
||||
@@ -137,6 +136,8 @@ class Plugin(object):
|
||||
headers['Connection'] = headers.get('Connection', 'keep-alive')
|
||||
headers['Cache-Control'] = headers.get('Cache-Control', 'max-age=0')
|
||||
|
||||
r = self.http_opener
|
||||
|
||||
# Don't try for failed requests
|
||||
if self.http_failed_disabled.get(host, 0) > 0:
|
||||
if self.http_failed_disabled[host] > (time.time() - 900):
|
||||
@@ -152,45 +153,18 @@ class Plugin(object):
|
||||
self.wait(host)
|
||||
try:
|
||||
|
||||
# Make sure opener has the correct headers
|
||||
if opener:
|
||||
opener.add_headers = headers
|
||||
kwargs = {
|
||||
'headers': headers,
|
||||
'data': data if len(data) > 0 else None,
|
||||
'timeout': timeout,
|
||||
'files': files,
|
||||
}
|
||||
method = 'post' if len(data) > 0 or files else 'get'
|
||||
|
||||
if multipart:
|
||||
log.info('Opening multipart url: %s, params: %s', (url, [x for x in params.iterkeys()] if isinstance(params, dict) else 'with data'))
|
||||
request = urllib2.Request(url, params, headers)
|
||||
log.info('Opening url: %s %s, data: %s', (method, url, [x for x in data.iterkeys()] if isinstance(data, dict) else 'with data'))
|
||||
response = r.request(method, url, verify = False, **kwargs)
|
||||
|
||||
if opener:
|
||||
opener.add_handler(MultipartPostHandler())
|
||||
else:
|
||||
cookies = cookielib.CookieJar()
|
||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
||||
|
||||
response = opener.open(request, timeout = timeout)
|
||||
else:
|
||||
log.info('Opening url: %s, params: %s', (url, [x for x in params.iterkeys()] if isinstance(params, dict) else 'with data'))
|
||||
|
||||
if isinstance(params, (str, unicode)) and len(params) > 0:
|
||||
data = params
|
||||
else:
|
||||
data = tryUrlencode(params) if len(params) > 0 else None
|
||||
|
||||
request = urllib2.Request(url, data, headers)
|
||||
|
||||
if opener:
|
||||
response = opener.open(request, timeout = timeout)
|
||||
else:
|
||||
response = urllib2.urlopen(request, timeout = timeout)
|
||||
|
||||
# unzip if needed
|
||||
if response.info().get('Content-Encoding') == 'gzip':
|
||||
buf = StringIO(response.read())
|
||||
f = gzip.GzipFile(fileobj = buf)
|
||||
data = f.read()
|
||||
f.close()
|
||||
else:
|
||||
data = response.read()
|
||||
response.close()
|
||||
data = response.content if return_raw else response.text
|
||||
|
||||
self.http_failed_request[host] = 0
|
||||
except IOError:
|
||||
@@ -218,15 +192,19 @@ class Plugin(object):
|
||||
return data
|
||||
|
||||
def wait(self, host = ''):
|
||||
if self.http_time_between_calls == 0:
|
||||
return
|
||||
|
||||
now = time.time()
|
||||
|
||||
last_use = self.http_last_use.get(host, 0)
|
||||
if last_use > 0:
|
||||
|
||||
wait = math.ceil(last_use - now + self.http_time_between_calls)
|
||||
wait = (last_use - now) + self.http_time_between_calls
|
||||
|
||||
if wait > 0:
|
||||
log.debug('Waiting for %s, %d seconds', (self.getName(), wait))
|
||||
time.sleep(last_use - now + self.http_time_between_calls)
|
||||
if wait > 0:
|
||||
log.debug('Waiting for %s, %d seconds', (self.getName(), wait))
|
||||
time.sleep(wait)
|
||||
|
||||
def beforeCall(self, handler):
|
||||
self.isRunning('%s.%s' % (self.getName(), handler.__name__))
|
||||
@@ -269,18 +247,19 @@ class Plugin(object):
|
||||
try:
|
||||
|
||||
cache_timeout = 300
|
||||
if kwargs.get('cache_timeout'):
|
||||
if kwargs.has_key('cache_timeout'):
|
||||
cache_timeout = kwargs.get('cache_timeout')
|
||||
del kwargs['cache_timeout']
|
||||
|
||||
data = self.urlopen(url, **kwargs)
|
||||
if data:
|
||||
if data and cache_timeout > 0:
|
||||
self.setCache(cache_key, data, timeout = cache_timeout)
|
||||
return data
|
||||
except:
|
||||
if not kwargs.get('show_error', True):
|
||||
raise
|
||||
|
||||
log.error('Failed getting cache: %s', (traceback.format_exc()))
|
||||
return ''
|
||||
|
||||
def setCache(self, cache_key, value, timeout = 300):
|
||||
|
||||
@@ -93,7 +93,7 @@ class FileManager(Plugin):
|
||||
return dest
|
||||
|
||||
try:
|
||||
filedata = self.urlopen(url, **urlopen_kwargs)
|
||||
filedata = self.urlopen(url, return_raw = True, **urlopen_kwargs)
|
||||
except:
|
||||
log.error('Failed downloading file %s: %s', (url, traceback.format_exc()))
|
||||
return False
|
||||
|
||||
@@ -314,6 +314,7 @@ class QualityPlugin(Plugin):
|
||||
'Movie.Name.1999.DVDRip-Group': 'dvdrip',
|
||||
'Movie.Name.1999.DVD-Rip-Group': 'dvdrip',
|
||||
'Movie.Name.1999.DVD-R-Group': 'dvdr',
|
||||
'Movie.Name.Camelie.1999.720p.BluRay.x264-Group': '720p',
|
||||
}
|
||||
|
||||
correct = 0
|
||||
|
||||
@@ -247,13 +247,13 @@ class Release(Plugin):
|
||||
'files': {}
|
||||
}), manual = True)
|
||||
|
||||
if success:
|
||||
if success == True:
|
||||
db.expunge_all()
|
||||
rel = db.query(Relea).filter_by(id = id).first() # Get release again @RuudBurger why do we need to get it again??
|
||||
|
||||
fireEvent('notify.frontend', type = 'release.manual_download', data = True, message = 'Successfully snatched "%s"' % item['name'])
|
||||
return {
|
||||
'success': success
|
||||
'success': success == True
|
||||
}
|
||||
|
||||
def download(self, data, media, manual = False):
|
||||
@@ -266,20 +266,21 @@ class Release(Plugin):
|
||||
# Test to see if any downloaders are enabled for this type
|
||||
downloader_enabled = fireEvent('download.enabled', manual, data, single = True)
|
||||
if not downloader_enabled:
|
||||
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('protocol')))
|
||||
log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', data.get('protocol'))
|
||||
return False
|
||||
|
||||
# Download NZB or torrent file
|
||||
filedata = None
|
||||
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||
log.info('Tried to download, but the "%s" provider gave an error', data.get('protocol'))
|
||||
if filedata == 'try_next':
|
||||
return filedata
|
||||
|
||||
# Send NZB or torrent file to downloader
|
||||
download_result = fireEvent('download', data = data, media = media, manual = manual, filedata = filedata, single = True)
|
||||
if not download_result:
|
||||
log.info('Tried to download, but the "%s" downloader gave an error', (data.get('protocol')))
|
||||
log.info('Tried to download, but the "%s" downloader gave an error', data.get('protocol'))
|
||||
return False
|
||||
log.debug('Downloader result: %s', download_result)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||
from couchpotato.core.helpers.encoding import toUnicode, ss, sp
|
||||
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle, \
|
||||
getImdb, link, symlink, tryInt, splitString, fnEscape
|
||||
getImdb, link, symlink, tryInt, splitString, fnEscape, isSubFolder
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Library, File, Profile, Release, \
|
||||
@@ -125,7 +125,7 @@ class Renamer(Plugin):
|
||||
return
|
||||
else:
|
||||
for item in no_process:
|
||||
if '%s%s' % (base_folder, os.path.sep) in item:
|
||||
if isSubFolder(item, base_folder):
|
||||
log.error('To protect your data, the media libraries can\'t be inside of or the same as the "from" folder.')
|
||||
return
|
||||
|
||||
@@ -157,13 +157,13 @@ class Renamer(Plugin):
|
||||
|
||||
if media_folder:
|
||||
for item in no_process:
|
||||
if '%s%s' % (media_folder, os.path.sep) in item:
|
||||
if isSubFolder(item, media_folder):
|
||||
log.error('To protect your data, the media libraries can\'t be inside of or the same as the provided media folder.')
|
||||
return
|
||||
|
||||
# Make sure a checkSnatched marked all downloads/seeds as such
|
||||
if not release_download and self.conf('run_every') > 0:
|
||||
fireEvent('renamer.check_snatched')
|
||||
self.checkSnatched(fire_scan = False)
|
||||
|
||||
self.renaming_started = True
|
||||
|
||||
@@ -508,7 +508,10 @@ class Renamer(Plugin):
|
||||
os.remove(src)
|
||||
|
||||
parent_dir = os.path.dirname(src)
|
||||
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and not '%s%s' % (parent_dir, os.path.sep) in [destination, media_folder] and not '%s%s' % (base_folder, os.path.sep) in parent_dir:
|
||||
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and \
|
||||
not isSubFolder(destination, parent_dir) and not isSubFolder(media_folder, parent_dir) and \
|
||||
not isSubFolder(parent_dir, base_folder):
|
||||
|
||||
delete_folders.append(parent_dir)
|
||||
|
||||
except:
|
||||
@@ -575,7 +578,7 @@ class Renamer(Plugin):
|
||||
# Break if CP wants to shut down
|
||||
if self.shuttingDown():
|
||||
break
|
||||
|
||||
|
||||
self.renaming_started = False
|
||||
|
||||
def getRenameExtras(self, extra_type = '', replacements = None, folder_name = '', file_name = '', destination = '', group = None, current_file = '', remove_multiple = False):
|
||||
@@ -806,7 +809,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
except:
|
||||
loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
|
||||
|
||||
def checkSnatched(self):
|
||||
def checkSnatched(self, fire_scan = True):
|
||||
|
||||
if self.checking_snatched:
|
||||
log.debug('Already checking snatched')
|
||||
@@ -829,20 +832,29 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
|
||||
# Collect all download information with the download IDs from the releases
|
||||
download_ids = []
|
||||
no_status_support = []
|
||||
try:
|
||||
for rel in rels:
|
||||
rel_dict = rel.to_dict({'info': {}})
|
||||
if rel_dict['info'].get('download_id') and rel_dict['info'].get('download_downloader'):
|
||||
download_ids.append({'id': rel_dict['info']['download_id'], 'downloader': rel_dict['info']['download_downloader']})
|
||||
|
||||
ds = rel_dict['info'].get('download_status_support')
|
||||
if ds == False or ds == 'False':
|
||||
no_status_support.append(ss(rel_dict['info'].get('download_downloader')))
|
||||
except:
|
||||
log.error('Error getting download IDs from database')
|
||||
self.checking_snatched = False
|
||||
return False
|
||||
|
||||
release_downloads = fireEvent('download.status', download_ids, merge = True)
|
||||
release_downloads = fireEvent('download.status', download_ids, merge = True) if download_ids else []
|
||||
|
||||
if len(no_status_support) > 0:
|
||||
log.debug('Download status functionality is not implemented for one of the active downloaders: %s', no_status_support)
|
||||
|
||||
if not release_downloads:
|
||||
log.debug('Download status functionality is not implemented for any active downloaders.')
|
||||
fireEvent('renamer.scan')
|
||||
if fire_scan:
|
||||
self.scan()
|
||||
|
||||
self.checking_snatched = False
|
||||
return True
|
||||
@@ -982,7 +994,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
if release_download['scan']:
|
||||
if release_download['pause'] and self.conf('file_action') == 'link':
|
||||
fireEvent('download.pause', release_download = release_download, pause = True, single = True)
|
||||
fireEvent('renamer.scan', release_download = release_download)
|
||||
self.scan(release_download = release_download)
|
||||
if release_download['pause'] and self.conf('file_action') == 'link':
|
||||
fireEvent('download.pause', release_download = release_download, pause = False, single = True)
|
||||
if release_download['process_complete']:
|
||||
@@ -993,8 +1005,8 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
# Ask the downloader to process the item
|
||||
fireEvent('download.process_complete', release_download = release_download, single = True)
|
||||
|
||||
if scan_required:
|
||||
fireEvent('renamer.scan')
|
||||
if fire_scan and (scan_required or len(no_status_support) > 0):
|
||||
self.scan()
|
||||
|
||||
self.checking_snatched = False
|
||||
return True
|
||||
@@ -1044,7 +1056,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
||||
return release_download['id'] and release_download['downloader'] and release_download['folder']
|
||||
|
||||
def movieInFromFolder(self, media_folder):
|
||||
return media_folder and '%s%s' % (sp(self.conf('from')), os.path.sep) in sp(media_folder) or not media_folder
|
||||
return media_folder and isSubFolder(media_folder, sp(self.conf('from'))) or not media_folder
|
||||
|
||||
def extractFiles(self, folder = None, media_folder = None, files = None, cleanup = False):
|
||||
if not files: files = []
|
||||
|
||||
@@ -80,7 +80,8 @@ class Scanner(Plugin):
|
||||
'hdtv': ['hdtv']
|
||||
}
|
||||
|
||||
clean = '[ _\,\.\(\)\[\]\-](extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
||||
clean = '[ _\,\.\(\)\[\]\-]?(extended.cut|directors.cut|french|swedisch|danish|dutch|swesub|spanish|german|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdr|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip' \
|
||||
'|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|video_ts|audio_ts|480p|480i|576p|576i|720p|720i|1080p|1080i|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|cd[1-9]|\[.*\])([ _\,\.\(\)\[\]\-]|$)'
|
||||
multipart_regex = [
|
||||
'[ _\.-]+cd[ _\.-]*([0-9a-d]+)', #*cd1
|
||||
'[ _\.-]+dvd[ _\.-]*([0-9a-d]+)', #*dvd1
|
||||
|
||||
@@ -59,7 +59,7 @@ config = [{
|
||||
{
|
||||
'name': 'automation_charts_boxoffice',
|
||||
'type': 'bool',
|
||||
'label': 'Box offce TOP 10',
|
||||
'label': 'Box office TOP 10',
|
||||
'description': 'IMDB Box office <a href="http://www.imdb.com/chart/">TOP 10</a> chart',
|
||||
'default': True,
|
||||
},
|
||||
|
||||
@@ -5,12 +5,10 @@ from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
from urlparse import urlparse
|
||||
import cookielib
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
import urllib2
|
||||
import xml.etree.ElementTree as XMLTree
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -95,7 +93,7 @@ class Provider(Plugin):
|
||||
|
||||
def getHTMLData(self, url, **kwargs):
|
||||
|
||||
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('params', {})))
|
||||
cache_key = '%s%s' % (md5(url), md5('%s' % kwargs.get('data', {})))
|
||||
return self.getCache(cache_key, url, **kwargs)
|
||||
|
||||
|
||||
@@ -111,8 +109,7 @@ class YarrProvider(Provider):
|
||||
sizeMb = ['mb', 'mib']
|
||||
sizeKb = ['kb', 'kib']
|
||||
|
||||
login_opener = None
|
||||
last_login_check = 0
|
||||
last_login_check = None
|
||||
|
||||
def __init__(self):
|
||||
addEvent('provider.enabled_protocols', self.getEnabledProtocol)
|
||||
@@ -131,35 +128,30 @@ class YarrProvider(Provider):
|
||||
|
||||
# Check if we are still logged in every hour
|
||||
now = time.time()
|
||||
if self.login_opener and self.last_login_check < (now - 3600):
|
||||
if self.last_login_check and self.last_login_check < (now - 3600):
|
||||
try:
|
||||
output = self.urlopen(self.urls['login_check'], opener = self.login_opener)
|
||||
output = self.urlopen(self.urls['login_check'])
|
||||
if self.loginCheckSuccess(output):
|
||||
self.last_login_check = now
|
||||
return True
|
||||
else:
|
||||
self.login_opener = None
|
||||
except:
|
||||
self.login_opener = None
|
||||
except: pass
|
||||
self.last_login_check = None
|
||||
|
||||
if self.login_opener:
|
||||
if self.last_login_check:
|
||||
return True
|
||||
|
||||
try:
|
||||
cookiejar = cookielib.CookieJar()
|
||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))
|
||||
output = self.urlopen(self.urls['login'], params = self.getLoginParams(), opener = opener)
|
||||
output = self.urlopen(self.urls['login'], data = self.getLoginParams())
|
||||
|
||||
if self.loginSuccess(output):
|
||||
self.last_login_check = now
|
||||
self.login_opener = opener
|
||||
return True
|
||||
|
||||
error = 'unknown'
|
||||
except:
|
||||
error = traceback.format_exc()
|
||||
|
||||
self.login_opener = None
|
||||
self.last_login_check = None
|
||||
log.error('Failed to login %s: %s', (self.getName(), error))
|
||||
return False
|
||||
|
||||
@@ -173,12 +165,12 @@ class YarrProvider(Provider):
|
||||
try:
|
||||
if not self.login():
|
||||
log.error('Failed downloading from %s', self.getName())
|
||||
return self.urlopen(url, opener = self.login_opener)
|
||||
return self.urlopen(url)
|
||||
except:
|
||||
log.error('Failed downloading from %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
def getLoginParams(self):
|
||||
return ''
|
||||
return {}
|
||||
|
||||
def download(self, url = '', nzb_id = ''):
|
||||
try:
|
||||
|
||||
@@ -32,14 +32,17 @@ class Movie(ModifierBase):
|
||||
'poster': [],
|
||||
'backdrop': [],
|
||||
'poster_original': [],
|
||||
'backdrop_original': []
|
||||
'backdrop_original': [],
|
||||
'actors': {}
|
||||
},
|
||||
'runtime': 0,
|
||||
'plot': '',
|
||||
'tagline': '',
|
||||
'imdb': '',
|
||||
'genres': [],
|
||||
'mpaa': None
|
||||
'mpaa': None,
|
||||
'actors': [],
|
||||
'actor_roles': {}
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -74,7 +74,7 @@ class CouchPotatoApi(MovieProvider):
|
||||
|
||||
return True
|
||||
|
||||
def getInfo(self, identifier = None):
|
||||
def getInfo(self, identifier = None, **kwargs):
|
||||
|
||||
if not identifier:
|
||||
return
|
||||
@@ -97,7 +97,7 @@ class CouchPotatoApi(MovieProvider):
|
||||
if not ignore: ignore = []
|
||||
if not movies: movies = []
|
||||
|
||||
suggestions = self.getJsonData(self.urls['suggest'], params = {
|
||||
suggestions = self.getJsonData(self.urls['suggest'], data = {
|
||||
'movies': ','.join(movies),
|
||||
'ignore': ','.join(ignore),
|
||||
}, headers = self.getRequestHeaders())
|
||||
|
||||
@@ -46,7 +46,7 @@ class OMDBAPI(MovieProvider):
|
||||
|
||||
return []
|
||||
|
||||
def getInfo(self, identifier = None):
|
||||
def getInfo(self, identifier = None, **kwargs):
|
||||
|
||||
if not identifier:
|
||||
return {}
|
||||
@@ -88,10 +88,6 @@ class OMDBAPI(MovieProvider):
|
||||
|
||||
year = tryInt(movie.get('Year', ''))
|
||||
|
||||
actors = {}
|
||||
for actor in splitString(movie.get('Actors', '')):
|
||||
actors[actor] = '' #omdb does not return actor roles
|
||||
|
||||
movie_data = {
|
||||
'type': 'movie',
|
||||
'via_imdb': True,
|
||||
@@ -113,7 +109,7 @@ class OMDBAPI(MovieProvider):
|
||||
'genres': splitString(movie.get('Genre', '')),
|
||||
'directors': splitString(movie.get('Director', '')),
|
||||
'writers': splitString(movie.get('Writer', '')),
|
||||
'actor_roles': actors,
|
||||
'actors': splitString(movie.get('Actors', '')),
|
||||
}
|
||||
movie_data = dict((k, v) for k, v in movie_data.iteritems() if v)
|
||||
except:
|
||||
|
||||
@@ -11,8 +11,8 @@ log = CPLog(__name__)
|
||||
class TheMovieDb(MovieProvider):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('info.search', self.search, priority = 2)
|
||||
addEvent('movie.search', self.search, priority = 2)
|
||||
#addEvent('info.search', self.search, priority = 2)
|
||||
#addEvent('movie.search', self.search, priority = 2)
|
||||
addEvent('movie.info', self.getInfo, priority = 2)
|
||||
addEvent('movie.info_by_tmdb', self.getInfo)
|
||||
|
||||
@@ -45,7 +45,7 @@ class TheMovieDb(MovieProvider):
|
||||
nr = 0
|
||||
|
||||
for movie in raw:
|
||||
results.append(self.parseMovie(movie, with_titles = False))
|
||||
results.append(self.parseMovie(movie, extended = False))
|
||||
|
||||
nr += 1
|
||||
if nr == limit:
|
||||
@@ -61,7 +61,7 @@ class TheMovieDb(MovieProvider):
|
||||
|
||||
return results
|
||||
|
||||
def getInfo(self, identifier = None):
|
||||
def getInfo(self, identifier = None, extended = True):
|
||||
|
||||
if not identifier:
|
||||
return {}
|
||||
@@ -73,14 +73,14 @@ class TheMovieDb(MovieProvider):
|
||||
try:
|
||||
log.debug('Getting info: %s', cache_key)
|
||||
movie = tmdb3.Movie(identifier)
|
||||
result = self.parseMovie(movie)
|
||||
result = self.parseMovie(movie, extended = extended)
|
||||
self.setCache(cache_key, result)
|
||||
except:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
def parseMovie(self, movie, with_titles = True):
|
||||
def parseMovie(self, movie, extended = True):
|
||||
|
||||
cache_key = 'tmdb.cache.%s' % movie.id
|
||||
movie_data = self.getCache(cache_key)
|
||||
@@ -97,6 +97,7 @@ class TheMovieDb(MovieProvider):
|
||||
#'backdrop': [backdrop] if backdrop else [],
|
||||
'poster_original': [poster_original] if poster_original else [],
|
||||
'backdrop_original': [backdrop_original] if backdrop_original else [],
|
||||
'actors': {}
|
||||
}
|
||||
|
||||
# Genres
|
||||
@@ -112,12 +113,13 @@ class TheMovieDb(MovieProvider):
|
||||
|
||||
# Gather actors data
|
||||
actors = {}
|
||||
for cast_item in movie.cast:
|
||||
try:
|
||||
actors[toUnicode(cast_item.name)] = toUnicode(cast_item.character)
|
||||
images['actor %s' % toUnicode(cast_item.name)] = self.getImage(cast_item, type = 'profile', size = 'original')
|
||||
except:
|
||||
log.debug('Error getting cast info for %s: %s', (cast_item, traceback.format_exc()))
|
||||
if extended:
|
||||
for cast_item in movie.cast:
|
||||
try:
|
||||
actors[toUnicode(cast_item.name)] = toUnicode(cast_item.character)
|
||||
images['actors'][toUnicode(cast_item.name)] = self.getImage(cast_item, type = 'profile', size = 'original')
|
||||
except:
|
||||
log.debug('Error getting cast info for %s: %s', (cast_item, traceback.format_exc()))
|
||||
|
||||
movie_data = {
|
||||
'type': 'movie',
|
||||
@@ -139,7 +141,7 @@ class TheMovieDb(MovieProvider):
|
||||
movie_data = dict((k, v) for k, v in movie_data.iteritems() if v)
|
||||
|
||||
# Add alternative names
|
||||
if with_titles:
|
||||
if extended:
|
||||
movie_data['titles'].append(movie.originaltitle)
|
||||
for alt in movie.alternate_titles:
|
||||
alt_name = alt.title
|
||||
|
||||
@@ -98,9 +98,9 @@ class XBMC(MetaDataBase):
|
||||
if role_name:
|
||||
role = SubElement(actor, 'role')
|
||||
role.text = toUnicode(role_name)
|
||||
if movie_info['images'].get('actor %s' % actor_name, ''):
|
||||
if movie_info['images']['actors'].get(actor_name):
|
||||
thumb = SubElement(actor, 'thumb')
|
||||
thumb.text = toUnicode(movie_info['images'].get('actor %s' % actor_name))
|
||||
thumb.text = toUnicode(movie_info['images']['actors'].get(actor_name))
|
||||
|
||||
# Directors
|
||||
for director_name in movie_info.get('directors', []):
|
||||
|
||||
@@ -85,13 +85,13 @@ class Base(NZBProvider):
|
||||
|
||||
def download(self, url = '', nzb_id = ''):
|
||||
|
||||
params = {
|
||||
data = {
|
||||
'action': 'nzb',
|
||||
nzb_id: 'on'
|
||||
}
|
||||
|
||||
try:
|
||||
return self.urlopen(url, params = params, show_error = False)
|
||||
return self.urlopen(url, data = data, show_error = False)
|
||||
except:
|
||||
log.error('Failed getting nzb from %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ class Base(NZBProvider, RSS):
|
||||
# Get final redirected url
|
||||
log.debug('Checking %s for redirects.', url)
|
||||
req = urllib2.Request(url)
|
||||
req.add_header('User-Agent', self.user_agent)
|
||||
res = urllib2.urlopen(req)
|
||||
finalurl = res.geturl()
|
||||
if finalurl != url:
|
||||
|
||||
@@ -34,7 +34,7 @@ class Base(TorrentProvider):
|
||||
|
||||
url = "%s&%s" % (self.urls['search'], query)
|
||||
|
||||
data = self.getHTMLData(url, opener = self.login_opener)
|
||||
data = self.getHTMLData(url)
|
||||
|
||||
if data:
|
||||
# Remove BiT-HDTV's output garbage so outdated BS4 versions successfully parse the HTML
|
||||
@@ -71,10 +71,10 @@ class Base(TorrentProvider):
|
||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
def getLoginParams(self):
|
||||
return tryUrlencode({
|
||||
return {
|
||||
'username': self.conf('username'),
|
||||
'password': self.conf('password'),
|
||||
})
|
||||
}
|
||||
|
||||
def getMoreInfo(self, item):
|
||||
full_description = self.getCache('bithdtv.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||
@@ -116,4 +116,4 @@ class Episode(EpisodeProvider, Base):
|
||||
'search': fireEvent('library.query', media['library'], single = True),
|
||||
'cat': 10 # Episode cat
|
||||
})
|
||||
return query
|
||||
return query
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from bs4 import BeautifulSoup
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||
@@ -23,7 +22,7 @@ class HDBits(TorrentProvider):
|
||||
|
||||
def _search(self, movie, quality, results):
|
||||
|
||||
data = self.getJsonData(self.urls['search'] % movie['library']['identifier'], opener = self.login_opener)
|
||||
data = self.getJsonData(self.urls['search'] % movie['library']['identifier'])
|
||||
|
||||
if data:
|
||||
try:
|
||||
@@ -42,15 +41,17 @@ class HDBits(TorrentProvider):
|
||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
def getLoginParams(self):
|
||||
data = self.getHTMLData('https://hdbits.org/login')
|
||||
data = self.getHTMLData('https://hdbits.org/login', cache_timeout = 0)
|
||||
|
||||
bs = BeautifulSoup(data)
|
||||
secret = bs.find('input', attrs = {'name': 'lol'})['value']
|
||||
|
||||
return tryUrlencode({
|
||||
return {
|
||||
'uname': self.conf('username'),
|
||||
'password': self.conf('password'),
|
||||
'returnto': '/',
|
||||
'lol': secret
|
||||
})
|
||||
}
|
||||
|
||||
def loginSuccess(self, output):
|
||||
return '/logout.php' in output.lower()
|
||||
|
||||
@@ -42,7 +42,7 @@ class ILoveTorrents(TorrentProvider):
|
||||
search_url = self.urls['search'] % (movieTitle, page, cats[0])
|
||||
page += 1
|
||||
|
||||
data = self.getHTMLData(search_url, opener = self.login_opener)
|
||||
data = self.getHTMLData(search_url)
|
||||
if data:
|
||||
try:
|
||||
soup = BeautifulSoup(data)
|
||||
@@ -96,11 +96,11 @@ class ILoveTorrents(TorrentProvider):
|
||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
def getLoginParams(self):
|
||||
return tryUrlencode({
|
||||
return {
|
||||
'username': self.conf('username'),
|
||||
'password': self.conf('password'),
|
||||
'submit': 'Welcome to ILT',
|
||||
})
|
||||
}
|
||||
|
||||
def getMoreInfo(self, item):
|
||||
cache_key = 'ilt.%s' % item['id']
|
||||
@@ -109,7 +109,7 @@ class ILoveTorrents(TorrentProvider):
|
||||
if not description:
|
||||
|
||||
try:
|
||||
full_description = self.getHTMLData(item['detail_url'], opener = self.login_opener)
|
||||
full_description = self.getHTMLData(item['detail_url'])
|
||||
html = BeautifulSoup(full_description)
|
||||
nfo_pre = html.find('td', attrs = {'class':'main'}).findAll('table')[1]
|
||||
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
||||
|
||||
@@ -44,7 +44,7 @@ class Base(TorrentProvider):
|
||||
})
|
||||
|
||||
url = '%s?json=noredirect&%s' % (self.urls['torrent'], tryUrlencode(params))
|
||||
res = self.getJsonData(url, opener = self.login_opener)
|
||||
res = self.getJsonData(url)
|
||||
|
||||
try:
|
||||
if not 'Movies' in res:
|
||||
@@ -167,13 +167,13 @@ class Base(TorrentProvider):
|
||||
return self.unicodeToASCII(self.htmlToUnicode(text))
|
||||
|
||||
def getLoginParams(self):
|
||||
return tryUrlencode({
|
||||
return {
|
||||
'username': self.conf('username'),
|
||||
'password': self.conf('password'),
|
||||
'passkey': self.conf('passkey'),
|
||||
'keeplogged': '1',
|
||||
'login': 'Login'
|
||||
})
|
||||
}
|
||||
|
||||
def loginSuccess(self, output):
|
||||
try:
|
||||
@@ -211,4 +211,4 @@ class Movie(MovieProvider, Base):
|
||||
'tc': {'Source': ['TC']},
|
||||
'ts': {'Source': ['TS']},
|
||||
'cam': {'Source': ['CAM']}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class TorrentBytes(TorrentProvider):
|
||||
def _searchOnTitle(self, title, movie, quality, results):
|
||||
|
||||
url = self.urls['search'] % (tryUrlencode('%s %s' % (title.replace(':', ''), movie['library']['year'])), self.getCatId(quality['identifier'])[0])
|
||||
data = self.getHTMLData(url, opener = self.login_opener)
|
||||
data = self.getHTMLData(url)
|
||||
|
||||
if data:
|
||||
html = BeautifulSoup(data)
|
||||
@@ -69,11 +69,11 @@ class TorrentBytes(TorrentProvider):
|
||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||
|
||||
def getLoginParams(self):
|
||||
return tryUrlencode({
|
||||
return {
|
||||
'username': self.conf('username'),
|
||||
'password': self.conf('password'),
|
||||
'login': 'submit',
|
||||
})
|
||||
}
|
||||
|
||||
def loginSuccess(self, output):
|
||||
return 'logout.php' in output.lower() or 'Welcome' in output.lower()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.event import fireEvent
|
||||
@@ -31,7 +30,7 @@ class Base(TorrentProvider):
|
||||
|
||||
query = self.buildUrl(media)
|
||||
|
||||
params = {
|
||||
data = {
|
||||
'/browse.php?': None,
|
||||
'cata': 'yes',
|
||||
'jxt': 8,
|
||||
@@ -39,7 +38,7 @@ class Base(TorrentProvider):
|
||||
'search': query,
|
||||
}
|
||||
|
||||
data = self.getJsonData(self.urls['search'], params = params, opener = self.login_opener)
|
||||
data = self.getJsonData(self.urls['search'], data = data)
|
||||
try: torrents = data.get('Fs', [])[0].get('Cn', {}).get('torrents', [])
|
||||
except: return
|
||||
|
||||
@@ -55,11 +54,13 @@ class Base(TorrentProvider):
|
||||
})
|
||||
|
||||
def getLoginParams(self):
|
||||
return tryUrlencode({
|
||||
return {
|
||||
'username': self.conf('username'),
|
||||
'password': self.conf('password'),
|
||||
'submit.x': 18,
|
||||
'submit.y': 11,
|
||||
'submit': 'submit',
|
||||
})
|
||||
}
|
||||
|
||||
def loginSuccess(self, output):
|
||||
return 'Password not correct' not in output
|
||||
|
||||
@@ -25,7 +25,7 @@ class UserscriptBase(Plugin):
|
||||
result = fireEvent('movie.search', q = '%s %s' % (name, year), limit = 1, merge = True)
|
||||
|
||||
if len(result) > 0:
|
||||
movie = fireEvent('movie.info', identifier = result[0].get('imdb'), merge = True)
|
||||
movie = fireEvent('movie.info', identifier = result[0].get('imdb'), extended = False, merge = True)
|
||||
return movie
|
||||
else:
|
||||
return None
|
||||
@@ -54,7 +54,7 @@ class UserscriptBase(Plugin):
|
||||
return self.getInfo(getImdb(data))
|
||||
|
||||
def getInfo(self, identifier):
|
||||
return fireEvent('movie.info', identifier = identifier, merge = True)
|
||||
return fireEvent('movie.info', identifier = identifier, extended = False, merge = True)
|
||||
|
||||
def getInclude(self):
|
||||
return self.includes
|
||||
|
||||
@@ -8,4 +8,4 @@ class IMDB(UserscriptBase):
|
||||
includes = ['*://*.imdb.com/title/tt*', '*://imdb.com/title/tt*']
|
||||
|
||||
def getMovie(self, url):
|
||||
return fireEvent('movie.info', identifier = getImdb(url), merge = True)
|
||||
return self.getInfo(getImdb(url))
|
||||
|
||||
@@ -9,7 +9,7 @@ class TMDB(UserscriptBase):
|
||||
|
||||
def getMovie(self, url):
|
||||
match = re.search('(?P<id>\d+)', url)
|
||||
movie = fireEvent('movie.info_by_tmdb', identifier = match.group('id'), merge = True)
|
||||
movie = fireEvent('movie.info_by_tmdb', identifier = match.group('id'), extended = False, merge = True)
|
||||
|
||||
if movie['imdb']:
|
||||
return self.getInfo(movie['imdb'])
|
||||
|
||||
Reference in New Issue
Block a user