Merge branch 'refs/heads/develop' into desktop
This commit is contained in:
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
/data/
|
/data/
|
||||||
/_source/
|
/_source/
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
|||||||
+5
-1
@@ -9,7 +9,7 @@ import socket
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
# Root path
|
# Root path
|
||||||
base_path = dirname(os.path.abspath(__file__))
|
base_path = dirname(os.path.abspath(__file__))
|
||||||
@@ -96,6 +96,10 @@ class Loader(object):
|
|||||||
except:
|
except:
|
||||||
self.log.critical(traceback.format_exc())
|
self.log.critical(traceback.format_exc())
|
||||||
|
|
||||||
|
# Release log files and shutdown logger
|
||||||
|
logging.shutdown()
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
args = [sys.executable] + [os.path.join(base_path, __file__)] + sys.argv[1:]
|
args = [sys.executable] + [os.path.join(base_path, __file__)] + sys.argv[1:]
|
||||||
subprocess.Popen(args)
|
subprocess.Popen(args)
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ Once a movie is found, it will send it to SABnzbd or download the torrent to a s
|
|||||||
|
|
||||||
## Running from Source
|
## Running from Source
|
||||||
|
|
||||||
CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed also.
|
CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed also.
|
||||||
|
|
||||||
Windows:
|
Windows, see [the CP forum](http://couchpota.to/forum/showthread.php?tid=14) for more details:
|
||||||
|
|
||||||
* Install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/)
|
* Install [Python 2.7](http://www.python.org/download/releases/2.7.3/)
|
||||||
|
* Then install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/)
|
||||||
* If you come and ask on the forums 'why directory selection no work?', I will kill a kitten, also this is because you need PyWin32
|
* If you come and ask on the forums 'why directory selection no work?', I will kill a kitten, also this is because you need PyWin32
|
||||||
* Open up `Git Bash` (or CMD) and go to the folder you want to install CP. Something like Program Files.
|
* Open up `Git Bash` (or CMD) and go to the folder you want to install CP. Something like Program Files.
|
||||||
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`.
|
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`.
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ class SourceUpdater(BaseUpdater):
|
|||||||
if not os.path.isdir(dirname):
|
if not os.path.isdir(dirname):
|
||||||
self.makeDir(dirname)
|
self.makeDir(dirname)
|
||||||
|
|
||||||
os.rename(fromfile, tofile)
|
shutil.move(fromfile, tofile)
|
||||||
try:
|
try:
|
||||||
existing_files.remove(tofile)
|
existing_files.remove(tofile)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
from base64 import b32decode, b16encode
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import toSafeString
|
from couchpotato.core.helpers.encoding import toSafeString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -12,14 +15,25 @@ class Downloader(Plugin):
|
|||||||
|
|
||||||
type = []
|
type = []
|
||||||
|
|
||||||
|
torrent_sources = [
|
||||||
|
'http://torrage.com/torrent/%s.torrent',
|
||||||
|
'http://torrage.ws/torrent/%s.torrent',
|
||||||
|
'http://torcache.net/torrent/%s.torrent',
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('download', self.download)
|
addEvent('download', self.download)
|
||||||
|
addEvent('download.status', self.getDownloadStatus)
|
||||||
|
|
||||||
def download(self, data = {}):
|
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getDownloadStatus(self, data = {}, movie = {}):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def createNzbName(self, data, movie):
|
def createNzbName(self, data, movie):
|
||||||
return '%s%s' % (toSafeString(data.get('name')), self.cpTag(movie))
|
tag = self.cpTag(movie)
|
||||||
|
return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag)
|
||||||
|
|
||||||
def createFileName(self, data, filedata, movie):
|
def createFileName(self, data, filedata, movie):
|
||||||
name = os.path.join(self.createNzbName(data, movie))
|
name = os.path.join(self.createNzbName(data, movie))
|
||||||
@@ -41,6 +55,26 @@ class Downloader(Plugin):
|
|||||||
|
|
||||||
return is_correct
|
return is_correct
|
||||||
|
|
||||||
|
def magnetToTorrent(self, magnet_link):
|
||||||
|
torrent_hash = re.findall('urn:btih:([\w]{32,40})', magnet_link)[0]
|
||||||
|
|
||||||
|
# Convert base 32 to hex
|
||||||
|
if len(torrent_hash) == 32:
|
||||||
|
torrent_hash = b16encode(b32decode(torrent_hash))
|
||||||
|
|
||||||
|
sources = self.torrent_sources
|
||||||
|
random.shuffle(sources)
|
||||||
|
|
||||||
|
for source in sources:
|
||||||
|
try:
|
||||||
|
filedata = self.urlopen(source % torrent_hash, show_error = False)
|
||||||
|
return filedata
|
||||||
|
except:
|
||||||
|
log.debug('Torrent hash "%s" wasn\'t found on: %s', (torrent_hash, source))
|
||||||
|
|
||||||
|
log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
|
||||||
|
return False
|
||||||
|
|
||||||
def isDisabled(self, manual):
|
def isDisabled(self, manual):
|
||||||
return not self.isEnabled(manual)
|
return not self.isEnabled(manual)
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
class Blackhole(Downloader):
|
class Blackhole(Downloader):
|
||||||
|
|
||||||
type = ['nzb', 'torrent']
|
type = ['nzb', 'torrent', 'torrent_magnet']
|
||||||
|
|
||||||
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||||
if self.isDisabled(manual) or (not self.isCorrectType(data.get('type')) or (not self.conf('use_for') in ['both', data.get('type')])):
|
if self.isDisabled(manual) or \
|
||||||
|
(not self.isCorrectType(data.get('type')) or \
|
||||||
|
(not self.conf('use_for') in ['both', 'torrent' if 'torrent' in data.get('type') else data.get('type')])):
|
||||||
return
|
return
|
||||||
|
|
||||||
directory = self.conf('directory')
|
directory = self.conf('directory')
|
||||||
@@ -20,8 +22,16 @@ class Blackhole(Downloader):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if not filedata or len(filedata) < 50:
|
if not filedata or len(filedata) < 50:
|
||||||
log.error('No nzb/torrent available!')
|
try:
|
||||||
return False
|
if data.get('type') == 'torrent_magnet':
|
||||||
|
filedata = self.magnetToTorrent(data.get('url'))
|
||||||
|
data['type'] = 'torrent'
|
||||||
|
except:
|
||||||
|
log.error('Failed download torrent via magnet url: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
if not filedata or len(filedata) < 50:
|
||||||
|
log.error('No nzb/torrent available: %s', data.get('url'))
|
||||||
|
return False
|
||||||
|
|
||||||
fullPath = os.path.join(directory, self.createFileName(data, filedata, movie))
|
fullPath = os.path.join(directory, self.createFileName(data, filedata, movie))
|
||||||
|
|
||||||
|
|||||||
@@ -35,11 +35,17 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'manual',
|
'name': 'manual',
|
||||||
'default': 0,
|
'default': False,
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'delete_failed',
|
||||||
|
'default': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'description': 'Delete a release after the download has failed.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from couchpotato.core.helpers.encoding import tryUrlencode
|
|||||||
from couchpotato.core.helpers.variable import cleanHost
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
import traceback
|
import traceback
|
||||||
|
import json
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -39,14 +40,14 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if params.get('mode') is 'addfile':
|
if params.get('mode') is 'addfile':
|
||||||
data = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False)
|
sab = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False)
|
||||||
else:
|
else:
|
||||||
data = self.urlopen(url, timeout = 60, show_error = False)
|
sab = self.urlopen(url, timeout = 60, show_error = False)
|
||||||
except:
|
except:
|
||||||
log.error(traceback.format_exc())
|
log.error('Failed sending release: %s', traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
result = data.strip()
|
result = sab.strip()
|
||||||
if not result:
|
if not result:
|
||||||
log.error("SABnzbd didn't return anything.")
|
log.error("SABnzbd didn't return anything.")
|
||||||
return False
|
return False
|
||||||
@@ -61,3 +62,100 @@ class Sabnzbd(Downloader):
|
|||||||
else:
|
else:
|
||||||
log.error("Unknown error: " + result[:40])
|
log.error("Unknown error: " + result[:40])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getDownloadStatus(self, data = {}, movie = {}):
|
||||||
|
if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')):
|
||||||
|
return
|
||||||
|
|
||||||
|
nzbname = self.createNzbName(data, movie)
|
||||||
|
log.info('Checking download status of "%s" at SABnzbd.', nzbname)
|
||||||
|
|
||||||
|
# Go through Queue
|
||||||
|
params = {
|
||||||
|
'apikey': self.conf('api_key'),
|
||||||
|
'mode': 'queue',
|
||||||
|
'output': 'json'
|
||||||
|
}
|
||||||
|
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sab = self.urlopen(url, timeout = 60, show_error = False)
|
||||||
|
except:
|
||||||
|
log.error('Failed checking status: %s', traceback.format_exc())
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
history = json.loads(sab)
|
||||||
|
except:
|
||||||
|
log.debug("Result text from SAB: " + sab[:40])
|
||||||
|
log.error('Failed parsing json status: %s', traceback.format_exc())
|
||||||
|
return False
|
||||||
|
|
||||||
|
for slot in history['queue']['slots']:
|
||||||
|
log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft']))
|
||||||
|
if slot['filename'] == nzbname:
|
||||||
|
return slot['status'].lower()
|
||||||
|
|
||||||
|
# Go through history items
|
||||||
|
params = {
|
||||||
|
'apikey': self.conf('api_key'),
|
||||||
|
'mode': 'history',
|
||||||
|
'output': 'json'
|
||||||
|
}
|
||||||
|
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sab = self.urlopen(url, timeout = 60, show_error = False)
|
||||||
|
except:
|
||||||
|
log.error('Failed getting history: %s', traceback.format_exc())
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
history = json.loads(sab)
|
||||||
|
except:
|
||||||
|
log.debug("Result text from SAB: " + sab[:40])
|
||||||
|
log.error('Failed parsing history json: %s', traceback.format_exc())
|
||||||
|
return
|
||||||
|
|
||||||
|
for slot in history['history']['slots']:
|
||||||
|
log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status']))
|
||||||
|
if slot['name'] == nzbname:
|
||||||
|
# Note: if post process even if failed is on in SabNZBd, it will complete with a fail message
|
||||||
|
if slot['status'] == 'Failed' or (slot['status'] == 'Completed' and slot['fail_message'].strip()):
|
||||||
|
|
||||||
|
# Delete failed download
|
||||||
|
if self.conf('delete_failed', default = True):
|
||||||
|
|
||||||
|
log.info('%s failed downloading, deleting...', slot['name'])
|
||||||
|
params = {
|
||||||
|
'apikey': self.conf('api_key'),
|
||||||
|
'mode': 'history',
|
||||||
|
'name': 'delete',
|
||||||
|
'del_files': '1',
|
||||||
|
'value': slot['nzo_id']
|
||||||
|
}
|
||||||
|
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sab = self.urlopen(url, timeout = 60, show_error = False)
|
||||||
|
except:
|
||||||
|
log.error('Failed deleting: %s', traceback.format_exc())
|
||||||
|
return False
|
||||||
|
|
||||||
|
result = sab.strip()
|
||||||
|
if not result:
|
||||||
|
log.error("SABnzbd didn't return anything.")
|
||||||
|
|
||||||
|
log.debug("Result text from SAB: " + result[:40])
|
||||||
|
if result == "ok":
|
||||||
|
log.info('SabNZBd deleted failed release %s successfully.', slot['name'])
|
||||||
|
elif result == "Missing authentication":
|
||||||
|
log.error("Incorrect username/password or API?.")
|
||||||
|
else:
|
||||||
|
log.error("Unknown error: " + result[:40])
|
||||||
|
|
||||||
|
return 'failed'
|
||||||
|
else:
|
||||||
|
return slot['status'].lower()
|
||||||
|
|
||||||
|
return 'not_found'
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from couchpotato.core.logger import CPLog
|
|||||||
from string import ascii_letters, digits
|
from string import ascii_letters, digits
|
||||||
from urllib import quote_plus
|
from urllib import quote_plus
|
||||||
import re
|
import re
|
||||||
|
import traceback
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -30,8 +31,8 @@ def toUnicode(original, *args):
|
|||||||
return ek(original, *args)
|
return ek(original, *args)
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
except UnicodeDecodeError:
|
except:
|
||||||
log.error('Unable to decode value: %s... ', repr(original)[:20])
|
log.error('Unable to decode value "%s..." : %s ', (repr(original)[:20], traceback.format_exc()))
|
||||||
ascii_text = str(original).encode('string_escape')
|
ascii_text = str(original).encode('string_escape')
|
||||||
return toUnicode(ascii_text)
|
return toUnicode(ascii_text)
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ var NotificationBase = new Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
testButtonName: function(fieldset){
|
testButtonName: function(fieldset){
|
||||||
var name = fieldset.getElement('h2').get('text');
|
var name = String(fieldset.getElement('h2').innerHTML).substring(0,String(fieldset.getElement('h2').innerHTML).indexOf("<span")); //.get('text');
|
||||||
return 'Test '+name;
|
return 'Test '+name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class NMJ(Notification):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('renamer.after', self.addToLibrary)
|
addEvent('renamer.after', self.addToLibrary)
|
||||||
|
addApiView(self.testNotifyName(), self.test)
|
||||||
addApiView('notify.nmj.auto_config', self.autoConfig)
|
addApiView('notify.nmj.auto_config', self.autoConfig)
|
||||||
|
|
||||||
def autoConfig(self):
|
def autoConfig(self):
|
||||||
@@ -76,7 +76,7 @@ class NMJ(Notification):
|
|||||||
mount = self.conf('mount')
|
mount = self.conf('mount')
|
||||||
database = self.conf('database')
|
database = self.conf('database')
|
||||||
|
|
||||||
if self.mount:
|
if mount:
|
||||||
log.debug('Try to mount network drive via url: %s', (mount))
|
log.debug('Try to mount network drive via url: %s', (mount))
|
||||||
try:
|
try:
|
||||||
data = self.urlopen(mount)
|
data = self.urlopen(mount)
|
||||||
@@ -114,3 +114,8 @@ class NMJ(Notification):
|
|||||||
|
|
||||||
def failed(self):
|
def failed(self):
|
||||||
return jsonified({'success': False})
|
return jsonified({'success': False})
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
return jsonified({'success': self.addToLibrary()})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,22 @@ class NotifyMyAndroid(Notification):
|
|||||||
nma.addkey(keys)
|
nma.addkey(keys)
|
||||||
nma.developerkey(self.conf('dev_key'))
|
nma.developerkey(self.conf('dev_key'))
|
||||||
|
|
||||||
# hacky fix for the event type
|
# hacky fix for the event type
|
||||||
# as it seems to be part of the message now
|
# as it seems to be part of the message now
|
||||||
self.event = message.split(' ')[0]
|
self.event = message.split(' ')[0]
|
||||||
|
response = nma.push(
|
||||||
|
application = self.default_title,
|
||||||
|
event = self.event,
|
||||||
|
description = message,
|
||||||
|
priority = self.conf('priority'),
|
||||||
|
batch_mode = len(keys) > 1
|
||||||
|
)
|
||||||
|
|
||||||
response = nma.push(self.default_title, self.event , message, self.conf('priority'), batch_mode = len(keys) > 1)
|
successful = 0
|
||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if not response[str(key)]['code'] == u'200':
|
if not response[str(key)]['code'] == u'200':
|
||||||
log.error('Could not send notification to NotifyMyAndroid (%s). %s', (key, response[key]['message']))
|
log.error('Could not send notification to NotifyMyAndroid (%s). %s', (key, response[key]['message']))
|
||||||
|
else:
|
||||||
|
successful += 1
|
||||||
|
|
||||||
return response
|
return successful == len(keys)
|
||||||
|
|||||||
@@ -13,11 +13,15 @@ class XBMC(Notification):
|
|||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
for host in [x.strip() for x in self.conf('host').split(",")]:
|
hosts = [x.strip() for x in self.conf('host').split(",")]
|
||||||
self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host)
|
successful = 0
|
||||||
self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host)
|
for host in hosts:
|
||||||
|
if self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host):
|
||||||
|
successful += 1
|
||||||
|
if self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host):
|
||||||
|
successful += 1
|
||||||
|
|
||||||
return True
|
return successful == len(hosts)*2
|
||||||
|
|
||||||
def send(self, command, host):
|
def send(self, command, host):
|
||||||
|
|
||||||
|
|||||||
@@ -340,6 +340,32 @@
|
|||||||
.movies .movie .hide_trailer.hide {
|
.movies .movie .hide_trailer.hide {
|
||||||
top: -30px;
|
top: -30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies .movie .try_container {
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .try_container a {
|
||||||
|
margin: 0 5px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .releases .next_release {
|
||||||
|
border-left: 6px solid #2aa300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .releases .next_release > :first-child {
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .releases .last_release {
|
||||||
|
border-left: 6px solid #ffa200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies .movie .releases .last_release > :first-child {
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .load_more {
|
.movies .load_more {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -140,28 +140,33 @@ var Movie = new Class({
|
|||||||
self.profile.getTypes().each(function(type){
|
self.profile.getTypes().each(function(type){
|
||||||
|
|
||||||
var q = self.addQuality(type.quality_id || type.get('quality_id'));
|
var q = self.addQuality(type.quality_id || type.get('quality_id'));
|
||||||
if(type.finish == true || type.get('finish'))
|
if((type.finish == true || type.get('finish')) && !q.hasClass('finish')){
|
||||||
q.addClass('finish');
|
q.addClass('finish');
|
||||||
|
q.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.')
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add done releases
|
// Add done releases
|
||||||
Array.each(self.data.releases, function(release){
|
self.data.releases.each(function(release){
|
||||||
|
|
||||||
var q = self.quality.getElement('.q_id'+ release.quality_id),
|
var q = self.quality.getElement('.q_id'+ release.quality_id),
|
||||||
status = Status.get(release.status_id);
|
status = Status.get(release.status_id);
|
||||||
|
|
||||||
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
|
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
|
||||||
var q = self.addQuality(release.quality_id)
|
var q = self.addQuality(release.quality_id)
|
||||||
if (status && q)
|
|
||||||
|
if (status && q && !q.hasClass(status.identifier)){
|
||||||
q.addClass(status.identifier);
|
q.addClass(status.identifier);
|
||||||
|
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label)
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.each(self.options.actions, function(action, key){
|
Object.each(self.options.actions, function(action, key){
|
||||||
self.actions.adopt(
|
self.action[key.toLowerCase()] = action = new self.options.actions[key](self)
|
||||||
self.action[key.toLowerCase()] = new self.options.actions[key](self)
|
if(action.el)
|
||||||
)
|
self.actions.adopt(action)
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!self.data.library.rating)
|
if(!self.data.library.rating)
|
||||||
@@ -175,7 +180,8 @@ var Movie = new Class({
|
|||||||
var q = Quality.getQuality(quality_id);
|
var q = Quality.getQuality(quality_id);
|
||||||
return new Element('span', {
|
return new Element('span', {
|
||||||
'text': q.label,
|
'text': q.label,
|
||||||
'class': 'q_'+q.identifier + ' q_id' + q.id
|
'class': 'q_'+q.identifier + ' q_id' + q.id,
|
||||||
|
'title': ''
|
||||||
}).inject(self.quality);
|
}).inject(self.quality);
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -280,6 +286,31 @@ var MovieAction = new Class({
|
|||||||
this.el.removeClass('disable')
|
this.el.removeClass('disable')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createMask: function(){
|
||||||
|
var self = this;
|
||||||
|
self.mask = new Element('div.mask', {
|
||||||
|
'styles': {
|
||||||
|
'z-index': '1'
|
||||||
|
}
|
||||||
|
}).inject(self.movie, 'top').fade('hide');
|
||||||
|
self.positionMask();
|
||||||
|
},
|
||||||
|
|
||||||
|
positionMask: function(){
|
||||||
|
var self = this,
|
||||||
|
movie = $(self.movie),
|
||||||
|
s = movie.getSize()
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
return self.mask.setStyles({
|
||||||
|
'width': s.x,
|
||||||
|
'height': s.y
|
||||||
|
}).position({
|
||||||
|
'relativeTo': movie
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
toElement: function(){
|
toElement: function(){
|
||||||
return this.el || null
|
return this.el || null
|
||||||
}
|
}
|
||||||
@@ -298,19 +329,11 @@ var IMDBAction = new Class({
|
|||||||
|
|
||||||
self.el = new Element('a.imdb', {
|
self.el = new Element('a.imdb', {
|
||||||
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
|
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
|
||||||
'events': {
|
'href': 'http://www.imdb.com/title/'+self.id+'/',
|
||||||
'click': self.gotoIMDB.bind(self)
|
'target': '_blank'
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!self.id) self.disable();
|
if(!self.id) self.disable();
|
||||||
},
|
|
||||||
|
|
||||||
gotoIMDB: function(e){
|
|
||||||
var self = this;
|
|
||||||
(e).preventDefault();
|
|
||||||
|
|
||||||
window.open('http://www.imdb.com/title/'+self.id+'/');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -318,13 +341,10 @@ var IMDBAction = new Class({
|
|||||||
var ReleaseAction = new Class({
|
var ReleaseAction = new Class({
|
||||||
|
|
||||||
Extends: MovieAction,
|
Extends: MovieAction,
|
||||||
id: null,
|
|
||||||
|
|
||||||
create: function(){
|
create: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.id = self.movie.get('identifier');
|
|
||||||
|
|
||||||
self.el = new Element('a.releases.icon.download', {
|
self.el = new Element('a.releases.icon.download', {
|
||||||
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
|
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
|
||||||
'events': {
|
'events': {
|
||||||
@@ -332,15 +352,33 @@ var ReleaseAction = new Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var buttons_done = false;
|
||||||
|
|
||||||
|
self.movie.data.releases.sortBy('-info.score').each(function(release){
|
||||||
|
if(buttons_done) return;
|
||||||
|
|
||||||
|
var status = Status.get(release.status_id);
|
||||||
|
|
||||||
|
if((status.identifier == 'ignored' || status.identifier == 'failed') || (!self.next_release && status.identifier == 'available')){
|
||||||
|
self.hide_on_click = false;
|
||||||
|
self.show();
|
||||||
|
buttons_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
show: function(e){
|
show: function(e){
|
||||||
var self = this;
|
var self = this;
|
||||||
(e).preventDefault();
|
if(e)
|
||||||
|
(e).preventDefault();
|
||||||
|
|
||||||
if(!self.options_container){
|
if(!self.options_container){
|
||||||
self.options_container = new Element('div.options').adopt(
|
self.options_container = new Element('div.options').adopt(
|
||||||
self.release_container = new Element('div.releases.table')
|
self.release_container = new Element('div.releases.table').adopt(
|
||||||
|
self.trynext_container = new Element('div.buttons.try_container')
|
||||||
|
)
|
||||||
).inject(self.movie, 'top');
|
).inject(self.movie, 'top');
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
@@ -354,29 +392,35 @@ var ReleaseAction = new Class({
|
|||||||
new Element('span.provider', {'text': 'Provider'})
|
new Element('span.provider', {'text': 'Provider'})
|
||||||
).inject(self.release_container)
|
).inject(self.release_container)
|
||||||
|
|
||||||
Array.each(self.movie.data.releases, function(release){
|
self.movie.data.releases.sortBy('-info.score').each(function(release){
|
||||||
|
|
||||||
var status = Status.get(release.status_id),
|
var status = Status.get(release.status_id),
|
||||||
quality = Quality.getProfile(release.quality_id) || {},
|
quality = Quality.getProfile(release.quality_id) || {},
|
||||||
info = release.info;
|
info = release.info;
|
||||||
|
|
||||||
try {
|
if( status.identifier == 'ignored' || status.identifier == 'failed'){
|
||||||
var details_url = info.filter(function(item){ return item.identifier == 'detail_url' }).pick().value;
|
self.last_release = release;
|
||||||
} catch(e){}
|
}
|
||||||
|
else if(!self.next_release && status.identifier == 'available'){
|
||||||
|
self.next_release = release;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create release
|
||||||
new Element('div', {
|
new Element('div', {
|
||||||
'class': 'item '+status.identifier,
|
'class': 'item '+status.identifier +
|
||||||
|
(self.next_release && self.next_release.id == release.id ? ' next_release' : '') +
|
||||||
|
(self.last_release && self.last_release.id == release.id ? ' last_release' : ''),
|
||||||
'id': 'release_'+release.id
|
'id': 'release_'+release.id
|
||||||
}).adopt(
|
}).adopt(
|
||||||
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
|
||||||
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
|
||||||
new Element('span.quality', {'text': quality.label || 'n/a'}),
|
new Element('span.quality', {'text': quality.get('label') || 'n/a'}),
|
||||||
new Element('span.size', {'text': (self.get(release, 'size'))}),
|
new Element('span.size', {'text': release.info['size'] ? Math.floor(self.get(release, 'size')) : 'n/a'}),
|
||||||
new Element('span.age', {'text': self.get(release, 'age')}),
|
new Element('span.age', {'text': self.get(release, 'age')}),
|
||||||
new Element('span.score', {'text': self.get(release, 'score')}),
|
new Element('span.score', {'text': self.get(release, 'score')}),
|
||||||
new Element('span.provider', {'text': self.get(release, 'provider')}),
|
new Element('span.provider', {'text': self.get(release, 'provider')}),
|
||||||
details_url ? new Element('a.info.icon', {
|
release.info['detail_url'] ? new Element('a.info.icon', {
|
||||||
'href': details_url,
|
'href': release.info['detail_url'],
|
||||||
'target': '_blank'
|
'target': '_blank'
|
||||||
}) : null,
|
}) : null,
|
||||||
new Element('a.download.icon', {
|
new Element('a.download.icon', {
|
||||||
@@ -400,17 +444,37 @@ var ReleaseAction = new Class({
|
|||||||
).inject(self.release_container)
|
).inject(self.release_container)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.trynext_container.adopt(
|
||||||
|
new Element('span.or', {
|
||||||
|
'text': 'Download'
|
||||||
|
}),
|
||||||
|
self.last_release ? new Element('a.button.orange', {
|
||||||
|
'text': 'the same release again',
|
||||||
|
'events': {
|
||||||
|
'click': self.trySameRelease.bind(self)
|
||||||
|
}
|
||||||
|
}) : null,
|
||||||
|
self.next_release && self.last_release ? new Element('span.or', {
|
||||||
|
'text': 'or'
|
||||||
|
}) : null,
|
||||||
|
self.next_release ? [new Element('a.button.green', {
|
||||||
|
'text': self.last_release ? 'another release' : 'the best release',
|
||||||
|
'events': {
|
||||||
|
'click': self.tryNextRelease.bind(self)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Element('span.or', {
|
||||||
|
'text': 'or pick one below'
|
||||||
|
})] : null
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.movie.slide('in', self.options_container);
|
self.movie.slide('in', self.options_container);
|
||||||
},
|
},
|
||||||
|
|
||||||
get: function(release, type){
|
get: function(release, type){
|
||||||
var self = this;
|
return release.info[type] || 'n/a'
|
||||||
|
|
||||||
return (release.info.filter(function(info){
|
|
||||||
return type == info.identifier
|
|
||||||
}).pick() || {}).value || 'n/a'
|
|
||||||
},
|
},
|
||||||
|
|
||||||
download: function(release){
|
download: function(release){
|
||||||
@@ -444,6 +508,25 @@ var ReleaseAction = new Class({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
tryNextRelease: function(movie_id){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(self.last_release)
|
||||||
|
self.ignore(self.last_release);
|
||||||
|
|
||||||
|
if(self.next_release)
|
||||||
|
self.download(self.next_release);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
trySameRelease: function(movie_id){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if(self.last_release)
|
||||||
|
self.download(self.last_release);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -186,7 +186,6 @@
|
|||||||
|
|
||||||
.movie_result .info h2 span {
|
.movie_result .info h2 span {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
content: ")";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .info h2 span:before { content: "("; }
|
.movie_result .info h2 span:before { content: "("; }
|
||||||
|
|||||||
@@ -170,7 +170,9 @@ class Release(Plugin):
|
|||||||
|
|
||||||
# Get matching provider
|
# Get matching provider
|
||||||
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
|
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
|
||||||
item['download'] = provider.download
|
|
||||||
|
if item['type'] != 'torrent_magnet':
|
||||||
|
item['download'] = provider.download
|
||||||
|
|
||||||
success = fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
|
success = fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
|
||||||
'profile': {'types': {'quality': {}}},
|
'profile': {'types': {'quality': {}}},
|
||||||
|
|||||||
@@ -73,6 +73,22 @@ config = [{
|
|||||||
'description': 'Cleanup leftover files after successful rename.',
|
'description': 'Cleanup leftover files after successful rename.',
|
||||||
'default': False,
|
'default': False,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'advanced': True,
|
||||||
|
'name': 'run_every',
|
||||||
|
'label': 'Run every',
|
||||||
|
'default': 1,
|
||||||
|
'type': 'int',
|
||||||
|
'unit': 'min(s)',
|
||||||
|
'description': 'Detect movie status every X minutes. Will start the renamer if movie is <strong>completed</strong> or handle <strong>failed</strong> download if these options are enabled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'advanced': True,
|
||||||
|
'name': 'next_on_failed',
|
||||||
|
'default': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'description': 'Try the next best release for a movie after a download failed.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'move_leftover',
|
'name': 'move_leftover',
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
@@ -86,15 +102,6 @@ config = [{
|
|||||||
'label': 'Separator',
|
'label': 'Separator',
|
||||||
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
|
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'advanced': True,
|
|
||||||
'name': 'run_every',
|
|
||||||
'label': 'Run every',
|
|
||||||
'default': 1,
|
|
||||||
'type': 'int',
|
|
||||||
'unit': 'min(s)',
|
|
||||||
'description': 'Search for new movies inside the folder every X minutes.',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
'tab': 'renamer',
|
'tab': 'renamer',
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ from couchpotato.core.helpers.request import jsonified
|
|||||||
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle
|
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Library, File, Profile
|
from couchpotato.core.settings.model import Library, File, Profile, Release
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
|
import errno
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import traceback
|
import traceback
|
||||||
import errno
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -28,9 +28,11 @@ class Renamer(Plugin):
|
|||||||
})
|
})
|
||||||
|
|
||||||
addEvent('renamer.scan', self.scan)
|
addEvent('renamer.scan', self.scan)
|
||||||
|
addEvent('renamer.check_snatched', self.checkSnatched)
|
||||||
|
|
||||||
addEvent('app.load', self.scan)
|
addEvent('app.load', self.scan)
|
||||||
|
|
||||||
fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every'))
|
fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
|
||||||
|
|
||||||
def scanView(self):
|
def scanView(self):
|
||||||
|
|
||||||
@@ -314,6 +316,7 @@ class Renamer(Plugin):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Remove files
|
# Remove files
|
||||||
|
delete_folders = []
|
||||||
for src in remove_files:
|
for src in remove_files:
|
||||||
|
|
||||||
if isinstance(src, File):
|
if isinstance(src, File):
|
||||||
@@ -327,10 +330,19 @@ class Renamer(Plugin):
|
|||||||
try:
|
try:
|
||||||
if os.path.isfile(src):
|
if os.path.isfile(src):
|
||||||
os.remove(src)
|
os.remove(src)
|
||||||
|
|
||||||
|
parent_dir = os.path.normpath(os.path.dirname(src))
|
||||||
|
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and destination != parent_dir:
|
||||||
|
delete_folders.append(parent_dir)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed removing %s: %s', (src, traceback.format_exc()))
|
log.error('Failed removing %s: %s', (src, traceback.format_exc()))
|
||||||
self.tagDir(group, 'failed_remove')
|
self.tagDir(group, 'failed_remove')
|
||||||
|
|
||||||
|
# Delete leftover folder from older releases
|
||||||
|
for delete_folder in delete_folders:
|
||||||
|
self.deleteEmptyFolder(delete_folder, show_error = False)
|
||||||
|
|
||||||
# Rename all files marked
|
# Rename all files marked
|
||||||
group['renamed_files'] = []
|
group['renamed_files'] = []
|
||||||
for src in rename_files:
|
for src in rename_files:
|
||||||
@@ -464,8 +476,9 @@ class Renamer(Plugin):
|
|||||||
def replaceDoubles(self, string):
|
def replaceDoubles(self, string):
|
||||||
return string.replace(' ', ' ').replace(' .', '.')
|
return string.replace(' ', ' ').replace(' .', '.')
|
||||||
|
|
||||||
def deleteEmptyFolder(self, folder):
|
def deleteEmptyFolder(self, folder, show_error = True):
|
||||||
|
|
||||||
|
loge = log.error if show_error else log.debug
|
||||||
for root, dirs, files in os.walk(folder):
|
for root, dirs, files in os.walk(folder):
|
||||||
|
|
||||||
for dir_name in dirs:
|
for dir_name in dirs:
|
||||||
@@ -474,9 +487,74 @@ class Renamer(Plugin):
|
|||||||
try:
|
try:
|
||||||
os.rmdir(full_path)
|
os.rmdir(full_path)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
|
loge('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.rmdir(folder)
|
os.rmdir(folder)
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
|
loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc()))
|
||||||
|
|
||||||
|
def checkSnatched(self):
|
||||||
|
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||||
|
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
||||||
|
failed_status = fireEvent('status.get', 'failed', single = True)
|
||||||
|
|
||||||
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
|
|
||||||
|
db = get_session()
|
||||||
|
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all()
|
||||||
|
|
||||||
|
if rels:
|
||||||
|
log.debug('Checking status snatched releases...')
|
||||||
|
|
||||||
|
scan_required = False
|
||||||
|
|
||||||
|
for rel in rels:
|
||||||
|
|
||||||
|
# Get current selected title
|
||||||
|
default_title = ''
|
||||||
|
for title in rel.movie.library.titles:
|
||||||
|
if title.default: default_title = title.title
|
||||||
|
|
||||||
|
# Check if movie has already completed and is manage tab (legacy db correction)
|
||||||
|
if rel.movie.status_id == done_status.get('id'):
|
||||||
|
log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title)
|
||||||
|
rel.status_id = ignored_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
continue
|
||||||
|
|
||||||
|
item = {}
|
||||||
|
for info in rel.info:
|
||||||
|
item[info.identifier] = info.value
|
||||||
|
|
||||||
|
movie_dict = fireEvent('movie.get', rel.movie_id, single = True)
|
||||||
|
|
||||||
|
# check status
|
||||||
|
downloadstatus = fireEvent('download.status', data = item, movie = movie_dict, single = True)
|
||||||
|
if not downloadstatus:
|
||||||
|
log.debug('Download status functionality is not implemented for active downloaders.')
|
||||||
|
scan_required = True
|
||||||
|
else:
|
||||||
|
log.debug('Download status: %s' , downloadstatus)
|
||||||
|
|
||||||
|
if downloadstatus == 'failed':
|
||||||
|
if self.conf('next_on_failed'):
|
||||||
|
fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
|
||||||
|
else:
|
||||||
|
rel.status_id = failed_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
log.info('Download of %s failed.', item['name'])
|
||||||
|
|
||||||
|
elif downloadstatus == 'completed':
|
||||||
|
log.info('Download of %s completed!', item['name'])
|
||||||
|
scan_required = True
|
||||||
|
|
||||||
|
elif downloadstatus == 'not_found':
|
||||||
|
log.info('%s not found in downloaders', item['name'])
|
||||||
|
rel.status_id = ignored_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd
|
||||||
|
if scan_required:
|
||||||
|
fireEvent('renamer.scan')
|
||||||
|
|||||||
@@ -303,7 +303,15 @@ class Scanner(Plugin):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if file_too_new:
|
if file_too_new:
|
||||||
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time.ctime(file_time[0]), identifier))
|
try:
|
||||||
|
time_string = time.ctime(file_time[0])
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
time_string = time.ctime(file_time[1])
|
||||||
|
except:
|
||||||
|
time_string = 'unknown'
|
||||||
|
|
||||||
|
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s', (time_string, identifier))
|
||||||
|
|
||||||
# Delete the unsorted list
|
# Delete the unsorted list
|
||||||
del group['unsorted_files']
|
del group['unsorted_files']
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||||
from couchpotato.core.helpers.variable import md5, getImdb, getTitle
|
from couchpotato.core.helpers.request import jsonified, getParam
|
||||||
|
from couchpotato.core.helpers.variable import md5, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
||||||
@@ -21,15 +23,24 @@ class Searcher(Plugin):
|
|||||||
in_progress = False
|
in_progress = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('searcher.all', self.all_movies)
|
addEvent('searcher.all', self.allMovies)
|
||||||
addEvent('searcher.single', self.single)
|
addEvent('searcher.single', self.single)
|
||||||
addEvent('searcher.correct_movie', self.correctMovie)
|
addEvent('searcher.correct_movie', self.correctMovie)
|
||||||
addEvent('searcher.download', self.download)
|
addEvent('searcher.download', self.download)
|
||||||
|
addEvent('searcher.try_next_release', self.tryNextRelease)
|
||||||
|
|
||||||
|
addApiView('searcher.try_next', self.tryNextReleaseView, docs = {
|
||||||
|
'desc': 'Marks the snatched results as ignored and try the next best release',
|
||||||
|
'params': {
|
||||||
|
'id': {'desc': 'The id of the movie'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
# Schedule cronjob
|
# Schedule cronjob
|
||||||
fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
|
||||||
|
|
||||||
def all_movies(self):
|
|
||||||
|
def allMovies(self):
|
||||||
|
|
||||||
if self.in_progress:
|
if self.in_progress:
|
||||||
log.info('Search already in progress')
|
log.info('Search already in progress')
|
||||||
@@ -138,7 +149,7 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
for info in nzb:
|
for info in nzb:
|
||||||
try:
|
try:
|
||||||
if not isinstance(nzb[info], (str, unicode, int, long)):
|
if not isinstance(nzb[info], (str, unicode, int, long, float)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rls_info = ReleaseInfo(
|
rls_info = ReleaseInfo(
|
||||||
@@ -239,7 +250,6 @@ class Searcher(Plugin):
|
|||||||
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
|
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
|
||||||
|
|
||||||
imdb_results = kwargs.get('imdb_results', False)
|
imdb_results = kwargs.get('imdb_results', False)
|
||||||
single_category = kwargs.get('single_category', False)
|
|
||||||
retention = Env.setting('retention', section = 'nzb')
|
retention = Env.setting('retention', section = 'nzb')
|
||||||
|
|
||||||
if nzb.get('seeds') is None and retention < nzb.get('age', 0):
|
if nzb.get('seeds') is None and retention < nzb.get('age', 0):
|
||||||
@@ -272,7 +282,7 @@ class Searcher(Plugin):
|
|||||||
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
||||||
|
|
||||||
# Contains lower quality string
|
# Contains lower quality string
|
||||||
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality, single_category = single_category):
|
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality):
|
||||||
log.info('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
|
log.info('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -317,14 +327,10 @@ class Searcher(Plugin):
|
|||||||
if len(movie_words) <= 2 and self.correctYear([nzb['name']], movie['library']['year'], 0):
|
if len(movie_words) <= 2 and self.correctYear([nzb['name']], movie['library']['year'], 0):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Get the nfo and see if it contains the proper imdb url
|
|
||||||
if self.checkNFO(nzb['name'], movie['library']['identifier']):
|
|
||||||
return True
|
|
||||||
|
|
||||||
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie_name, movie['library']['year']))
|
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie_name, movie['library']['year']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}, single_category = False):
|
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}):
|
||||||
|
|
||||||
name = nzb['name']
|
name = nzb['name']
|
||||||
size = nzb.get('size', 0)
|
size = nzb.get('size', 0)
|
||||||
@@ -355,9 +361,6 @@ class Searcher(Plugin):
|
|||||||
if found.get(allowed):
|
if found.get(allowed):
|
||||||
del found[allowed]
|
del found[allowed]
|
||||||
|
|
||||||
if (len(found) == 0 and single_category):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return not (found.get(preferred_quality['identifier']) and len(found) == 1)
|
return not (found.get(preferred_quality['identifier']) and len(found) == 1)
|
||||||
|
|
||||||
def checkIMDB(self, haystack, imdbId):
|
def checkIMDB(self, haystack, imdbId):
|
||||||
@@ -398,19 +401,6 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def checkNFO(self, check_name, imdb_id):
|
|
||||||
cache_key = 'srrdb.com %s' % simplifyString(check_name)
|
|
||||||
|
|
||||||
nfo = self.getCache(cache_key)
|
|
||||||
if not nfo:
|
|
||||||
try:
|
|
||||||
nfo = self.urlopen('http://www.srrdb.com/showfile.php?release=%s' % check_name, show_error = False)
|
|
||||||
self.setCache(cache_key, nfo)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return nfo and getImdb(nfo) == imdb_id
|
|
||||||
|
|
||||||
def couldBeReleased(self, wanted_quality, dates, pre_releases):
|
def couldBeReleased(self, wanted_quality, dates, pre_releases):
|
||||||
|
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
@@ -439,3 +429,37 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def tryNextReleaseView(self):
|
||||||
|
|
||||||
|
trynext = self.tryNextRelease(getParam('id'))
|
||||||
|
|
||||||
|
return jsonified({
|
||||||
|
'success': trynext
|
||||||
|
})
|
||||||
|
|
||||||
|
def tryNextRelease(self, movie_id, manual = False):
|
||||||
|
|
||||||
|
snatched_status = fireEvent('status.get', 'snatched', single = True)
|
||||||
|
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = get_session()
|
||||||
|
rels = db.query(Release).filter_by(
|
||||||
|
status_id = snatched_status.get('id'),
|
||||||
|
movie_id = movie_id
|
||||||
|
).all()
|
||||||
|
|
||||||
|
for rel in rels:
|
||||||
|
rel.status_id = ignored_status.get('id')
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
||||||
|
log.info('Trying next release for: %s', getTitle(movie_dict['library']))
|
||||||
|
fireEvent('searcher.single', movie_dict)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed searching for next release: %s', traceback.format_exc())
|
||||||
|
return False
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class StatusPlugin(Plugin):
|
|||||||
'downloaded': 'Downloaded',
|
'downloaded': 'Downloaded',
|
||||||
'wanted': 'Wanted',
|
'wanted': 'Wanted',
|
||||||
'snatched': 'Snatched',
|
'snatched': 'Snatched',
|
||||||
|
'failed': 'Failed',
|
||||||
'deleted': 'Deleted',
|
'deleted': 'Deleted',
|
||||||
'ignored': 'Ignored',
|
'ignored': 'Ignored',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
class Userscript(Plugin):
|
class Userscript(Plugin):
|
||||||
|
|
||||||
version = 2
|
version = 3
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addApiView('userscript.get/<random>/<path:filename>', self.getUserScript, static = True)
|
addApiView('userscript.get/<random>/<path:filename>', self.getUserScript, static = True)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name CouchPotato UserScript
|
// @name CouchPotato UserScript
|
||||||
// @description Add movies like a real CouchPotato
|
// @description Add movies like a real CouchPotato
|
||||||
|
// @grant none
|
||||||
// @version {{version}}
|
// @version {{version}}
|
||||||
|
|
||||||
// @match {{host}}*
|
// @match {{host}}*
|
||||||
@@ -44,21 +45,19 @@ function create() {
|
|||||||
return A;
|
return A;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof GM_addStyle == 'undefined'){
|
var addStyle = function(css) {
|
||||||
GM_addStyle = function(css) {
|
var head = document.getElementsByTagName('head')[0],
|
||||||
var head = document.getElementsByTagName('head')[0],
|
style = document.createElement('style');
|
||||||
style = document.createElement('style');
|
if (!head)
|
||||||
if (!head)
|
return;
|
||||||
return;
|
|
||||||
|
|
||||||
style.type = 'text/css';
|
style.type = 'text/css';
|
||||||
style.textContent = css;
|
style.textContent = css;
|
||||||
head.appendChild(style);
|
head.appendChild(style);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
GM_addStyle('\
|
addStyle('\
|
||||||
#cp_popup { font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -moz-border-radius: 6px 0px 0px 6px; -webkit-border-radius: 6px 0px 0px 6px; border-radius: 6px 0px 0px 6px; -moz-box-shadow: 0 0 20px rgba(0,0,0,0.5); -webkit-box-shadow: 0 0 20px rgba(0,0,0,0.5); box-shadow: 0 0 20px rgba(0,0,0,0.5); position:fixed; z-index:9999; bottom:0; right:0; font-size:15px; margin: 20px 0; display: block; background:#4E5969; } \
|
#cp_popup { font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -moz-border-radius: 6px 0px 0px 6px; -webkit-border-radius: 6px 0px 0px 6px; border-radius: 6px 0px 0px 6px; -moz-box-shadow: 0 0 20px rgba(0,0,0,0.5); -webkit-box-shadow: 0 0 20px rgba(0,0,0,0.5); box-shadow: 0 0 20px rgba(0,0,0,0.5); position:fixed; z-index:9999; bottom:0; right:0; font-size:15px; margin: 20px 0; display: block; background:#4E5969; } \
|
||||||
#cp_popup.opened { width: 492px; } \
|
#cp_popup.opened { width: 492px; } \
|
||||||
#cp_popup a#add_to { cursor:pointer; text-align:center; text-decoration:none; color: #000; display:block; padding:5px 0 5px 5px; } \
|
#cp_popup a#add_to { cursor:pointer; text-align:center; text-decoration:none; color: #000; display:block; padding:5px 0 5px 5px; } \
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class MovieResultModifier(Plugin):
|
|||||||
order.append(imdb)
|
order.append(imdb)
|
||||||
|
|
||||||
if item.get('via_imdb'):
|
if item.get('via_imdb'):
|
||||||
if order.index(imdb):
|
if order.count(imdb):
|
||||||
order.remove(imdb)
|
order.remove(imdb)
|
||||||
order.insert(0, imdb)
|
order.insert(0, imdb)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class IMDBAPI(MovieProvider):
|
|||||||
|
|
||||||
name_year = fireEvent('scanner.name_year', q, single = True)
|
name_year = fireEvent('scanner.name_year', q, single = True)
|
||||||
|
|
||||||
if not q or not name_year.get('name'):
|
if not q or not name_year or (name_year and not name_year.get('name')):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
cache_key = 'imdbapi.cache.%s' % q
|
cache_key = 'imdbapi.cache.%s' % q
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'Mysterbin',
|
'name': 'Mysterbin',
|
||||||
'description': '',
|
'description': 'Free provider, less accurate. See <a href="http://www.mysterbin.com/">Mysterbin</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class Mysterbin(NZBProvider):
|
|||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = False, single_category = False, single = True)
|
imdb_results = False, single = True)
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
results.append(new)
|
results.append(new)
|
||||||
self.found(new)
|
self.found(new)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'newzbin',
|
'name': 'newzbin',
|
||||||
|
'description': 'See <a href="https://www.newzbin2.es/">Newzbin</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -96,13 +96,12 @@ class Newznab(NZBProvider, RSS):
|
|||||||
url = "%s&%s" % (self.getUrl(host['host'], self.urls['search']), arguments)
|
url = "%s&%s" % (self.getUrl(host['host'], self.urls['search']), arguments)
|
||||||
|
|
||||||
cache_key = 'newznab.%s.%s.%s' % (host['host'], movie['library']['identifier'], cat_id[0])
|
cache_key = 'newznab.%s.%s.%s' % (host['host'], movie['library']['identifier'], cat_id[0])
|
||||||
single_cat = (len(cat_id) == 1 and cat_id[0] != self.cat_backup_id)
|
|
||||||
|
|
||||||
results = self.createItems(url, cache_key, host, single_cat = single_cat, movie = movie, quality = quality)
|
results = self.createItems(url, cache_key, host, movie = movie, quality = quality)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def createItems(self, url, cache_key, host, single_cat = False, movie = None, quality = None, for_feed = False):
|
def createItems(self, url, cache_key, host, movie = None, quality = None, for_feed = False):
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
@@ -146,7 +145,7 @@ class Newznab(NZBProvider, RSS):
|
|||||||
if not for_feed:
|
if not for_feed:
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = True, single_category = single_cat, single = True)
|
imdb_results = True, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'NZBClub',
|
'name': 'NZBClub',
|
||||||
'description': '',
|
'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class NZBClub(NZBProvider, RSS):
|
|||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = False, single_category = False, single = True)
|
imdb_results = False, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'nzbindex',
|
'name': 'nzbindex',
|
||||||
'description': 'Free provider, but less accurate.',
|
'description': 'Free provider, less accurate. See <a href="http://www.nzbindex.nl/">NZBIndex</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class NzbIndex(NZBProvider, RSS):
|
|||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = False, single_category = False, single = True)
|
imdb_results = False, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ config = [{
|
|||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'nzbmatrix',
|
'name': 'nzbmatrix',
|
||||||
'label': 'NZBMatrix',
|
'label': 'NZBMatrix',
|
||||||
|
'description': 'See <a href="https://nzbmatrix.com/">NZBMatrix</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
cat_ids = [
|
cat_ids = [
|
||||||
([50], ['bd50']),
|
([50], ['bd50']),
|
||||||
([42, 53], ['720p', '1080p']),
|
([42, 53], ['720p', '1080p']),
|
||||||
([2], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
([2, 9], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||||
([54], ['brrip']),
|
([54], ['brrip']),
|
||||||
([1], ['dvdr']),
|
([1], ['dvdr']),
|
||||||
]
|
]
|
||||||
@@ -49,7 +49,6 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
url = "%s?%s" % (self.urls['search'], arguments)
|
url = "%s?%s" % (self.urls['search'], arguments)
|
||||||
|
|
||||||
cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids)
|
cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids)
|
||||||
single_cat = True
|
|
||||||
|
|
||||||
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
if data:
|
if data:
|
||||||
@@ -86,7 +85,7 @@ class NZBMatrix(NZBProvider, RSS):
|
|||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = True, single_category = single_cat, single = True)
|
imdb_results = True, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
from .main import Nzbsrus
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Nzbsrus()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'nzbsrus',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'searcher',
|
||||||
|
'subtab': 'providers',
|
||||||
|
'name': 'nzbsrus',
|
||||||
|
'label': 'Nzbsrus',
|
||||||
|
'description': 'See <a href="https://www.nzbsrus.com/">NZBsRus</a>',
|
||||||
|
'wizard': True,
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'userid',
|
||||||
|
'label': 'User ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'api_key',
|
||||||
|
'default': '',
|
||||||
|
'label': 'Api Key',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'english_only',
|
||||||
|
'default': 1,
|
||||||
|
'type': 'bool',
|
||||||
|
'label': 'English only',
|
||||||
|
'description': 'Only search for English spoken movies on Nzbsrus',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
|
from couchpotato.core.helpers.rss import RSS
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
import time
|
||||||
|
import xml.etree.ElementTree as XMLTree
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
class Nzbsrus(NZBProvider, RSS):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'download': 'https://www.nzbsrus.com/nzbdownload_rss.php/%s',
|
||||||
|
'detail': 'https://www.nzbsrus.com/nzbdetails.php?id=%s',
|
||||||
|
'search': 'https://www.nzbsrus.com/api.php?extended=1&xml=1&listname={date,grabs}',
|
||||||
|
}
|
||||||
|
|
||||||
|
cat_ids = [
|
||||||
|
([90, 45, 51], ['720p', '1080p', 'brrip', 'bd50', 'dvdr']),
|
||||||
|
([48, 51], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||||
|
]
|
||||||
|
cat_backup_id = 240
|
||||||
|
|
||||||
|
def search(self, movie, quality):
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return results
|
||||||
|
|
||||||
|
cat_id_string = '&'.join(['c%s=1' % x for x in self.getCatId(quality.get('identifier'))])
|
||||||
|
|
||||||
|
arguments = tryUrlencode({
|
||||||
|
'searchtext': 'imdb:' + movie['library']['identifier'][2:],
|
||||||
|
'uid': self.conf('userid'),
|
||||||
|
'key': self.conf('api_key'),
|
||||||
|
'age': Env.setting('retention', section = 'nzb'),
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
# check for english_only
|
||||||
|
if self.conf('english_only'):
|
||||||
|
arguments += "&lang0=1&lang3=1&lang1=1"
|
||||||
|
|
||||||
|
url = "%s&%s&%s" % (self.urls['search'], arguments , cat_id_string)
|
||||||
|
|
||||||
|
cache_key = 'nzbsrus_1.%s.%s' % (movie['library'].get('identifier'), cat_id_string)
|
||||||
|
single_cat = True
|
||||||
|
|
||||||
|
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
data = XMLTree.fromstring(data)
|
||||||
|
nzbs = self.getElements(data, 'results/result')
|
||||||
|
except Exception, e:
|
||||||
|
log.debug('%s, %s', (self.getName(), e))
|
||||||
|
return results
|
||||||
|
|
||||||
|
for nzb in nzbs:
|
||||||
|
|
||||||
|
title = self.getTextElement(nzb, "name")
|
||||||
|
if 'error' in title.lower(): continue
|
||||||
|
|
||||||
|
id = self.getTextElement(nzb, "id")
|
||||||
|
size = int(round(int(self.getTextElement(nzb, "size")) / 1048576))
|
||||||
|
age = int(round((time.time() - int(self.getTextElement(nzb, "postdate"))) / 86400))
|
||||||
|
|
||||||
|
new = {
|
||||||
|
'id': id,
|
||||||
|
'type': 'nzb',
|
||||||
|
'provider': self.getName(),
|
||||||
|
'name': title,
|
||||||
|
'age': age,
|
||||||
|
'size': size,
|
||||||
|
'url': self.urls['download'] % id + self.getApiExt() + self.getTextElement(nzb, "key"),
|
||||||
|
'download': self.download,
|
||||||
|
'detail_url': self.urls['detail'] % id,
|
||||||
|
'description': self.getTextElement(nzb, "addtext"),
|
||||||
|
'check_nzb': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
|
nzb = new, movie = movie, quality = quality,
|
||||||
|
imdb_results = True, single = True)
|
||||||
|
|
||||||
|
if is_correct_movie:
|
||||||
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
|
results.append(new)
|
||||||
|
self.found(new)
|
||||||
|
|
||||||
|
return results
|
||||||
|
except SyntaxError:
|
||||||
|
log.error('Failed to parse XML response from Nzbsrus.com')
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def download(self, url = '', nzb_id = ''):
|
||||||
|
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
|
||||||
|
def getApiExt(self):
|
||||||
|
return '/%s/' % (self.conf('userid'))
|
||||||
@@ -10,6 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'KickAssTorrents',
|
'name': 'KickAssTorrents',
|
||||||
|
'description': 'See <a href="https://kat.ph/">KickAssTorrents</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ from couchpotato.core.event import fireEvent
|
|||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||||
import StringIO
|
|
||||||
import gzip
|
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -14,10 +12,9 @@ log = CPLog(__name__)
|
|||||||
class KickAssTorrents(TorrentProvider):
|
class KickAssTorrents(TorrentProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'test': 'http://www.kat.ph/',
|
'test': 'http://kat.ph/',
|
||||||
'detail': 'http://www.kat.ph/%s-t%s.html',
|
'detail': 'http://kat.ph/%s',
|
||||||
'search': 'http://www.kat.ph/i%s/',
|
'search': 'http://kat.ph/i%s/',
|
||||||
'download': 'http://torcache.net/',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cat_ids = [
|
cat_ids = [
|
||||||
@@ -60,11 +57,10 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
new = {
|
new = {
|
||||||
'type': 'torrent',
|
'type': 'torrent_magnet',
|
||||||
'check_nzb': False,
|
'check_nzb': False,
|
||||||
'description': '',
|
'description': '',
|
||||||
'provider': self.getName(),
|
'provider': self.getName(),
|
||||||
'download': self.download,
|
|
||||||
'score': 0,
|
'score': 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,9 +73,8 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
|
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
|
||||||
new['id'] = temp.get('id')[-8:]
|
new['id'] = temp.get('id')[-8:]
|
||||||
new['name'] = link.text
|
new['name'] = link.text
|
||||||
new['url'] = td.find_all('a', 'idownload')[1]['href']
|
new['url'] = td.find('a', 'imagnet')['href']
|
||||||
if new['url'][:2] == '//':
|
new['detail_url'] = self.urls['detail'] % link['href'][1:]
|
||||||
new['url'] = 'http:%s' % new['url']
|
|
||||||
new['score'] = 20 if td.find('a', 'iverif') else 0
|
new['score'] = 20 if td.find('a', 'iverif') else 0
|
||||||
elif column_name is 'size':
|
elif column_name is 'size':
|
||||||
new['size'] = self.parseSize(td.text)
|
new['size'] = self.parseSize(td.text)
|
||||||
@@ -95,7 +90,7 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
new['score'] += fireEvent('score.calculate', new, movie, single = True)
|
new['score'] += fireEvent('score.calculate', new, movie, single = True)
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
nzb = new, movie = movie, quality = quality,
|
nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = True, single_category = False, single = True)
|
imdb_results = True, single = True)
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
results.append(new)
|
results.append(new)
|
||||||
self.found(new)
|
self.found(new)
|
||||||
@@ -129,13 +124,3 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
age += tryInt(nr) * mult
|
age += tryInt(nr) * mult
|
||||||
|
|
||||||
return tryInt(age)
|
return tryInt(age)
|
||||||
|
|
||||||
def download(self, url = '', nzb_id = ''):
|
|
||||||
compressed_data = self.urlopen(url = url, headers = {'Referer': 'http://kat.ph/'})
|
|
||||||
|
|
||||||
compressedstream = StringIO.StringIO(compressed_data)
|
|
||||||
gzipper = gzip.GzipFile(fileobj = compressedstream)
|
|
||||||
data = gzipper.read()
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from main import PassThePopcorn
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return PassThePopcorn()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'passthepopcorn',
|
||||||
|
'groups': [{
|
||||||
|
'tab': 'searcher',
|
||||||
|
'subtab': 'providers',
|
||||||
|
'name': 'PassThePopcorn',
|
||||||
|
'description': 'See <a href="http://passthepopcorn.me">PassThePopcorn.me</a>',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'type': 'enabler',
|
||||||
|
'default': False
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'domain',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Proxy server',
|
||||||
|
'description': 'Domain for requests (HTTPS only!), keep empty to use default (tls.passthepopcorn.me).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'username',
|
||||||
|
'default': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'password',
|
||||||
|
'default': '',
|
||||||
|
'type': 'password',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
}]
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
|
from couchpotato.core.helpers.variable import getTitle, tryInt, mergeDicts
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||||
|
from dateutil.parser import parse
|
||||||
|
import cookielib
|
||||||
|
import htmlentitydefs
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PassThePopcorn(TorrentProvider):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'domain': 'https://tls.passthepopcorn.me',
|
||||||
|
'detail': 'https://tls.passthepopcorn.me/torrents.php?torrentid=%s',
|
||||||
|
'torrent': 'https://tls.passthepopcorn.me/torrents.php',
|
||||||
|
'login': 'https://tls.passthepopcorn.me/login.php',
|
||||||
|
'search': 'https://tls.passthepopcorn.me/search/%s/0/7/%d'
|
||||||
|
}
|
||||||
|
|
||||||
|
quality_search_params = {
|
||||||
|
'bd50': {'media': 'Blu-ray', 'format': 'BD50'},
|
||||||
|
'1080p': {'resolution': '1080p'},
|
||||||
|
'720p': {'resolution': '720p'},
|
||||||
|
'brrip': {'media': 'Blu-ray'},
|
||||||
|
'dvdr': {'resolution': 'anysd'},
|
||||||
|
'dvdrip': {'media': 'DVD'},
|
||||||
|
'scr': {'media': 'DVD-Screener'},
|
||||||
|
'r5': {'media': 'R5'},
|
||||||
|
'tc': {'media': 'TC'},
|
||||||
|
'ts': {'media': 'TS'},
|
||||||
|
'cam': {'media': 'CAM'}
|
||||||
|
}
|
||||||
|
|
||||||
|
post_search_filters = {
|
||||||
|
'bd50': {'Codec': ['BD50']},
|
||||||
|
'1080p': {'Resolution': ['1080p']},
|
||||||
|
'720p': {'Resolution': ['720p']},
|
||||||
|
'brrip': {'Source': ['Blu-ray'], 'Quality': ['High Definition'], 'Container': ['!ISO']},
|
||||||
|
'dvdr': {'Codec': ['DVD5', 'DVD9']},
|
||||||
|
'dvdrip': {'Source': ['DVD'], 'Codec': ['!DVD5', '!DVD9']},
|
||||||
|
'scr': {'Source': ['DVD-Screener']},
|
||||||
|
'r5': {'Source': ['R5']},
|
||||||
|
'tc': {'Source': ['TC']},
|
||||||
|
'ts': {'Source': ['TS']},
|
||||||
|
'cam': {'Source': ['CAM']}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotLoggedInHTTPError(urllib2.HTTPError):
|
||||||
|
def __init__(self, url, code, msg, headers, fp):
|
||||||
|
urllib2.HTTPError.__init__(self, url, code, msg, headers, fp)
|
||||||
|
|
||||||
|
class PTPHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
|
||||||
|
def http_error_302(self, req, fp, code, msg, headers):
|
||||||
|
log.debug("302 detected; redirected to %s" % headers['Location'])
|
||||||
|
if (headers['Location'] != 'login.php'):
|
||||||
|
return urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
|
||||||
|
else:
|
||||||
|
raise PassThePopcorn.NotLoggedInHTTPError(req.get_full_url(), code, msg, headers, fp)
|
||||||
|
|
||||||
|
def search(self, movie, quality):
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return results
|
||||||
|
|
||||||
|
movie_title = getTitle(movie['library'])
|
||||||
|
quality_id = quality['identifier']
|
||||||
|
|
||||||
|
log.info('Searching for %s at quality %s' % (movie_title, quality_id))
|
||||||
|
|
||||||
|
params = mergeDicts(self.quality_search_params[quality_id].copy(), {
|
||||||
|
'order_by': 'relevance',
|
||||||
|
'order_way': 'descending',
|
||||||
|
'searchstr': movie['library']['identifier']
|
||||||
|
})
|
||||||
|
|
||||||
|
# Do login for the cookies
|
||||||
|
if not self.login_opener and not self.login():
|
||||||
|
return results
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = '%s?json=noredirect&%s' % (self.urls['torrent'], tryUrlencode(params))
|
||||||
|
txt = self.urlopen(url, opener = self.login_opener)
|
||||||
|
res = json.loads(txt)
|
||||||
|
except:
|
||||||
|
log.error('Search on PassThePopcorn.me (%s) failed (could not decode JSON)' % params)
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not 'Movies' in res:
|
||||||
|
log.info("PTP search returned nothing for '%s' at quality '%s' with search parameters %s" % (movie_title, quality_id, params))
|
||||||
|
return []
|
||||||
|
|
||||||
|
authkey = res['AuthKey']
|
||||||
|
passkey = res['PassKey']
|
||||||
|
|
||||||
|
for ptpmovie in res['Movies']:
|
||||||
|
if not 'Torrents' in ptpmovie:
|
||||||
|
log.debug('Movie %s (%s) has NO torrents' % (ptpmovie['Title'], ptpmovie['Year']))
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.debug('Movie %s (%s) has %d torrents' % (ptpmovie['Title'], ptpmovie['Year'], len(ptpmovie['Torrents'])))
|
||||||
|
for torrent in ptpmovie['Torrents']:
|
||||||
|
torrent_id = tryInt(torrent['Id'])
|
||||||
|
torrentdesc = '%s %s %s' % (torrent['Resolution'], torrent['Source'], torrent['Codec'])
|
||||||
|
|
||||||
|
if 'GoldenPopcorn' in torrent and torrent['GoldenPopcorn']:
|
||||||
|
torrentdesc += ' HQ'
|
||||||
|
if 'Scene' in torrent and torrent['Scene']:
|
||||||
|
torrentdesc += ' Scene'
|
||||||
|
if 'RemasterTitle' in torrent and torrent['RemasterTitle']:
|
||||||
|
# eliminate odd characters...
|
||||||
|
torrentdesc += self.htmlToASCII(' %s' % torrent['RemasterTitle'])
|
||||||
|
|
||||||
|
torrentdesc += ' (%s)' % quality_id
|
||||||
|
torrent_name = re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) - %s' % (movie_title, ptpmovie['Year'], torrentdesc))
|
||||||
|
|
||||||
|
def extra_check(item):
|
||||||
|
return self.torrentMeetsQualitySpec(item, type)
|
||||||
|
|
||||||
|
def extra_score(item):
|
||||||
|
return 50 if torrent['GoldenPopcorn'] else 0
|
||||||
|
|
||||||
|
new = {
|
||||||
|
'id': torrent_id,
|
||||||
|
'type': 'torrent',
|
||||||
|
'provider': self.getName(),
|
||||||
|
'name': torrent_name,
|
||||||
|
'description': '',
|
||||||
|
'url': '%s?action=download&id=%d&authkey=%s&torrent_pass=%s' % (self.urls['torrent'], torrent_id, authkey, passkey),
|
||||||
|
'detail_url': self.urls['detail'] % torrent_id,
|
||||||
|
'date': tryInt(time.mktime(parse(torrent['UploadTime']).timetuple())),
|
||||||
|
'size': tryInt(torrent['Size']) / (1024 * 1024),
|
||||||
|
'provider': self.getName(),
|
||||||
|
'seeders': tryInt(torrent['Seeders']),
|
||||||
|
'leechers': tryInt(torrent['Leechers']),
|
||||||
|
'extra_score': extra_score,
|
||||||
|
'extra_check': extra_check,
|
||||||
|
'download': self.loginDownload,
|
||||||
|
}
|
||||||
|
|
||||||
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
|
|
||||||
|
if fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality):
|
||||||
|
results.append(new)
|
||||||
|
self.found(new)
|
||||||
|
|
||||||
|
return results
|
||||||
|
except:
|
||||||
|
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
|
||||||
|
cookieprocessor = urllib2.HTTPCookieProcessor(cookielib.CookieJar())
|
||||||
|
opener = urllib2.build_opener(cookieprocessor, PassThePopcorn.PTPHTTPRedirectHandler())
|
||||||
|
opener.addheaders = [
|
||||||
|
('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.75 Safari/537.1'),
|
||||||
|
('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
|
||||||
|
('Accept-Language', 'en-gb,en;q=0.5'),
|
||||||
|
('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
|
||||||
|
('Keep-Alive', '115'),
|
||||||
|
('Connection', 'keep-alive'),
|
||||||
|
('Cache-Control', 'max-age=0'),
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = opener.open(self.urls['login'], self.getLoginParams())
|
||||||
|
except urllib2.URLError as e:
|
||||||
|
log.error('Login to PassThePopcorn failed: %s' % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if response.getcode() == 200:
|
||||||
|
log.debug('Login HTTP status 200; seems successful')
|
||||||
|
self.login_opener = opener
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
log.error('Login to PassThePopcorn failed: returned code %d' % response.getcode())
|
||||||
|
return False
|
||||||
|
|
||||||
|
def torrentMeetsQualitySpec(self, torrent, quality):
|
||||||
|
|
||||||
|
if not quality in self.post_search_filters:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for field, specs in self.post_search_filters[quality].items():
|
||||||
|
matches_one = False
|
||||||
|
seen_one = False
|
||||||
|
|
||||||
|
if not field in torrent:
|
||||||
|
log.debug('Torrent with ID %s has no field "%s"; cannot apply post-search-filter for quality "%s"' % (torrent['Id'], field, quality))
|
||||||
|
continue
|
||||||
|
|
||||||
|
for spec in specs:
|
||||||
|
if len(spec) > 0 and spec[0] == '!':
|
||||||
|
# a negative rule; if the field matches, return False
|
||||||
|
if torrent[field] == spec[1:]:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# a positive rule; if any of the possible positive values match the field, return True
|
||||||
|
seen_one = True
|
||||||
|
if torrent[field] == spec:
|
||||||
|
matches_one = True
|
||||||
|
|
||||||
|
if seen_one and not matches_one:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def htmlToUnicode(self, text):
|
||||||
|
def fixup(m):
|
||||||
|
text = m.group(0)
|
||||||
|
if text[:2] == "&#":
|
||||||
|
# character reference
|
||||||
|
try:
|
||||||
|
if text[:3] == "&#x":
|
||||||
|
return unichr(int(text[3:-1], 16))
|
||||||
|
else:
|
||||||
|
return unichr(int(text[2:-1]))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# named entity
|
||||||
|
try:
|
||||||
|
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return text # leave as is
|
||||||
|
return re.sub("&#?\w+;", fixup, u'%s' % text)
|
||||||
|
|
||||||
|
def unicodeToASCII(self, text):
|
||||||
|
import unicodedata
|
||||||
|
return ''.join(c for c in unicodedata.normalize('NFKD', text) if unicodedata.category(c) != 'Mn')
|
||||||
|
|
||||||
|
def htmlToASCII(self, text):
|
||||||
|
return self.unicodeToASCII(self.htmlToUnicode(text))
|
||||||
|
|
||||||
|
def getLoginParams(self):
|
||||||
|
return tryUrlencode({
|
||||||
|
'username': self.conf('username'),
|
||||||
|
'password': self.conf('password'),
|
||||||
|
'keeplogged': '1',
|
||||||
|
'login': 'Login'
|
||||||
|
})
|
||||||
@@ -10,7 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'PublicHD',
|
'name': 'PublicHD',
|
||||||
'description': 'Public Torrent site with only HD content.',
|
'description': 'Public Torrent site with only HD content. See <a href="http://publichd.eu/">PublicHD</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
@@ -20,4 +20,4 @@ config = [{
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}]
|
}]
|
||||||
|
|||||||
@@ -15,19 +15,9 @@ class PublicHD(TorrentProvider):
|
|||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'test': 'http://publichd.eu',
|
'test': 'http://publichd.eu',
|
||||||
'download': 'http://publichd.eu/%s',
|
|
||||||
'detail': 'http://publichd.eu/index.php?page=torrent-details&id=%s',
|
'detail': 'http://publichd.eu/index.php?page=torrent-details&id=%s',
|
||||||
'search': 'http://publichd.eu/index.php',
|
'search': 'http://publichd.eu/index.php',
|
||||||
}
|
}
|
||||||
|
|
||||||
cat_ids = [
|
|
||||||
([9], ['bd50']),
|
|
||||||
([5], ['1080p']),
|
|
||||||
([2], ['720p']),
|
|
||||||
([15, 16], ['brrip']),
|
|
||||||
]
|
|
||||||
|
|
||||||
cat_backup_id = 0
|
|
||||||
http_time_between_calls = 0
|
http_time_between_calls = 0
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
@@ -39,9 +29,8 @@ class PublicHD(TorrentProvider):
|
|||||||
|
|
||||||
params = tryUrlencode({
|
params = tryUrlencode({
|
||||||
'page':'torrents',
|
'page':'torrents',
|
||||||
'search': getTitle(movie['library']) + ' ' + quality['identifier'],
|
'search': '%s %s' % (getTitle(movie['library']), movie['library']['year']),
|
||||||
'active': 1,
|
'active': 1,
|
||||||
'category': self.getCatId(quality['identifier'])[0]
|
|
||||||
})
|
})
|
||||||
url = '%s?%s' % (self.urls['search'], params)
|
url = '%s?%s' % (self.urls['search'], params)
|
||||||
|
|
||||||
@@ -58,7 +47,7 @@ class PublicHD(TorrentProvider):
|
|||||||
|
|
||||||
for result in entries[2:len(entries) - 1]:
|
for result in entries[2:len(entries) - 1]:
|
||||||
info_url = result.find(href = re.compile('torrent-details'))
|
info_url = result.find(href = re.compile('torrent-details'))
|
||||||
download = result.find(href = re.compile('\.torrent'))
|
download = result.find(href = re.compile('magnet:'))
|
||||||
|
|
||||||
if info_url and download:
|
if info_url and download:
|
||||||
|
|
||||||
@@ -67,12 +56,11 @@ class PublicHD(TorrentProvider):
|
|||||||
new = {
|
new = {
|
||||||
'id': url['id'][0],
|
'id': url['id'][0],
|
||||||
'name': info_url.string,
|
'name': info_url.string,
|
||||||
'type': 'torrent',
|
'type': 'torrent_magnet',
|
||||||
'check_nzb': False,
|
'check_nzb': False,
|
||||||
'description': '',
|
'description': '',
|
||||||
'provider': self.getName(),
|
'provider': self.getName(),
|
||||||
'download': self.download,
|
'url': download['href'],
|
||||||
'url': self.urls['download'] % download['href'],
|
|
||||||
'detail_url': self.urls['detail'] % url['id'][0],
|
'detail_url': self.urls['detail'] % url['id'][0],
|
||||||
'size': self.parseSize(result.find_all('td')[7].string),
|
'size': self.parseSize(result.find_all('td')[7].string),
|
||||||
'seeders': tryInt(result.find_all('td')[4].string),
|
'seeders': tryInt(result.find_all('td')[4].string),
|
||||||
@@ -82,7 +70,7 @@ class PublicHD(TorrentProvider):
|
|||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = False, single_category = False, single = True)
|
imdb_results = False, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
results.append(new)
|
results.append(new)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'SceneAccess',
|
'name': 'SceneAccess',
|
||||||
|
'description': 'See <a href="https://sceneaccess.eu/">SceneAccess</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class SceneAccess(TorrentProvider):
|
|||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = False, single_category = False, single = True)
|
imdb_results = False, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
results.append(new)
|
results.append(new)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'SceneHD',
|
'name': 'SceneHD',
|
||||||
|
'description': 'See <a href="http://scenehd.org">SceneHD</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class SceneHD(TorrentProvider):
|
|||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = imdb_results, single_category = False, single = True)
|
imdb_results = imdb_results, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
results.append(new)
|
results.append(new)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'ThePirateBay',
|
'name': 'ThePirateBay',
|
||||||
'description': 'The world\'s largest bittorrent tracker.',
|
'description': 'The world\'s largest bittorrent tracker. See <a href="http://fucktimkuik.org/">ThePirateBay</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ class ThePirateBay(TorrentProvider):
|
|||||||
'https://piratereverse.info',
|
'https://piratereverse.info',
|
||||||
'https://tpb.pirateparty.org.uk',
|
'https://tpb.pirateparty.org.uk',
|
||||||
'https://argumentomteemigreren.nl',
|
'https://argumentomteemigreren.nl',
|
||||||
|
'https://livepirate.com/',
|
||||||
|
'https://www.getpirate.com/',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -120,7 +122,7 @@ class ThePirateBay(TorrentProvider):
|
|||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = False, single_category = False, single = True)
|
imdb_results = False, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
results.append(new)
|
results.append(new)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'providers',
|
'subtab': 'providers',
|
||||||
'name': 'TorrentLeech',
|
'name': 'TorrentLeech',
|
||||||
|
'description': 'See <a href="http://torrentleech.org">TorrentLeech</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class TorrentLeech(TorrentProvider):
|
|||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
||||||
imdb_results = imdb_results, single_category = False, single = True)
|
imdb_results = imdb_results, single = True)
|
||||||
|
|
||||||
if is_correct_movie:
|
if is_correct_movie:
|
||||||
results.append(new)
|
results.append(new)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from couchpotato.core.helpers.encoding import tryUrlencode
|
|||||||
from couchpotato.core.helpers.variable import mergeDicts, getTitle
|
from couchpotato.core.helpers.variable import mergeDicts, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.trailer.base import TrailerProvider
|
from couchpotato.core.providers.trailer.base import TrailerProvider
|
||||||
from string import letters, digits
|
from string import digits, ascii_letters
|
||||||
import re
|
import re
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -46,7 +46,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
|
|
||||||
movie_name = getTitle(group['library'])
|
movie_name = getTitle(group['library'])
|
||||||
|
|
||||||
url = "%s?%s" % (self.url['backup'], tryUrlencode({'s':movie_name}))
|
url = "%s?%s" % (self.urls['backup'], tryUrlencode({'s':movie_name}))
|
||||||
data = self.getCache('hdtrailers.alt.%s' % group['library']['identifier'], url)
|
data = self.getCache('hdtrailers.alt.%s' % group['library']['identifier'], url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -100,7 +100,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def movieUrlName(self, string):
|
def movieUrlName(self, string):
|
||||||
safe_chars = letters + digits + ' '
|
safe_chars = ascii_letters + digits + ' '
|
||||||
r = ''.join([char if char in safe_chars else ' ' for char in string])
|
r = ''.join([char if char in safe_chars else ' ' for char in string])
|
||||||
name = re.sub('\s+' , '-', r).lower()
|
name = re.sub('\s+' , '-', r).lower()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
from bs4 import BeautifulSoup
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.userscript.base import UserscriptBase
|
from couchpotato.core.providers.userscript.base import UserscriptBase
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AlloCine(UserscriptBase):
|
class AlloCine(UserscriptBase):
|
||||||
|
|
||||||
@@ -15,11 +19,17 @@ class AlloCine(UserscriptBase):
|
|||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
|
||||||
html = BeautifulSoup(data)
|
name = None
|
||||||
title = html.find('title').contents[0].strip()
|
year = None
|
||||||
split = title.split(') - ')
|
|
||||||
|
|
||||||
name = split[0][:-5].strip()
|
try:
|
||||||
year = split[0][-4:]
|
start = data.find('<title>')
|
||||||
|
end = data.find('</title>', start)
|
||||||
|
page_title = data[start + len('<title>'):end].strip().split('-')
|
||||||
|
|
||||||
|
name = page_title[0].strip()
|
||||||
|
year = page_title[1].strip()[-4:]
|
||||||
|
return self.search(name, year)
|
||||||
|
except:
|
||||||
|
log.error('Failed parsing page for title and year: %s', traceback.format_exc())
|
||||||
|
|
||||||
return self.search(name, year)
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
from bs4 import BeautifulSoup
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.providers.userscript.base import UserscriptBase
|
from couchpotato.core.providers.userscript.base import UserscriptBase
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RottenTomatoes(UserscriptBase):
|
class RottenTomatoes(UserscriptBase):
|
||||||
|
|
||||||
@@ -16,7 +20,20 @@ class RottenTomatoes(UserscriptBase):
|
|||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
|
||||||
html = BeautifulSoup(data)
|
try:
|
||||||
title = html.find('span', {'itemprop':'name'}).text
|
name = None
|
||||||
info = fireEvent('scanner.name_year', title, single = True)
|
year = None
|
||||||
return self.search(info['name'], info['year'])
|
metas = re.findall("property=\"(video:release_date|og:title)\" content=\"([^\"]*)\"", data)
|
||||||
|
|
||||||
|
for meta in metas:
|
||||||
|
mname, mvalue = meta
|
||||||
|
if mname == 'og:title':
|
||||||
|
name = mvalue.decode('unicode_escape')
|
||||||
|
elif mname == 'video:release_date':
|
||||||
|
year = mvalue[:4]
|
||||||
|
|
||||||
|
if name and year:
|
||||||
|
return self.search(name, year)
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed parsing page for title and year: %s', traceback.format_exc())
|
||||||
|
|||||||
@@ -103,6 +103,22 @@ class Release(Entity):
|
|||||||
files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True)
|
files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True)
|
||||||
info = OneToMany('ReleaseInfo', cascade = 'all, delete-orphan')
|
info = OneToMany('ReleaseInfo', cascade = 'all, delete-orphan')
|
||||||
|
|
||||||
|
def to_dict(self, deep = {}, exclude = []):
|
||||||
|
orig_dict = super(Release, self).to_dict(deep = deep, exclude = exclude)
|
||||||
|
|
||||||
|
new_info = {}
|
||||||
|
for info in orig_dict.get('info', []):
|
||||||
|
|
||||||
|
value = info['value']
|
||||||
|
try: value = int(info['value'])
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
new_info[info['identifier']] = value
|
||||||
|
|
||||||
|
orig_dict['info'] = new_info
|
||||||
|
|
||||||
|
return orig_dict
|
||||||
|
|
||||||
|
|
||||||
class ReleaseInfo(Entity):
|
class ReleaseInfo(Entity):
|
||||||
"""Properties that can be bound to a file for off-line usage"""
|
"""Properties that can be bound to a file for off-line usage"""
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var CouchPotato = new Class({
|
|||||||
|
|
||||||
History.addEvent('change', self.openPage.bind(self));
|
History.addEvent('change', self.openPage.bind(self));
|
||||||
self.c.addEvent('click:relay(a[href^=/]:not([target]))', self.pushState.bind(self));
|
self.c.addEvent('click:relay(a[href^=/]:not([target]))', self.pushState.bind(self));
|
||||||
|
self.c.addEvent('click:relay(a[href^=http])', self.openDerefered.bind(self));
|
||||||
},
|
},
|
||||||
|
|
||||||
getOption: function(name){
|
getOption: function(name){
|
||||||
@@ -187,7 +188,7 @@ var CouchPotato = new Class({
|
|||||||
restart: function(message, title){
|
restart: function(message, title){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.blockPage(message || 'Restarting... please wait. If this takes to long, something must have gone wrong.', title);
|
self.blockPage(message || 'Restarting... please wait. If this takes too long, something must have gone wrong.', title);
|
||||||
Api.request('app.restart');
|
Api.request('app.restart');
|
||||||
self.checkAvailable(1000);
|
self.checkAvailable(1000);
|
||||||
},
|
},
|
||||||
@@ -216,7 +217,7 @@ var CouchPotato = new Class({
|
|||||||
|
|
||||||
Updater.check(onComplete)
|
Updater.check(onComplete)
|
||||||
|
|
||||||
self.blockPage('Please wait. If this takes to long, something must have gone wrong.', 'Checking for updates');
|
self.blockPage('Please wait. If this takes too long, something must have gone wrong.', 'Checking for updates');
|
||||||
self.checkAvailable(3000);
|
self.checkAvailable(3000);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -269,6 +270,17 @@ var CouchPotato = new Class({
|
|||||||
|
|
||||||
createUrl: function(action, params){
|
createUrl: function(action, params){
|
||||||
return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '')
|
return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '')
|
||||||
|
},
|
||||||
|
|
||||||
|
openDerefered: function(e, el){
|
||||||
|
(e).stop();
|
||||||
|
|
||||||
|
var url = 'http://www.dereferer.org/?' + el.get('href');
|
||||||
|
|
||||||
|
if(el.get('target') == '_blank' || (e.meta && Browser.Platform.mac) || (e.control && !Browser.Platform.mac))
|
||||||
|
window.open(url);
|
||||||
|
else
|
||||||
|
window.location = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -419,15 +431,18 @@ function randomString(length, extra) {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Array.implement('sortBy', function(){
|
Array.implement({
|
||||||
keyPaths.empty();
|
sortBy: function(){
|
||||||
Array.each(arguments, function(argument) {
|
keyPaths.empty();
|
||||||
switch (typeOf(argument)) {
|
|
||||||
case "array": saveKeyPath(argument); break;
|
Array.each(arguments, function(argument) {
|
||||||
case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break;
|
switch (typeOf(argument)) {
|
||||||
}
|
case "array": saveKeyPath(argument); break;
|
||||||
});
|
case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break;
|
||||||
return this.sort(comparer);
|
}
|
||||||
|
});
|
||||||
|
return this.sort(comparer);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ var AboutSettingTab = new Class({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
new Element('dt[text=Updater]'),
|
||||||
|
self.updater_type = new Element('dd.updater'),
|
||||||
new Element('dt[text=ID]'),
|
new Element('dt[text=ID]'),
|
||||||
new Element('dd', {'text': App.getOption('pid')}),
|
new Element('dd', {'text': App.getOption('pid')}),
|
||||||
new Element('dt[text=Directories]'),
|
new Element('dt[text=Directories]'),
|
||||||
@@ -103,12 +105,8 @@ var AboutSettingTab = new Class({
|
|||||||
),
|
),
|
||||||
new Element('div.donate', {
|
new Element('div.donate', {
|
||||||
'html':
|
'html':
|
||||||
'Or, buy me a (24 pack) Pepsi, for while I\'m coding ;)' +
|
'Or support me via:' +
|
||||||
'<form action="https://www.paypal.com/cgi-bin/webscr" method="post">' +
|
'<iframe src="http://couchpota.to/donate.html" style="border:none; height: 200px;" scrolling="no"></iframe>'
|
||||||
'<input type="hidden" name="cmd" value="_s-xclick">' +
|
|
||||||
'<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHPwYJKoZIhvcNAQcEoIIHMDCCBywCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBUq4nmDbyDV07WGd0wijGKDf/OWNA7hd2NRaxTaCVyAoaZQEGE0DQuDUHBBk7/oqWTo5Rcp1XN0A0nbYkrajWgY21lzSivGrDlWys1UjZaq0JOI89WWcy4YJMWX8chjECxicmVvk2OWgI/SOe7fhHdK4BNhQZO9ccLpfxTi2XnEDELMAkGBSsOAwIaBQAwgbwGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI0YRtA8KWmG6AgZjKL/bDyL4JG3JN/GlKsb6863opfWLUjwJf7P7DeR10j0YZQds516TcRrSLqCSoII9KpivUUBCMknWmch8xUy4i0tyb26aNh3un7HQ6lVBQLGfnqVvKFC0iUNa6i0gTLufDKuVjzl+WkqqiOvgsg8rAE3IG2oYBCAAgzJbvyZkD4SoMr74pWAvQS19gwGG56JWNIdCy5eTXu6CCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTEwMDcyNjA4NDA0NlowIwYJKoZIhvcNAQkEMRYEFICseROR67FmINx7sa6IYP7eCVoaMA0GCSqGSIb3DQEBAQUABIGAfDx2KDyUHT6ISrTSnqtVWUHJWGjtM2T41m464maJ6nH7pEu6JZUHf53vD7Ey7d0MLFmF3IfGyIw2zAGfyEJHldeluPccFLhDmrDbRdxM0D/zwtWrYUwVXKQ4v3rskdp0avadX9ZRWrQplJkVsJDcLvRY4P/EhScBiA5ughJS7xc=-----END PKCS7-----">' +
|
|
||||||
'<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">' +
|
|
||||||
'</form>'
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -119,6 +117,7 @@ var AboutSettingTab = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
var date = new Date(json.version.date * 1000);
|
var date = new Date(json.version.date * 1000);
|
||||||
self.version_text.set('text', json.version.hash + ' ('+date.toUTCString()+')');
|
self.version_text.set('text', json.version.hash + ' ('+date.toUTCString()+')');
|
||||||
|
self.updater_type.set('text', json.version.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ window.addEvent('domready', function(){
|
|||||||
'IMDB': IMDBAction
|
'IMDB': IMDBAction
|
||||||
,'Trailer': TrailerAction
|
,'Trailer': TrailerAction
|
||||||
,'Releases': ReleaseAction
|
,'Releases': ReleaseAction
|
||||||
|
|
||||||
,'Edit': new Class({
|
,'Edit': new Class({
|
||||||
|
|
||||||
Extends: MovieAction,
|
Extends: MovieAction,
|
||||||
@@ -74,20 +73,23 @@ window.addEvent('domready', function(){
|
|||||||
new Element('option', {
|
new Element('option', {
|
||||||
'text': alt.title
|
'text': alt.title
|
||||||
}).inject(self.title_select);
|
}).inject(self.title_select);
|
||||||
|
|
||||||
if(alt['default'])
|
if(alt['default'])
|
||||||
self.title_select.set('value', alt.title);
|
self.title_select.set('value', alt.title);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Quality.getActiveProfiles().each(function(profile){
|
Quality.getActiveProfiles().each(function(profile){
|
||||||
|
|
||||||
|
var profile_id = profile.id ? profile.id : profile.data.id;
|
||||||
|
|
||||||
new Element('option', {
|
new Element('option', {
|
||||||
'value': profile.id ? profile.id : profile.data.id,
|
'value': profile_id,
|
||||||
'text': profile.label ? profile.label : profile.data.label
|
'text': profile.label ? profile.label : profile.data.label
|
||||||
}).inject(self.profile_select);
|
}).inject(self.profile_select);
|
||||||
|
|
||||||
if(self.movie.profile)
|
if(self.movie.profile && self.movie.profile.data.id == profile_id)
|
||||||
self.profile_select.set('value', profile.id ? profile.id : profile.data.id);
|
self.profile_select.set('value', profile_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -170,7 +172,7 @@ window.addEvent('domready', function(){
|
|||||||
(e).preventDefault();
|
(e).preventDefault();
|
||||||
|
|
||||||
if(!self.delete_container){
|
if(!self.delete_container){
|
||||||
self.delete_container = new Element('div.delete_container').adopt(
|
self.delete_container = new Element('div.buttons.delete_container').adopt(
|
||||||
new Element('a.cancel', {
|
new Element('a.cancel', {
|
||||||
'text': 'Cancel',
|
'text': 'Cancel',
|
||||||
'events': {
|
'events': {
|
||||||
|
|||||||
@@ -24,39 +24,36 @@
|
|||||||
}
|
}
|
||||||
.page.settings .tabs a {
|
.page.settings .tabs a {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 11px 15px;
|
padding: 7px 15px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
transition: all 0.1s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
.page.settings .tabs a:hover, .page.settings .tabs .active a {
|
.page.settings .tabs a:hover,
|
||||||
|
.page.settings .tabs .active a {
|
||||||
background: rgb(78, 89, 105);
|
background: rgb(78, 89, 105);
|
||||||
font-weight: bold;
|
|
||||||
font-size: 25px;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
.page.settings .tabs > li {
|
||||||
|
border-bottom: 1px solid rgb(78, 89, 105);
|
||||||
|
}
|
||||||
|
|
||||||
.page.settings .tabs .subtabs {
|
.page.settings .tabs .subtabs {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
margin: -5px 0 10px;
|
||||||
transition: all 1s ease-in-out;
|
|
||||||
max-height: 0;
|
|
||||||
}
|
}
|
||||||
.page.settings .tabs > .active .subtabs {
|
|
||||||
max-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page.settings .tabs .subtabs a {
|
.page.settings .tabs .subtabs a {
|
||||||
font-size: 15px;
|
font-size: 13px;
|
||||||
padding: 1px 15px;
|
padding: 0 15px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
transition: all .3s ease-in-out;
|
||||||
background: rgba(78, 89, 105, 0.4);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page.settings .tabs .subtabs .active a {
|
.page.settings .tabs .subtabs .active a {
|
||||||
font-weight: bold;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: rgb(78, 89, 105);
|
background: rgb(78, 89, 105);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<link href="{{ url_for('web.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" />
|
<link href="{{ url_for('web.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" />
|
||||||
<link rel="apple-touch-icon" href="{{ url_for('web.static', filename='images/homescreen.png') }}" />
|
<link rel="apple-touch-icon" href="{{ url_for('web.static', filename='images/homescreen.png') }}" />
|
||||||
|
|
||||||
<script type="text/javascript" src="https://www.youtube.com/player_api" defer="defer"></script>
|
<script type="text/javascript" src="https://www.youtube.com/player_api" defer="defer"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
new Uniform();
|
new Uniform();
|
||||||
|
|
||||||
Api.setup({
|
Api.setup({
|
||||||
'host': {{ fireEvent('app.api_url', single = True)|tojson|safe }},
|
|
||||||
'url': {{ url_for('api.index')|tojson|safe }},
|
'url': {{ url_for('api.index')|tojson|safe }},
|
||||||
'path_sep': {{ sep|tojson|safe }},
|
'path_sep': {{ sep|tojson|safe }},
|
||||||
'is_remote': false
|
'is_remote': false
|
||||||
|
|||||||
Reference in New Issue
Block a user