diff --git a/gui/slick/images/providers/pretome.png b/gui/slick/images/providers/pretome.png new file mode 100644 index 0000000000000000000000000000000000000000..86c61681b02a358a516c6e48e01359e84ca767f5 Binary files /dev/null and b/gui/slick/images/providers/pretome.png differ diff --git a/gui/slick/views/config_providers.mako b/gui/slick/views/config_providers.mako index dd7be25cf69191ad1a1137027e9766dabec5490d..baf50e9e83ef210524ce90dee493e2af883b3fab 100644 --- a/gui/slick/views/config_providers.mako +++ b/gui/slick/views/config_providers.mako @@ -368,6 +368,17 @@ $('#config-components').tabs(); </label> </div> % endif + + % if hasattr(curTorrentProvider, 'pin'): + <div class="field-pair"> + <label for="${curTorrentProvider.getID()}_pin"> + <span class="component-title">Pin:</span> + <span class="component-desc"> + <input type="text" name="${curTorrentProvider.getID()}_pin" id="${curTorrentProvider.getID()}_pin" value="${curTorrentProvider.pin}" class="form-control input-sm input100" /> + </span> + </label> + </div> + % endif % if hasattr(curTorrentProvider, 'ratio'): <div class="field-pair"> diff --git a/gui/slick/views/displayShow.mako b/gui/slick/views/displayShow.mako index 0e57871c52d29a8fb2968a29608c259e27430425..6e239e5217dbe6de28027c664a576d130dd2b18b 100644 --- a/gui/slick/views/displayShow.mako +++ b/gui/slick/views/displayShow.mako @@ -477,9 +477,13 @@ % endif </td> <td class="col-airdate"> - % if int(epResult['airdate']) > 1 and show.network and show.airs: + % if int(epResult['airdate']) != 1: ## Lets do this exactly like ComingEpisodes and History - <% airDate = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(epResult['airdate'], show.airs, show.network)) %> + ## Avoid issues with dateutil's _isdst on Windows but still provide air dates + <% airDate = datetime.datetime.fromordinal(epResult['airdate']) %> + % if airDate.year >= 1970 or show.network: + <% airDate = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(epResult['airdate'], show.airs, show.network)) %> + % endif <time datetime="${airDate.isoformat('T')}" class="date">${sbdatetime.sbdatetime.sbfdatetime(airDate)}</time> % else: Never diff --git a/lib/libtrakt/trakt.py b/lib/libtrakt/trakt.py index 5470eb96840cdd4e1f25e0bd5ead9e4acfba59d1..951f39a824ae329acde417757fd2ecd2097dcb79 100644 --- a/lib/libtrakt/trakt.py +++ b/lib/libtrakt/trakt.py @@ -97,10 +97,10 @@ class TraktAPI(): # This is pretty much a fatal error if there is no status_code # It means there basically was no response at all else: - logger.log(u'Could not connect to Trakt. Error: {0}'.format(e), logger.WARNING) + logger.log(u'Could not connect to Trakt. Error: {0}'.format(e), logger.DEBUG) elif code == 502: # Retry the request, cloudflare had a proxying issue - logger.log(u'Retrying trakt api request: %s' % path, logger.WARNING) + logger.log(u'Retrying trakt api request: %s' % path, logger.DEBUG) return self.traktRequest(path, data, headers, url, method) elif code == 401: if self.traktToken(refresh=True, count=count): @@ -109,9 +109,9 @@ class TraktAPI(): logger.log(u'Unauthorized. Please check your Trakt settings', logger.WARNING) elif code in (500,501,503,504,520,521,522): #http://docs.trakt.apiary.io/#introduction/status-codes - logger.log(u'Trakt may have some issues and it\'s unavailable. Try again later please', logger.WARNING) + logger.log(u'Trakt may have some issues and it\'s unavailable. Try again later please', logger.DEBUG) elif code == 404: - logger.log(u'Trakt error (404) the resource does not exist: %s' % url + path, logger.WARNING) + logger.log(u'Trakt error (404) the resource does not exist: %s' % url + path, logger.ERROR) else: logger.log(u'Could not connect to Trakt. Code error: {0}'.format(code), logger.ERROR) return {} diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 3ed5cbfbbd88be03395b76fd54558de576d17383..ec389484d6aff09c02586ac9a0b2c26855144daf 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -1,4 +1,4 @@ -# Author: Nic Wolfe <nic@wolfeden.ca> +# Author: Nic Wolfe <nic@wolfeden.ca> # URL: http://code.google.com/p/sickbeard/ # # This file is part of SickRage. @@ -40,7 +40,7 @@ from sickbeard.providers.generic import GenericProvider from sickbeard.providers import btn, newznab, womble, thepiratebay, torrentleech, kat, iptorrents, torrentz, \ omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, hounddawgs, nextgen, speedcd, nyaatorrents, animenzb, bluetigers, cpasbien, fnt, xthor, torrentbytes, \ frenchtorrentdb, freshontv, titansoftv, libertalia, morethantv, bitsoup, t411, tokyotoshokan, shazbat, rarbg, alpharatio, tntvillage, binsearch, torrentproject, extratorrent, \ - scenetime, btdigg, strike, transmitthenet, tvchaosuk, bitcannon + scenetime, btdigg, strike, transmitthenet, tvchaosuk, bitcannon, pretome from sickbeard.config import CheckSection, check_setting_int, check_setting_str, check_setting_float, ConfigMigrator, \ naming_ep_type from sickbeard import searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser, \ @@ -1245,6 +1245,9 @@ def initialize(consoleLogging=True): if hasattr(curTorrentProvider, 'passkey'): curTorrentProvider.passkey = check_setting_str(CFG, curTorrentProvider.getID().upper(), curTorrentProvider.getID() + '_passkey', '', censor_log=True) + if hasattr(curTorrentProvider, 'pin'): + curTorrentProvider.pin = check_setting_str(CFG, curTorrentProvider.getID().upper(), + curTorrentProvider.getID() + '_pin', '', censor_log=True) if hasattr(curTorrentProvider, 'proxy'): curTorrentProvider.proxy.enabled = bool(check_setting_int(CFG, curTorrentProvider.getID().upper(), curTorrentProvider.getID() + '_proxy', 0)) @@ -1811,6 +1814,9 @@ def save_config(): if hasattr(curTorrentProvider, 'passkey'): new_config[curTorrentProvider.getID().upper()][ curTorrentProvider.getID() + '_passkey'] = curTorrentProvider.passkey + if hasattr(curTorrentProvider, 'pin'): + new_config[curTorrentProvider.getID().upper()][ + curTorrentProvider.getID() + '_pin'] = curTorrentProvider.pin if hasattr(curTorrentProvider, 'confirmed'): new_config[curTorrentProvider.getID().upper()][curTorrentProvider.getID() + '_confirmed'] = int( curTorrentProvider.confirmed) diff --git a/sickbeard/clients/rtorrent_client.py b/sickbeard/clients/rtorrent_client.py index b6d6c1cc9b7afe30a78dd4cba0452af93598ea1b..867599cee2bb49b745b477ed193c5fdbc35a04f0 100644 --- a/sickbeard/clients/rtorrent_client.py +++ b/sickbeard/clients/rtorrent_client.py @@ -72,7 +72,7 @@ class rTorrentAPI(GenericClient): if result.show.is_anime: label = sickbeard.TORRENT_LABEL_ANIME if label: - torrent.set_custom(1, label.lower()) + torrent.set_custom(1, label) if sickbeard.TORRENT_PATH: torrent.set_directory(sickbeard.TORRENT_PATH) @@ -112,7 +112,7 @@ class rTorrentAPI(GenericClient): if result.show.is_anime: label = sickbeard.TORRENT_LABEL_ANIME if label: - torrent.set_custom(1, label.lower()) + torrent.set_custom(1, label) if sickbeard.TORRENT_PATH: torrent.set_directory(sickbeard.TORRENT_PATH) diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index 399b4999c5971307cd383ec16139da467e542f12..2b0102611ce9e467f11c4f63ea11c95e5290eacb 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -164,12 +164,15 @@ class PostProcessor(object): if not file_path: return [] + # don't confuse glob with chars we didn't mean to use + globbable_file_path = helpers.fixGlob(file_path) + file_path_list = [] if subfolders: - base_name = ek(os.path.basename, file_path).rpartition('.')[0] + base_name = ek(os.path.basename, globbable_file_path).rpartition('.')[0] else: - base_name = file_path.rpartition('.')[0] + base_name = globbable_file_path.rpartition('.')[0] if not base_name_only: base_name = base_name + '.' @@ -178,20 +181,17 @@ class PostProcessor(object): if not base_name: return [] - # don't confuse glob with chars we didn't mean to use - base_name = re.sub(r'[\[\]\*\?]', r'[\g<0>]', base_name) - if subfolders: # subfolders are only checked in show folder, so names will always be exactly alike - filelist = ek(recursive_glob, ek(os.path.dirname, file_path), base_name + '*') # just create the list of all files starting with the basename + filelist = recursive_glob(ek(os.path.dirname, globbable_file_path), base_name + '*') # just create the list of all files starting with the basename else: # this is called when PP, so we need to do the filename check case-insensitive filelist = [] - checklist = ek(glob.glob, helpers.fixGlob(ek(os.path.join, ek(os.path.dirname, file_path), '*'))) # get a list of all the files in the folder + checklist = glob.glob(ek(os.path.join, ek(os.path.dirname, globbable_file_path), '*')) # get a list of all the files in the folder for filefound in checklist: # loop through all the files in the folder, and check if they are the same name even when the cases don't match file_name = filefound.rpartition('.')[0] if not base_name_only: file_name = file_name + '.' - if file_name.lower() == base_name.lower(): # if there's no difference in the filename add it to the filelist + if file_name.lower() == base_name.lower().replace('[[]', '[').replace('[]]', ']'): # if there's no difference in the filename add it to the filelist filelist.append(filefound) for associated_file_path in filelist: @@ -910,7 +910,7 @@ class PostProcessor(object): if not priority_download: # Not a priority and the quality is lower than what we already have - if (new_ep_quality < old_ep_quality and new_ep_quality != common.Quality.UNKNOWN) and not existing_file_status == PostProcessor.DOESNT_EXIST: + if (new_ep_quality < old_ep_quality and old_ep_quality != common.Quality.UNKNOWN) and not existing_file_status == PostProcessor.DOESNT_EXIST: self._log(u"File exists and new file quality is lower than existing, marking it unsafe to replace") return False diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py index 2880981f61157049b6b477f6a17412787cd9fddf..c7903a69fa4f15bd123fc694fcda6eb4122a125a 100644 --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -1,4 +1,4 @@ -# Author: Nic Wolfe <nic@wolfeden.ca> +# Author: Nic Wolfe <nic@wolfeden.ca> # URL: http://code.google.com/p/sickbeard/ # # This file is part of SickRage. @@ -58,7 +58,8 @@ __all__ = ['womble', 'torrentproject', 'extratorrent', 'bitcannon', - 'torrentz' + 'torrentz', + 'pretome' ] import sickbeard diff --git a/sickbeard/providers/pretome.py b/sickbeard/providers/pretome.py new file mode 100644 index 0000000000000000000000000000000000000000..4cadb672140ce212c301e37cd7df347479b1b7b9 --- /dev/null +++ b/sickbeard/providers/pretome.py @@ -0,0 +1,206 @@ +# Author: Nick Sologoub +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of SickRage. +# +# SickRage is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SickRage is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SickRage. If not, see <http://www.gnu.org/licenses/>. + +import re +import urllib +import traceback + +from sickbeard import logger +from sickbeard import tvcache +from sickbeard.providers import generic +from sickbeard.bs4_parser import BS4Parser + + +class PretomeProvider(generic.TorrentProvider): + + def __init__(self): + + generic.TorrentProvider.__init__(self, "Pretome") + + self.supportsBacklog = True + + self.username = None + self.password = None + self.pin = None + self.ratio = None + self.minseed = None + self.minleech = None + self.freeleech = False + + self.urls = {'base_url': 'https://pretome.info', + 'login': 'https://pretome.info/takelogin.php', + 'detail': 'https://pretome.info/details.php?id=%s', + 'search': 'https://pretome.info/browse.php?search=%s%s', + 'download': 'https://pretome.info/download.php/%s/%s.torrent'} + + self.url = self.urls['base_url'] + + self.categories = "&st=1&cat%5B%5D=7" + + self.proper_strings = ['PROPER', 'REPACK'] + + self.cache = PretomeCache(self) + + def isEnabled(self): + return self.enabled + + def _checkAuth(self): + + if not self.username or not self.password or not self.pin: + logger.log(u"Invalid username or password or pin. Check your settings", logger.WARNING) + + return True + + def _doLogin(self): + + login_params = {'username': self.username, + 'password': self.password, + 'login_pin': self.pin} + + response = self.getURL(self.urls['login'], post_data=login_params, timeout=30) + if not response: + logger.log(u"Unable to connect to provider", logger.WARNING) + return False + + if re.search('Username or password incorrect', response): + logger.log(u"Invalid username or password. Check your settings", logger.WARNING) + return False + + return True + + def _doSearch(self, search_params, search_mode='eponly', epcount=0, age=0, epObj=None): + + results = [] + items = {'Season': [], 'Episode': [], 'RSS': []} + + if not self._doLogin(): + return results + + for mode in search_params.keys(): + logger.log(u"Search Mode: %s" % mode, logger.DEBUG) + for search_string in search_params[mode]: + + if mode != 'RSS': + logger.log(u"Search string: %s " % search_string, logger.DEBUG) + + searchURL = self.urls['search'] % (urllib.quote(search_string.encode('utf-8')), self.categories) + logger.log(u"Search URL: %s" % searchURL, logger.DEBUG) + + data = self.getURL(searchURL) + if not data: + continue + + try: + with BS4Parser(data, features=["html5lib", "permissive"]) as html: + #Continue only if one Release is found + empty = html.find('h2', text="No .torrents fit this filter criteria") + if empty: + logger.log(u"Data returned from provider does not contain any torrents", logger.DEBUG) + continue + + torrent_table = html.find('table', attrs={'style': 'border: none; width: 100%;'}) + if not torrent_table: + logger.log(u"Could not find table of torrents", logger.ERROR) + continue + + torrent_rows = torrent_table.find_all('tr', attrs={'class': 'browse'}) + + for result in torrent_rows: + cells = result.find_all('td') + size = None + link = cells[1].find('a', attrs={'style': 'font-size: 1.25em; font-weight: bold;'}) + + torrent_id = link['href'].replace('details.php?id=', '') + + try: + if link.has_key('title'): + title = link['title'] + else: + title = link.contents[0] + + download_url = self.urls['download'] % (torrent_id, link.contents[0]) + seeders = int(cells[9].contents[0]) + leechers = int(cells[10].contents[0]) + + # Need size for failed downloads handling + if size is None: + if re.match(r'[0-9]+,?\.?[0-9]*[KkMmGg]+[Bb]+', cells[7].text): + size = self._convertSize(cells[7].text) + if not size: + size = -1 + + except (AttributeError, TypeError): + continue + + if not all([title, download_url]): + continue + + #Filter unseeded torrent + if seeders < self.minseed or leechers < self.minleech: + if mode != 'RSS': + logger.log(u"Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format(title, seeders, leechers), logger.DEBUG) + continue + + item = title, download_url, size, seeders, leechers + if mode != 'RSS': + logger.log(u"Found result: %s " % title, logger.DEBUG) + + items[mode].append(item) + + except Exception, e: + logger.log(u"Failed parsing provider. Traceback: %s" % traceback.format_exc(), logger.ERROR) + + #For each search mode sort all the items by seeders if available + items[mode].sort(key=lambda tup: tup[3], reverse=True) + + results += items[mode] + + return results + + def seedRatio(self): + return self.ratio + + def _convertSize(self, sizeString): + size = sizeString[:-2] + modifier = sizeString[-2:] + size = float(size) + if modifier in 'KB': + size = size * 1024 + elif modifier in 'MB': + size = size * 1024**2 + elif modifier in 'GB': + size = size * 1024**3 + elif modifier in 'TB': + size = size * 1024**4 + return int(size) + + +class PretomeCache(tvcache.TVCache): + def __init__(self, provider_obj): + + tvcache.TVCache.__init__(self, provider_obj) + + # only poll Pretome every 20 minutes max + self.minTime = 20 + + def _getRSSData(self): + search_params = {'RSS': ['']} + return {'entries': self.provider._doSearch(search_params)} + + +provider = PretomeProvider() diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index b275308e5a5859dfffcb34091855bedff501f3a5..ff758b143dd6425fc86be8a00c823b4ae47b031a 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1,4 +1,4 @@ -# Author: Nic Wolfe <nic@wolfeden.ca> +# Author: Nic Wolfe <nic@wolfeden.ca> # URL: http://code.google.com/p/sickbeard/ # # This file is part of SickRage. @@ -4418,6 +4418,12 @@ class ConfigProviders(Config): curTorrentProvider.passkey = str(kwargs[curTorrentProvider.getID() + '_passkey']).strip() except: curTorrentProvider.passkey = None + + if hasattr(curTorrentProvider, 'pin'): + try: + curTorrentProvider.pin = str(kwargs[curTorrentProvider.getID() + '_pin']).strip() + except: + curTorrentProvider.pin = None if hasattr(curTorrentProvider, 'confirmed'): try: