diff --git a/CHANGES.md b/CHANGES.md
index f6af4e15..bfa1e324 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -87,14 +87,16 @@
* Add DigitalHive torrent provider
* Add RevTT torrent provider
* Add PTF torrent provider
-* Add ILT torrent provider
* Add Fano torrent provider
* Add BTScene torrent provider
* Add Extratorrent provider
* Add Limetorrents provider
+* Add HD-Torrents provider
* Add nCore torrent provider
* Add TorLock provider
* Add Torrentz2 provider
+* Add freeleech options to fano, freshon, hdspace, phd, ptf providers
+* Change SceneTime to cookie auth
* Remove Usenet-Crawler provider
* Change CPU throttling on General Config/Advanced to "Disabled" by default for new installs
* Change provider OMGWTFNZBS api url and auto reject nuked releases
@@ -180,6 +182,7 @@
* Add handler for when rar files can not be opened during post processing
* Fix join clean up
* Fix add custom torrent RSS
+* Remove ILT torrent provider
### 0.11.15 (2016-09-13 19:50:00 UTC)
diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css
index 4c43ece1..174ed9c2 100644
--- a/gui/slick/css/style.css
+++ b/gui/slick/css/style.css
@@ -2290,7 +2290,7 @@ config*.tmpl
}
#config label.space-right{
- margin-right:20px
+ margin-right:16px
}
#config .metadataDiv{
diff --git a/gui/slick/images/providers/hdtorrents.png b/gui/slick/images/providers/hdtorrents.png
new file mode 100644
index 00000000..a19defc1
Binary files /dev/null and b/gui/slick/images/providers/hdtorrents.png differ
diff --git a/gui/slick/images/providers/ilovetorrents.png b/gui/slick/images/providers/ilovetorrents.png
deleted file mode 100644
index 8d17f2b5..00000000
Binary files a/gui/slick/images/providers/ilovetorrents.png and /dev/null differ
diff --git a/gui/slick/interfaces/default/config_providers.tmpl b/gui/slick/interfaces/default/config_providers.tmpl
index 6d2b3290..0466eceb 100644
--- a/gui/slick/interfaces/default/config_providers.tmpl
+++ b/gui/slick/interfaces/default/config_providers.tmpl
@@ -520,6 +520,23 @@ name = '' if not client else get_client_instance(sickbeard.TORRENT_METHOD)().nam
#end if
+ #if $hasattr($cur_torrent_provider, 'may_filter'):
+
+
Allow releases that are
+
+ #for $cur_fval, $filter in $cur_torrent_provider.may_filter.iteritems()
+ #set $cur_fname, $cur_is_default = $filter[0], $filter[1]
+ #set $filter_id = '%s_filter_%s' % ($cur_torrent_provider.get_id(), $cur_fval)
+
+ #end for
+ (see site for meaning)
+ nothing selected allows everything (no filter, default)
+
+
+ #end if
#if $hasattr($cur_torrent_provider, 'reject_m2ts'):
', '', html)
+ html = re.sub('(?im)href=([^\\"][^>]+)>', r'href="\1">', html)
+ html = (html.replace('"/>', '" />')
+ .replace('"title="', '" title="')
+ .replace('', ''))
+ html = re.sub('(?im)]+)', r'\1.
-
-import re
-import traceback
-
-from . import generic
-from sickbeard import logger
-from sickbeard.bs4_parser import BS4Parser
-from sickbeard.helpers import tryInt
-from lib.unidecode import unidecode
-
-
-class ILTProvider(generic.TorrentProvider):
-
- def __init__(self):
- generic.TorrentProvider.__init__(self, 'ILoveTorrents')
-
- self.url_base = 'https://www.ilovetorrents.me/'
- self.urls = {'config_provider_home_uri': self.url_base,
- 'login_action': self.url_base + 'login.php',
- 'search': self.url_base + 'browse.php?search=%s&%s&incldead=0&blah=0',
- 'get': self.url_base + '%s'}
-
- self.categories = {'Season': [5], 'Episode': [7, 8, 40], 'anime': [23]}
- self.categories['Cache'] = self.categories['Season'] + self.categories['Episode']
-
- self.url = self.urls['config_provider_home_uri']
-
- self.username, self.password, self.minseed, self.minleech = 4 * [None]
-
- def _authorised(self, **kwargs):
-
- return super(ILTProvider, self)._authorised()
-
- def _search_provider(self, search_params, **kwargs):
-
- results = []
- if not self._authorised():
- return results
-
- items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}
-
- rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {'info': 'details', 'get': 'download'}.items())
- for mode in search_params.keys():
- rc['cats'] = re.compile('(?i)cat=(?:%s)' % self._categories_string(mode, template='', delimiter='|'))
- for search_string in search_params[mode]:
- search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string
-
- html = self.get_url(self.urls['search'] % ('+'.join(search_string.split()),
- self._categories_string(mode)))
-
- cnt = len(items[mode])
- try:
- if not html or self._has_no_results(html):
- raise generic.HaltParseException
-
- with BS4Parser(html, features=['html5lib', 'permissive']) as soup:
- torrent_table = soup.find('table', 'koptekst')
- torrent_rows = [] if not torrent_table else torrent_table.find_all('tr')
-
- if 2 > len(torrent_rows):
- raise generic.HaltParseException
-
- for tr in torrent_rows[1:]:
- try:
- seeders, leechers, size = [tryInt(n, n) for n in [
- tr.find_all('td')[x].get_text().strip() for x in -3, -2, -5]]
- if self._peers_fail(mode, seeders, leechers) or not tr.find('a', href=rc['cats']):
- continue
-
- title = tr.find('a', href=rc['info']).get_text().strip()
- download_url = self._link(tr.find('a', href=rc['get'])['href'])
- except (AttributeError, TypeError, ValueError, IndexError):
- continue
-
- if title and download_url:
- items[mode].append((title, download_url, seeders, self._bytesizer(size)))
-
- except generic.HaltParseException:
- pass
- except (StandardError, Exception):
- logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
-
- self._log_search(mode, len(items[mode]) - cnt, self.session.response.get('url'))
-
- results = self._sort_seeding(mode, results + items[mode])
-
- return results
-
-
-provider = ILTProvider()
diff --git a/sickbeard/providers/ncore.py b/sickbeard/providers/ncore.py
index 0ac76f09..0316fce0 100644
--- a/sickbeard/providers/ncore.py
+++ b/sickbeard/providers/ncore.py
@@ -49,7 +49,7 @@ class NcoreProvider(generic.TorrentProvider):
return super(NcoreProvider, self)._authorised(
logged_in=(lambda y='': all([bool(y), 'action="login' not in y, self.has_all_cookies('PHPSESSID')])),
- post_params={'nev': self.username, 'pass': self.password, 'form_tmpl': 'name=[\'"]login[\'"]'})
+ post_params={'nev': self.username, 'form_tmpl': 'name=[\'"]login[\'"]'})
def _search_provider(self, search_params, **kwargs):
diff --git a/sickbeard/providers/pisexy.py b/sickbeard/providers/pisexy.py
index c3fbb882..439aa856 100644
--- a/sickbeard/providers/pisexy.py
+++ b/sickbeard/providers/pisexy.py
@@ -36,7 +36,7 @@ class PiSexyProvider(generic.TorrentProvider):
self.url = self.urls['config_provider_home_uri']
- self.username, self.password, self.minseed, self.minleech = 4 * [None]
+ self.username, self.password, self.freeleech, self.minseed, self.minleech = 5 * [None]
def _authorised(self, **kwargs):
@@ -51,9 +51,9 @@ class PiSexyProvider(generic.TorrentProvider):
items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}
- rc = dict((k, re.compile('(?i)' + v))
- for (k, v) in {'info': 'download', 'get': 'download', 'valid_cat': 'cat=(?:0|50[12])',
- 'title': r'Download\s([^\s]+).*', 'seeders': r'(^\d+)', 'leechers': r'(\d+)$'}.items())
+ rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {
+ 'info': 'download', 'get': 'download', 'valid_cat': 'cat=(?:0|50[12])', 'filter': 'free',
+ 'title': r'Download\s([^\s]+).*', 'seeders': r'(^\d+)', 'leechers': r'(\d+)$'}.items())
for mode in search_params.keys():
for search_string in search_params[mode]:
search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string
@@ -78,7 +78,8 @@ class PiSexyProvider(generic.TorrentProvider):
seeders, leechers = 2 * [tr.find_all('td')[-4].get_text().strip()]
seeders, leechers = [tryInt(n) for n in [
rc['seeders'].findall(seeders)[0], rc['leechers'].findall(leechers)[0]]]
- if self._peers_fail(mode, seeders, leechers) or not tr.find('a', href=rc['valid_cat']):
+ if self._peers_fail(mode, seeders, leechers) or not tr.find('a', href=rc['valid_cat']) \
+ or (self.freeleech and not tr.find('img', src=rc['filter'])):
continue
info = tr.find('a', href=rc['info'])
diff --git a/sickbeard/providers/privatehd.py b/sickbeard/providers/privatehd.py
index d316135b..e0ba5b17 100644
--- a/sickbeard/providers/privatehd.py
+++ b/sickbeard/providers/privatehd.py
@@ -15,6 +15,10 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see .
+try:
+ from collections import OrderedDict
+except ImportError:
+ from requests.compat import OrderedDict
import re
import traceback
@@ -35,13 +39,18 @@ class PrivateHDProvider(generic.TorrentProvider):
'login_action': self.url_base + 'auth/login',
'search': self.url_base + 'torrents?%s' % '&'.join(
['in=1', 'tags=', 'type=2', 'language=0', 'subtitle=0', 'rip_type=0',
- 'video_quality=0', 'uploader=', 'search=%s', 'tv_type[]=%s', 'discount[]=%s'])}
+ 'video_quality=0', 'uploader=', 'search=%s', 'tv_type[]=%s'])}
self.categories = {'Season': [2], 'Episode': [1], 'Cache': [0]}
self.url = self.urls['config_provider_home_uri']
- self.username, self.password, self.freeleech, self.minseed, self.minleech = 5 * [None]
+ self.filter = []
+ self.may_filter = OrderedDict([
+ ('f0', ('not marked', False)), ('free', ('free', True)),
+ ('half', ('50% down', True)), ('double', ('2x up', True))])
+ self.username, self.password, self.minseed, self.minleech = 4 * [None]
+ self.confirmed = False
def _authorised(self, **kwargs):
@@ -59,6 +68,21 @@ class PrivateHDProvider(generic.TorrentProvider):
rc = dict((k, re.compile('(?i)' + v))
for (k, v) in {'info': '.*?details\s*-\s*', 'get': 'download'}.items())
+ log = ''
+ if self.filter:
+ non_marked = 'f0' in self.filter
+ # if search_any, use unselected to exclude, else use selected to keep
+ filters = ([f for f in self.may_filter if f in self.filter],
+ [f for f in self.may_filter if f not in self.filter])[non_marked]
+ filters += (((all([x in filters for x in 'free', 'double']) and ['freedouble'] or [])
+ + (all([x in filters for x in 'half', 'double']) and ['halfdouble'] or [])),
+ ((not all([x not in filters for x in 'free', 'double']) and ['freedouble'] or [])
+ + (not all([x not in filters for x in 'half', 'double']) and ['halfdouble'] or []))
+ )[non_marked]
+ rc['filter'] = re.compile('(?i)^(%s)$' % '|'.join(
+ ['%s' % f for f in filters if (f in self.may_filter and self.may_filter[f][1]) or f]))
+ log = '%sing (%s) ' % (('keep', 'skipp')[non_marked], ', '.join(
+ [f in self.may_filter and self.may_filter[f][0] or f for f in filters]))
for mode in search_params.keys():
if mode in ['Season', 'Episode']:
show_type = self.show.air_by_date and 'Air By Date' \
@@ -70,7 +94,7 @@ class PrivateHDProvider(generic.TorrentProvider):
for search_string in search_params[mode]:
search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string
search_url = self.urls['search'] % (
- '+'.join(search_string.split()), self._categories_string(mode, ''), (1, 0)[not self.freeleech])
+ '+'.join(search_string.split()), self._categories_string(mode, ''))
html = self.get_url(search_url)
@@ -87,6 +111,15 @@ class PrivateHDProvider(generic.TorrentProvider):
raise generic.HaltParseException
for tr in torrent_rows[1:]:
+ if self.confirmed and tr.find('i', title=re.compile('(?i)unverified')):
+ continue
+ if any(self.filter):
+ marked = ','.join([x.attrs.get('title', '').lower() for x in tr.find_all(
+ 'i', attrs={'class': ['fa-star', 'fa-diamond', 'fa-star-half-o']})])
+ munged = ''.join(filter(marked.__contains__, ['free', 'half', 'double']))
+ if ((non_marked and rc['filter'].search(munged)) or
+ (not non_marked and not rc['filter'].search(munged))):
+ continue
try:
seeders, leechers, size = [tryInt(n, n) for n in [
tr.find_all('td')[x].get_text().strip() for x in -3, -2, -4]]
@@ -106,7 +139,7 @@ class PrivateHDProvider(generic.TorrentProvider):
except (StandardError, Exception):
logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
- self._log_search(mode, len(items[mode]) - cnt, search_url)
+ self._log_search(mode, len(items[mode]) - cnt, log + search_url)
results = self._sort_seeding(mode, results + items[mode])
diff --git a/sickbeard/providers/ptf.py b/sickbeard/providers/ptf.py
index f48e6f05..6d51ac8d 100644
--- a/sickbeard/providers/ptf.py
+++ b/sickbeard/providers/ptf.py
@@ -15,6 +15,10 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see .
+try:
+ from collections import OrderedDict
+except ImportError:
+ from requests.compat import OrderedDict
import re
import time
import traceback
@@ -22,7 +26,7 @@ import traceback
from . import generic
from sickbeard import logger
from sickbeard.bs4_parser import BS4Parser
-from sickbeard.helpers import tryInt
+from sickbeard.helpers import tryInt, anon_url
from lib.unidecode import unidecode
@@ -33,9 +37,8 @@ class PTFProvider(generic.TorrentProvider):
self.url_base = 'https://ptfiles.net/'
self.urls = {'config_provider_home_uri': self.url_base,
- 'login_action': self.url_base + 'loginproc/',
- 'login_base': self.url_base + 'loginproc/',
- 'search': self.url_base + 'browse.php?search=%s&%s&incldead=0&title=0%s',
+ 'login': self.url_base + 'panel.php?tool=links',
+ 'search': self.url_base + 'browse.php?search=%s&%s&incldead=0&title=0',
'get': self.url_base + '%s'}
self.categories = {'Season': [39], 'Episode': [7, 33, 42], 'anime': [23]}
@@ -43,12 +46,19 @@ class PTFProvider(generic.TorrentProvider):
self.url = self.urls['config_provider_home_uri']
- self.username, self.password, self.freeleech, self.minseed, self.minleech = 5 * [None]
+ self.filter = []
+ self.may_filter = OrderedDict([
+ ('f0', ('not marked', False, '')), ('free', ('free', True, '^free$')),
+ ('freeday', ('free day', True, '^free[^!]+day')), ('freeweek', ('free week', True, '^free[^!]+week'))])
+ self.digest, self.minseed, self.minleech = 3 * [None]
def _authorised(self, **kwargs):
- return super(PTFProvider, self)._authorised(logged_in=(lambda y=None: self.has_all_cookies('session_key')),
- post_params={'force_ssl': 'on', 'ssl': '', 'form_tmpl': True})
+ return super(PTFProvider, self)._authorised(
+ logged_in=(lambda y='': all(
+ ['RSS Feed' in y, self.has_all_cookies('session_key')] +
+ [(self.session.cookies.get(x) or 'sg!no!pw') in self.digest for x in ['session_key']])),
+ failed_msg=(lambda y=None: u'Invalid cookie details for %s. Check settings'))
def _search_provider(self, search_params, **kwargs):
@@ -60,13 +70,21 @@ class PTFProvider(generic.TorrentProvider):
rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {'info': 'details', 'get': 'dl.php', 'snatch': 'snatches',
'seeders': r'(^\d+)', 'leechers': r'(\d+)$'}.items())
+ log = ''
+ if self.filter:
+ non_marked = 'f0' in self.filter
+ # if search_any, use unselected to exclude, else use selected to keep
+ filters = ([f for f in self.may_filter if f in self.filter],
+ [f for f in self.may_filter if f not in self.filter])[non_marked]
+ rc['filter'] = re.compile('(?i)(%s)' % '|'.join(
+ [self.may_filter[f][2] for f in filters if self.may_filter[f][1]]))
+ log = '%sing (%s) ' % (('keep', 'skipp')[non_marked], ', '.join([self.may_filter[f][0] for f in filters]))
for mode in search_params.keys():
rc['cats'] = re.compile('(?i)cat=(?:%s)' % self._categories_string(mode, template='', delimiter='|'))
for search_string in search_params[mode]:
search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string
- search_url = self.urls['search'] % ('+'.join(search_string.split()), self._categories_string(mode),
- ('&free=1', '')[not self.freeleech])
+ search_url = self.urls['search'] % ('+'.join(search_string.split()), self._categories_string(mode))
html = self.get_url(search_url)
time.sleep(2)
if not self.has_all_cookies(['session_key']):
@@ -87,6 +105,15 @@ class PTFProvider(generic.TorrentProvider):
raise generic.HaltParseException
for tr in torrent_rows[1:]:
+ if any(self.filter):
+ marker = ''
+ try:
+ marker = tr.select('a[href^="browse"] .tip')[0].get_text().strip()
+ except (StandardError, Exception):
+ pass
+ if ((non_marked and rc['filter'].search(marker)) or
+ (not non_marked and not rc['filter'].search(marker))):
+ continue
try:
seeders, leechers = 2 * [tr.find_all('td')[-2].get_text().strip()]
seeders, leechers = [tryInt(n) for n in [
@@ -110,11 +137,19 @@ class PTFProvider(generic.TorrentProvider):
except (StandardError, Exception):
logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
- self._log_search(mode, len(items[mode]) - cnt, self.session.response.get('url'))
+ self._log_search(mode, len(items[mode]) - cnt, log + self.session.response.get('url'))
results = self._sort_seeding(mode, results + items[mode])
return results
+ def ui_string(self, key):
+ if 'ptfiles_digest' == key and self._valid_home():
+ current_url = getattr(self, 'urls', {}).get('config_provider_home_uri')
+ return ('use... \'session_key=xx\'' +
+ (current_url and (' from a session logged in at %s' %
+ (anon_url(current_url), current_url.strip('/'))) or ''))
+ return ''
+
provider = PTFProvider()
diff --git a/sickbeard/providers/scenetime.py b/sickbeard/providers/scenetime.py
index b202e822..82f6a6e7 100644
--- a/sickbeard/providers/scenetime.py
+++ b/sickbeard/providers/scenetime.py
@@ -22,7 +22,7 @@ import traceback
from . import generic
from sickbeard import logger
from sickbeard.bs4_parser import BS4Parser
-from sickbeard.helpers import tryInt
+from sickbeard.helpers import tryInt, anon_url
from lib.unidecode import unidecode
@@ -31,22 +31,27 @@ class SceneTimeProvider(generic.TorrentProvider):
def __init__(self):
generic.TorrentProvider.__init__(self, 'SceneTime', cache_update_freq=15)
- self.url_base = 'https://www.scenetime.com/'
- self.urls = {'config_provider_home_uri': self.url_base,
- 'login_action': self.url_base + 'login.php',
- 'browse': self.url_base + 'browse_API.php',
- 'params': {'sec': 'jax', 'cata': 'yes'},
- 'get': self.url_base + 'download.php/%(id)s/%(title)s.torrent'}
+ self.url_home = ['https://%s.scenetime.com/' % u for u in 'www', 'uk']
+
+ self.url_vars = {'login': 'support.php', 'browse': 'browse_API.php', 'get': 'download.php/%s.torrent'}
+ self.url_tmpl = {'config_provider_home_uri': '%(home)s', 'login': '%(home)s%(vars)s',
+ 'browse': '%(home)s%(vars)s', 'get': '%(home)s%(vars)s'}
self.categories = {'shows': [2, 43, 9, 63, 77, 79, 83]}
- self.url = self.urls['config_provider_home_uri']
-
- self.username, self.password, self.freeleech, self.minseed, self.minleech = 5 * [None]
+ self.digest, self.freeleech, self.minseed, self.minleech = 4 * [None]
def _authorised(self, **kwargs):
- return super(SceneTimeProvider, self)._authorised(post_params={'form_tmpl': True})
+ return super(SceneTimeProvider, self)._authorised(
+ logged_in=(lambda y='': all(
+ ['staff-support' in y, self.has_all_cookies()] +
+ [(self.session.cookies.get(x) or 'sg!no!pw') in self.digest for x in 'uid', 'pass'])),
+ failed_msg=(lambda y=None: u'Invalid cookie details for %s. Check settings'))
+
+ @staticmethod
+ def _has_signature(data=None):
+ return generic.TorrentProvider._has_signature(data) or (data and re.search(r'(?i)%s' %
+ (anon_url(current_url), current_url.strip('/'))) or ''))
+ return ''
+
provider = SceneTimeProvider()
diff --git a/sickbeard/providers/shazbat.py b/sickbeard/providers/shazbat.py
index c8b6c433..bcdf1876 100644
--- a/sickbeard/providers/shazbat.py
+++ b/sickbeard/providers/shazbat.py
@@ -51,8 +51,7 @@ class ShazbatProvider(generic.TorrentProvider):
return super(ShazbatProvider, self)._authorised(
logged_in=(lambda y=None: '