Initial commit off passthepopcorn search provider (port from version for CouchPotato 1)
This commit is contained in:
1 parent
0cd2206e97
commit
2cac21bdc3
2 files changed
+309
No files matched your search
@@ -0,0 +1,36 @@
|
|||||||
|
from main import PassThePopcorn
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return PassThePopcorn()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'passthepopcorn',
|
||||||
|
'groups': [{
|
||||||
|
'tab': 'searcher',
|
||||||
|
'subtab': 'providers',
|
||||||
|
'name': 'PassThePopcorn',
|
||||||
|
'description': 'passthepopcorn.me',
|
||||||
|
'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,273 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
|
from couchpotato.core.helpers.variable import getTitle, getImdb, tryInt, cleanHost
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
from dateutil.parser import parse
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import htmlentitydefs
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
import cookielib
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PassThePopcorn(TorrentProvider):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'detail': '%s/torrent/%s',
|
||||||
|
'search': '%s/search/%s/0/7/%d'
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_provider = False
|
||||||
|
|
||||||
|
domain = 'tls.passthepopcorn.me'
|
||||||
|
|
||||||
|
opener = None
|
||||||
|
cookiejar = None
|
||||||
|
quality_search_params = {
|
||||||
|
'1080p': {'resolution': '1080p'},
|
||||||
|
'720p': {'resolution': '720p'},
|
||||||
|
'brrip': {'media': 'Blu-ray'}, # results are filtered by post_search_filters to narrow down the search
|
||||||
|
'dvdr': {'resolution': 'anysd'}, # results are filtered by post_search_filters to narrow down the search
|
||||||
|
'dvdrip': {'media': 'DVD'}, # results are filtered by post_search_filters to narrow down the search
|
||||||
|
'scr': {'media': 'DVD-Screener'},
|
||||||
|
'r5': {'media': 'R5'},
|
||||||
|
'tc': {'media': 'TC'},
|
||||||
|
'ts': {'media': 'TS'},
|
||||||
|
'cam': {'media': 'CAM'}
|
||||||
|
}
|
||||||
|
|
||||||
|
post_search_filters = {
|
||||||
|
'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'):
|
||||||
|
#log.info("Redirect NOT due to not being logged in detected; allowing redirect")
|
||||||
|
return urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
|
||||||
|
else:
|
||||||
|
#log.info("NotLoggedInHTTPError detected; raising it")
|
||||||
|
raise PassThePopcorn.NotLoggedInHTTPError(req.get_full_url(), code, msg, headers, fp)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
cookieprocessor = urllib2.HTTPCookieProcessor(self.cookiejar)
|
||||||
|
self.cookiejar = cookielib.CookieJar()
|
||||||
|
self.opener = urllib2.build_opener(cookieprocessor, PassThePopcorn.PTPHTTPRedirectHandler())
|
||||||
|
self.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'),
|
||||||
|
]
|
||||||
|
self.domain = self.conf('domain') if self.conf('domain') else self.domain
|
||||||
|
super(PassThePopcorn, self).__init__()
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
log.info("Trying to log into passthepopcorn.me...")
|
||||||
|
url = 'https://%s/login.php' % (self.domain)
|
||||||
|
postdata = urllib.urlencode({'username': self.conf('username'), 'password': self.conf('password'), 'keeplogged': '1', 'login': 'Login'})
|
||||||
|
try:
|
||||||
|
response = self.opener.open(url, postdata)
|
||||||
|
log.debug('Login POST did not throw exception')
|
||||||
|
except urllib2.URLError as e:
|
||||||
|
log.error('Login to passthepopcorn failed: %s' % e)
|
||||||
|
return False
|
||||||
|
if response.getcode() == 200:
|
||||||
|
log.info('Login HTTP status 200; seems successful')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
log.error('Login to passthepopcorn failed: returned code %d' % response.getcode())
|
||||||
|
return False
|
||||||
|
|
||||||
|
def protected_request(self, url):
|
||||||
|
log.debug('Retrieving %s' % url)
|
||||||
|
maxattempts = 3
|
||||||
|
while maxattempts > 0:
|
||||||
|
try:
|
||||||
|
response = self.opener.open(url)
|
||||||
|
txt = response.read()
|
||||||
|
#log.info('Response was:\n %s' % jsontxt)
|
||||||
|
if response.getcode() != 200:
|
||||||
|
log.error('Retrieving \'%s\' resulted in HTTP response code %d' % (url, response.getcode()))
|
||||||
|
return None
|
||||||
|
return txt
|
||||||
|
except PassThePopcorn.NotLoggedInHTTPError as e:
|
||||||
|
if not self.login(): # if we can login, just retry
|
||||||
|
log.error('Login failed, could not execute request %s' % url)
|
||||||
|
return None
|
||||||
|
log.debug("Should now be logged into passthepopcorn.me, trying request again...")
|
||||||
|
except urllib2.URLError as e:
|
||||||
|
log.error('Retrieving JSON from url %s failed: %s' % (url, e))
|
||||||
|
return None
|
||||||
|
maxattempts = maxattempts - 1
|
||||||
|
|
||||||
|
def json_request(self, path, params):
|
||||||
|
url = 'https://%s/%s?json=noredirect&%s' % (self.domain, path, urllib.urlencode(params))
|
||||||
|
txt = self.protected_request(url)
|
||||||
|
if txt:
|
||||||
|
return json.loads(txt)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def enabled(self):
|
||||||
|
return (not self.isDisabled() and \
|
||||||
|
self.conf('username') and \
|
||||||
|
self.conf('password'))
|
||||||
|
|
||||||
|
def torrent_meets_quality_spec(self, torrent, quality):
|
||||||
|
if not quality in self.post_search_filters:
|
||||||
|
return True
|
||||||
|
for field, specs in self.post_search_filters[quality].items():
|
||||||
|
matchesOne = False
|
||||||
|
seenOne = False
|
||||||
|
if not field in torrent:
|
||||||
|
log.info('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
|
||||||
|
seenOne = True
|
||||||
|
if torrent[field] == spec:
|
||||||
|
matchesOne = True
|
||||||
|
if seenOne and not matchesOne:
|
||||||
|
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 download(self, url = '', nzb_id = ''):
|
||||||
|
return self.protected_request(url)
|
||||||
|
|
||||||
|
def search(self, movie, quality):
|
||||||
|
movieTitle = getTitle(movie['library'])
|
||||||
|
qualityID = quality['identifier']
|
||||||
|
imdbID = movie['library']['info']['imdb']
|
||||||
|
movieYear = movie['library']['info']['year']
|
||||||
|
|
||||||
|
log.info('Searching for %s' % movieTitle)
|
||||||
|
if not self.enabled():
|
||||||
|
log.info('PTP not enabled, skipping search')
|
||||||
|
return []
|
||||||
|
|
||||||
|
params = []
|
||||||
|
if qualityID:
|
||||||
|
params = self.quality_search_params[qualityID]
|
||||||
|
if imdbID:
|
||||||
|
params['searchstr'] = imdbID
|
||||||
|
else:
|
||||||
|
params['searchstr'] = movieTitle
|
||||||
|
params['year'] = movieYear
|
||||||
|
|
||||||
|
params['order_by'] = 'relevance'
|
||||||
|
params['order_way'] = 'descending'
|
||||||
|
|
||||||
|
res = self.json_request('torrents.php', params)
|
||||||
|
if not res:
|
||||||
|
log.error('Search on passthepopcorn.me (%s) failed' % params)
|
||||||
|
return []
|
||||||
|
|
||||||
|
#log.info('JSON: %s' % json.dumps(res))
|
||||||
|
|
||||||
|
if not 'Movies' in res:
|
||||||
|
return []
|
||||||
|
results = []
|
||||||
|
for ptpmovie in res['Movies']:
|
||||||
|
if not 'Torrents' in ptpmovie:
|
||||||
|
continue
|
||||||
|
for torrent in ptpmovie['Torrents']:
|
||||||
|
if not self.torrent_meets_quality_spec(torrent, type):
|
||||||
|
continue
|
||||||
|
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')
|
||||||
|
new = {
|
||||||
|
'id': int(torrent['Id']),
|
||||||
|
'type': 'torrent',
|
||||||
|
'name': re.sub('[^A-Za-z0-9\-_ ]+', '', '%s - %s - %s' % (self.htmltoascii(ptpmovie['Title']), ptpmovie['Year'], torrentdesc)),
|
||||||
|
'check_nzb': False,
|
||||||
|
'description': '',
|
||||||
|
'date': int(time.mktime(parse(torrent['UploadTime']).timetuple())),
|
||||||
|
'size': int(torrent['Size']) / (1024*1024),
|
||||||
|
'provider': self.getName(),
|
||||||
|
'seeders': int(torrent['Seeders']),
|
||||||
|
'leechers': int(torrent['Leechers']),
|
||||||
|
'ptpobj': self,
|
||||||
|
'torrentjson': torrent,
|
||||||
|
'extra_score': (lambda torrent: (50 if torrent['torrentjson']['GoldenPopcorn'] else 0)),
|
||||||
|
'download': self.download,
|
||||||
|
}
|
||||||
|
new['url'] = 'https://%s/torrents.php?action=download&id=%d' % (self.domain, new['id'])
|
||||||
|
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)
|
||||||
|
if not results:
|
||||||
|
log.info("Found nothing for '%s'" % movie.name)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def getMoreInfo(self, item):
|
||||||
|
return {}
|
||||||
Reference in new issue
Block a user