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 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
</span>
</div>
#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])
<div>
<span class="details-title">Other shows</span>

View file

@ -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'),

View file

@ -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())

View file

@ -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]

View file

@ -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

View file

@ -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)