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
This commit is contained in:
Prinz23 2023-09-18 14:35:21 +01:00 committed by JackDandy
parent da9a6a829c
commit b59084d27d
6 changed files with 108 additions and 57 deletions

View file

@ -2,7 +2,7 @@
#import re #import re
#import sickgear #import sickgear
#from sickgear import TVInfoAPI #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.helpers import anon_url
#from sickgear.tv import PersonGenders #from sickgear.tv import PersonGenders
#from sg_helpers import spoken_height #from sg_helpers import spoken_height
@ -212,7 +212,7 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
</span> </span>
</div> </div>
#end if #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]) #if any([$person.ids.get($cur_src) for ($cur_src, _) in $src])
<div> <div>
<span class="details-title">Other shows</span> <span class="details-title">Other shows</span>

View file

@ -60,7 +60,7 @@
// Set initial text // Set initial text
overviewEl.html('Fetching overview...'); 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'), tvid_prodid: showcardEl.attr('data-id'),
oldest_dt: $('#oldest').attr('data-oldest-dt'), oldest_dt: $('#oldest').attr('data-oldest-dt'),
newest_dt: $('#newest').attr('data-newest-dt'), newest_dt: $('#newest').attr('data-newest-dt'),

View file

@ -27,7 +27,6 @@ import warnings
from bs4_parser import BS4Parser from bs4_parser import BS4Parser
from collections import OrderedDict from collections import OrderedDict
from sg_helpers import clean_data, get_url, try_int from sg_helpers import clean_data, get_url, try_int
from sickgear import ENV
from lib.cachecontrol import CacheControl, caches from lib.cachecontrol import CacheControl, caches
from lib.dateutil.parser import parse from lib.dateutil.parser import parse
@ -46,6 +45,8 @@ if False:
from typing import Any, AnyStr, Dict, List, Optional, Union from typing import Any, AnyStr, Dict, List, Optional, Union
ENV = os.environ
THETVDB_V2_API_TOKEN = {'token': None, 'datetime': datetime.datetime.fromordinal(1)} THETVDB_V2_API_TOKEN = {'token': None, 'datetime': datetime.datetime.fromordinal(1)}
log = logging.getLogger('tvdb.api') log = logging.getLogger('tvdb.api')
log.addHandler(logging.NullHandler()) log.addHandler(logging.NullHandler())

View file

