You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
5.0 KiB
146 lines
5.0 KiB
from bs4 import BeautifulSoup
|
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
|
from couchpotato.core.helpers.variable import tryInt
|
|
from couchpotato.core.logger import CPLog
|
|
from couchpotato.core.event import fireEvent
|
|
from couchpotato.core.providers.base import MultiProvider
|
|
from couchpotato.core.providers.info.base import MovieProvider, SeasonProvider, EpisodeProvider
|
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
|
from couchpotato.environment import Env
|
|
import re
|
|
import traceback
|
|
|
|
log = CPLog(__name__)
|
|
|
|
class BinSearch(MultiProvider):
|
|
|
|
def getTypes(self):
|
|
return [Movie, Season, Episode]
|
|
|
|
class Base(NZBProvider):
|
|
|
|
urls = {
|
|
'download': 'https://www.binsearch.info/fcgi/nzb.fcgi?q=%s',
|
|
'detail': 'https://www.binsearch.info%s',
|
|
'search': 'https://www.binsearch.info/index.php?%s',
|
|
}
|
|
|
|
http_time_between_calls = 4 # Seconds
|
|
|
|
def _search(self, media, quality, results):
|
|
|
|
data = self.getHTMLData(self.urls['search'] % self.buildUrl(media, quality))
|
|
|
|
if data:
|
|
try:
|
|
|
|
html = BeautifulSoup(data)
|
|
main_table = html.find('table', attrs = {'id':'r2'})
|
|
|
|
if not main_table:
|
|
return
|
|
|
|
items = main_table.find_all('tr')
|
|
|
|
for row in items:
|
|
title = row.find('span', attrs = {'class':'s'})
|
|
|
|
if not title: continue
|
|
|
|
nzb_id = row.find('input', attrs = {'type':'checkbox'})['name']
|
|
info = row.find('span', attrs = {'class':'d'})
|
|
size_match = re.search('size:.(?P<size>[0-9\.]+.[GMB]+)', info.text)
|
|
|
|
age = 0
|
|
try: age = re.search('(?P<size>\d+d)', row.find_all('td')[-1:][0].text).group('size')[:-1]
|
|
except: pass
|
|
|
|
def extra_check(item):
|
|
parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text)
|
|
total = tryInt(parts.group('total'))
|
|
parts = tryInt(parts.group('parts'))
|
|
|
|
if (total / parts) < 0.95 or ((total / parts) >= 0.95 and not ('par2' in info.text.lower() or 'pa3' in info.text.lower())):
|
|
log.info2('Wrong: \'%s\', not complete: %s out of %s', (item['name'], parts, total))
|
|
return False
|
|
|
|
if 'requires password' in info.text.lower():
|
|
log.info2('Wrong: \'%s\', passworded', (item['name']))
|
|
return False
|
|
|
|
return True
|
|
|
|
results.append({
|
|
'id': nzb_id,
|
|
'name': title.text,
|
|
'age': tryInt(age),
|
|
'size': self.parseSize(size_match.group('size')),
|
|
'url': self.urls['download'] % nzb_id,
|
|
'detail_url': self.urls['detail'] % info.find('a')['href'],
|
|
'extra_check': extra_check
|
|
})
|
|
|
|
except:
|
|
log.error('Failed to parse HTML response from BinSearch: %s', traceback.format_exc())
|
|
|
|
def download(self, url = '', nzb_id = ''):
|
|
|
|
params = {
|
|
'action': 'nzb',
|
|
nzb_id: 'on'
|
|
}
|
|
|
|
try:
|
|
return self.urlopen(url, params = params, show_error = False)
|
|
except:
|
|
log.error('Failed getting nzb from %s: %s', (self.getName(), traceback.format_exc()))
|
|
|
|
return 'try_next'
|
|
|
|
class Movie(MovieProvider, Base):
|
|
|
|
def buildUrl(self, media, quality):
|
|
query = tryUrlencode({
|
|
'q': media['library']['identifier'], # TODO should this use library.title?
|
|
'm': 'n',
|
|
'max': 400,
|
|
'adv_age': Env.setting('retention', 'nzb'),
|
|
'adv_sort': 'date',
|
|
'adv_col': 'on',
|
|
'adv_nfo': 'on',
|
|
'minsize': quality.get('size_min'),
|
|
'maxsize': quality.get('size_max'),
|
|
})
|
|
return query
|
|
|
|
class Season(SeasonProvider, Base):
|
|
|
|
def buildUrl(self, media, quality):
|
|
query = tryUrlencode({
|
|
'q': fireEvent('library.title', media['library'], single = True),
|
|
'm': 'n',
|
|
'max': 400,
|
|
'adv_age': Env.setting('retention', 'nzb'),
|
|
'adv_sort': 'date',
|
|
'adv_col': 'on',
|
|
'adv_nfo': 'on',
|
|
'minsize': quality.get('size_min'),
|
|
'maxsize': quality.get('size_max'),
|
|
})
|
|
return query
|
|
|
|
class Episode(EpisodeProvider, Base):
|
|
|
|
def buildUrl(self, media, quality):
|
|
query = tryUrlencode({
|
|
'q': fireEvent('library.title', media['library'], single = True),
|
|
'm': 'n',
|
|
'max': 400,
|
|
'adv_age': Env.setting('retention', 'nzb'),
|
|
'adv_sort': 'date',
|
|
'adv_col': 'on',
|
|
'adv_nfo': 'on',
|
|
'minsize': quality.get('size_min'),
|
|
'maxsize': quality.get('size_max'),
|
|
})
|
|
return query
|
|
|