Merge branch 'refs/heads/develop'
This commit is contained in:
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
/data/
|
/data/
|
||||||
/_source/
|
/_source/
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
|||||||
@@ -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,3 +1,4 @@
|
|||||||
|
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
|
||||||
@@ -48,7 +49,12 @@ class Downloader(Plugin):
|
|||||||
return is_correct
|
return is_correct
|
||||||
|
|
||||||
def magnetToTorrent(self, magnet_link):
|
def magnetToTorrent(self, magnet_link):
|
||||||
torrent_hash = re.findall('urn:btih:([\w]{40})', magnet_link)[0]
|
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))
|
||||||
|
|
||||||
url = 'http://torrage.com/torrent/%s.torrent' % torrent_hash
|
url = 'http://torrage.com/torrent/%s.torrent' % torrent_hash
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ class Blackhole(Downloader):
|
|||||||
type = ['nzb', 'torrent', 'torrent_magnet']
|
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')
|
||||||
|
|||||||
@@ -92,10 +92,9 @@ class Sabnzbd(Downloader):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
for slot in history['queue']['slots']:
|
for slot in history['queue']['slots']:
|
||||||
if slot['cat'] == self.conf('category'):
|
log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft']))
|
||||||
log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft']))
|
if slot['filename'] == nzbname:
|
||||||
if slot['filename'] == nzbname:
|
return slot['status'].lower()
|
||||||
return slot['status'].lower()
|
|
||||||
|
|
||||||
# Go through history items
|
# Go through history items
|
||||||
params = {
|
params = {
|
||||||
@@ -119,44 +118,44 @@ class Sabnzbd(Downloader):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for slot in history['history']['slots']:
|
for slot in history['history']['slots']:
|
||||||
if slot['category'] == self.conf('category'):
|
log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status']))
|
||||||
log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status']))
|
if slot['name'] == nzbname:
|
||||||
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['fail_message'].strip():
|
if slot['status'] == 'Failed' or (slot['status'] == 'Completed' and slot['fail_message'].strip()):
|
||||||
|
|
||||||
# Delete failed download
|
# Delete failed download
|
||||||
if self.conf('delete_failed', default = True):
|
if self.conf('delete_failed', default = True):
|
||||||
|
|
||||||
log.info('%s failed downloading, deleting...', slot['name'])
|
log.info('%s failed downloading, deleting...', slot['name'])
|
||||||
params = {
|
params = {
|
||||||
'apikey': self.conf('api_key'),
|
'apikey': self.conf('api_key'),
|
||||||
'mode': 'history',
|
'mode': 'history',
|
||||||
'name': 'delete',
|
'name': 'delete',
|
||||||
'del_files': '1',
|
'del_files': '1',
|
||||||
'value': slot['nzo_id']
|
'value': slot['nzo_id']
|
||||||
}
|
}
|
||||||
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
|
url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sab = self.urlopen(url, timeout = 60, show_error = False)
|
sab = self.urlopen(url, timeout = 60, show_error = False)
|
||||||
except:
|
except:
|
||||||
log.error('Failed deleting: %s', traceback.format_exc())
|
log.error('Failed deleting: %s', traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
result = sab.strip()
|
result = sab.strip()
|
||||||
if not result:
|
if not result:
|
||||||
log.error("SABnzbd didn't return anything.")
|
log.error("SABnzbd didn't return anything.")
|
||||||
|
|
||||||
log.debug("Result text from SAB: " + result[:40])
|
log.debug("Result text from SAB: " + result[:40])
|
||||||
if result == "ok":
|
if result == "ok":
|
||||||
log.info('SabNZBd deleted failed release %s successfully.', slot['name'])
|
log.info('SabNZBd deleted failed release %s successfully.', slot['name'])
|
||||||
elif result == "Missing authentication":
|
elif result == "Missing authentication":
|
||||||
log.error("Incorrect username/password or API?.")
|
log.error("Incorrect username/password or API?.")
|
||||||
else:
|
else:
|
||||||
log.error("Unknown error: " + result[:40])
|
log.error("Unknown error: " + result[:40])
|
||||||
|
|
||||||
return 'failed'
|
return 'failed'
|
||||||
else:
|
else:
|
||||||
return slot['status'].lower()
|
return slot['status'].lower()
|
||||||
|
|
||||||
return 'not_found'
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -314,6 +314,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):
|
||||||
@@ -329,13 +330,17 @@ class Renamer(Plugin):
|
|||||||
os.remove(src)
|
os.remove(src)
|
||||||
|
|
||||||
parent_dir = os.path.normpath(os.path.dirname(src))
|
parent_dir = os.path.normpath(os.path.dirname(src))
|
||||||
if os.path.isdir(parent_dir) and destination != parent_dir:
|
if delete_folders.count(parent_dir) == 0 and os.path.isdir(parent_dir) and destination != parent_dir:
|
||||||
self.deleteEmptyFolder(parent_dir, show_error = False)
|
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:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ 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)
|
||||||
@@ -37,11 +37,11 @@ class Searcher(Plugin):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# 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'))
|
||||||
fireEvent('schedule.interval', 'searcher.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
|
fireEvent('schedule.interval', 'searcher.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
|
||||||
|
|
||||||
|
|
||||||
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')
|
||||||
@@ -328,10 +328,6 @@ 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
|
||||||
|
|
||||||
@@ -406,19 +402,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())
|
||||||
@@ -453,6 +436,8 @@ class Searcher(Plugin):
|
|||||||
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
||||||
failed_status = fireEvent('status.get', 'failed', single = True)
|
failed_status = fireEvent('status.get', 'failed', single = True)
|
||||||
|
|
||||||
|
done_status = fireEvent('status.get', 'done', single = True)
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
rels = db.query(Release).filter_by(status_id = snatched_status.get('id'))
|
rels = db.query(Release).filter_by(status_id = snatched_status.get('id'))
|
||||||
|
|
||||||
@@ -470,6 +455,13 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
log.debug('Checking snatched movie: %s' , default_title)
|
log.debug('Checking snatched movie: %s' , default_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 = {}
|
item = {}
|
||||||
for info in rel.info:
|
for info in rel.info:
|
||||||
item[info.identifier] = info.value
|
item[info.identifier] = info.value
|
||||||
@@ -520,8 +512,6 @@ class Searcher(Plugin):
|
|||||||
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
ignored_status = fireEvent('status.get', 'ignored', single = True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
|
||||||
|
|
||||||
db = get_session()
|
db = get_session()
|
||||||
rels = db.query(Release).filter_by(
|
rels = db.query(Release).filter_by(
|
||||||
status_id = snatched_status.get('id'),
|
status_id = snatched_status.get('id'),
|
||||||
@@ -532,6 +522,7 @@ class Searcher(Plugin):
|
|||||||
rel.status_id = ignored_status.get('id')
|
rel.status_id = ignored_status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
movie_dict = fireEvent('movie.get', movie_id, single = True)
|
||||||
log.info('Trying next release for: %s', getTitle(movie_dict['library']))
|
log.info('Trying next release for: %s', getTitle(movie_dict['library']))
|
||||||
fireEvent('searcher.single', movie_dict)
|
fireEvent('searcher.single', movie_dict)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
})
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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