@ -9,6 +9,7 @@ __api_version__ = '1.0.0'
import base64 import base64
import datetime import datetime
import logging import logging
import os
import re import re
from bs4_parser import BS4Parser 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_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) 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 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 from six import integer_types, iteritems, PY3, string_types
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
if False: if False:
from typing import Any, AnyStr, Dict, List, Optional, Tuple, Union from typing import Any, AnyStr, Dict, List, Optional, Tuple, Union
ENV = os.environ
log = logging.getLogger('tvdb_v4.api') log = logging.getLogger('tvdb_v4.api')
log.addHandler(logging.NullHandler()) log.addHandler(logging.NullHandler())
@ -101,7 +104,10 @@ class TvdbAuth(RequestsAuthBase):
@property @property
def token(self): def token(self):
if not self._token: if not self._token:
try:
self.get_token() self.get_token()
except TvdbTokenFailure as e:
log.error(f'Tvdb Taken Failure: {e}')
return self._token return self._token
def handle_401(self, r, **kwargs): def handle_401(self, r, **kwargs):
@ -146,7 +152,7 @@ s = requests.Session()
retries = Retry(total=3, retries = Retry(total=3,
backoff_factor=1, backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504], 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 # noinspection HttpUrlsUsage
s.mount('http://', HTTPAdapter(TimeoutHTTPAdapter(max_retries=retries))) s.mount('http://', HTTPAdapter(TimeoutHTTPAdapter(max_retries=retries)))
s.mount('https://', 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 ''): if f'?page={page}' in ((resp.get('links') or {}).get('next') or ''):
return page return page
def _convert_person(self, p, ids=None): def _convert_person(self, p, ids=None, include_guests=False):
# type: (Dict, Dict) -> List[TVInfoPerson] # type: (Dict, Dict, bool) -> List[TVInfoPerson]
ch, ids = [], ids or {} ch, ids = [], ids or {}
p_types = ([3], [3, 4])[include_guests]
for cur_c in sorted(filter( for cur_c in sorted(filter(
lambda a: (3 == a['type'] or 'Actor' == a['peopleType']) lambda a: (a['type'] in p_types or 'Actor' == a['peopleType'])
and a['name'] and a['seriesId'] and not a.get('episodeId'), and a['seriesId'] and ((not a.get('episodeId') and a['name']) or include_guests),
p.get('characters') or []), p.get('characters') or []),
key=lambda a: (not a['isFeatured'], a['sort'])): key=lambda a: (not a['isFeatured'], a['sort'])):
ti_show = TVInfoShow() ti_show = TVInfoShow()
@ -392,8 +399,8 @@ class TvdbAPIv4(TVInfoBase):
resp = None resp = None
return resp return resp
def get_person(self, p_id, get_show_credits=False, get_images=False, try_cache=True, **kwargs): 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, ...) -> Optional[TVInfoPerson] # type: (integer_types, bool, bool, bool, bool, ...) -> Optional[TVInfoPerson]
""" """
get person's data for id or list of matching persons for name 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_show_credits: get show credits
:param get_images: get person images :param get_images: get person images
:param try_cache: use cached data if available :param try_cache: use cached data if available
:param include_guests: include guest roles
:return: person object or None :return: person object or None
""" """
if bool(p_id) and self._check_resp(dict, resp := self.get_cached_or_url( if bool(p_id) and self._check_resp(dict, resp := self.get_cached_or_url(
f'/people/{p_id}/extended', f'/people/{p_id}/extended',
f'p-v4-{p_id}', try_cache=try_cache)): 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): def _search_person(self, name=None, ids=None):
# type: (AnyStr, Dict[integer_types, integer_types]) -> List[TVInfoPerson] # type: (AnyStr, Dict[integer_types, integer_types]) -> List[TVInfoPerson]

View file

@ -1195,14 +1195,15 @@ class TVInfoBase(object):
except (BaseException, Exception) as e: except (BaseException, Exception) as e:
log.error('Error setting %s to cache: %s' % (key, ex(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): def get_person(self, p_id, get_show_credits=False, get_images=False, include_guests=False, **kwargs):
# type: (integer_types, bool, bool, Any) -> Optional[TVInfoPerson] # type: (integer_types, bool, bool, bool, Any) -> Optional[TVInfoPerson]
""" """
get person's data for id or list of matching persons for name get person's data for id or list of matching persons for name
:param p_id: persons id :param p_id: persons id
:param get_show_credits: get show credits :param get_show_credits: get show credits
:param get_images: get person images :param get_images: get person images
:param include_guests: include guest roles
:return: person object :return: person object
""" """
pass pass

View file

@ -96,6 +96,7 @@ from lib.api_trakt import TraktAPI
from lib.api_trakt.exceptions import TraktException, TraktAuthException from lib.api_trakt.exceptions import TraktException, TraktAuthException
from lib.tvinfo_base import TVInfoEpisode, RoleTypes from lib.tvinfo_base import TVInfoEpisode, RoleTypes
from lib.tvinfo_base.base import tv_src_names from lib.tvinfo_base.base import tv_src_names
from lib.tvinfo_base.exceptions import *
import lib.rarfile.rarfile as rarfile import lib.rarfile.rarfile as rarfile
@ -6039,7 +6040,7 @@ class AddShows(Home):
def tvdb_default(self): def tvdb_default(self):
method = getattr(self, sickgear.TVDB_MRU, None) 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 self.tvdb_upcoming()
return method() return method()
@ -6051,6 +6052,10 @@ class AddShows(Home):
return self.browse_tvdb( return self.browse_tvdb(
'Top rated at TVDb', mode='toprated', **kwargs) '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): def browse_tvdb(self, browse_title, **kwargs):
browse_type = 'TVDb' browse_type = 'TVDb'
@ -6058,20 +6063,41 @@ class AddShows(Home):
footnote = None footnote = None
filtered = [] filtered = []
p_ref = None
overview_ajax = 'person' == mode
tvid = TVINFO_TVDB tvid = TVINFO_TVDB
tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy() tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy()
t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvdbIndexer, TVInfoBase] t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvdbIndexer, TVInfoBase]
top_year = helpers.try_int(kwargs.get('year'), None) top_year = helpers.try_int(kwargs.get('year'), None)
try:
if 'upcoming' == mode: if 'upcoming' == mode:
items = t.discover() 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: else:
items = t.get_top_rated(year=top_year, dup[c.ti_show.id].cast[RoleTypes.ActorMain].extend(c.ti_show.cast[RoleTypes.ActorMain])
in_last_year=1 == datetime.date.today().month and 7 > datetime.date.today().day) 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 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, [] oldest, newest, oldest_dt, newest_dt, dedupe = None, None, 9999999, 0, []
use_networks = False use_networks = False
parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True) parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True)
@ -6085,7 +6111,7 @@ class AddShows(Home):
airtime = cur_show_info.airs_time airtime = cur_show_info.airs_time
if not airtime or (0, 0) == (airtime.hour, airtime.minute): if not airtime or (0, 0) == (airtime.hour, airtime.minute):
airtime = dateutil.parser.parse('23:59').time() airtime = dateutil.parser.parse('23:59').time()
dt = datetime.datetime.combine( dt = datetime.combine(
dateutil.parser.parse(cur_show_info.firstaired, parseinfo).date(), airtime) dateutil.parser.parse(cur_show_info.firstaired, parseinfo).date(), airtime)
ord_premiered, str_premiered, started_past, oldest_dt, newest_dt, oldest, newest, _, _, _, _ \ ord_premiered, str_premiered, started_past, oldest_dt, newest_dt, oldest, newest, _, _, _, _ \
= self.sanitise_dates(dt, 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, ids=ids,
images=images, images=images,
overview=self.clean_overview(cur_show_info), overview=self.clean_overview(cur_show_info),
overview_ajax=(0, 1)[overview_ajax],
title=cur_show_info.seriesname, title=cur_show_info.seriesname,
language=language, language=language,
language_img=sickgear.MEMCACHE_FLAG_IMAGES.get(language, False), language_img=sickgear.MEMCACHE_FLAG_IMAGES.get(language, False),
@ -6129,11 +6156,17 @@ class AddShows(Home):
votes=cur_show_info.rating or 0, votes=cur_show_info.rating or 0,
rank=cur_show_info.rating and ranking.get(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): except (BaseException, Exception):
pass 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 = [ years = [
(this_year - cur_y, (this_year - cur_y,
'tvdb_toprated?year=%s' % (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)] for cur_y in range(0, 10)]
kwargs.update(dict(footnote=footnote, use_networks=use_networks, year=top_year or '', rate_years=years)) 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 func = 'tvdb_%s' % mode
if callable(getattr(self, func, None)): if callable(getattr(self, func, None)):
sickgear.TVDB_MRU = func sickgear.TVDB_MRU = func
@ -6183,13 +6216,21 @@ class AddShows(Home):
return result.replace('.....', '...') return result.replace('.....', '...')
return 'No overview yet' 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 = {} result = {}
if 'tvmaze' in tvid_prodid: if isinstance(tvid_prodid, str):
if tvid_prodid.startswith('tvmaze'):
tvid = TVINFO_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() tvinfo_config = sickgear.TVInfoAPI(tvid).api_params.copy()
t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase] t = sickgear.TVInfoAPI(tvid).setup(**tvinfo_config) # type: Union[TvmazeIndexer, TVInfoBase]
show_info = t.get_show(int(tvid_prodid.replace('tvmaze:','')), load_episodes=False) show_info = t.get_show(prod_id, load_episodes=False)
oldest_dt, newest_dt = int(oldest_dt), int(newest_dt) oldest_dt, newest_dt = int(oldest_dt), int(newest_dt)
ord_premiered, str_premiered, started_past, old_dt, new_dt, oldest, newest, \ ord_premiered, str_premiered, started_past, old_dt, new_dt, oldest, newest, \
@ -6352,7 +6393,7 @@ class AddShows(Home):
@staticmethod @staticmethod
def sanitise_dates(date, oldest_dt, newest_dt, oldest, newest, episode_info=None, combine_ep_airtime=False): 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: if None is date:
return 9, '', True, oldest_dt, newest_dt, oldest, newest, True, 9, 'TBC', False return 9, '', True, oldest_dt, newest_dt, oldest, newest, True, 9, 'TBC', False
parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True) parseinfo = dateutil.parser.parserinfo(dayfirst=False, yearfirst=True)