Merge branch 'refs/heads/develop' into desktop
This commit is contained in:
+3
-1
@@ -1,3 +1,5 @@
|
||||
*.pyc
|
||||
/data/
|
||||
/_source/
|
||||
/_source/
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
+5
-1
@@ -9,7 +9,7 @@ import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import time
|
||||
|
||||
# Root path
|
||||
base_path = dirname(os.path.abspath(__file__))
|
||||
@@ -96,6 +96,10 @@ class Loader(object):
|
||||
except:
|
||||
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:]
|
||||
subprocess.Popen(args)
|
||||
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
|
||||
|
||||
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
|
||||
* 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`.
|
||||
|
||||
@@ -305,7 +305,7 @@ class SourceUpdater(BaseUpdater):
|
||||
if not os.path.isdir(dirname):
|
||||
self.makeDir(dirname)
|
||||
|
||||
os.rename(fromfile, tofile)
|
||||
shutil.move(fromfile, tofile)
|
||||
try:
|
||||
existing_files.remove(tofile)
|
||||
except ValueError:
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from base64 import b32decode, b16encode
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import toSafeString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -12,14 +15,25 @@ class Downloader(Plugin):
|
||||
|
||||
type = []
|
||||
|
||||
torrent_sources = [
|
||||
'http://torrage.com/torrent/%s.torrent',
|
||||
'http://torrage.ws/torrent/%s.torrent',
|
||||
'http://torcache.net/torrent/%s.torrent',
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
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
|
||||
|
||||
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):
|
||||
name = os.path.join(self.createNzbName(data, movie))
|
||||
@@ -41,6 +55,26 @@ class Downloader(Plugin):
|
||||
|
||||
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):
|
||||
return not self.isEnabled(manual)
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ log = CPLog(__name__)
|
||||
|
||||
class Blackhole(Downloader):
|
||||
|
||||
type = ['nzb', 'torrent']
|
||||
type = ['nzb', 'torrent', 'torrent_magnet']
|
||||
|
||||
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
|
||||
|
||||
directory = self.conf('directory')
|
||||
@@ -20,8 +22,16 @@ class Blackhole(Downloader):
|
||||
else:
|
||||
try:
|
||||
if not filedata or len(filedata) < 50:
|
||||
log.error('No nzb/torrent available!')
|
||||
return False
|
||||
try:
|
||||
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))
|
||||
|
||||
|
||||
@@ -35,11 +35,17 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'manual',
|
||||
'default': 0,
|
||||
'default': False,
|
||||
'type': 'bool',
|
||||
'advanced': True,
|
||||
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
||||
},
|
||||
{
|
||||
'name': 'delete_failed',
|
||||
'default': True,
|
||||
'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.logger import CPLog
|
||||
import traceback
|
||||
import json
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -39,14 +40,14 @@ class Sabnzbd(Downloader):
|
||||
|
||||
try:
|
||||
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:
|
||||
data = self.urlopen(url, timeout = 60, show_error = False)
|
||||
sab = self.urlopen(url, timeout = 60, show_error = False)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
log.error('Failed sending release: %s', traceback.format_exc())
|
||||
return False
|
||||
|
||||
result = data.strip()
|
||||
result = sab.strip()
|
||||
if not result:
|
||||
log.error("SABnzbd didn't return anything.")
|
||||
return False
|
||||
@@ -61,3 +62,100 @@ class Sabnzbd(Downloader):
|
||||
else:
|
||||
log.error("Unknown error: " + result[:40])
|
||||
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 urllib import quote_plus
|
||||
import re
|
||||
import traceback
|
||||
import unicodedata
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -30,8 +31,8 @@ def toUnicode(original, *args):
|
||||
return ek(original, *args)
|
||||
except:
|
||||
raise
|
||||
except UnicodeDecodeError:
|
||||
log.error('Unable to decode value: %s... ', repr(original)[:20])
|
||||
except:
|
||||
log.error('Unable to decode value "%s..." : %s ', (repr(original)[:20], traceback.format_exc()))
|
||||
ascii_text = str(original).encode('string_escape')
|
||||
return toUnicode(ascii_text)
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ var NotificationBase = new Class({
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class NMJ(Notification):
|
||||
|
||||
def __init__(self):
|
||||
addEvent('renamer.after', self.addToLibrary)
|
||||
|
||||
addApiView(self.testNotifyName(), self.test)
|
||||
addApiView('notify.nmj.auto_config', self.autoConfig)
|
||||
|
||||
def autoConfig(self):
|
||||
@@ -76,7 +76,7 @@ class NMJ(Notification):
|
||||
mount = self.conf('mount')
|
||||
database = self.conf('database')
|
||||
|
||||
if self.mount:
|
||||
if mount:
|
||||
log.debug('Try to mount network drive via url: %s', (mount))
|
||||
try:
|
||||
data = self.urlopen(mount)
|
||||
@@ -114,3 +114,8 @@ class NMJ(Notification):
|
||||
|
||||
def failed(self):
|
||||
return jsonified({'success': False})
|
||||
|
||||
def test(self):
|
||||
return jsonified({'success': self.addToLibrary()})
|
||||
|
||||
|
||||
|
||||
@@ -15,14 +15,22 @@ class NotifyMyAndroid(Notification):
|
||||
nma.addkey(keys)
|
||||
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
|
||||
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:
|
||||
if not response[str(key)]['code'] == u'200':
|
||||
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):
|
||||
if self.isDisabled(): return
|
||||
|
||||
for host in [x.strip() for x in self.conf('host').split(",")]:
|
||||
self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host)
|
||||
self.send({'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}, host)
|
||||
hosts = [x.strip() for x in self.conf('host').split(",")]
|
||||
successful = 0
|
||||
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):
|
||||
|
||||
|
||||
@@ -340,6 +340,32 @@
|
||||
.movies .movie .hide_trailer.hide {
|
||||
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 {
|
||||
display: block;
|
||||
|
||||
@@ -140,28 +140,33 @@ var Movie = new Class({
|
||||
self.profile.getTypes().each(function(type){
|
||||
|
||||
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.set('title', q.get('title') + ' Will finish searching for this movie if this quality is found.')
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 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),
|
||||
status = Status.get(release.status_id);
|
||||
|
||||
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
|
||||
var q = self.addQuality(release.quality_id)
|
||||
if (status && q)
|
||||
|
||||
if (status && q && !q.hasClass(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){
|
||||
self.actions.adopt(
|
||||
self.action[key.toLowerCase()] = new self.options.actions[key](self)
|
||||
)
|
||||
self.action[key.toLowerCase()] = action = new self.options.actions[key](self)
|
||||
if(action.el)
|
||||
self.actions.adopt(action)
|
||||
});
|
||||
|
||||
if(!self.data.library.rating)
|
||||
@@ -175,7 +180,8 @@ var Movie = new Class({
|
||||
var q = Quality.getQuality(quality_id);
|
||||
return new Element('span', {
|
||||
'text': q.label,
|
||||
'class': 'q_'+q.identifier + ' q_id' + q.id
|
||||
'class': 'q_'+q.identifier + ' q_id' + q.id,
|
||||
'title': ''
|
||||
}).inject(self.quality);
|
||||
|
||||
},
|
||||
@@ -280,6 +286,31 @@ var MovieAction = new Class({
|
||||
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(){
|
||||
return this.el || null
|
||||
}
|
||||
@@ -298,19 +329,11 @@ var IMDBAction = new Class({
|
||||
|
||||
self.el = new Element('a.imdb', {
|
||||
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
|
||||
'events': {
|
||||
'click': self.gotoIMDB.bind(self)
|
||||
}
|
||||
'href': 'http://www.imdb.com/title/'+self.id+'/',
|
||||
'target': '_blank'
|
||||
});
|
||||
|
||||
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({
|
||||
|
||||
Extends: MovieAction,
|
||||
id: null,
|
||||
|
||||
create: function(){
|
||||
var self = this;
|
||||
|
||||
self.id = self.movie.get('identifier');
|
||||
|
||||
self.el = new Element('a.releases.icon.download', {
|
||||
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
|
||||
'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){
|
||||
var self = this;
|
||||
(e).preventDefault();
|
||||
if(e)
|
||||
(e).preventDefault();
|
||||
|
||||
if(!self.options_container){
|
||||
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');
|
||||
|
||||
// Header
|
||||
@@ -354,29 +392,35 @@ var ReleaseAction = new Class({
|
||||
new Element('span.provider', {'text': 'Provider'})
|
||||
).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),
|
||||
quality = Quality.getProfile(release.quality_id) || {},
|
||||
info = release.info;
|
||||
|
||||
try {
|
||||
var details_url = info.filter(function(item){ return item.identifier == 'detail_url' }).pick().value;
|
||||
} catch(e){}
|
||||
if( status.identifier == 'ignored' || status.identifier == 'failed'){
|
||||
self.last_release = release;
|
||||
}
|
||||
else if(!self.next_release && status.identifier == 'available'){
|
||||
self.next_release = release;
|
||||
}
|
||||
|
||||
// Create release
|
||||
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
|
||||
}).adopt(
|
||||
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.quality', {'text': quality.label || 'n/a'}),
|
||||
new Element('span.size', {'text': (self.get(release, 'size'))}),
|
||||
new Element('span.quality', {'text': quality.get('label') || 'n/a'}),
|
||||
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.score', {'text': self.get(release, 'score')}),
|
||||
new Element('span.provider', {'text': self.get(release, 'provider')}),
|
||||
details_url ? new Element('a.info.icon', {
|
||||
'href': details_url,
|
||||
release.info['detail_url'] ? new Element('a.info.icon', {
|
||||
'href': release.info['detail_url'],
|
||||
'target': '_blank'
|
||||
}) : null,
|
||||
new Element('a.download.icon', {
|
||||
@@ -400,17 +444,37 @@ var ReleaseAction = new Class({
|
||||
).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);
|
||||
},
|
||||
|
||||
get: function(release, type){
|
||||
var self = this;
|
||||
|
||||
return (release.info.filter(function(info){
|
||||
return type == info.identifier
|
||||
}).pick() || {}).value || 'n/a'
|
||||
return release.info[type] || 'n/a'
|
||||
},
|
||||
|
||||
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 {
|
||||
padding: 0 5px;
|
||||
content: ")";
|
||||
}
|
||||
|
||||
.movie_result .info h2 span:before { content: "("; }
|
||||
|
||||
@@ -170,7 +170,9 @@ class Release(Plugin):
|
||||
|
||||
# Get matching provider
|
||||
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({
|
||||
'profile': {'types': {'quality': {}}},
|
||||
|
||||
@@ -73,6 +73,22 @@ config = [{
|
||||
'description': 'Cleanup leftover files after successful rename.',
|
||||
'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',
|
||||
'type': 'bool',
|
||||
@@ -86,15 +102,6 @@ config = [{
|
||||
'label': 'Separator',
|
||||
'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',
|
||||
|
||||
@@ -6,13 +6,13 @@ from couchpotato.core.helpers.request import jsonified
|
||||
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle
|
||||
from couchpotato.core.logger import CPLog
|
||||
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
|
||||
import errno
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import traceback
|
||||
import errno
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -28,9 +28,11 @@ class Renamer(Plugin):
|
||||
})
|
||||
|
||||
addEvent('renamer.scan', self.scan)
|
||||
addEvent('renamer.check_snatched', self.checkSnatched)
|
||||
|
||||
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):
|
||||
|
||||
@@ -314,6 +316,7 @@ class Renamer(Plugin):
|
||||
break
|
||||
|
||||
# Remove files
|
||||
delete_folders = []
|
||||
for src in remove_files:
|
||||
|
||||
if isinstance(src, File):
|
||||
@@ -327,10 +330,19 @@ class Renamer(Plugin):
|
||||
try:
|
||||
if os.path.isfile(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:
|
||||
log.error('Failed removing %s: %s', (src, traceback.format_exc()))
|
||||
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
|
||||
group['renamed_files'] = []
|
||||
for src in rename_files:
|
||||
@@ -464,8 +476,9 @@ class Renamer(Plugin):
|
||||
def replaceDoubles(self, string):
|
||||
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 dir_name in dirs:
|
||||
@@ -474,9 +487,74 @@ class Renamer(Plugin):
|
||||
try:
|
||||
os.rmdir(full_path)
|
||||
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:
|
||||
os.rmdir(folder)
|
||||
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
|
||||
|
||||
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
|
||||
del group['unsorted_files']
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from couchpotato import get_session
|
||||
from couchpotato.api import addApiView
|
||||
from couchpotato.core.event import addEvent, fireEvent
|
||||
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.plugins.base import Plugin
|
||||
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
||||
@@ -21,15 +23,24 @@ class Searcher(Plugin):
|
||||
in_progress = False
|
||||
|
||||
def __init__(self):
|
||||
addEvent('searcher.all', self.all_movies)
|
||||
addEvent('searcher.all', self.allMovies)
|
||||
addEvent('searcher.single', self.single)
|
||||
addEvent('searcher.correct_movie', self.correctMovie)
|
||||
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
|
||||
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:
|
||||
log.info('Search already in progress')
|
||||
@@ -138,7 +149,7 @@ class Searcher(Plugin):
|
||||
|
||||
for info in nzb:
|
||||
try:
|
||||
if not isinstance(nzb[info], (str, unicode, int, long)):
|
||||
if not isinstance(nzb[info], (str, unicode, int, long, float)):
|
||||
continue
|
||||
|
||||
rls_info = ReleaseInfo(
|
||||
@@ -239,7 +250,6 @@ class Searcher(Plugin):
|
||||
def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs):
|
||||
|
||||
imdb_results = kwargs.get('imdb_results', False)
|
||||
single_category = kwargs.get('single_category', False)
|
||||
retention = Env.setting('retention', section = 'nzb')
|
||||
|
||||
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)
|
||||
|
||||
# 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']))
|
||||
return False
|
||||
|
||||
@@ -317,14 +327,10 @@ class Searcher(Plugin):
|
||||
if len(movie_words) <= 2 and self.correctYear([nzb['name']], movie['library']['year'], 0):
|
||||
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']))
|
||||
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']
|
||||
size = nzb.get('size', 0)
|
||||
@@ -355,9 +361,6 @@ class Searcher(Plugin):
|
||||
if found.get(allowed):
|
||||
del found[allowed]
|
||||
|
||||
if (len(found) == 0 and single_category):
|
||||
return False
|
||||
|
||||
return not (found.get(preferred_quality['identifier']) and len(found) == 1)
|
||||
|
||||
def checkIMDB(self, haystack, imdbId):
|
||||
@@ -398,19 +401,6 @@ class Searcher(Plugin):
|
||||
|
||||
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):
|
||||
|
||||
now = int(time.time())
|
||||
@@ -439,3 +429,37 @@ class Searcher(Plugin):
|
||||
|
||||
|
||||
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',
|
||||
'wanted': 'Wanted',
|
||||
'snatched': 'Snatched',
|
||||
'failed': 'Failed',
|
||||
'deleted': 'Deleted',
|
||||
'ignored': 'Ignored',
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ log = CPLog(__name__)
|
||||
|
||||
class Userscript(Plugin):
|
||||
|
||||
version = 2
|
||||
version = 3
|
||||
|
||||
def __init__(self):
|
||||
addApiView('userscript.get/<random>/<path:filename>', self.getUserScript, static = True)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name CouchPotato UserScript
|
||||
// @description Add movies like a real CouchPotato
|
||||
// @grant none
|
||||
// @version {{version}}
|
||||
|
||||
// @match {{host}}*
|
||||
@@ -44,21 +45,19 @@ function create() {
|
||||
return A;
|
||||
}
|
||||
|
||||
if (typeof GM_addStyle == 'undefined'){
|
||||
GM_addStyle = function(css) {
|
||||
var head = document.getElementsByTagName('head')[0],
|
||||
style = document.createElement('style');
|
||||
if (!head)
|
||||
return;
|
||||
var addStyle = function(css) {
|
||||
var head = document.getElementsByTagName('head')[0],
|
||||
style = document.createElement('style');
|
||||
if (!head)
|
||||
return;
|
||||
|
||||
style.type = 'text/css';
|
||||
style.textContent = css;
|
||||
head.appendChild(style);
|
||||
}
|
||||
style.type = 'text/css';
|
||||
style.textContent = css;
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
// 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.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; } \
|
||||
|
||||
@@ -31,7 +31,7 @@ class MovieResultModifier(Plugin):
|
||||
order.append(imdb)
|
||||
|
||||
if item.get('via_imdb'):
|
||||
if order.index(imdb):
|
||||
if order.count(imdb):
|
||||
order.remove(imdb)
|
||||
order.insert(0, imdb)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class IMDBAPI(MovieProvider):
|
||||
|
||||
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 []
|
||||
|
||||
cache_key = 'imdbapi.cache.%s' % q
|
||||
|
||||
@@ -10,7 +10,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'name': 'Mysterbin',
|
||||
'description': '',
|
||||
'description': 'Free provider, less accurate. See <a href="http://www.mysterbin.com/">Mysterbin</a>',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -88,7 +88,7 @@ class Mysterbin(NZBProvider):
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
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:
|
||||
results.append(new)
|
||||
self.found(new)
|
||||
|
||||
@@ -10,6 +10,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'name': 'newzbin',
|
||||
'description': 'See <a href="https://www.newzbin2.es/">Newzbin</a>',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -96,13 +96,12 @@ class Newznab(NZBProvider, RSS):
|
||||
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])
|
||||
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
|
||||
|
||||
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 = []
|
||||
|
||||
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:
|
||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||
nzb = new, movie = movie, quality = quality,
|
||||
imdb_results = True, single_category = single_cat, single = True)
|
||||
imdb_results = True, single = True)
|
||||
|
||||
if is_correct_movie:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
|
||||
@@ -10,7 +10,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'name': 'NZBClub',
|
||||
'description': '',
|
||||
'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -86,7 +86,7 @@ class NZBClub(NZBProvider, RSS):
|
||||
|
||||
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:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
|
||||
@@ -10,7 +10,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'name': 'nzbindex',
|
||||
'description': 'Free provider, but less accurate.',
|
||||
'description': 'Free provider, less accurate. See <a href="http://www.nzbindex.nl/">NZBIndex</a>',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -93,7 +93,7 @@ class NzbIndex(NZBProvider, RSS):
|
||||
|
||||
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:
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
|
||||
@@ -11,6 +11,7 @@ config = [{
|
||||
'subtab': 'providers',
|
||||
'name': 'nzbmatrix',
|
||||
'label': 'NZBMatrix',
|
||||
'description': 'See <a href="https://nzbmatrix.com/">NZBMatrix</a>',
|
||||
'wizard': True,
|
||||
'options': [
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ class NZBMatrix(NZBProvider, RSS):
|
||||
cat_ids = [
|
||||
([50], ['bd50']),
|
||||
([42, 53], ['720p', '1080p']),
|
||||
([2], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||
([2, 9], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||
([54], ['brrip']),
|
||||
([1], ['dvdr']),
|
||||
]
|
||||
@@ -49,7 +49,6 @@ class NZBMatrix(NZBProvider, RSS):
|
||||
url = "%s?%s" % (self.urls['search'], arguments)
|
||||
|
||||
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()})
|
||||
if data:
|
||||
@@ -86,7 +85,7 @@ class NZBMatrix(NZBProvider, RSS):
|
||||
|
||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||
nzb = new, movie = movie, quality = quality,
|
||||
imdb_results = True, single_category = single_cat, single = True)
|
||||
imdb_results = True, single = True)
|
||||
|
||||
if is_correct_movie:
|
||||
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',
|
||||
'subtab': 'providers',
|
||||
'name': 'KickAssTorrents',
|
||||
'description': 'See <a href="https://kat.ph/">KickAssTorrents</a>',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -3,8 +3,6 @@ from couchpotato.core.event import fireEvent
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||
import StringIO
|
||||
import gzip
|
||||
import re
|
||||
import traceback
|
||||
|
||||
@@ -14,10 +12,9 @@ log = CPLog(__name__)
|
||||
class KickAssTorrents(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'test': 'http://www.kat.ph/',
|
||||
'detail': 'http://www.kat.ph/%s-t%s.html',
|
||||
'search': 'http://www.kat.ph/i%s/',
|
||||
'download': 'http://torcache.net/',
|
||||
'test': 'http://kat.ph/',
|
||||
'detail': 'http://kat.ph/%s',
|
||||
'search': 'http://kat.ph/i%s/',
|
||||
}
|
||||
|
||||
cat_ids = [
|
||||
@@ -60,11 +57,10 @@ class KickAssTorrents(TorrentProvider):
|
||||
continue
|
||||
|
||||
new = {
|
||||
'type': 'torrent',
|
||||
'type': 'torrent_magnet',
|
||||
'check_nzb': False,
|
||||
'description': '',
|
||||
'provider': self.getName(),
|
||||
'download': self.download,
|
||||
'score': 0,
|
||||
}
|
||||
|
||||
@@ -77,9 +73,8 @@ class KickAssTorrents(TorrentProvider):
|
||||
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
|
||||
new['id'] = temp.get('id')[-8:]
|
||||
new['name'] = link.text
|
||||
new['url'] = td.find_all('a', 'idownload')[1]['href']
|
||||
if new['url'][:2] == '//':
|
||||
new['url'] = 'http:%s' % new['url']
|
||||
new['url'] = td.find('a', 'imagnet')['href']
|
||||
new['detail_url'] = self.urls['detail'] % link['href'][1:]
|
||||
new['score'] = 20 if td.find('a', 'iverif') else 0
|
||||
elif column_name is 'size':
|
||||
new['size'] = self.parseSize(td.text)
|
||||
@@ -95,7 +90,7 @@ class KickAssTorrents(TorrentProvider):
|
||||
new['score'] += fireEvent('score.calculate', new, movie, single = True)
|
||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||
nzb = new, movie = movie, quality = quality,
|
||||
imdb_results = True, single_category = False, single = True)
|
||||
imdb_results = True, single = True)
|
||||
if is_correct_movie:
|
||||
results.append(new)
|
||||
self.found(new)
|
||||
@@ -129,13 +124,3 @@ class KickAssTorrents(TorrentProvider):
|
||||
age += tryInt(nr) * mult
|
||||
|
||||
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',
|
||||
'subtab': 'providers',
|
||||
'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': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
@@ -20,4 +20,4 @@ config = [{
|
||||
],
|
||||
},
|
||||
],
|
||||
}]
|
||||
}]
|
||||
|
||||
@@ -15,19 +15,9 @@ class PublicHD(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'test': 'http://publichd.eu',
|
||||
'download': 'http://publichd.eu/%s',
|
||||
'detail': 'http://publichd.eu/index.php?page=torrent-details&id=%s',
|
||||
'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
|
||||
|
||||
def search(self, movie, quality):
|
||||
@@ -39,9 +29,8 @@ class PublicHD(TorrentProvider):
|
||||
|
||||
params = tryUrlencode({
|
||||
'page':'torrents',
|
||||
'search': getTitle(movie['library']) + ' ' + quality['identifier'],
|
||||
'search': '%s %s' % (getTitle(movie['library']), movie['library']['year']),
|
||||
'active': 1,
|
||||
'category': self.getCatId(quality['identifier'])[0]
|
||||
})
|
||||
url = '%s?%s' % (self.urls['search'], params)
|
||||
|
||||
@@ -58,7 +47,7 @@ class PublicHD(TorrentProvider):
|
||||
|
||||
for result in entries[2:len(entries) - 1]:
|
||||
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:
|
||||
|
||||
@@ -67,12 +56,11 @@ class PublicHD(TorrentProvider):
|
||||
new = {
|
||||
'id': url['id'][0],
|
||||
'name': info_url.string,
|
||||
'type': 'torrent',
|
||||
'type': 'torrent_magnet',
|
||||
'check_nzb': False,
|
||||
'description': '',
|
||||
'provider': self.getName(),
|
||||
'download': self.download,
|
||||
'url': self.urls['download'] % download['href'],
|
||||
'url': download['href'],
|
||||
'detail_url': self.urls['detail'] % url['id'][0],
|
||||
'size': self.parseSize(result.find_all('td')[7].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)
|
||||
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:
|
||||
results.append(new)
|
||||
|
||||
@@ -10,6 +10,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'name': 'SceneAccess',
|
||||
'description': 'See <a href="https://sceneaccess.eu/">SceneAccess</a>',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -86,7 +86,7 @@ class SceneAccess(TorrentProvider):
|
||||
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
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:
|
||||
results.append(new)
|
||||
|
||||
@@ -10,6 +10,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'name': 'SceneHD',
|
||||
'description': 'See <a href="http://scenehd.org">SceneHD</a>',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -79,7 +79,7 @@ class SceneHD(TorrentProvider):
|
||||
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
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:
|
||||
results.append(new)
|
||||
|
||||
@@ -9,7 +9,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'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': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -37,6 +37,8 @@ class ThePirateBay(TorrentProvider):
|
||||
'https://piratereverse.info',
|
||||
'https://tpb.pirateparty.org.uk',
|
||||
'https://argumentomteemigreren.nl',
|
||||
'https://livepirate.com/',
|
||||
'https://www.getpirate.com/',
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
@@ -120,7 +122,7 @@ class ThePirateBay(TorrentProvider):
|
||||
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
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:
|
||||
results.append(new)
|
||||
|
||||
@@ -10,6 +10,7 @@ config = [{
|
||||
'tab': 'searcher',
|
||||
'subtab': 'providers',
|
||||
'name': 'TorrentLeech',
|
||||
'description': 'See <a href="http://torrentleech.org">TorrentLeech</a>',
|
||||
'options': [
|
||||
{
|
||||
'name': 'enabled',
|
||||
|
||||
@@ -80,7 +80,7 @@ class TorrentLeech(TorrentProvider):
|
||||
|
||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
||||
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:
|
||||
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.logger import CPLog
|
||||
from couchpotato.core.providers.trailer.base import TrailerProvider
|
||||
from string import letters, digits
|
||||
from string import digits, ascii_letters
|
||||
import re
|
||||
|
||||
log = CPLog(__name__)
|
||||
@@ -46,7 +46,7 @@ class HDTrailers(TrailerProvider):
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
@@ -100,7 +100,7 @@ class HDTrailers(TrailerProvider):
|
||||
return results
|
||||
|
||||
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])
|
||||
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
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class AlloCine(UserscriptBase):
|
||||
|
||||
@@ -15,11 +19,17 @@ class AlloCine(UserscriptBase):
|
||||
except:
|
||||
return
|
||||
|
||||
html = BeautifulSoup(data)
|
||||
title = html.find('title').contents[0].strip()
|
||||
split = title.split(') - ')
|
||||
name = None
|
||||
year = None
|
||||
|
||||
name = split[0][:-5].strip()
|
||||
year = split[0][-4:]
|
||||
try:
|
||||
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.event import fireEvent
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.providers.userscript.base import UserscriptBase
|
||||
import re
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
|
||||
class RottenTomatoes(UserscriptBase):
|
||||
|
||||
@@ -16,7 +20,20 @@ class RottenTomatoes(UserscriptBase):
|
||||
except:
|
||||
return
|
||||
|
||||
html = BeautifulSoup(data)
|
||||
title = html.find('span', {'itemprop':'name'}).text
|
||||
info = fireEvent('scanner.name_year', title, single = True)
|
||||
return self.search(info['name'], info['year'])
|
||||
try:
|
||||
name = None
|
||||
year = None
|
||||
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)
|
||||
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):
|
||||
"""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));
|
||||
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){
|
||||
@@ -187,7 +188,7 @@ var CouchPotato = new Class({
|
||||
restart: function(message, title){
|
||||
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');
|
||||
self.checkAvailable(1000);
|
||||
},
|
||||
@@ -216,7 +217,7 @@ var CouchPotato = new Class({
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
@@ -269,6 +270,17 @@ var CouchPotato = new Class({
|
||||
|
||||
createUrl: function(action, 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;
|
||||
};
|
||||
|
||||
Array.implement('sortBy', function(){
|
||||
keyPaths.empty();
|
||||
Array.each(arguments, function(argument) {
|
||||
switch (typeOf(argument)) {
|
||||
case "array": saveKeyPath(argument); break;
|
||||
case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break;
|
||||
}
|
||||
});
|
||||
return this.sort(comparer);
|
||||
Array.implement({
|
||||
sortBy: function(){
|
||||
keyPaths.empty();
|
||||
|
||||
Array.each(arguments, function(argument) {
|
||||
switch (typeOf(argument)) {
|
||||
case "array": saveKeyPath(argument); break;
|
||||
case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break;
|
||||
}
|
||||
});
|
||||
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('dd', {'text': App.getOption('pid')}),
|
||||
new Element('dt[text=Directories]'),
|
||||
@@ -103,12 +105,8 @@ var AboutSettingTab = new Class({
|
||||
),
|
||||
new Element('div.donate', {
|
||||
'html':
|
||||
'Or, buy me a (24 pack) Pepsi, for while I\'m coding ;)' +
|
||||
'<form action="https://www.paypal.com/cgi-bin/webscr" method="post">' +
|
||||
'<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>'
|
||||
'Or support me via:' +
|
||||
'<iframe src="http://couchpota.to/donate.html" style="border:none; height: 200px;" scrolling="no"></iframe>'
|
||||
})
|
||||
);
|
||||
|
||||
@@ -119,6 +117,7 @@ var AboutSettingTab = new Class({
|
||||
var self = this;
|
||||
var date = new Date(json.version.date * 1000);
|
||||
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
|
||||
,'Trailer': TrailerAction
|
||||
,'Releases': ReleaseAction
|
||||
|
||||
,'Edit': new Class({
|
||||
|
||||
Extends: MovieAction,
|
||||
@@ -74,20 +73,23 @@ window.addEvent('domready', function(){
|
||||
new Element('option', {
|
||||
'text': alt.title
|
||||
}).inject(self.title_select);
|
||||
|
||||
|
||||
if(alt['default'])
|
||||
self.title_select.set('value', alt.title);
|
||||
});
|
||||
|
||||
|
||||
Quality.getActiveProfiles().each(function(profile){
|
||||
|
||||
var profile_id = profile.id ? profile.id : profile.data.id;
|
||||
|
||||
new Element('option', {
|
||||
'value': profile.id ? profile.id : profile.data.id,
|
||||
'value': profile_id,
|
||||
'text': profile.label ? profile.label : profile.data.label
|
||||
}).inject(self.profile_select);
|
||||
|
||||
if(self.movie.profile)
|
||||
self.profile_select.set('value', profile.id ? profile.id : profile.data.id);
|
||||
if(self.movie.profile && self.movie.profile.data.id == profile_id)
|
||||
self.profile_select.set('value', profile_id);
|
||||
});
|
||||
|
||||
}
|
||||
@@ -170,7 +172,7 @@ window.addEvent('domready', function(){
|
||||
(e).preventDefault();
|
||||
|
||||
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', {
|
||||
'text': 'Cancel',
|
||||
'events': {
|
||||
|
||||
@@ -24,39 +24,36 @@
|
||||
}
|
||||
.page.settings .tabs a {
|
||||
display: block;
|
||||
padding: 11px 15px;
|
||||
padding: 7px 15px;
|
||||
font-weight: normal;
|
||||
transition: all 0.1s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;
|
||||
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);
|
||||
font-weight: bold;
|
||||
font-size: 25px;
|
||||
color: #fff;
|
||||
}
|
||||
.page.settings .tabs > li {
|
||||
border-bottom: 1px solid rgb(78, 89, 105);
|
||||
}
|
||||
|
||||
.page.settings .tabs .subtabs {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
transition: all 1s ease-in-out;
|
||||
max-height: 0;
|
||||
margin: -5px 0 10px;
|
||||
}
|
||||
.page.settings .tabs > .active .subtabs {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.page.settings .tabs .subtabs a {
|
||||
font-size: 15px;
|
||||
padding: 1px 15px;
|
||||
font-size: 13px;
|
||||
padding: 0 15px;
|
||||
font-weight: normal;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
background: rgba(78, 89, 105, 0.4);
|
||||
transition: all .3s ease-in-out;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.page.settings .tabs .subtabs .active a {
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
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 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">
|
||||
@@ -51,7 +51,6 @@
|
||||
new Uniform();
|
||||
|
||||
Api.setup({
|
||||
'host': {{ fireEvent('app.api_url', single = True)|tojson|safe }},
|
||||
'url': {{ url_for('api.index')|tojson|safe }},
|
||||
'path_sep': {{ sep|tojson|safe }},
|
||||
'is_remote': false
|
||||
|
||||
Reference in New Issue
Block a user