diff --git a/gui/slick/interfaces/default/home_browseShows.tmpl b/gui/slick/interfaces/default/home_browseShows.tmpl index a780c583..a11a2a73 100644 --- a/gui/slick/interfaces/default/home_browseShows.tmpl +++ b/gui/slick/interfaces/default/home_browseShows.tmpl @@ -196,6 +196,18 @@ $(document).ready(function(){ #else + #end if + + #if any($sg_var('TRAKT_ACCOUNTS', [])) + + #for $account in $sg_var('TRAKT_ACCOUNTS') + #if $sg_var('TRAKT_ACCOUNTS').get($account).active and $sg_var('TRAKT_ACCOUNTS').get($account).name + + #end if + #end for + #else + + #end if #elif 'IMDb' == $browse_type 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/webserve.py b/sickbeard/webserve.py index c061740e..7baa3109 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)