From b59084d27da0dd539064045608782447c53141a6 Mon Sep 17 00:00:00 2001 From: Prinz23 Date: Mon, 18 Sep 2023 14:35:21 +0100 Subject: [PATCH] Add TVDb person search fix handle tvdb error on browse tvdb page adjust for datetime import changes in webserve remove any references to sickgear in tvdb api libs replace deprecated old method_whitelist with allowed_methods --- gui/slick/interfaces/default/cast_person.tmpl | 4 +- .../interfaces/default/home_browseShows.tmpl | 2 +- lib/api_tvdb/tvdb_api.py | 3 +- lib/api_tvdb/tvdb_api_v4.py | 28 ++-- lib/tvinfo_base/base.py | 5 +- sickgear/webserve.py | 123 ++++++++++++------ 6 files changed, 108 insertions(+), 57 deletions(-) diff --git a/gui/slick/interfaces/default/cast_person.tmpl b/gui/slick/interfaces/default/cast_person.tmpl index b60de076..fa35d05d 100644 --- a/gui/slick/interfaces/default/cast_person.tmpl +++ b/gui/slick/interfaces/default/cast_person.tmpl @@ -2,7 +2,7 @@ #import re #import sickgear #from sickgear import TVInfoAPI -#from sickgear.indexers.indexer_config import TVINFO_TMDB, TVINFO_TRAKT, TVINFO_TVMAZE +#from sickgear.indexers.indexer_config import TVINFO_TMDB, TVINFO_TVDB, TVINFO_TRAKT, TVINFO_TVMAZE #from sickgear.helpers import anon_url #from sickgear.tv import PersonGenders #from sg_helpers import spoken_height @@ -212,7 +212,7 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non #end if -#set $src = (($TVINFO_TVMAZE, 'tvm'), ($TVINFO_TMDB, 'tmdb'), ($TVINFO_TRAKT, 'trakt')) +#set $src = (($TVINFO_TVDB, 'tvdb'), ($TVINFO_TVMAZE, 'tvm'), ($TVINFO_TMDB, 'tmdb'), ($TVINFO_TRAKT, 'trakt')) #if any([$person.ids.get($cur_src) for ($cur_src, _) in $src])
Other shows diff --git a/gui/slick/interfaces/default/home_browseShows.tmpl b/gui/slick/interfaces/default/home_browseShows.tmpl index 48a37175..21955914 100644 --- a/gui/slick/interfaces/default/home_browseShows.tmpl +++ b/gui/slick/interfaces/default/home_browseShows.tmpl @@ -60,7 +60,7 @@ // Set initial text overviewEl.html('Fetching overview...'); - $.getJSON($.SickGear.Root + '/add-shows/tvm-get-showinfo', { + $.getJSON($.SickGear.Root + '/add-shows/tvi-get-showinfo', { tvid_prodid: showcardEl.attr('data-id'), oldest_dt: $('#oldest').attr('data-oldest-dt'), newest_dt: $('#newest').attr('data-newest-dt'), diff --git a/lib/api_tvdb/tvdb_api.py b/lib/api_tvdb/tvdb_api.py index ffd5bb24..56201dd4 100644 --- a/lib/api_tvdb/tvdb_api.py +++ b/lib/api_tvdb/tvdb_api.py @@ -27,7 +27,6 @@ import warnings from bs4_parser import BS4Parser from collections import OrderedDict from sg_helpers import clean_data, get_url, try_int -from sickgear import ENV from lib.cachecontrol import CacheControl, caches from lib.dateutil.parser import parse @@ -46,6 +45,8 @@ if False: from typing import Any, AnyStr, Dict, List, Optional, Union +ENV = os.environ + THETVDB_V2_API_TOKEN = {'token': None, 'datetime': datetime.datetime.fromordinal(1)} log = logging.getLogger('tvdb.api') log.addHandler(logging.NullHandler()) diff --git a/lib/api_tvdb/tvdb_api_v4.py b/lib/api_tvdb/tvdb_api_v4.py index 1bb4bbf1..a1e0ea45 100644 --- a/lib/api_tvdb/tvdb_api_v4.py +++ b/lib/api_tvdb/tvdb_api_v4.py @@ -9,6 +9,7 @@ __api_version__ = '1.0.0' import base64 import datetime import logging +import os import re from bs4_parser import BS4Parser @@ -29,13 +30,15 @@ from lib.tvinfo_base import ( TVINFO_MID_SEASON_FINALE, TVINFO_SEASON_FINALE, TVINFO_SERIES_FINALE, TVINFO_TIKTOK, TVINFO_TMDB, TVINFO_TVDB, TVINFO_TVDB_SLUG, TVINFO_TVMAZE, TVINFO_TWITTER, TVINFO_WIKIDATA, TVINFO_WIKIPEDIA, TVINFO_YOUTUBE) from sg_helpers import clean_data, clean_str, enforce_type, get_url, try_date, try_int -from sickgear import ENV from six import integer_types, iteritems, PY3, string_types # noinspection PyUnreachableCode if False: from typing import Any, AnyStr, Dict, List, Optional, Tuple, Union + +ENV = os.environ + log = logging.getLogger('tvdb_v4.api') log.addHandler(logging.NullHandler()) @@ -101,7 +104,10 @@ class TvdbAuth(RequestsAuthBase): @property def token(self): if not self._token: - self.get_token() + try: + self.get_token() + except TvdbTokenFailure as e: + log.error(f'Tvdb Taken Failure: {e}') return self._token def handle_401(self, r, **kwargs): @@ -146,7 +152,7 @@ s = requests.Session() retries = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], - method_whitelist=['HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST']) + allowed_methods=['HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST']) # noinspection HttpUrlsUsage s.mount('http://', HTTPAdapter(TimeoutHTTPAdapter(max_retries=retries))) s.mount('https://', HTTPAdapter(TimeoutHTTPAdapter(max_retries=retries))) @@ -280,12 +286,13 @@ class TvdbAPIv4(TVInfoBase): if f'?page={page}' in ((resp.get('links') or {}).get('next') or ''): return page - def _convert_person(self, p, ids=None): - # type: (Dict, Dict) -> List[TVInfoPerson] + def _convert_person(self, p, ids=None, include_guests=False): + # type: (Dict, Dict, bool) -> List[TVInfoPerson] ch, ids = [], ids or {} + p_types = ([3], [3, 4])[include_guests] for cur_c in sorted(filter( - lambda a: (3 == a['type'] or 'Actor' == a['peopleType']) - and a['name'] and a['seriesId'] and not a.get('episodeId'), + lambda a: (a['type'] in p_types or 'Actor' == a['peopleType']) + and a['seriesId'] and ((not a.get('episodeId') and a['name']) or include_guests), p.get('characters') or []), key=lambda a: (not a['isFeatured'], a['sort'])): ti_show = TVInfoShow() @@ -392,8 +399,8 @@ class TvdbAPIv4(TVInfoBase): resp = None return resp - def get_person(self, p_id, get_show_credits=False, get_images=False, try_cache=True, **kwargs): - # type: (integer_types, bool, bool, bool, ...) -> Optional[TVInfoPerson] + def get_person(self, p_id, get_show_credits=False, get_images=False, try_cache=True, include_guests=False, **kwargs): + # type: (integer_types, bool, bool, bool, bool, ...) -> Optional[TVInfoPerson] """ get person's data for id or list of matching persons for name @@ -401,12 +408,13 @@ class TvdbAPIv4(TVInfoBase): :param get_show_credits: get show credits :param get_images: get person images :param try_cache: use cached data if available + :param include_guests: include guest roles :return: person object or None """ if bool(p_id) and self._check_resp(dict, resp := self.get_cached_or_url( f'/people/{p_id}/extended', f'p-v4-{p_id}', try_cache=try_cache)): - return self._convert_person(resp['data'])[0] + return self._convert_person(resp['data'], include_guests=include_guests)[0] def _search_person(self, name=None, ids=None): # type: (AnyStr, Dict[integer_types, integer_types]) -> List[TVInfoPerson] diff --git a/lib/tvinfo_base/base.py b/lib/tvinfo_base/base.py index 723e81d1..70cdbb07 100644 --- a/lib/tvinfo_base/base.py +++ b/lib/tvinfo_base/base.py @@ -1195,14 +1195,15 @@ class TVInfoBase(object): except (BaseException, Exception) as e: log.error('Error setting %s to cache: %s' % (key, ex(e))) - def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs): - # type: (integer_types, bool, bool, Any) -> Optional[TVInfoPerson] + def get_person(self, p_id, get_show_credits=False, get_images=False, include_guests=False, **kwargs): + # type: (integer_types, bool, bool, bool, Any) -> Optional[TVInfoPerson] """ 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 :param get_images: get person images + :param include_guests: include guest roles :return: person object """ pass diff --git a/sickgear/webserve.py b/sickgear/webserve.py index 0a01f096..aedb1485 100644 --- a/sickgear/webserve.py +++ b/sickgear/webserve.py @@ -96,6 +96,7 @@ from lib.api_trakt import TraktAPI from lib.api_trakt.exceptions import TraktException, TraktAuthException from lib.tvinfo_base import TVInfoEpisode, RoleTypes from lib.tvinfo_base.base import tv_src_names +from lib.tvinfo_base.exceptions import * import lib.rarfile.rarfile as rarfile @@ -6039,7 +6040,7 @@ class AddShows(Home): def tvdb_default(self): method = getattr(self, sickgear.TVDB_MRU, None) - if not callable(method): + if not callable(method) or not self.allow_browse_mru(sickgear.TVDB_MRU): return self.tvdb_upcoming() return method() @@ -6051,6 +6052,10 @@ class AddShows(Home): return self.browse_tvdb( 'Top rated at TVDb', mode='toprated', **kwargs) + def tvdb_person(self, person_tvdb_id=None, **kwargs): + return self.browse_tvdb( + 'Person at TVDb', mode='person', p_id=person_tvdb_id, **kwargs) + def browse_tvdb(self, browse_title, **kwargs): browse_type = 'TVDb' @@ -6058,20 +6063,41 @@ class AddShows(Home): footnote = None filtered = [] + p_ref = None + overview_ajax = 'person' == mode tvid = TVINFO_TVDB tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy() t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvdbIndexer, TVInfoBase] top_year = helpers.try_int(kwargs.get('year'), None) - if 'upcoming' == mode: - items = t.discover() - else: - items = t.get_top_rated(year=top_year, - in_last_year=1 == datetime.date.today().month and 7 > datetime.date.today().day) + try: + if 'upcoming' == mode: + items = t.discover() + elif 'person' == mode: + items = [] + p_item = t.get_person(get_show_credits=True, include_guests=True, **kwargs) # type: TVInfoPerson + if p_item: + p_ref = f'{TVINFO_TVDB}:{p_item.id}' + dup = {} # type: Dict[int, TVInfoShow] + for c in p_item.characters: # type: TVInfoCharacter + c.ti_show.cast[(RoleTypes.ActorGuest, RoleTypes.ActorMain)[True is c.regular]].append(c) + if c.ti_show.id not in dup: + dup[c.ti_show.id] = c.ti_show + items.append(c.ti_show) + else: + dup[c.ti_show.id].cast[RoleTypes.ActorMain].extend(c.ti_show.cast[RoleTypes.ActorMain]) + dup[c.ti_show.id].cast[RoleTypes.ActorGuest].extend(c.ti_show.cast[RoleTypes.ActorGuest]) + del dup + else: + p_item = None + else: + items = t.get_top_rated(year=top_year, in_last_year=1 == dt_date.today().month and 7 > dt_date.today().day) + except (BaseTVinfoError, BaseException, Exception) as e: + return self.browse_shows(browse_type, browse_title, filtered, **kwargs) ranking = dict((val, idx+1) for idx, val in - enumerate(sorted([cur_show_info.rating for cur_show_info in items], reverse=True))) + enumerate(sorted([cur_show_info.rating or 0 for cur_show_info in items], reverse=True))) oldest, newest, oldest_dt, newest_dt, dedupe = None, None, 9999999, 0, [] use_networks = False parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True) @@ -6085,7 +6111,7 @@ class AddShows(Home): airtime = cur_show_info.airs_time if not airtime or (0, 0) == (airtime.hour, airtime.minute): airtime = dateutil.parser.parse('23:59').time() - dt = datetime.datetime.combine( + dt = datetime.combine( dateutil.parser.parse(cur_show_info.firstaired, parseinfo).date(), airtime) ord_premiered, str_premiered, started_past, oldest_dt, newest_dt, oldest, newest, _, _, _, _ \ = self.sanitise_dates(dt, oldest_dt, newest_dt, oldest, newest) @@ -6118,6 +6144,7 @@ class AddShows(Home): ids=ids, images=images, overview=self.clean_overview(cur_show_info), + overview_ajax=(0, 1)[overview_ajax], title=cur_show_info.seriesname, language=language, language_img=sickgear.MEMCACHE_FLAG_IMAGES.get(language, False), @@ -6129,11 +6156,17 @@ class AddShows(Home): votes=cur_show_info.rating or 0, rank=cur_show_info.rating and ranking.get(cur_show_info.rating) or 0, )) + if p_ref: + filtered[-1].update(dict( + p_name=p_item.name or None, + p_ref=p_ref, + p_chars=self._make_char_person_list(cur_show_info) + )) except (BaseException, Exception): pass - kwargs.update(dict(oldest=oldest, newest=newest, use_ratings=False, term_vote='Score')) + kwargs.update(dict(oldest=oldest, newest=newest, oldest_dt=oldest_dt, newest_dt=newest_dt, use_ratings=False, term_vote='Score')) - this_year = datetime.date.today().year + this_year = dt_date.today().year years = [ (this_year - cur_y, 'tvdb_toprated?year=%s' % (this_year - cur_y), @@ -6141,7 +6174,7 @@ class AddShows(Home): for cur_y in range(0, 10)] kwargs.update(dict(footnote=footnote, use_networks=use_networks, year=top_year or '', rate_years=years)) - if mode: + if mode and self.allow_browse_mru(mode): func = 'tvdb_%s' % mode if callable(getattr(self, func, None)): sickgear.TVDB_MRU = func @@ -6183,37 +6216,45 @@ class AddShows(Home): return result.replace('.....', '...') return 'No overview yet' - def tvm_get_showinfo(self, tvid_prodid=None, oldest_dt=9999999, newest_dt=0): + def tvi_get_showinfo(self, tvid_prodid=None, oldest_dt=9999999, newest_dt=0): result = {} - if 'tvmaze' in tvid_prodid: - tvid = TVINFO_TVMAZE - tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy() - t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase] - show_info = t.get_show(int(tvid_prodid.replace('tvmaze:','')), load_episodes=False) + if isinstance(tvid_prodid, str): + if tvid_prodid.startswith('tvmaze'): + tvid = TVINFO_TVMAZE + prod_id = int(tvid_prodid.replace('tvmaze:','')) + elif tvid_prodid.startswith('tvdb'): + tvid = TVINFO_TVDB + prod_id = int(tvid_prodid.replace('tvdb:', '')) + else: + tvid = None + if tvid: + tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy() + t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase] + show_info = t.get_show(prod_id, load_episodes=False) - oldest_dt, newest_dt = int(oldest_dt), int(newest_dt) - ord_premiered, str_premiered, started_past, old_dt, new_dt, oldest, newest, \ - ok_returning, ord_returning, str_returning, return_past \ - = self.sanitise_dates(show_info.firstaired, oldest_dt, newest_dt, None, None) - result = dict( - ord_premiered=ord_premiered, - str_premiered=str_premiered, - #ord_returning=ord_returning, - #str_returning=str_returning, - started_past=started_past, - #return_past=return_past, - genres=((show_info.genre or '') - or ', '.join(show_info.genre_list) - or ', '.join(show_info.show_type) or '').strip('|').replace('|', ', ').lower(), - overview=self.clean_overview(show_info), - network=show_info.network or ', '.join(show_info.networks) or '', - ) - if old_dt < oldest_dt: - result['oldest_dt'] = old_dt - result['oldest'] = oldest - elif new_dt > newest_dt: - result['newest_dt'] = old_dt - result['newest'] = newest, + oldest_dt, newest_dt = int(oldest_dt), int(newest_dt) + ord_premiered, str_premiered, started_past, old_dt, new_dt, oldest, newest, \ + ok_returning, ord_returning, str_returning, return_past \ + = self.sanitise_dates(show_info.firstaired, oldest_dt, newest_dt, None, None) + result = dict( + ord_premiered=ord_premiered, + str_premiered=str_premiered, + #ord_returning=ord_returning, + #str_returning=str_returning, + started_past=started_past, + #return_past=return_past, + genres=((show_info.genre or '') + or ', '.join(show_info.genre_list) + or ', '.join(show_info.show_type) or '').strip('|').replace('|', ', ').lower(), + overview=self.clean_overview(show_info), + network=show_info.network or ', '.join(show_info.networks) or '', + ) + if old_dt < oldest_dt: + result['oldest_dt'] = old_dt + result['oldest'] = oldest + elif new_dt > newest_dt: + result['newest_dt'] = old_dt + result['newest'] = newest, return json_dumps(result) @@ -6352,7 +6393,7 @@ class AddShows(Home): @staticmethod def sanitise_dates(date, oldest_dt, newest_dt, oldest, newest, episode_info=None, combine_ep_airtime=False): - # in case of person search (tvmaze) guest starring entires have only show name/id, no dates + # in case of person search (tvmaze) guest starring entries have only show name/id, no dates if None is date: return 9, '', True, oldest_dt, newest_dt, oldest, newest, True, 9, 'TBC', False parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True)