diff --git a/CHANGES.md b/CHANGES.md index e87e75e..97eb5e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -90,6 +90,7 @@ * Change revert all IMDb ids to 7 chars * Fix new unique_name in templates * Fix genre field in tvmaze_api +* Change api interface folders to api_* to fix legacy tmdb_api folder cleanup on new installs ### 0.24.15 (2021-08-05 11:45:00 UTC) diff --git a/_cleaner.py b/_cleaner.py index 4aaa1de..ada93b5 100644 --- a/_cleaner.py +++ b/_cleaner.py @@ -46,6 +46,11 @@ if old_magic != magic_number: # skip cleaned005 as used during dev by testers cleanups = [ + ['.cleaned007.tmp', ('lib', 'tvmaze_api'), [ + ('lib', 'imdb_api', '__pycache__'), ('lib', 'imdb_api'), + ('lib', 'libtrakt', '__pycache__'), ('lib', 'libtrakt'), + ('lib', 'tvdb_api', '__pycache__'), ('lib', 'tvdb_api'), + ('lib', 'tvmaze_api', '__pycache__'), ('lib', 'tvmaze_api')]], ['.cleaned006.tmp', ('lib', 'boto'), [ ('lib', 'boto'), ('lib', 'growl'), ('lib', 'hachoir', 'core'), ('lib', 'hachoir', 'field'), ('lib', 'hachoir', 'metadata'), diff --git a/gui/slick/interfaces/default/config_notifications.tmpl b/gui/slick/interfaces/default/config_notifications.tmpl index 328363a..d141ffa 100644 --- a/gui/slick/interfaces/default/config_notifications.tmpl +++ b/gui/slick/interfaces/default/config_notifications.tmpl @@ -1,7 +1,7 @@ #import base64 #import sickbeard #import re -#from lib.libtrakt import TraktAPI +#from lib.api_trakt import TraktAPI #from sickbeard.helpers import anon_url, starify #from sickbeard.notifiers import NotifierFactory <% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp# diff --git a/lib/imdb_api/__init__.py b/lib/api_imdb/__init__.py similarity index 100% rename from lib/imdb_api/__init__.py rename to lib/api_imdb/__init__.py diff --git a/lib/imdb_api/imdb_api.py b/lib/api_imdb/imdb_api.py similarity index 100% rename from lib/imdb_api/imdb_api.py rename to lib/api_imdb/imdb_api.py diff --git a/lib/imdb_api/imdb_exceptions.py b/lib/api_imdb/imdb_exceptions.py similarity index 100% rename from lib/imdb_api/imdb_exceptions.py rename to lib/api_imdb/imdb_exceptions.py diff --git a/lib/tmdb_api/__init__.py b/lib/api_tmdb/__init__.py similarity index 100% rename from lib/tmdb_api/__init__.py rename to lib/api_tmdb/__init__.py diff --git a/lib/tmdb_api/tmdb_api.py b/lib/api_tmdb/tmdb_api.py similarity index 100% rename from lib/tmdb_api/tmdb_api.py rename to lib/api_tmdb/tmdb_api.py diff --git a/lib/tmdb_api/tmdb_exceptions.py b/lib/api_tmdb/tmdb_exceptions.py similarity index 100% rename from lib/tmdb_api/tmdb_exceptions.py rename to lib/api_tmdb/tmdb_exceptions.py diff --git a/lib/libtrakt/__init__.py b/lib/api_trakt/__init__.py similarity index 97% rename from lib/libtrakt/__init__.py rename to lib/api_trakt/__init__.py index f3dd7b1..5255ce3 100644 --- a/lib/libtrakt/__init__.py +++ b/lib/api_trakt/__init__.py @@ -1,2 +1,2 @@ -from .trakt import TraktAPI -from .indexerapiinterface import TraktIndexer +from .trakt import TraktAPI +from .indexerapiinterface import TraktIndexer diff --git a/lib/libtrakt/exceptions.py b/lib/api_trakt/exceptions.py similarity index 94% rename from lib/libtrakt/exceptions.py rename to lib/api_trakt/exceptions.py index 5adf1c2..9e330c6 100644 --- a/lib/libtrakt/exceptions.py +++ b/lib/api_trakt/exceptions.py @@ -1,49 +1,49 @@ -class TraktException(Exception): - pass - - -class TraktAuthException(TraktException): - pass - - -class TraktServerBusy(TraktException): - pass - - -class TraktShowNotFound(TraktException): - pass - - -class TraktCloudFlareException(TraktException): - pass - - -class TraktMethodNotExisting(TraktException): - pass - - -class TraktTimeout(TraktException): - pass - - -class TraktValueError(TraktException): - pass - - -class TraktServerError(TraktException): - def __init__(self, *args, **kwargs): - self.error_code = kwargs.get('error_code') - kwargs = {} - if 0 < len(args): - args = tuple(['%s, Server Error: %s' % (args[0], self.error_code)]) - else: - args = tuple(['Server Error: %s' % self.error_code]) - super(TraktServerError, self).__init__(*args, **kwargs) - - -class TraktLockedUserAccount(TraktException): - pass - - -class TraktInvalidGrant(TraktException): - pass +class TraktException(Exception): + pass + + +class TraktAuthException(TraktException): + pass + + +class TraktServerBusy(TraktException): + pass + + +class TraktShowNotFound(TraktException): + pass + + +class TraktCloudFlareException(TraktException): + pass + + +class TraktMethodNotExisting(TraktException): + pass + + +class TraktTimeout(TraktException): + pass + + +class TraktValueError(TraktException): + pass + + +class TraktServerError(TraktException): + def __init__(self, *args, **kwargs): + self.error_code = kwargs.get('error_code') + kwargs = {} + if 0 < len(args): + args = tuple(['%s, Server Error: %s' % (args[0], self.error_code)]) + else: + args = tuple(['Server Error: %s' % self.error_code]) + super(TraktServerError, self).__init__(*args, **kwargs) + + +class TraktLockedUserAccount(TraktException): + pass + + +class TraktInvalidGrant(TraktException): + pass diff --git a/lib/libtrakt/indexerapiinterface.py b/lib/api_trakt/indexerapiinterface.py similarity index 97% rename from lib/libtrakt/indexerapiinterface.py rename to lib/api_trakt/indexerapiinterface.py index b854131..afcfc95 100644 --- a/lib/libtrakt/indexerapiinterface.py +++ b/lib/api_trakt/indexerapiinterface.py @@ -1,348 +1,348 @@ -import logging -import re -from .exceptions import TraktException -from exceptions_helper import ConnectionSkipException, ex -from six import iteritems -from .trakt import TraktAPI -from lib.tvinfo_base.exceptions import BaseTVinfoShownotfound -from lib.tvinfo_base import TVInfoBase, TVINFO_TRAKT, TVINFO_TMDB, TVINFO_TVDB, TVINFO_TVRAGE, TVINFO_IMDB, \ - TVINFO_SLUG, Person, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA, TVINFO_INSTAGRAM, Character, TVInfoShow, \ - TVInfoIDs, TVINFO_TRAKT_SLUG -from sg_helpers import try_int -from lib.dateutil.parser import parser - -# noinspection PyUnreachableCode -if False: - from typing import Any, AnyStr, Dict, List, Optional, Union - from six import integer_types - -id_map = { - 'trakt': TVINFO_TRAKT, - 'slug': TVINFO_SLUG, - 'tvdb': TVINFO_TVDB, - 'imdb': TVINFO_IMDB, - 'tmdb': TVINFO_TMDB, - 'tvrage': TVINFO_TVRAGE -} - -id_map_reverse = {v: k for k, v in iteritems(id_map)} - -tz_p = parser() -log = logging.getLogger('libtrakt.api') -log.addHandler(logging.NullHandler()) - - -def _convert_imdb_id(src, s_id): - if TVINFO_IMDB == src: - try: - return try_int(re.search(r'(\d+)', s_id).group(1), s_id) - except (BaseException, Exception): - pass - return s_id - - -class TraktSearchTypes(object): - text = 1 - trakt_id = 'trakt' - trakt_slug = 'trakt_slug' - tvdb_id = 'tvdb' - imdb_id = 'imdb' - tmdb_id = 'tmdb' - tvrage_id = 'tvrage' - all = [text, trakt_id, tvdb_id, imdb_id, tmdb_id, tvrage_id, trakt_slug] - - def __init__(self): - pass - - -map_id_search = {TVINFO_TVDB: TraktSearchTypes.tvdb_id, TVINFO_IMDB: TraktSearchTypes.imdb_id, - TVINFO_TMDB: TraktSearchTypes.tmdb_id, TVINFO_TRAKT: TraktSearchTypes.trakt_id, - TVINFO_TRAKT_SLUG: TraktSearchTypes.trakt_slug} - - -class TraktResultTypes(object): - show = 'show' - episode = 'episode' - movie = 'movie' - person = 'person' - list = 'list' - all = [show, episode, movie, person, list] - - def __init__(self): - pass - - -class TraktIndexer(TVInfoBase): - supported_id_searches = [TVINFO_TVDB, TVINFO_IMDB, TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TRAKT_SLUG] - supported_person_id_searches = [TVINFO_TRAKT, TVINFO_IMDB, TVINFO_TMDB] - - # noinspection PyUnusedLocal - # noinspection PyDefaultArgument - def __init__(self, custom_ui=None, sleep_retry=None, search_type=TraktSearchTypes.text, - result_types=[TraktResultTypes.show], *args, **kwargs): - super(TraktIndexer, self).__init__(*args, **kwargs) - self.config.update({ - 'apikey': '', - 'debug_enabled': False, - 'custom_ui': custom_ui, - 'proxy': None, - 'cache_enabled': False, - 'cache_location': '', - 'valid_languages': [], - 'langabbv_to_id': {}, - 'language': 'en', - 'base_url': '', - 'search_type': search_type if search_type in TraktSearchTypes.all else TraktSearchTypes.text, - 'sleep_retry': sleep_retry, - 'result_types': result_types if isinstance(result_types, list) and all( - [x in TraktResultTypes.all for x in result_types]) else [TraktResultTypes.show], - }) - - @staticmethod - def _make_result_obj(shows, results): - if shows: - try: - for s in shows: - if s['ids']['trakt'] not in [i['ids'].trakt for i in results]: - s['id'] = s['ids']['trakt'] - s['ids'] = TVInfoIDs( - trakt=s['ids']['trakt'], tvdb=s['ids']['tvdb'], tmdb=s['ids']['tmdb'], - rage=s['ids']['tvrage'], - imdb=s['ids']['imdb'] and try_int(s['ids']['imdb'].replace('tt', ''), None)) - results.append(s) - except (BaseException, Exception) as e: - log.debug('Error creating result dict: %s' % ex(e)) - - def _search_show(self, name=None, ids=None, **kwargs): - # type: (AnyStr, Dict[integer_types, integer_types], Optional[Any]) -> List[TVInfoShow] - """This searches Trakt for the series name, - If a custom_ui UI is configured, it uses this to select the correct - series. - """ - results = [] - if ids: - for t, p in iteritems(ids): - if t in self.supported_id_searches: - if t in (TVINFO_TVDB, TVINFO_IMDB, TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TRAKT_SLUG): - cache_id_key = 's-id-%s-%s' % (t, p) - is_none, shows = self._get_cache_entry(cache_id_key) - if not self.config.get('cache_search') or (None is shows and not is_none): - try: - show = self.search(p, search_type=map_id_search[t]) - except (BaseException, Exception): - continue - self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire) - else: - show = shows - else: - continue - self._make_result_obj(show, results) - if name: - names = ([name], name)[isinstance(name, list)] - len_names = len(names) - for i, n in enumerate(names, 1): - cache_name_key = 's-name-%s' % n - is_none, shows = self._get_cache_entry(cache_name_key) - if not self.config.get('cache_search') or (None is shows and not is_none): - try: - all_series = self.search(n) - self._set_cache_entry(cache_name_key, all_series, expire=self.search_cache_expire) - except (BaseException, Exception): - all_series = [] - else: - all_series = shows - if not isinstance(all_series, list): - all_series = [all_series] - - if i == len_names and 0 == len(all_series) and not results: - log.debug('Series result returned zero') - raise BaseTVinfoShownotfound('Show-name search returned zero results (cannot find show on TVDB)') - - if all_series: - if None is not self.config['custom_ui']: - log.debug('Using custom UI %s' % self.config['custom_ui'].__name__) - custom_ui = self.config['custom_ui'] - ui = custom_ui(config=self.config) - self._make_result_obj(ui.select_series(all_series), results) - - else: - self._make_result_obj(all_series, results) - - seen = set() - results = [seen.add(r['id']) or r for r in results if r['id'] not in seen] - return results - - @staticmethod - def _dict_prevent_none(d, key, default): - v = None - if isinstance(d, dict): - v = d.get(key, default) - return (v, default)[None is v] - - def search(self, series, search_type=None): - # type: (AnyStr, Union[int, AnyStr]) -> List - search_type = search_type or self.config['search_type'] - if TraktSearchTypes.trakt_slug == search_type: - url = '/shows/%s?extended=full' % series - elif TraktSearchTypes.text != search_type: - url = '/search/%s/%s?type=%s&extended=full&limit=100' % (search_type, (series, 'tt%07d' % series)[ - TraktSearchTypes.imdb_id == search_type and not str(series).startswith('tt')], - ','.join(self.config['result_types'])) - else: - url = '/search/%s?query=%s&extended=full&limit=100' % (','.join(self.config['result_types']), series) - filtered = [] - kwargs = {} - if None is not self.config['sleep_retry']: - kwargs['sleep_retry'] = self.config['sleep_retry'] - try: - from sickbeard.helpers import clean_data - resp = TraktAPI().trakt_request(url, failure_monitor=False, raise_skip_exception=False, **kwargs) - if len(resp): - if isinstance(resp, dict): - resp = [{'type': 'show', 'score': 1, 'show': resp}] - for d in resp: - if isinstance(d, dict) and 'type' in d and d['type'] in self.config['result_types']: - for k, v in iteritems(d): - d[k] = clean_data(v) - if 'show' in d and TraktResultTypes.show == d['type']: - d.update(d['show']) - del d['show'] - d['seriesname'] = self._dict_prevent_none(d, 'title', '') - d['genres_list'] = d.get('genres', []) - d['genres'] = ', '.join(['%s' % v for v in d.get('genres', []) or [] if v]) - d['firstaired'] = (d.get('first_aired') and - re.sub(r'T.*$', '', str(d.get('first_aired'))) or d.get('year')) - filtered.append(d) - except (ConnectionSkipException, TraktException) as e: - log.debug('Could not connect to Trakt service: %s' % ex(e)) - - return filtered - - @staticmethod - def _convert_person_obj(person_obj): - # type: (Dict) -> Person - try: - birthdate = person_obj['birthday'] and tz_p.parse(person_obj['birthday']).date() - except (BaseException, Exception): - birthdate = None - try: - deathdate = person_obj['death'] and tz_p.parse(person_obj['death']).date() - except (BaseException, Exception): - deathdate = None - - return Person(p_id=person_obj['ids']['trakt'], - name=person_obj['name'], - bio=person_obj['biography'], - birthdate=birthdate, - deathdate=deathdate, - homepage=person_obj['homepage'], - birthplace=person_obj['birthplace'], - social_ids={TVINFO_TWITTER: person_obj['social_ids']['twitter'], - TVINFO_FACEBOOK: person_obj['social_ids']['facebook'], - TVINFO_INSTAGRAM: person_obj['social_ids']['instagram'], - TVINFO_WIKIPEDIA: person_obj['social_ids']['wikipedia'] - }, - ids={TVINFO_TRAKT: person_obj['ids']['trakt'], TVINFO_SLUG: person_obj['ids']['slug'], - TVINFO_IMDB: - person_obj['ids']['imdb'] and - try_int(person_obj['ids']['imdb'].replace('nm', ''), None), - TVINFO_TMDB: person_obj['ids']['tmdb'], - TVINFO_TVRAGE: person_obj['ids']['tvrage']}) - - def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs): - # type: (integer_types, bool, bool, Any) -> Optional[Person] - """ - get person's data for id or list of matching persons for name - - :param p_id: persons id - :param get_show_credits: get show credits (only for native id) - :param get_images: get images for person - :return: person object - """ - if not p_id: - return - - urls = [('/people/%s?extended=full' % p_id, False)] - if get_show_credits: - urls.append(('/people/%s/shows?extended=full' % p_id, True)) - - if not urls: - return - - result = None - - for url, show_credits in urls: - try: - cache_key_name = 'p-%s-%s' % (('main', 'credits')[show_credits], p_id) - is_none, resp = self._get_cache_entry(cache_key_name) - if None is resp and not is_none: - resp = TraktAPI().trakt_request(url, **kwargs) - self._set_cache_entry(cache_key_name, resp) - if resp: - if show_credits: - pc = [] - for c in resp.get('cast') or []: - show = TVInfoShow() - show.id = c['show']['ids'].get('trakt') - show.seriesname = c['show']['title'] - show.ids = TVInfoIDs(ids={id_map[src]: _convert_imdb_id(id_map[src], sid) - for src, sid in iteritems(c['show']['ids']) if src in id_map}) - show.network = c['show']['network'] - show.firstaired = c['show']['first_aired'] - show.overview = c['show']['overview'] - show.status = c['show']['status'] - show.imdb_id = c['show']['ids'].get('imdb') - show.runtime = c['show']['runtime'] - show.genre_list = c['show']['genres'] - for ch in c.get('characters') or []: - pc.append( - Character( - name=ch, regular=c.get('series_regular'), - show=show - ) - ) - result.characters = pc - else: - result = self._convert_person_obj(resp) - except ConnectionSkipException as e: - raise e - except TraktException as e: - log.debug('Could not connect to Trakt service: %s' % ex(e)) - return result - - def _search_person(self, name=None, ids=None): - # type: (AnyStr, Dict[integer_types, integer_types]) -> List[Person] - urls, result, ids = [], [], ids or {} - for tv_src in self.supported_person_id_searches: - if tv_src in ids: - if TVINFO_TRAKT == tv_src: - url = '/people/%s?extended=full' % ids.get(tv_src) - elif tv_src in (TVINFO_IMDB, TVINFO_TMDB): - url = '/search/%s/%s?type=person&extended=full&limit=100' % \ - (id_map_reverse[tv_src], (ids.get(tv_src), 'nm%07d' % ids.get(tv_src))[TVINFO_IMDB == tv_src]) - else: - continue - urls.append((tv_src, ids.get(tv_src), url)) - if name: - urls.append(('text', name, '/search/person?query=%s&extended=full&limit=100' % name)) - - for src, s_id, url in urls: - try: - cache_key_name = 'p-src-%s-%s' % (src, s_id) - is_none, resp = self._get_cache_entry(cache_key_name) - if None is resp and not is_none: - resp = TraktAPI().trakt_request(url) - self._set_cache_entry(cache_key_name, resp) - if resp: - for per in (resp, [{'person': resp, 'type': 'person'}])[url.startswith('/people')]: - if 'person' != per['type']: - continue - person = per['person'] - if not any(1 for p in result if person['ids']['trakt'] == p.id): - result.append(self._convert_person_obj(person)) - except ConnectionSkipException as e: - raise e - except TraktException as e: - log.debug('Could not connect to Trakt service: %s' % ex(e)) - - return result +import logging +import re +from .exceptions import TraktException +from exceptions_helper import ConnectionSkipException, ex +from six import iteritems +from .trakt import TraktAPI +from lib.tvinfo_base.exceptions import BaseTVinfoShownotfound +from lib.tvinfo_base import TVInfoBase, TVINFO_TRAKT, TVINFO_TMDB, TVINFO_TVDB, TVINFO_TVRAGE, TVINFO_IMDB, \ + TVINFO_SLUG, Person, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA, TVINFO_INSTAGRAM, Character, TVInfoShow, \ + TVInfoIDs, TVINFO_TRAKT_SLUG +from sg_helpers import try_int +from lib.dateutil.parser import parser + +# noinspection PyUnreachableCode +if False: + from typing import Any, AnyStr, Dict, List, Optional, Union + from six import integer_types + +id_map = { + 'trakt': TVINFO_TRAKT, + 'slug': TVINFO_SLUG, + 'tvdb': TVINFO_TVDB, + 'imdb': TVINFO_IMDB, + 'tmdb': TVINFO_TMDB, + 'tvrage': TVINFO_TVRAGE +} + +id_map_reverse = {v: k for k, v in iteritems(id_map)} + +tz_p = parser() +log = logging.getLogger('api_trakt.api') +log.addHandler(logging.NullHandler()) + + +def _convert_imdb_id(src, s_id): + if TVINFO_IMDB == src: + try: + return try_int(re.search(r'(\d+)', s_id).group(1), s_id) + except (BaseException, Exception): + pass + return s_id + + +class TraktSearchTypes(object): + text = 1 + trakt_id = 'trakt' + trakt_slug = 'trakt_slug' + tvdb_id = 'tvdb' + imdb_id = 'imdb' + tmdb_id = 'tmdb' + tvrage_id = 'tvrage' + all = [text, trakt_id, tvdb_id, imdb_id, tmdb_id, tvrage_id, trakt_slug] + + def __init__(self): + pass + + +map_id_search = {TVINFO_TVDB: TraktSearchTypes.tvdb_id, TVINFO_IMDB: TraktSearchTypes.imdb_id, + TVINFO_TMDB: TraktSearchTypes.tmdb_id, TVINFO_TRAKT: TraktSearchTypes.trakt_id, + TVINFO_TRAKT_SLUG: TraktSearchTypes.trakt_slug} + + +class TraktResultTypes(object): + show = 'show' + episode = 'episode' + movie = 'movie' + person = 'person' + list = 'list' + all = [show, episode, movie, person, list] + + def __init__(self): + pass + + +class TraktIndexer(TVInfoBase): + supported_id_searches = [TVINFO_TVDB, TVINFO_IMDB, TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TRAKT_SLUG] + supported_person_id_searches = [TVINFO_TRAKT, TVINFO_IMDB, TVINFO_TMDB] + + # noinspection PyUnusedLocal + # noinspection PyDefaultArgument + def __init__(self, custom_ui=None, sleep_retry=None, search_type=TraktSearchTypes.text, + result_types=[TraktResultTypes.show], *args, **kwargs): + super(TraktIndexer, self).__init__(*args, **kwargs) + self.config.update({ + 'apikey': '', + 'debug_enabled': False, + 'custom_ui': custom_ui, + 'proxy': None, + 'cache_enabled': False, + 'cache_location': '', + 'valid_languages': [], + 'langabbv_to_id': {}, + 'language': 'en', + 'base_url': '', + 'search_type': search_type if search_type in TraktSearchTypes.all else TraktSearchTypes.text, + 'sleep_retry': sleep_retry, + 'result_types': result_types if isinstance(result_types, list) and all( + [x in TraktResultTypes.all for x in result_types]) else [TraktResultTypes.show], + }) + + @staticmethod + def _make_result_obj(shows, results): + if shows: + try: + for s in shows: + if s['ids']['trakt'] not in [i['ids'].trakt for i in results]: + s['id'] = s['ids']['trakt'] + s['ids'] = TVInfoIDs( + trakt=s['ids']['trakt'], tvdb=s['ids']['tvdb'], tmdb=s['ids']['tmdb'], + rage=s['ids']['tvrage'], + imdb=s['ids']['imdb'] and try_int(s['ids']['imdb'].replace('tt', ''), None)) + results.append(s) + except (BaseException, Exception) as e: + log.debug('Error creating result dict: %s' % ex(e)) + + def _search_show(self, name=None, ids=None, **kwargs): + # type: (AnyStr, Dict[integer_types, integer_types], Optional[Any]) -> List[TVInfoShow] + """This searches Trakt for the series name, + If a custom_ui UI is configured, it uses this to select the correct + series. + """ + results = [] + if ids: + for t, p in iteritems(ids): + if t in self.supported_id_searches: + if t in (TVINFO_TVDB, TVINFO_IMDB, TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TRAKT_SLUG): + cache_id_key = 's-id-%s-%s' % (t, p) + is_none, shows = self._get_cache_entry(cache_id_key) + if not self.config.get('cache_search') or (None is shows and not is_none): + try: + show = self.search(p, search_type=map_id_search[t]) + except (BaseException, Exception): + continue + self._set_cache_entry(cache_id_key, show, expire=self.search_cache_expire) + else: + show = shows + else: + continue + self._make_result_obj(show, results) + if name: + names = ([name], name)[isinstance(name, list)] + len_names = len(names) + for i, n in enumerate(names, 1): + cache_name_key = 's-name-%s' % n + is_none, shows = self._get_cache_entry(cache_name_key) + if not self.config.get('cache_search') or (None is shows and not is_none): + try: + all_series = self.search(n) + self._set_cache_entry(cache_name_key, all_series, expire=self.search_cache_expire) + except (BaseException, Exception): + all_series = [] + else: + all_series = shows + if not isinstance(all_series, list): + all_series = [all_series] + + if i == len_names and 0 == len(all_series) and not results: + log.debug('Series result returned zero') + raise BaseTVinfoShownotfound('Show-name search returned zero results (cannot find show on TVDB)') + + if all_series: + if None is not self.config['custom_ui']: + log.debug('Using custom UI %s' % self.config['custom_ui'].__name__) + custom_ui = self.config['custom_ui'] + ui = custom_ui(config=self.config) + self._make_result_obj(ui.select_series(all_series), results) + + else: + self._make_result_obj(all_series, results) + + seen = set() + results = [seen.add(r['id']) or r for r in results if r['id'] not in seen] + return results + + @staticmethod + def _dict_prevent_none(d, key, default): + v = None + if isinstance(d, dict): + v = d.get(key, default) + return (v, default)[None is v] + + def search(self, series, search_type=None): + # type: (AnyStr, Union[int, AnyStr]) -> List + search_type = search_type or self.config['search_type'] + if TraktSearchTypes.trakt_slug == search_type: + url = '/shows/%s?extended=full' % series + elif TraktSearchTypes.text != search_type: + url = '/search/%s/%s?type=%s&extended=full&limit=100' % (search_type, (series, 'tt%07d' % series)[ + TraktSearchTypes.imdb_id == search_type and not str(series).startswith('tt')], + ','.join(self.config['result_types'])) + else: + url = '/search/%s?query=%s&extended=full&limit=100' % (','.join(self.config['result_types']), series) + filtered = [] + kwargs = {} + if None is not self.config['sleep_retry']: + kwargs['sleep_retry'] = self.config['sleep_retry'] + try: + from sickbeard.helpers import clean_data + resp = TraktAPI().trakt_request(url, failure_monitor=False, raise_skip_exception=False, **kwargs) + if len(resp): + if isinstance(resp, dict): + resp = [{'type': 'show', 'score': 1, 'show': resp}] + for d in resp: + if isinstance(d, dict) and 'type' in d and d['type'] in self.config['result_types']: + for k, v in iteritems(d): + d[k] = clean_data(v) + if 'show' in d and TraktResultTypes.show == d['type']: + d.update(d['show']) + del d['show'] + d['seriesname'] = self._dict_prevent_none(d, 'title', '') + d['genres_list'] = d.get('genres', []) + d['genres'] = ', '.join(['%s' % v for v in d.get('genres', []) or [] if v]) + d['firstaired'] = (d.get('first_aired') and + re.sub(r'T.*$', '', str(d.get('first_aired'))) or d.get('year')) + filtered.append(d) + except (ConnectionSkipException, TraktException) as e: + log.debug('Could not connect to Trakt service: %s' % ex(e)) + + return filtered + + @staticmethod + def _convert_person_obj(person_obj): + # type: (Dict) -> Person + try: + birthdate = person_obj['birthday'] and tz_p.parse(person_obj['birthday']).date() + except (BaseException, Exception): + birthdate = None + try: + deathdate = person_obj['death'] and tz_p.parse(person_obj['death']).date() + except (BaseException, Exception): + deathdate = None + + return Person(p_id=person_obj['ids']['trakt'], + name=person_obj['name'], + bio=person_obj['biography'], + birthdate=birthdate, + deathdate=deathdate, + homepage=person_obj['homepage'], + birthplace=person_obj['birthplace'], + social_ids={TVINFO_TWITTER: person_obj['social_ids']['twitter'], + TVINFO_FACEBOOK: person_obj['social_ids']['facebook'], + TVINFO_INSTAGRAM: person_obj['social_ids']['instagram'], + TVINFO_WIKIPEDIA: person_obj['social_ids']['wikipedia'] + }, + ids={TVINFO_TRAKT: person_obj['ids']['trakt'], TVINFO_SLUG: person_obj['ids']['slug'], + TVINFO_IMDB: + person_obj['ids']['imdb'] and + try_int(person_obj['ids']['imdb'].replace('nm', ''), None), + TVINFO_TMDB: person_obj['ids']['tmdb'], + TVINFO_TVRAGE: person_obj['ids']['tvrage']}) + + def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs): + # type: (integer_types, bool, bool, Any) -> Optional[Person] + """ + get person's data for id or list of matching persons for name + + :param p_id: persons id + :param get_show_credits: get show credits (only for native id) + :param get_images: get images for person + :return: person object + """ + if not p_id: + return + + urls = [('/people/%s?extended=full' % p_id, False)] + if get_show_credits: + urls.append(('/people/%s/shows?extended=full' % p_id, True)) + + if not urls: + return + + result = None + + for url, show_credits in urls: + try: + cache_key_name = 'p-%s-%s' % (('main', 'credits')[show_credits], p_id) + is_none, resp = self._get_cache_entry(cache_key_name) + if None is resp and not is_none: + resp = TraktAPI().trakt_request(url, **kwargs) + self._set_cache_entry(cache_key_name, resp) + if resp: + if show_credits: + pc = [] + for c in resp.get('cast') or []: + show = TVInfoShow() + show.id = c['show']['ids'].get('trakt') + show.seriesname = c['show']['title'] + show.ids = TVInfoIDs(ids={id_map[src]: _convert_imdb_id(id_map[src], sid) + for src, sid in iteritems(c['show']['ids']) if src in id_map}) + show.network = c['show']['network'] + show.firstaired = c['show']['first_aired'] + show.overview = c['show']['overview'] + show.status = c['show']['status'] + show.imdb_id = c['show']['ids'].get('imdb') + show.runtime = c['show']['runtime'] + show.genre_list = c['show']['genres'] + for ch in c.get('characters') or []: + pc.append( + Character( + name=ch, regular=c.get('series_regular'), + show=show + ) + ) + result.characters = pc + else: + result = self._convert_person_obj(resp) + except ConnectionSkipException as e: + raise e + except TraktException as e: + log.debug('Could not connect to Trakt service: %s' % ex(e)) + return result + + def _search_person(self, name=None, ids=None): + # type: (AnyStr, Dict[integer_types, integer_types]) -> List[Person] + urls, result, ids = [], [], ids or {} + for tv_src in self.supported_person_id_searches: + if tv_src in ids: + if TVINFO_TRAKT == tv_src: + url = '/people/%s?extended=full' % ids.get(tv_src) + elif tv_src in (TVINFO_IMDB, TVINFO_TMDB): + url = '/search/%s/%s?type=person&extended=full&limit=100' % \ + (id_map_reverse[tv_src], (ids.get(tv_src), 'nm%07d' % ids.get(tv_src))[TVINFO_IMDB == tv_src]) + else: + continue + urls.append((tv_src, ids.get(tv_src), url)) + if name: + urls.append(('text', name, '/search/person?query=%s&extended=full&limit=100' % name)) + + for src, s_id, url in urls: + try: + cache_key_name = 'p-src-%s-%s' % (src, s_id) + is_none, resp = self._get_cache_entry(cache_key_name) + if None is resp and not is_none: + resp = TraktAPI().trakt_request(url) + self._set_cache_entry(cache_key_name, resp) + if resp: + for per in (resp, [{'person': resp, 'type': 'person'}])[url.startswith('/people')]: + if 'person' != per['type']: + continue + person = per['person'] + if not any(1 for p in result if person['ids']['trakt'] == p.id): + result.append(self._convert_person_obj(person)) + except ConnectionSkipException as e: + raise e + except TraktException as e: + log.debug('Could not connect to Trakt service: %s' % ex(e)) + + return result diff --git a/lib/libtrakt/trakt.py b/lib/api_trakt/trakt.py similarity index 99% rename from lib/libtrakt/trakt.py rename to lib/api_trakt/trakt.py index 9c29e20..589f14c 100644 --- a/lib/libtrakt/trakt.py +++ b/lib/api_trakt/trakt.py @@ -14,7 +14,7 @@ from .exceptions import * if False: from typing import Any, AnyStr, Dict -log = logging.getLogger('libtrakt') +log = logging.getLogger('api_trakt') log.addHandler(logging.NullHandler()) diff --git a/lib/tvdb_api/UNLICENSE b/lib/api_tvdb/UNLICENSE similarity index 100% rename from lib/tvdb_api/UNLICENSE rename to lib/api_tvdb/UNLICENSE diff --git a/lib/tvdb_api/__init__.py b/lib/api_tvdb/__init__.py similarity index 100% rename from lib/tvdb_api/__init__.py rename to lib/api_tvdb/__init__.py diff --git a/lib/tvdb_api/tvdb_api.py b/lib/api_tvdb/tvdb_api.py similarity index 100% rename from lib/tvdb_api/tvdb_api.py rename to lib/api_tvdb/tvdb_api.py diff --git a/lib/tvdb_api/tvdb_cache.py b/lib/api_tvdb/tvdb_cache.py similarity index 100% rename from lib/tvdb_api/tvdb_cache.py rename to lib/api_tvdb/tvdb_cache.py diff --git a/lib/tvdb_api/tvdb_exceptions.py b/lib/api_tvdb/tvdb_exceptions.py similarity index 100% rename from lib/tvdb_api/tvdb_exceptions.py rename to lib/api_tvdb/tvdb_exceptions.py diff --git a/lib/tvdb_api/tvdb_ui.py b/lib/api_tvdb/tvdb_ui.py similarity index 100% rename from lib/tvdb_api/tvdb_ui.py rename to lib/api_tvdb/tvdb_ui.py diff --git a/lib/tvmaze_api/__init__.py b/lib/api_tvmaze/__init__.py similarity index 100% rename from lib/tvmaze_api/__init__.py rename to lib/api_tvmaze/__init__.py diff --git a/lib/tvmaze_api/tvmaze_api.py b/lib/api_tvmaze/tvmaze_api.py similarity index 100% rename from lib/tvmaze_api/tvmaze_api.py rename to lib/api_tvmaze/tvmaze_api.py diff --git a/lib/tvmaze_api/tvmaze_exceptions.py b/lib/api_tvmaze/tvmaze_exceptions.py similarity index 100% rename from lib/tvmaze_api/tvmaze_exceptions.py rename to lib/api_tvmaze/tvmaze_exceptions.py diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index f0f20b3..d747fa2 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -57,7 +57,7 @@ from adba.aniDBerrors import AniDBError # noinspection PyProtectedMember from browser_ua import get_ua from configobj import ConfigObj -from libtrakt import TraktAPI +from api_trakt import TraktAPI from _23 import b64encodestring, decode_bytes, filter_iter, list_items, map_list, ordered_dict, scandir from six import iteritems, PY2, string_types @@ -69,7 +69,7 @@ if False: from adba import Connection from .event_queue import Events from .tv import TVShow - from lib.libtrakt.trakt import TraktAccount + from lib.api_trakt.trakt import TraktAccount PID = None ENV = {} diff --git a/sickbeard/config.py b/sickbeard/config.py index 3bca7fb..088b192 100644 --- a/sickbeard/config.py +++ b/sickbeard/config.py @@ -26,7 +26,7 @@ import encodingKludge as ek import sickbeard import sickbeard.providers from . import db, helpers, logger, naming -from lib.libtrakt import TraktAPI +from lib.api_trakt import TraktAPI from _23 import filter_list, urlsplit, urlunsplit from six import string_types diff --git a/sickbeard/indexers/indexer_config.py b/sickbeard/indexers/indexer_config.py index d55d83b..9577b31 100644 --- a/sickbeard/indexers/indexer_config.py +++ b/sickbeard/indexers/indexer_config.py @@ -1,8 +1,8 @@ -from lib.tvdb_api.tvdb_api import Tvdb -from lib.libtrakt.indexerapiinterface import TraktIndexer -from lib.tvmaze_api.tvmaze_api import TvMaze -from lib.tmdb_api.tmdb_api import TmdbIndexer -from lib.imdb_api.imdb_api import IMDbIndexer +from lib.api_tvdb.tvdb_api import Tvdb +from lib.api_trakt.indexerapiinterface import TraktIndexer +from lib.api_tvmaze.tvmaze_api import TvMaze +from lib.api_tmdb.tmdb_api import TmdbIndexer +from lib.api_imdb.imdb_api import IMDbIndexer # noinspection PyUnresolvedReferences from lib.tvinfo_base import ( TVINFO_FACEBOOK, TVINFO_INSTAGRAM, TVINFO_TWITTER, TVINFO_WIKIPEDIA, diff --git a/sickbeard/logger.py b/sickbeard/logger.py index 6a41d6f..4d26213 100644 --- a/sickbeard/logger.py +++ b/sickbeard/logger.py @@ -76,7 +76,7 @@ class SBRotatingLogHandler(object): self.log_lock = threading.Lock() self.log_types = ['sickbeard', 'tornado.application', 'tornado.general', 'subliminal', 'adba', 'encodingKludge', 'tvdb.api', 'TVInfo'] - self.external_loggers = ['sg.helper', 'libtrakt', 'libtrakt.api'] + self.external_loggers = ['sg.helper', 'api_trakt', 'api_trakt.api'] self.log_types_null = ['tornado.access'] def __del__(self): diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py index 8f5f729..0c5d35e 100644 --- a/sickbeard/notifiers/__init__.py +++ b/sickbeard/notifiers/__init__.py @@ -20,7 +20,7 @@ import os import re # import pushalot -# from lib import libtrakt +# from lib import api_trakt from . import emby, kodi, plex, xbmc, \ boxcar2, nmj, nmjv2, pushbullet, pushover, pytivo, synoindex, synologynotifier, \ discord, emailnotify, gitter, libnotify, growl, prowl, slack, telegram, trakt diff --git a/sickbeard/notifiers/trakt.py b/sickbeard/notifiers/trakt.py index 03c1486..716162c 100644 --- a/sickbeard/notifiers/trakt.py +++ b/sickbeard/notifiers/trakt.py @@ -20,7 +20,7 @@ import os from .generic import BaseNotifier import sickbeard -from lib.libtrakt import TraktAPI, exceptions +from lib.api_trakt import TraktAPI, exceptions from exceptions_helper import ConnectionSkipException from _23 import list_keys diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 25b737e..b459355 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -91,8 +91,8 @@ from lib.cfscrape import CloudflareScraper from lib.dateutil import tz, zoneinfo from lib.dateutil.relativedelta import relativedelta from lib.fuzzywuzzy import fuzz -from lib.libtrakt import TraktAPI -from lib.libtrakt.exceptions import TraktException, TraktAuthException +from lib.api_trakt import TraktAPI +from lib.api_trakt.exceptions import TraktException, TraktAuthException import lib.rarfile.rarfile as rarfile