diff --git a/CHANGES.md b/CHANGES.md index 4fcddee..c50a493 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,15 @@ +### 0.18.14 (2019-02-11 15:10:00 UTC) + +* Fix ETTV provider cache search +* Fix Snowfl provider +* Fix HorribleSubs provider single digit episode search +* Change TokyoToshokan provider to prefer magnets and ignore invalid nyaa links +* Fix saving duplicate filename extension .nzb and .torrent + + ### 0.18.13 (2019-02-09 16:00:00 UTC) * Fix Nyaa provider diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index 463da93..a8083d7 100755 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -81,7 +81,7 @@ def sortedProviderList(): def makeProviderList(): providers = [x.provider for x in [getProviderModule(y) for y in __all__] if x] import browser_ua, zlib - headers = [1449593765, 1597250020] + headers = [1449593765, 1597250020, 1524942228] for p in providers: if abs(zlib.crc32(p.name)) + 40000400 in headers: p.headers.update({'User-Agent': browser_ua.get_ua()}) diff --git a/sickbeard/providers/ettv.py b/sickbeard/providers/ettv.py index 1bd511b..e0a19c8 100644 --- a/sickbeard/providers/ettv.py +++ b/sickbeard/providers/ettv.py @@ -31,8 +31,10 @@ class ETTVProvider(generic.TorrentProvider): generic.TorrentProvider.__init__(self, 'ETTV') self.url_home = ['https://www.ettv.tv/'] - self.url_vars = {'search': 'torrents-search.php?%s&search=%s&sort=id&order=desc'} - self.url_tmpl = {'config_provider_home_uri': '%(home)s', 'search': '%(home)s%(vars)s'} + self.url_vars = {'browse': 'torrents.php?%s&search=%s&sort=id&order=desc', + 'search': 'torrents-search.php?%s&search=%s&sort=id&order=desc'} + self.url_tmpl = {'config_provider_home_uri': '%(home)s', + 'browse': '%(home)s%(vars)s', 'search': '%(home)s%(vars)s'} self.url_drop = ['http://et'] self.categories = {'Season': [7], 'Episode': [41, 5, 50, 72, 77]} @@ -59,7 +61,7 @@ class ETTVProvider(generic.TorrentProvider): search_string = isinstance(search_string, unicode) and unidecode(search_string) or search_string - search_url = self.urls['search'] % ( + search_url = self.urls[('browse', 'search')['Cache' != mode]] % ( self._categories_string(mode), search_string) html = self.get_url(search_url) @@ -117,8 +119,11 @@ class ETTVProvider(generic.TorrentProvider): def change_params(params): for x, types in enumerate(params): for y, ep_type in enumerate(types): - search_string = '.'.join(params[x][ep_type][y].split()) - params[x][ep_type] = ['%2B ' + search_string, search_string] + new_list = [] + for t in params[0]['Episode']: + t = t.replace(' ', '.') + new_list += ['%2B ' + t, t] + params[x][ep_type] = new_list return params def _season_strings(self, ep_obj, **kwargs): diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py index 2c4b1ce..42e2b0b 100644 --- a/sickbeard/providers/generic.py +++ b/sickbeard/providers/generic.py @@ -671,7 +671,8 @@ class GenericProvider(object): saved = False for url in urls: cache_dir = sickbeard.CACHE_DIR or helpers._getTempDir() - base_name = '%s.%s' % (helpers.sanitizeFileName(result.name), self.providerType) + base_name = '%s.%s' % (re.sub('.%s$' % self.providerType, '', helpers.sanitizeFileName(result.name)), + self.providerType) final_file = ek.ek(os.path.join, final_dir, base_name) cached = getattr(result, 'cache_file', None) if cached and ek.ek(os.path.isfile, cached): diff --git a/sickbeard/providers/horriblesubs.py b/sickbeard/providers/horriblesubs.py index 840c15c..b23a32a 100644 --- a/sickbeard/providers/horriblesubs.py +++ b/sickbeard/providers/horriblesubs.py @@ -47,8 +47,7 @@ class HorribleSubsProvider(generic.TorrentProvider): items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []} - rc = dict((k, re.compile('(?i)' + v)) for (k, v) in { - 'info': 'dl-label', 'get': 'magnet:', 'nodots': '[\.\s]+'}.items()) + rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {'nodots': r'[\.\s]+'}.items()) for mode in search_params.keys(): for search_string in search_params[mode]: @@ -100,6 +99,12 @@ class HorribleSubsProvider(generic.TorrentProvider): return results + def _season_strings(self, *args, **kwargs): + return [{'Season': show_name_helpers.makeSceneSeasonSearchString(self.show, *args)}] + + def _episode_strings(self, *args, **kwargs): + return [{'Episode': show_name_helpers.makeSceneSearchString(self.show, *args)}] + def get_data(self, url): result = None html = self.get_url(url) diff --git a/sickbeard/providers/snowfl.py b/sickbeard/providers/snowfl.py index 6bf5ad8..949f6c6 100644 --- a/sickbeard/providers/snowfl.py +++ b/sickbeard/providers/snowfl.py @@ -38,10 +38,10 @@ class SnowflProvider(generic.TorrentProvider): def __init__(self): generic.TorrentProvider.__init__(self, 'Snowfl') - self.url_base = 'https://www.snowfl.com/' + self.url_base = 'https://snowfl.com/' self.urls = {'config_provider_home_uri': self.url_base, - 'browse': self.url_base + '%(token)s/Q/%(ent)s/0/DATE/24/1?_=%(ts)s', + 'browse': self.url_base + '%(token)s/Q/%(ent)s/0/DATE/24/0?_=%(ts)s', 'search': self.url_base + '%(token)s/%(ss)s/%(ent)s/0/DATE/NONE/1?_=%(ts)s', 'get': self.url_base + '%(token)s/%(src)s/%(url)s?_=%(ts)s'} @@ -93,18 +93,24 @@ class SnowflProvider(generic.TorrentProvider): if self.should_skip(): return results - for item in filter(lambda di: re.match('(?i).*?(tv|television)', di.get('category', '')) and ( - not self.confirmed or di.get('verified')), data_json or {}): - seeders, leechers, size = map(lambda n: tryInt( - *([item.get(n)]) * 2), ('seed', 'leech', 'size')) + for item in filter(lambda di: re.match('(?i).*?(tv|television)', + di.get('type', '') or di.get('category', '')) + and (not self.confirmed or di.get('trusted') or di.get('verified')), + data_json or {}): + seeders, leechers, size = map(lambda (n1, n2): tryInt( + *([item.get(n1) if None is not item.get(n1) else item.get(n2)]) * 2), + (('seeder', 'seed'), ('leecher', 'leech'), ('size', 'size'))) if self._reject_item(seeders, leechers): continue - title = item.get('title') - download_url = item.get('magnetLink') - if not download_url and item.get('source') and item.get('pageLink'): + title = item.get('name') or item.get('title') + download_url = item.get('magnet') or item.get('magnetLink') + if not download_url: + source = item.get('site') or item.get('source') + link = self._link(item.get('url') or item.get('pageLink')) + if not source or not link: + continue download_url = self.urls['get'] % dict( - token=token[0], src=quote(item.get('source')), - url=base64.b64encode(quote(item.get('pageLink'))), ts='%(ts)s') + token=token[0], src=quote(source), url=base64.b64encode(quote(link)), ts='%(ts)s') if title and download_url: items[mode].append((title, download_url, seeders, size)) @@ -126,8 +132,8 @@ class SnowflProvider(generic.TorrentProvider): raise generic.HaltParseException rc = dict((k, re.compile('(?i)' + v)) for (k, v) in { - 'js': ']+?src="([^"]+?js\?v=[\w]{8,})"', - 'token': '\w\s*=\s*"(\w{30,40})"', 'seed': 'n random[^"]+"([^"]+)'}.items()) + 'js': r']+?src="([^"]+?js\?v=[\w]{8,})"', + 'token': r'\w\s*=\s*"(\w{30,40})"', 'seed': r'n random[^"]+"([^"]+)'}.items()) js_src = rc['js'].findall(html) for src in js_src: @@ -164,7 +170,7 @@ class SnowflProvider(generic.TorrentProvider): prov.enabled = state if prov.url: try: - result = prov.urls.get('get', '') % re.findall('(\d+).torrent', url)[0] + result = prov.urls.get('get', '') % re.findall(r'(\d+).torrent', url)[0] except (IndexError, TypeError): pass diff --git a/sickbeard/providers/tokyotoshokan.py b/sickbeard/providers/tokyotoshokan.py index b778f33..5fc3ef5 100644 --- a/sickbeard/providers/tokyotoshokan.py +++ b/sickbeard/providers/tokyotoshokan.py @@ -41,14 +41,14 @@ class TokyoToshokanProvider(generic.TorrentProvider): items = {'Season': [], 'Episode': [], 'Propers': []} + rc = dict((k, re.compile('(?i)' + v)) for (k, v) in { + 'nodots': r'[\.\s]+', 'stats': r'S:\s*?(\d)+\s*L:\s*(\d+)', 'size': r'size:\s*(\d+[.,]\d+\w+)'}.iteritems()) + for mode in search_params.keys(): for search_string in search_params[mode]: - params = urllib.urlencode({'terms': search_string.encode('utf-8'), 'type': 1}) # get anime types + params = urllib.urlencode({'terms': rc['nodots'].sub(' ', search_string).encode('utf-8'), 'type': 1}) search_url = '%ssearch.php?%s' % (self.url, params) - - rc = dict((k, re.compile('(?i)' + v)) for (k, v) in { - 'stats': 'S:\s*?(\d)+\s*L:\s*(\d+)', 'size': 'size:\s*(\d+[.,]\d+\w+)'}.iteritems()) html = self.get_url(search_url) if self.should_skip(): @@ -76,8 +76,10 @@ class TokyoToshokanProvider(generic.TorrentProvider): info = top.find('td', class_='desc-top') title = info and re.sub(r'[ .]{2,}', '.', info.get_text().strip()) - urls = info and sorted([x.get('href') for x in info.find_all('a') or []]) - download_url = urls and urls[0].startswith('http') and urls[0] or urls[1] + links = info and map(lambda l: l.get('href', ''), info.find_all('a')) or None + download_url = self._link( + (filter(lambda l: 'magnet:' in l, links) + or filter(lambda l: not re.search(r'(magnet:|\.se).+', l), links))[0]) except (AttributeError, TypeError, ValueError, IndexError): continue @@ -93,13 +95,11 @@ class TokyoToshokanProvider(generic.TorrentProvider): return results - def _season_strings(self, ep_obj, **kwargs): - return [{'Season': [ - x.replace('.', ' ') for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)]}] + def _season_strings(self, *args, **kwargs): + return [{'Season': show_name_helpers.makeSceneSeasonSearchString(self.show, *args)}] - def _episode_strings(self, ep_obj, **kwargs): - return [{'Episode': [ - x.replace('.', ' ') for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)]}] + def _episode_strings(self, *args, **kwargs): + return [{'Episode': show_name_helpers.makeSceneSearchString(self.show, *args)}] class TokyoToshokanCache(tvcache.TVCache): @@ -118,7 +118,7 @@ class TokyoToshokanCache(tvcache.TVCache): results = [] if data and 'entries' in data: - rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {'size': 'size:\s*(\d+[.,]\d+\w+)'}.iteritems()) + rc = dict((k, re.compile('(?i)' + v)) for (k, v) in {'size': r'size:\s*(\d+[.,]\d+\w+)'}.iteritems()) for cur_item in data.get('entries', []): try: