diff --git a/CHANGES.md b/CHANGES.md index 05c9311d..04234984 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -52,6 +52,10 @@ * Change do not have shows checked by default on import page. To re-enable import shows checked by default, 1) On config page 'Save' 2) Stop SG 3) Find 'import_default_checked_shows' in config.ini and set '1' 4) Start SG * Add Nyaa (.si) torrent provider +* Add Trakt watchlist to Add show/Trakt Cards +* Change revoke application access at Trakt when account is deleted in SG +* Add persistent hide/unhide cards to Add show/Trakt and Add show/IMDb Cards +* Change simplify dropdowns at all Add show/Cards [develop changelog] diff --git a/gui/slick/css/dark.css b/gui/slick/css/dark.css index c651622d..fe1bb8a5 100644 --- a/gui/slick/css/dark.css +++ b/gui/slick/css/dark.css @@ -293,11 +293,13 @@ a.ui-font{ background-image:linear-gradient(to left, rgba(51, 51, 51, 1), rgba(51, 51, 51, 0)) } +.show-toggle-hide, td.tvShow a{ color:#ddd; text-decoration:none } +.show-toggle-hide:hover, td.tvShow a:hover span, td.tvShow a:hover{ cursor:pointer; @@ -1288,6 +1290,7 @@ input sizing (for config pages) .showlist-select optgroup, #pickShow optgroup, #showfilter optgroup, +#showsort optgroup, #editAProvider optgroup{ color:#eee; background-color:rgb(51, 51, 51) @@ -1296,6 +1299,7 @@ input sizing (for config pages) .showlist-select optgroup option, #pickShow optgroup option, #showfilter optgroup option, +#showsort optgroup option, #editAProvider optgroup option{ color:#222; background-color:#ddd diff --git a/gui/slick/css/light.css b/gui/slick/css/light.css index b47c0e41..d031ef28 100644 --- a/gui/slick/css/light.css +++ b/gui/slick/css/light.css @@ -308,11 +308,13 @@ a.ui-font{ background-image:linear-gradient(to left, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)) } +.show-toggle-hide, td.tvShow a{ color:#000; text-decoration:none } +.show-toggle-hide:hover, td.tvShow a:hover span, td.tvShow a:hover{ cursor:pointer; @@ -1254,6 +1256,7 @@ input sizing (for config pages) .showlist-select optgroup, #pickShow optgroup, #showfilter optgroup, +#showsort optgroup, #editAProvider optgroup{ color:#eee; background-color:#888 @@ -1262,6 +1265,7 @@ input sizing (for config pages) .showlist-select optgroup option, #pickShow optgroup option, #showfilter optgroup option, +#showsort optgroup option, #editAProvider optgroup option{ color:#222; background-color:#fff diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index ff26b39d..78eca8f2 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -882,6 +882,12 @@ home.tmpl background-image:linear-gradient(to left, rgba(223, 218, 207, 1), rgba(223, 218, 207, 0)) } +.show-toggle-hide{ + position:absolute; + top:272px; + right:2px +} + .show-date{ position:relative; overflow:hidden; diff --git a/gui/slick/interfaces/default/home_browseShows.tmpl b/gui/slick/interfaces/default/home_browseShows.tmpl index a780c583..d4f7a12a 100644 --- a/gui/slick/interfaces/default/home_browseShows.tmpl +++ b/gui/slick/interfaces/default/home_browseShows.tmpl @@ -38,41 +38,40 @@ $(document).ready(function(){ // initialise combos for dirty page refreshes - $('#showsort').val('original'); - $('#showsortdirection').val('asc'); - $('#showfilter').val('*'); + $('#showsort').val('*'); - var $container = [$('#container')]; - jQuery.each($container, function(j){ - this.isotope({ - itemSelector: '.show-card', - sortBy: 'original-order', - layoutMode: 'masonry', - masonry: { - columnWidth: 188, - isFitWidth: !0, - gutter: 12 - }, - getSortData: { - premiered: '[data-premiered] parseInt', - name: function( itemElem ) { - var name = $( itemElem ).attr('data-name') || ''; + $('#container').isotope({ + itemSelector: '.show-card', + sortBy: 'original-order', + layoutMode: 'masonry', + masonry: { + columnWidth: 188, + isFitWidth: !0, + gutter: 12 + }, + getSortData: { + premiered: '[data-premiered] parseInt', + name: function( itemElem ) { + var name = $( itemElem ).attr('data-name') || ''; #end raw -#if not $sg_var('SORT_ARTICLE'): - name = name.replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1'); +#if not $sg_var('SORT_ARTICLE') + name = name.replace(/^(?:(?:A(?!\s+to)n?)|The)\s(\w)/i, '$1'); #end if #raw - return name.toLowerCase(); - }, - rating: '[data-rating] parseInt', - votes: '[data-votes] parseInt', - } - }); + return name.toLowerCase(); + }, + rating: '[data-rating] parseInt', + votes: '[data-votes] parseInt', + } }); $('#showsort').on('change', function(){ var sortCriteria, el$ = $('#container'), shuffle = !1; - switch (this.value) { + switch (this.value.replace('by_', '')) { + case 'asc': + case 'desc': + sortCriteria = 'order'; + break; case 'original': sortCriteria = 'original-order' break; @@ -95,33 +94,84 @@ $(document).ready(function(){ break; } - if (!shuffle){ + var showSort = $('#showsort option'); + if('order' === sortCriteria){ + showSort.filter($('option[value="asc"], option[value="desc"]')).removeClass('selected'); + showSort.filter($('option[value="' + this.value + '"]')).addClass('selected'); + el$.one('layoutComplete', llUpdate); - el$.isotope({sortBy: sortCriteria}); - } else { - // shuffle rating_votes where it can already be sorted by ratings which leaves it nothing to do. - function t(sortCriteria, lastPhase){ - return function(){ - var el$ = $('#container'); - if (!lastPhase){ - lastPhase = !0; - el$.isotope({sortBy: sortCriteria}); - } else { - el$.off('layoutComplete'); - el$.isotope('layout'); - llUpdate(); + el$.isotope({sortAscending: 'asc' == this.value}); + } else if(0 == this.value.indexOf('by_')){ + showSort.filter($('option[value^="by_"][class*="selected"]')).removeClass('selected'); + showSort.filter($('option[value="' + this.value + '"]')).addClass('selected'); + + if (!shuffle){ + el$.one('layoutComplete', llUpdate); + el$.isotope({sortBy: sortCriteria}); + } else { + // shuffle rating_votes where it can already be sorted by ratings which leaves it nothing to do. + function t(sortCriteria, lastPhase){ + return function(){ + var el$ = $('#container'); + if (!lastPhase){ + lastPhase = !0; + el$.isotope({sortBy: sortCriteria}); + } else { + el$.off('layoutComplete'); + el$.isotope('layout'); + llUpdate(); + } } } + el$.on('layoutComplete', t(sortCriteria, !1)); + el$.isotope({sortBy: 'random'}); } - el$.on('layoutComplete', t(sortCriteria, !1)); - el$.isotope({sortBy: 'random'}); + } else { + showSort.filter($('option[value^="*"], #showsort option[value^="."]')).removeClass('selected'); + showSort.filter($('option[value="' + this.value + '"]')).addClass('selected'); + + var showCards = $('.show-card'), filter = this.value; + if('.hide' === this.value){ + showCards.filter($('.hide')).removeClass('hide').addClass('to-hide'); + filter = '.to-hide'; + } else { + showCards.filter($('.to-hide')).removeClass('to-hide').addClass('hide'); + } + + var el$ = $('#container'); + el$.one('layoutComplete', llUpdate); + el$.isotope({ filter: filter }); } }); - $('#showsortdirection').on('change', function(){ - var el$ = $('#container') - el$.one('layoutComplete', llUpdate); - el$.isotope({sortAscending: ('asc' == this.value)}); + $('#container').on('click', '.show-toggle-hide', function(event){ + var that = $(this); + event.preventDefault(); + + $.getJSON(this.href, function(data){ + if(data.success){ + var showCards = $('.show-card'), thisCard = $(that).parents('div[class*="show-card "]'), + numShows = showCards.length, numHidden, showSort = $('#showsort option'), filter = 'to-hide'; + + if(thisCard.hasClass(filter)){ + title = 'Hide'; + thisCard.removeClass(filter); + } else { + filter = 'hide'; + title = 'Unhide'; + thisCard.addClass(filter); + } + numHidden = showCards.filter($('.' + filter)).length; + that.attr('title', title); + + showSort.filter($('option[value=".hide"]')).text('Hidden (' + numHidden + ')'); + showSort.filter($('option[value="*"]')).text('All (' + (0 == numHidden ? '' : (numShows - numHidden) + '/') + numShows + ')'); + + var el$ = $('#container') + el$.on('layoutComplete', llUpdate); + el$.isotope(); + } + }); }); $('#showfilter').on('change', function(){ @@ -160,15 +210,35 @@ $(document).ready(function(){ #set $mode = $kwargs and $kwargs.get('mode', '') #if $all_shows or ($kwargs and $kwargs.get('show_header'))
$this_show['rating']%$this_show['votes'] votes
- #if 'url_tvdb' in $this_show and $this_show['url_tvdb']: + #if 'url_tvdb' in $this_show and $this_show['url_tvdb'] #end ifIn library
#else Add Show @@ -311,7 +388,7 @@ $(document).ready(function(){- #if $kwargs and $kwargs.get('error_msg'): + #if $kwargs and $kwargs.get('error_msg') $kwargs['error_msg'] #else $browse_type API did not return results, this can happen from time to time. diff --git a/lib/libtrakt/trakt.py b/lib/libtrakt/trakt.py index 07dafa35..4096067d 100644 --- a/lib/libtrakt/trakt.py +++ b/lib/libtrakt/trakt.py @@ -15,26 +15,40 @@ class TraktAccount: def __init__(self, account_id=None, token='', refresh_token='', auth_fail=0, last_fail=None, token_valid_date=None): self.account_id = account_id self._name = '' + self._slug = '' self.token = token self.refresh_token = refresh_token self.auth_fail = auth_fail self.last_fail = last_fail self.token_valid_date = token_valid_date + def get_name_slug(self): + try: + resp = TraktAPI().trakt_request('users/settings', send_oauth=self.account_id, sleep_retry=20) + self.reset_auth_failure() + if 'user' in resp: + self._name = resp['user']['username'] + self._slug = resp['user']['ids']['slug'] + except TraktAuthException: + self.inc_auth_failure() + self._name = '' + except TraktException: + pass + + @property + def slug(self): + if self.token and self.active: + if not self._slug: + self.get_name_slug() + else: + self._slug = '' + return self._slug + @property def name(self): if self.token and self.active: if not self._name: - try: - resp = TraktAPI().trakt_request('users/settings', send_oauth=self.account_id, sleep_retry=20) - self.reset_auth_failure() - if 'user' in resp: - self._name = resp['user']['username'] - except TraktAuthException: - self.inc_auth_failure() - self._name = '' - except TraktException: - pass + self.get_name_slug() else: self._name = '' @@ -53,10 +67,10 @@ class TraktAccount: @property def token_expired(self): - return self.token_valid_date and datetime.datetime.now() > self.token_valid_date + return self.token_valid_date and self.token_valid_date < datetime.datetime.now() def reset_auth_failure(self): - if self.auth_fail != 0: + if 0 != self.auth_fail: self.auth_fail = 0 self.last_fail = None @@ -68,13 +82,13 @@ class TraktAccount: if self.auth_fail < self.max_auth_fail: if self.last_fail: time_diff = datetime.datetime.now() - self.last_fail - if self.auth_fail % 3 == 0: - if time_diff > datetime.timedelta(days=1): + if 0 == self.auth_fail % 3: + if datetime.timedelta(days=1) < time_diff: self.inc_auth_failure() sickbeard.save_config() - elif time_diff > datetime.timedelta(minutes=15): + elif datetime.timedelta(minutes=15) < time_diff: self.inc_auth_failure() - if self.auth_fail == self.max_auth_fail or time_diff > datetime.timedelta(hours=6): + if self.auth_fail == self.max_auth_fail or datetime.timedelta(hours=6) < time_diff: sickbeard.save_config() else: self.inc_auth_failure() @@ -99,19 +113,22 @@ class TraktAPI: return '!!!'.join('%s|%s|%s|%s|%s|%s' % ( value.account_id, value.token, value.refresh_token, value.auth_fail, value.last_fail.strftime('%Y%m%d%H%M') if value.last_fail else '0', - value.token_valid_date.strftime('%Y%m%d%H%M%S') if value.token_valid_date else '0') for (key, value) in data.items()) + value.token_valid_date.strftime('%Y%m%d%H%M%S') if value.token_valid_date else '0') + for (key, value) in data.items()) @staticmethod def read_config_string(data): return dict((int(a.split('|')[0]), TraktAccount( int(a.split('|')[0]), a.split('|')[1], a.split('|')[2], int(a.split('|')[3]), datetime.datetime.strptime(a.split('|')[4], '%Y%m%d%H%M') if a.split('|')[4] != '0' else None, - datetime.datetime.strptime(a.split('|')[5], '%Y%m%d%H%M%S') if a.split('|')[5] != '0' else None)) for a in data.split('!!!') if data) + datetime.datetime.strptime(a.split('|')[5], '%Y%m%d%H%M%S') if a.split('|')[5] != '0' else None)) + for a in data.split('!!!') if data) @staticmethod def add_account(token, refresh_token, token_valid_date): k = max(sickbeard.TRAKT_ACCOUNTS.keys() or [0]) + 1 - sickbeard.TRAKT_ACCOUNTS[k] = TraktAccount(account_id=k, token=token, refresh_token=refresh_token, token_valid_date=token_valid_date) + sickbeard.TRAKT_ACCOUNTS[k] = TraktAccount(account_id=k, token=token, refresh_token=refresh_token, + token_valid_date=token_valid_date) sickbeard.save_config() return k @@ -132,6 +149,7 @@ class TraktAPI: @staticmethod def delete_account(account): if account in sickbeard.TRAKT_ACCOUNTS: + TraktAPI().trakt_request('/oauth/revoke', send_oauth=account, method='POST') sickbeard.TRAKT_ACCOUNTS.pop(account) sickbeard.save_config() return True @@ -149,7 +167,7 @@ class TraktAPI: } if refresh: - if account and account in sickbeard.TRAKT_ACCOUNTS: + if None is not account and account in sickbeard.TRAKT_ACCOUNTS: data['grant_type'] = 'refresh_token' data['refresh_token'] = sickbeard.TRAKT_ACCOUNTS[account].refresh_token else: @@ -170,13 +188,22 @@ class TraktAPI: if 'access_token' in resp and 'refresh_token' in resp and 'expires_in' in resp: token_valid_date = now + datetime.timedelta(seconds=sickbeard.helpers.tryInt(resp['expires_in'])) - if refresh or (not refresh and account and account in sickbeard.TRAKT_ACCOUNTS): - return self.replace_account(account, resp['access_token'], resp['refresh_token'], token_valid_date, refresh) - else: - return self.add_account(resp['access_token'], resp['refresh_token'], token_valid_date) + if refresh or (not refresh and None is not account and account in sickbeard.TRAKT_ACCOUNTS): + return self.replace_account(account, resp['access_token'], resp['refresh_token'], + token_valid_date, refresh) + return self.add_account(resp['access_token'], resp['refresh_token'], token_valid_date) + return False - def trakt_request(self, path, data=None, headers=None, url=None, count=0, sleep_retry=60, send_oauth=None, **kwargs): + def trakt_request(self, path, data=None, headers=None, url=None, count=0, sleep_retry=60, + send_oauth=None, method=None, **kwargs): + + if method not in ['GET', 'POST', 'PUT', 'DELETE', None]: + return {} + if None is method: + method = ('GET', 'POST')['data' in kwargs.keys() or data is not None] + if path != 'oauth/token' and None is send_oauth and method in ['POST', 'PUT', 'DELETE']: + return {} count += 1 if count > self.max_retrys: @@ -186,7 +213,7 @@ class TraktAPI: count > 1 and time.sleep(sleep_retry) headers = headers or self.headers - if send_oauth and send_oauth in sickbeard.TRAKT_ACCOUNTS: + if None is not send_oauth and send_oauth in sickbeard.TRAKT_ACCOUNTS: if sickbeard.TRAKT_ACCOUNTS[send_oauth].active: if sickbeard.TRAKT_ACCOUNTS[send_oauth].needs_refresh: self.trakt_token(refresh=True, count=0, account=send_oauth) @@ -202,54 +229,77 @@ class TraktAPI: url = url or self.api_url try: - resp = self.session.request(('GET', 'POST')['data' in kwargs.keys()], - url + path, **kwargs) + resp = self.session.request(method, '%s%s' % (url, path), **kwargs) + + if 'DELETE' == method: + result = None + if 204 == resp.status_code: + result = {'result': 'success'} + elif 404 == resp.status_code: + result = {'result': 'failed'} + if result and None is not send_oauth and send_oauth in sickbeard.TRAKT_ACCOUNTS: + sickbeard.TRAKT_ACCOUNTS[send_oauth].reset_auth_failure() + return result + resp.raise_for_status() + return {} # check for http errors and raise if any are present resp.raise_for_status() # convert response to json resp = resp.json() + except requests.RequestException as e: code = getattr(e.response, 'status_code', None) if not code: if 'timed out' in e: logger.log(u'Timeout connecting to Trakt', logger.WARNING) + return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, + send_oauth=send_oauth, method=method) # This is pretty much a fatal error if there is no status_code - # It means there basically was no response at all + # 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.WARNING) + elif 502 == code: # Retry the request, Cloudflare had a proxying issue logger.log(u'Retrying Trakt api request: %s' % path, logger.WARNING) - return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, send_oauth=send_oauth) + return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, + send_oauth=send_oauth, method=method) + elif 401 == code and path != 'oauth/token': - if send_oauth: + if None is not send_oauth: if sickbeard.TRAKT_ACCOUNTS[send_oauth].needs_refresh: if self.trakt_token(refresh=True, count=count, account=send_oauth): - return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, send_oauth=send_oauth) - else: - logger.log(u'Unauthorized. Please check your Trakt settings', logger.WARNING) - sickbeard.TRAKT_ACCOUNTS[send_oauth].auth_failure() - raise TraktAuthException() - else: - # sometimes the trakt server sends invalid token error even if it isn't + return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, + send_oauth=send_oauth, method=method) + + logger.log(u'Unauthorized. Please check your Trakt settings', logger.WARNING) sickbeard.TRAKT_ACCOUNTS[send_oauth].auth_failure() - if count >= self.max_retrys: - raise TraktAuthException() - else: - return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, send_oauth=send_oauth) - else: - raise TraktAuthException() + raise TraktAuthException() + + # sometimes the trakt server sends invalid token error even if it isn't + sickbeard.TRAKT_ACCOUNTS[send_oauth].auth_failure() + if count >= self.max_retrys: + raise TraktAuthException() + + return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, + send_oauth=send_oauth, method=method) + + raise TraktAuthException() 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. Trying again', logger.WARNING) - self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, send_oauth=send_oauth) + self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, + send_oauth=send_oauth, method=method) elif 404 == code: - 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%s' % (url, path), logger.WARNING) else: logger.log(u'Could not connect to Trakt. Code error: {0}'.format(code), logger.ERROR) return {} + except ValueError as e: + logger.log(u'Value Error: {0}'.format(e), logger.ERROR) + return {} # check and confirm Trakt call did not fail if isinstance(resp, dict) and 'failure' == resp.get('status', None): @@ -257,9 +307,8 @@ class TraktAPI: raise TraktException(resp['message']) if 'error' in resp: raise TraktException(resp['error']) - else: - raise TraktException('Unknown Error') + raise TraktException('Unknown Error') - if send_oauth and send_oauth in sickbeard.TRAKT_ACCOUNTS: + if None is not send_oauth and send_oauth in sickbeard.TRAKT_ACCOUNTS: sickbeard.TRAKT_ACCOUNTS[send_oauth].reset_auth_failure() return resp diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 4afcd539..fda97e5a 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -450,6 +450,7 @@ EPISODE_VIEW_DISPLAY_PAUSED = False EPISODE_VIEW_POSTERS = True EPISODE_VIEW_MISSED_RANGE = None HISTORY_LAYOUT = None +BROWSELIST_HIDDEN = [] FUZZY_DATING = False TRIM_ZERO = False @@ -541,7 +542,7 @@ def initialize(console_logging=True): EPISODE_VIEW_MISSED_RANGE, EPISODE_VIEW_POSTERS, FANART_PANEL, FANART_RATINGS, \ EPISODE_VIEW_VIEWMODE, EPISODE_VIEW_BACKGROUND, EPISODE_VIEW_BACKGROUND_TRANSLUCENT, \ DISPLAY_SHOW_VIEWMODE, DISPLAY_SHOW_BACKGROUND, DISPLAY_SHOW_BACKGROUND_TRANSLUCENT, \ - DISPLAY_SHOW_VIEWART, DISPLAY_SHOW_MINIMUM, DISPLAY_SHOW_SPECIALS, HISTORY_LAYOUT + DISPLAY_SHOW_VIEWART, DISPLAY_SHOW_MINIMUM, DISPLAY_SHOW_SPECIALS, HISTORY_LAYOUT, BROWSELIST_HIDDEN # Gen Config/Misc global LAUNCH_BROWSER, UPDATE_SHOWS_ON_START, SHOW_UPDATE_HOUR, \ TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, ACTUAL_LOG_DIR, LOG_DIR, INDEXER_TIMEOUT, ROOT_DIRS, \ @@ -1099,6 +1100,9 @@ def initialize(console_logging=True): EPISODE_VIEW_MISSED_RANGE = check_setting_int(CFG, 'GUI', 'episode_view_missed_range', 7) HISTORY_LAYOUT = check_setting_str(CFG, 'GUI', 'history_layout', 'detailed') + BROWSELIST_HIDDEN = [ + x.strip() for x in check_setting_str(CFG, 'GUI', 'browselist_hidden', '').split('|~|') if x.strip()] + # initialize NZB and TORRENT providers providerList = providers.makeProviderList() @@ -1849,6 +1853,7 @@ def save_config(): new_config['GUI']['showlist_tagview'] = SHOWLIST_TAGVIEW new_config['GUI']['show_tag_default'] = SHOW_TAG_DEFAULT new_config['GUI']['history_layout'] = HISTORY_LAYOUT + new_config['GUI']['browselist_hidden'] = '|~|'.join(BROWSELIST_HIDDEN) new_config['Subtitles'] = {} new_config['Subtitles']['use_subtitles'] = int(USE_SUBTITLES) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index c061740e..be8414bc 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -3321,6 +3321,20 @@ class NewHomeAddShows(Home): 'recommendations/shows?limit=%s&' % 100, 'Recommended for %s by Trakt' % name, mode='recommended-%s' % account, send_oauth=account) + def trakt_watchlist(self, *args, **kwargs): + + if 'add' == kwargs.get('action'): + return self.redirect('/config/notifications/#tabs-3') + + account = sickbeard.helpers.tryInt(kwargs.get('account'), None) + try: + name = sickbeard.TRAKT_ACCOUNTS[account].name + except KeyError: + return self.trakt_default() + return self.browse_trakt( + 'users/%s/watchlist/shows?limit=%s&' % (sickbeard.TRAKT_ACCOUNTS[account].slug, 100), 'WatchList for %s by Trakt' % name, + mode='watchlist-%s' % account, send_oauth=account) + def trakt_default(self): return self.redirect('/home/addShows/%s' % ('trakt_trending', sickbeard.TRAKT_MRU)[any(sickbeard.TRAKT_MRU)]) @@ -3330,7 +3344,7 @@ class NewHomeAddShows(Home): browse_type = 'Trakt' normalised, filtered = ([], []) - if not sickbeard.USE_TRAKT and 'recommended' in kwargs.get('mode', ''): + if not sickbeard.USE_TRAKT and ('recommended' in kwargs.get('mode', '') or 'watchlist' in kwargs.get('mode', '')): error_msg = 'To browse personal recommendations, enable Trakt.tv in Config/Notifications/Social' return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs) @@ -3359,6 +3373,10 @@ class NewHomeAddShows(Home): except (IndexError, KeyError): pass + if not normalised: + error_msg = 'No items in watchlist. Use the "Add to watchlist" button at the Trakt website' + return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs) + oldest_dt = 9999999 newest_dt = 0 oldest = None @@ -3413,7 +3431,7 @@ class NewHomeAddShows(Home): kwargs.update(dict(oldest=oldest, newest=newest, error_msg=error_msg)) - if 'recommended' not in kwargs.get('mode', ''): + if 'recommended' not in kwargs.get('mode', '') and 'watchlist' not in kwargs.get('mode', ''): mode = kwargs.get('mode', '').split('-') if mode: func = 'trakt_%s' % mode[0] @@ -3424,6 +3442,20 @@ class NewHomeAddShows(Home): sickbeard.save_config() return self.browse_shows(browse_type, browse_title, filtered, **kwargs) + @staticmethod + def show_toggle_hide(ids): + save_config = False + for sid in ids.split(':'): + if 3 < len(sid) < 12: + save_config = True + if sid in sickbeard.BROWSELIST_HIDDEN: + sickbeard.BROWSELIST_HIDDEN.remove(sid) + else: + sickbeard.BROWSELIST_HIDDEN += [sid] + if save_config: + sickbeard.save_config() + return json.dumps({'success': save_config}) + @staticmethod def encode_html(text): @@ -3449,7 +3481,8 @@ class NewHomeAddShows(Home): t.kwargs = kwargs dedupe = [] - t.all_shows_inlibrary = 0 + t.num_inlibrary = 0 + t.num_hidden = 0 for item in shows: item['show_id'] = '' for index, tvdb in enumerate(['tvdb', 'tvrage']): @@ -3461,7 +3494,7 @@ class NewHomeAddShows(Home): # check tvshow indexer is not using the same id from another indexer if tvshow and (index + 1) == tvshow.indexer: item['show_id'] = u'%s:%s' % (tvshow.indexer, tvshow.indexerid) - t.all_shows_inlibrary += 1 + t.num_inlibrary += 1 break if None is not config.to_int(item['show_id'], None): @@ -3474,6 +3507,9 @@ class NewHomeAddShows(Home): dedupe.append(item['show_id']) t.all_shows.append(item) + if item['show_id'].split(':')[-1] in sickbeard.BROWSELIST_HIDDEN: + t.num_hidden += 1 + return t.respond() def import_shows(self, *args, **kwargs):