Merge branch 'feature/AddTvdbV4' into dev
Some checks are pending
Python Unit Tests / windows (windows-latest, 3.10) (push) Waiting to run
Python Unit Tests / windows (windows-latest, 3.11) (push) Waiting to run
Python Unit Tests / windows (windows-latest, 3.12) (push) Waiting to run
Python Unit Tests / windows (windows-latest, 3.8) (push) Waiting to run
Python Unit Tests / windows (windows-latest, 3.9) (push) Waiting to run
Python Unit Tests / linux (ubuntu-latest, 3.10) (push) Waiting to run
Python Unit Tests / linux (ubuntu-latest, 3.11) (push) Waiting to run
Python Unit Tests / linux (ubuntu-latest, 3.12) (push) Waiting to run
Python Unit Tests / linux (ubuntu-latest, 3.8) (push) Waiting to run
Python Unit Tests / linux (ubuntu-latest, 3.9) (push) Waiting to run
Python Unit Tests / macos (macos-latest, 3.10) (push) Waiting to run
Python Unit Tests / macos (macos-latest, 3.11) (push) Waiting to run
Python Unit Tests / macos (macos-latest, 3.12) (push) Waiting to run
Python Unit Tests / macos (macos-latest, 3.8) (push) Waiting to run
Python Unit Tests / macos (macos-latest, 3.9) (push) Waiting to run

This commit is contained in:
JackDandy 2024-10-07 01:24:59 +01:00
commit 29047e44d3
264 changed files with 1518 additions and 82 deletions

View file

@ -3,6 +3,10 @@
* Update filelock 3.14.0 (8556141) to 3.15.4 (9a979df) * Update filelock 3.14.0 (8556141) to 3.15.4 (9a979df)
* Update package resource API 68.2.2 (8ad627d) to 70.1.1 (222ebf9) * Update package resource API 68.2.2 (8ad627d) to 70.1.1 (222ebf9)
* Update urllib3 2.2.1 (54d6edf) to 2.2.2 (27e2a5c) * Update urllib3 2.2.1 (54d6edf) to 2.2.2 (27e2a5c)
* Change add TheTVDb v4 support
* Add menu Shows/"TVDb Cards"
* Add a persons available socials (Youtube, LinkedIn, Reddit, Fansite, TikTok, Wikidata)
* Change increase viewable history menu items from 13 to 15
### 3.32.8 (2024-10-07 00:30:00 UTC) ### 3.32.8 (2024-10-07 00:30:00 UTC)
@ -343,7 +347,6 @@
* Add config to change media process log message if there is no media to process * Add config to change media process log message if there is no media to process
* Change view-show text "invalid timeformat" to "time unknown" * Change view-show text "invalid timeformat" to "time unknown"
* Add menu Shows/"TMDB Cards" * Add menu Shows/"TMDB Cards"
* Add a persons available socials (Youtube, LinkedIn, Reddit, Fansite, TikTok, Wikidata)
* Change use TVDb genres on view-show if config/General/Interface/"Enable IMDb info" is disabled * Change use TVDb genres on view-show if config/General/Interface/"Enable IMDb info" is disabled
* Fix TVDb api episode issues * Fix TVDb api episode issues
* Change remove Python 3.7 from CI * Change remove Python 3.7 from CI

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
@ -186,7 +186,7 @@ def param(visible=True, rid=None, cache_person=None, cache_char=None, person=Non
#end if #end if
#set $section_links = False #set $section_links = False
#set $all_sources = $TVInfoAPI().all_sources #set $all_sources = $TVInfoAPI().all_non_fallback_sources
#for $cur_src, $cur_sid in sorted(iteritems($person.ids)) #for $cur_src, $cur_sid in sorted(iteritems($person.ids))
#if $cur_src not in $all_sources: #if $cur_src not in $all_sources:
#continue #continue
@ -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'),
@ -301,7 +301,7 @@ $(function() {
var filterValue = this.value; var filterValue = this.value;
if (-1 == filterValue.indexOf('trakt') && -1 == filterValue.indexOf('imdb') && -1 == filterValue.indexOf('mc_') if (-1 == filterValue.indexOf('trakt') && -1 == filterValue.indexOf('imdb') && -1 == filterValue.indexOf('mc_')
&& -1 == filterValue.indexOf('tmdb_') && -1 == filterValue.indexOf('tvc_') && -1 == filterValue.indexOf('tmdb_') && -1 == filterValue.indexOf('tvc_')
&& -1 == filterValue.indexOf('tvm_') && -1 == filterValue.indexOf('tvdb_') && -1 == filterValue.indexOf('tvm_')
&& -1 == filterValue.indexOf('ne_') && -1 == filterValue.indexOf('_ne') && -1 == filterValue.indexOf('ne_') && -1 == filterValue.indexOf('_ne')
&& -1 == filterValue.indexOf('default')) { && -1 == filterValue.indexOf('default')) {
var el$ = $('#container') var el$ = $('#container')
@ -507,6 +507,14 @@ $(function() {
<option value="tmdb_trending_today"#echo ('', selected)['trending_today' == $mode]#>Trending today</option> <option value="tmdb_trending_today"#echo ('', selected)['trending_today' == $mode]#>Trending today</option>
<option value="tmdb_trending_week"#echo ('', selected)['trending_week' == $mode]#>Trending this week</option> <option value="tmdb_trending_week"#echo ('', selected)['trending_week' == $mode]#>Trending this week</option>
</optgroup> </optgroup>
#elif 'TVDb' == $browse_type
<optgroup label="TVDb">
<option value="tvdb_upcoming"#echo ('', selected)['upcoming' == $mode]#>Upcoming</option>
<option value="tvdb_toprated"#echo ('', selected)['toprated' == $mode and not $kwargs.get('year')]#>Top rated all time</option>
#for $cur_y in $kwargs.get('rate_years') or []
<option value="$cur_y[1]"#echo ('', selected)[$cur_y[0] == $kwargs.get('year')]#>$cur_y[2]</option>
#end for
</optgroup>
#elif 'TVCalendar' == $browse_type #elif 'TVCalendar' == $browse_type
<optgroup label="TVCalendar"> <optgroup label="TVCalendar">
#for $page in $kwargs.get('pages') or [] #for $page in $kwargs.get('pages') or []
@ -623,7 +631,7 @@ $(function() {
#end if #end if
<div class="clearfix"> <div class="clearfix">
#if $use_ratings or $use_votes #if $use_ratings or $use_votes
<p>#if $use_ratings#<span class="rating">$this_show['rating']#if $re.search(r'^\d+(\.\d+)?$', (str($this_show['rating'])))#%</span>#end if##end if##if $use_votes#<i class="heart icon-glyph"></i><i>$this_show['votes'] $term_vote.lower()</i>#end if#</p>#slurp# <p>#if $this_show.get('rank')#&#x23;$this_show.get('rank') #end if##if $use_ratings#<span class="rating">$this_show['rating']#if $re.search(r'^\d+(\.\d+)?$', (str($this_show['rating'])))#%</span>#end if##end if##if $use_votes#<i class="heart icon-glyph"></i><i>$this_show['votes'] $term_vote.lower()</i>#end if#</p>#slurp#
#else #else
<p>&nbsp;</p> <p>&nbsp;</p>
#end if #end if

View file

@ -177,6 +177,10 @@
<li><a id="add-show-name" data-href="$sbRoot/add-shows/find/" tabindex="$tab#set $tab += 1#"><i class="sgicon-addshow"></i> <li><a id="add-show-name" data-href="$sbRoot/add-shows/find/" tabindex="$tab#set $tab += 1#"><i class="sgicon-addshow"></i>
<input class="form-control form-control-inline input-sm" type="text" placeholder="Search" tabindex="$tab#set $tab += 1#"> <input class="form-control form-control-inline input-sm" type="text" placeholder="Search" tabindex="$tab#set $tab += 1#">
<div class="menu-item-desc opacity60">find show at TV info source</div></a></li> <div class="menu-item-desc opacity60">find show at TV info source</div></a></li>
#set $tvdb_modes = dict(tvdb_upcoming='upcoming', tvdb_toprated='top rated')
#set $tvdb_mode = $tvdb_modes.get($sg_var('TVDB_MRU'), 'upcoming')
<li><a href="$sbRoot/add-shows/tvdb-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-addshow"></i>TVDb Cards
<div class="menu-item-desc opacity60">$tvdb_mode...</div></a></li>
#set $tvm_modes = dict(tvm_premieres='new shows', tvm_returning='returning') #set $tvm_modes = dict(tvm_premieres='new shows', tvm_returning='returning')
#set $tvm_mode = $tvm_modes.get($sg_var('TVM_MRU'), 'new shows') #set $tvm_mode = $tvm_modes.get($sg_var('TVM_MRU'), 'new shows')
<li><a href="$sbRoot/add-shows/tvm-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-tvmaze"></i>TVmaze Cards <li><a href="$sbRoot/add-shows/tvm-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-tvmaze"></i>TVmaze Cards

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

1218
lib/api_tvdb/tvdb_api_v4.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1809,7 +1809,7 @@ def is_virtualenv():
def enforce_type(value, allowed_types, default): def enforce_type(value, allowed_types, default):
# type: (Any, Union[Type, Tuple[Type]], Any) -> Any # type: (Any, Union[Type, Tuple[Type]], Any) -> Any
""" """
enforces that value is given type(s) enforce value to supplied type(s)
:param value: value to check :param value: value to check
:param allowed_types: type or tuple of types allowed :param allowed_types: type or tuple of types allowed
:param default: value to return if other type :param default: value to return if other type
@ -1817,3 +1817,12 @@ def enforce_type(value, allowed_types, default):
if not isinstance(value, allowed_types): if not isinstance(value, allowed_types):
return default return default
return value return value
def clean_str(value):
# type: (Any) -> AnyStr
"""
clean and enforced a value to a string type
:param value: to process
"""
return enforce_type(clean_data(value), str, '')

View file

@ -247,12 +247,7 @@ class TVInfoSocialIDs(object):
return self.__getitem__(key) return self.__getitem__(key)
def keys(self): def keys(self):
for k, v in iter(((TVINFO_TWITTER, self.twitter), (TVINFO_INSTAGRAM, self.instagram), for k, v in self.__iter__():
(TVINFO_FACEBOOK, self.facebook), (TVINFO_TIKTOK, self.tiktok),
(TVINFO_WIKIPEDIA, self.wikipedia), (TVINFO_WIKIDATA, self.wikidata),
(TVINFO_REDDIT, self.reddit), (TVINFO_YOUTUBE, self.youtube),
(TVINFO_LINKEDIN, self.linkedin), (TVINFO_FANSITE, self.fansite))):
if None is not v:
yield k yield k
def __iter__(self): def __iter__(self):
@ -329,7 +324,7 @@ class TVInfoImageSize(object):
class TVInfoImage(object): class TVInfoImage(object):
def __init__(self, image_type, sizes, img_id=None, main_image=False, type_str='', rating=None, votes=None, def __init__(self, image_type, sizes, img_id=None, main_image=False, type_str='', rating=None, votes=None,
lang=None, height=None, width=None, aspect_ratio=None, updated_at=None): lang=None, height=None, width=None, aspect_ratio=None, updated_at=None, has_text=None):
self.img_id = img_id # type: Optional[integer_types] self.img_id = img_id # type: Optional[integer_types]
self.image_type = image_type # type: integer_types self.image_type = image_type # type: integer_types
self.sizes = sizes # type: Union[TVInfoImageSize, Dict] self.sizes = sizes # type: Union[TVInfoImageSize, Dict]
@ -341,6 +336,7 @@ class TVInfoImage(object):
self.height = height # type: Optional[integer_types] self.height = height # type: Optional[integer_types]
self.width = width # type: Optional[integer_types] self.width = width # type: Optional[integer_types]
self.aspect_ratio = aspect_ratio # type: Optional[Union[float, integer_types]] self.aspect_ratio = aspect_ratio # type: Optional[Union[float, integer_types]]
self.has_text = has_text # type: Optional[bool]
self.updated_at = updated_at # type: Optional[integer_types] self.updated_at = updated_at # type: Optional[integer_types]
def __eq__(self, other): def __eq__(self, other):
@ -951,9 +947,21 @@ class TVInfoPerson(PersonBase):
class TVInfoCharacter(PersonBase): class TVInfoCharacter(PersonBase):
def __init__(self, person=None, voice=None, plays_self=None, regular=None, ti_show=None, start_year=None, def __init__(self,
end_year=None, ids=None, name=None, episode_count=None, guest_episodes_numbers=None, **kwargs): person=None, # type: List[TVInfoPerson]
# type: (List[TVInfoPerson], bool, bool, bool, TVInfoShow, int, int, TVInfoIDs, AnyStr, int, Dict[int, List[int]], ...) -> None voice=None, # type: bool
plays_self=None, # type: bool
regular=None, # type: bool
ti_show=None, # type: TVInfoShow
start_year=None, # type: int
end_year=None, # type: int
ids=None, # type: TVInfoIDs
name=None, # type: AnyStr
episode_count=None, # type: int
guest_episodes_numbers=None, # type: Dict[int, List[int]]
**kwargs):
# type: (...) -> None
super(TVInfoCharacter, self).__init__(ids=ids, **kwargs) super(TVInfoCharacter, self).__init__(ids=ids, **kwargs)
self.person = person # type: List[TVInfoPerson] self.person = person # type: List[TVInfoPerson]
self.voice = voice # type: Optional[bool] self.voice = voice # type: Optional[bool]
@ -1187,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 images for person :param get_images: get person images
:param include_guests: include guest roles
:return: person object :return: person object
""" """
pass pass
@ -1202,7 +1211,7 @@ class TVInfoBase(object):
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]
""" """
search for person by name search by name for person
:param name: name to search for :param name: name to search for
:param ids: dict of ids to search :param ids: dict of ids to search
:return: list of found person's :return: list of found person's
@ -1212,7 +1221,7 @@ class TVInfoBase(object):
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]
""" """
search for person by name search by name for person
:param name: name to search for :param name: name to search for
:param ids: dict of ids to search :param ids: dict of ids to search
:return: list of found person's :return: list of found person's
@ -1234,8 +1243,8 @@ class TVInfoBase(object):
seasonwides=False, fanart=False, actors=False, **kwargs): seasonwides=False, fanart=False, actors=False, **kwargs):
# type: (integer_types, AnyStr, bool, bool, bool, bool, bool, bool, bool, Optional[Any]) -> bool # type: (integer_types, AnyStr, bool, bool, bool, bool, bool, bool, bool, Optional[Any]) -> bool
""" """
internal function that should be overwritten in class to get data for given show id internal function that should be overwritten in subclass to get data
:param sid: show id :param sid: show id to get data for
:param language: language :param language: language
:param get_ep_info: get episodes :param get_ep_info: get episodes
:param banners: load banners :param banners: load banners
@ -1264,6 +1273,7 @@ class TVInfoBase(object):
# type: (...) -> Optional[TVInfoShow] # type: (...) -> Optional[TVInfoShow]
""" """
get data for show id get data for show id
:param show_id: id of show :param show_id: id of show
:param load_episodes: load episodes :param load_episodes: load episodes
:param banners: load banners :param banners: load banners
@ -1330,8 +1340,12 @@ class TVInfoBase(object):
self._old_config = None self._old_config = None
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
def _search_show(self, name=None, ids=None, lang=None, **kwargs): def _search_show(self,
# type: (Union[AnyStr, List[AnyStr]], Dict[integer_types, integer_types], Optional[string_types], Optional[Any]) -> List[Dict] name=None, # type: Union[AnyStr, List[AnyStr]]
ids=None, # type: Dict[integer_types, integer_types]
lang=None, # type: Optional[string_types]
**kwargs):
# type: (...) -> List[Dict]
""" """
internal search function to find shows, should be overwritten in class internal search function to find shows, should be overwritten in class
:param name: name to search for :param name: name to search for
@ -1398,8 +1412,8 @@ class TVInfoBase(object):
Since the nice-to-use tvinfo[1][24]['name] interface Since the nice-to-use tvinfo[1][24]['name] interface
makes it impossible to do tvinfo[1][24]['name] = "name" makes it impossible to do tvinfo[1][24]['name] = "name"
and still be capable of checking if an episode exists and still be capable of checking if an episode exists
so we can raise tvinfo_shownotfound, we have a slightly so that we can raise tvinfo_shownotfound, we have a slightly
less pretty method of setting items.. but since the API less pretty method of setting items... but since the API
is supposed to be read-only, this is the best way to is supposed to be read-only, this is the best way to
do it! do it!
The problem is that calling tvinfo[1][24]['episodename'] = "name" The problem is that calling tvinfo[1][24]['episodename'] = "name"
@ -1454,6 +1468,7 @@ class TVInfoBase(object):
# type: (integer_types, int, Any) -> List[TVInfoShow] # type: (integer_types, int, Any) -> List[TVInfoShow]
""" """
return list of similar shows to given id return list of similar shows to given id
:param tvid: id to give similar shows for :param tvid: id to give similar shows for
:param result_count: count of results requested :param result_count: count of results requested
""" """
@ -1463,6 +1478,7 @@ class TVInfoBase(object):
# type: (integer_types, int, Any) -> List[TVInfoShow] # type: (integer_types, int, Any) -> List[TVInfoShow]
""" """
list of recommended shows to the provided tv id list of recommended shows to the provided tv id
:param tvid: id to find recommended shows for :param tvid: id to find recommended shows for
:param result_count: result count to returned :param result_count: result count to returned
""" """
@ -1486,7 +1502,7 @@ class TVInfoBase(object):
def get_top_rated(self, result_count=100, **kwargs): def get_top_rated(self, result_count=100, **kwargs):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get top rated shows get top-rated shows
""" """
return [] return []
@ -1526,7 +1542,7 @@ class TVInfoBase(object):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get most played shows get most played shows
:param result_count: how many results are suppose to be returned :param result_count: how many results are supposed to be returned
""" """
return [] return []
@ -1534,7 +1550,7 @@ class TVInfoBase(object):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get most watched shows get most watched shows
:param result_count: how many results are suppose to be returned :param result_count: how many results are supposed to be returned
""" """
return [] return []
@ -1542,7 +1558,7 @@ class TVInfoBase(object):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get most collected shows get most collected shows
:param result_count: how many results are suppose to be returned :param result_count: how many results are supposed to be returned
""" """
return [] return []
@ -1550,7 +1566,7 @@ class TVInfoBase(object):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get most recommended shows get most recommended shows
:param result_count: how many results are suppose to be returned :param result_count: how many results are supposed to be returned
""" """
return [] return []
@ -1558,8 +1574,9 @@ class TVInfoBase(object):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get recommended shows for account get recommended shows for account
:param account: account to get recommendations for :param account: account to get recommendations for
:param result_count: how many results are suppose to be returned :param result_count: how many results are supposed to be returned
""" """
return [] return []
@ -1567,6 +1584,7 @@ class TVInfoBase(object):
# type: (integer_types, List[integer_types], Any) -> List[integer_types] # type: (integer_types, List[integer_types], Any) -> List[integer_types]
""" """
hide recommended show for account hide recommended show for account
:param account: account to get recommendations for :param account: account to get recommendations for
:param show_ids: list of show_ids to no longer recommend for account :param show_ids: list of show_ids to no longer recommend for account
:return: list of added ids :return: list of added ids
@ -1577,6 +1595,7 @@ class TVInfoBase(object):
# type: (integer_types, List[integer_types], Any) -> List[integer_types] # type: (integer_types, List[integer_types], Any) -> List[integer_types]
""" """
unhide recommended show for account unhide recommended show for account
:param account: account to get recommendations for :param account: account to get recommendations for
:param show_ids: list of show_ids to be included in possible recommend for account :param show_ids: list of show_ids to be included in possible recommend for account
:return: list of removed ids :return: list of removed ids
@ -1587,6 +1606,7 @@ class TVInfoBase(object):
# type: (integer_types, Any) -> List[TVInfoShow] # type: (integer_types, Any) -> List[TVInfoShow]
""" """
list hidden recommended show for account list hidden recommended show for account
:param account: account to get recommendations for :param account: account to get recommendations for
:return: list of hidden shows :return: list of hidden shows
""" """
@ -1596,8 +1616,9 @@ class TVInfoBase(object):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get most watchlisted shows for account get most watchlisted shows for account
:param account: account to get recommendations for :param account: account to get recommendations for
:param result_count: how many results are suppose to be returned :param result_count: how many results are supposed to be returned
""" """
return [] return []
@ -1605,7 +1626,7 @@ class TVInfoBase(object):
# type: (...) -> List[TVInfoShow] # type: (...) -> List[TVInfoShow]
""" """
get anticipated shows get anticipated shows
:param result_count: how many results are suppose to be returned :param result_count: how many results are supposed to be returned
""" """
return [] return []
@ -1625,7 +1646,7 @@ class TVInfoBase(object):
# Item is integer, treat as show id # Item is integer, treat as show id
return self.get_show(item, (True, arg)[None is not arg], old_call=True) return self.get_show(item, (True, arg)[None is not arg], old_call=True)
# maybe adding this to make callee use showname so that i can bring in the new endpoint # maybe adding this to make callee use showname so that I can bring in the new endpoint
if isinstance(arg, string_types) and 'Tvdb' == self.__class__.__name__: if isinstance(arg, string_types) and 'Tvdb' == self.__class__.__name__:
return self.search_show(item) return self.search_show(item)

View file

@ -625,6 +625,7 @@ MC_MRU = ''
NE_MRU = '' NE_MRU = ''
TMDB_MRU = '' TMDB_MRU = ''
TVC_MRU = '' TVC_MRU = ''
TVDB_MRU = ''
TVM_MRU = '' TVM_MRU = ''
COOKIE_SECRET = b64encodestring(uuid.uuid4().bytes + uuid.uuid4().bytes) COOKIE_SECRET = b64encodestring(uuid.uuid4().bytes + uuid.uuid4().bytes)
@ -773,7 +774,7 @@ def init_stage_1(console_logging):
global USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_MRU, TRAKT_VERIFY, \ global USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_MRU, TRAKT_VERIFY, \
TRAKT_USE_WATCHLIST, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, \ TRAKT_USE_WATCHLIST, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, \
TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \ TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \
MC_MRU, NE_MRU, TMDB_MRU, TVC_MRU, TVM_MRU, \ MC_MRU, NE_MRU, TMDB_MRU, TVC_MRU, TVDB_MRU, TVM_MRU, \
USE_SLACK, SLACK_NOTIFY_ONSNATCH, SLACK_NOTIFY_ONDOWNLOAD, SLACK_NOTIFY_ONSUBTITLEDOWNLOAD, \ USE_SLACK, SLACK_NOTIFY_ONSNATCH, SLACK_NOTIFY_ONDOWNLOAD, SLACK_NOTIFY_ONSUBTITLEDOWNLOAD, \
SLACK_CHANNEL, SLACK_AS_AUTHED, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \ SLACK_CHANNEL, SLACK_AS_AUTHED, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \
USE_DISCORD, DISCORD_NOTIFY_ONSNATCH, DISCORD_NOTIFY_ONDOWNLOAD, \ USE_DISCORD, DISCORD_NOTIFY_ONSNATCH, DISCORD_NOTIFY_ONDOWNLOAD, \
@ -1214,6 +1215,7 @@ def init_stage_1(console_logging):
NE_MRU = check_setting_str(CFG, 'NextEpisode', 'ne_mru', '') NE_MRU = check_setting_str(CFG, 'NextEpisode', 'ne_mru', '')
TMDB_MRU = check_setting_str(CFG, 'TMDB', 'tmdb_mru', '') TMDB_MRU = check_setting_str(CFG, 'TMDB', 'tmdb_mru', '')
TVC_MRU = check_setting_str(CFG, 'TVCalendar', 'tvc_mru', '') TVC_MRU = check_setting_str(CFG, 'TVCalendar', 'tvc_mru', '')
TVDB_MRU = check_setting_str(CFG, 'TVDb', 'tvdb_mru', '')
TVM_MRU = check_setting_str(CFG, 'TVmaze', 'tvm_mru', '') TVM_MRU = check_setting_str(CFG, 'TVmaze', 'tvm_mru', '')
USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0)) USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0))
@ -1727,7 +1729,7 @@ def init_stage_2():
background_mapping_task = threading.Thread(name='MAPPINGUPDATES', target=indexermapper.load_mapped_ids, background_mapping_task = threading.Thread(name='MAPPINGUPDATES', target=indexermapper.load_mapped_ids,
kwargs={'load_all': True}) kwargs={'load_all': True})
MEMCACHE['history_tab_limit'] = 13 MEMCACHE['history_tab_limit'] = 15
MEMCACHE['history_tab'] = History.menu_tab(MEMCACHE['history_tab_limit']) MEMCACHE['history_tab'] = History.menu_tab(MEMCACHE['history_tab_limit'])
try: try:
@ -2298,6 +2300,9 @@ def _save_config(force=False, **kwargs):
('TVCalendar', [ ('TVCalendar', [
('mru', TVC_MRU) ('mru', TVC_MRU)
]), ]),
('TVDb', [
('mru', TVDB_MRU)
]),
('TVmaze', [ ('TVmaze', [
('mru', TVM_MRU) ('mru', TVM_MRU)
]), ]),

View file

@ -78,27 +78,40 @@ class TVInfoAPI(object):
if sickgear.CACHE_DIR: if sickgear.CACHE_DIR:
return self.api_params['cache'] return self.api_params['cache']
@staticmethod
def _filter(condition):
return dict([(int(x['id']), x['name']) for x in list(tvinfo_config.values()) if condition(x)])
@property @property
def sources(self): def sources(self):
# type: () -> Dict[int, AnyStr] # type: () -> Dict[int, AnyStr]
return dict([(int(x['id']), x['name']) for x in list(tvinfo_config.values()) if not x['mapped_only'] and return self._filter(lambda x:
True is not x.get('fallback') and True is not x.get('people_only')]) not x['mapped_only'] and
True is not x.get('fallback') and True is not x.get('people_only'))
@property @property
def search_sources(self): def search_sources(self):
# type: () -> Dict[int, AnyStr] # type: () -> Dict[int, AnyStr]
return dict([(int(x['id']), x['name']) for x in list(tvinfo_config.values()) if not x['mapped_only'] and return self._filter(lambda x:
x.get('active') and not x.get('defunct') and True is not x.get('fallback') not x['mapped_only'] and x.get('active') and not x.get('defunct') and
and True is not x.get('people_only')]) True is not x.get('fallback') and True is not x.get('people_only'))
@property @property
def all_sources(self): def all_sources(self):
# type: () -> Dict[int, AnyStr] # type: () -> Dict[int, AnyStr]
""" """
:return: return all indexers including mapped only indexers excluding fallback indexers :return: return all indexers for show data including mapped only indexers excluding fallback indexers
""" """
return dict([(int(x['id']), x['name']) for x in list(tvinfo_config.values()) if True is not x.get('fallback') return self._filter(lambda x:
and True is not x.get('people_only')]) True is not x.get('fallback') and True is not x.get('people_only'))
@property
def all_non_fallback_sources(self):
# type: (...) -> Dict[int, AnyStr]
"""
return all sources with the exclusion of fallback indexer
"""
return self._filter(lambda x: True is not x.get('fallback'))
@property @property
def fallback_sources(self): def fallback_sources(self):
@ -106,9 +119,9 @@ class TVInfoAPI(object):
""" """
:return: return all fallback indexers :return: return all fallback indexers
""" """
return dict([(int(x['id']), x['name']) for x in list(tvinfo_config.values()) if True is x.get('fallback')]) return self._filter(lambda x: True is x.get('fallback'))
@property @property
def xem_supported_sources(self): def xem_supported_sources(self):
# type: () -> Dict[int, AnyStr] # type: () -> Dict[int, AnyStr]
return dict([(int(x['id']), x['name']) for x in list(tvinfo_config.values()) if x.get('xem_origin')]) return self._filter(lambda x: x.get('xem_origin'))

View file

@ -1,4 +1,6 @@
from lib.api_tvdb.tvdb_api import Tvdb from lib.api_tvdb.tvdb_api import Tvdb
from lib.api_tvdb.tvdb_api_v4 import TvdbAPIv4
import lib.api_tvdb.tvdb_api_v4
from lib.api_trakt.indexerapiinterface import TraktIndexer from lib.api_trakt.indexerapiinterface import TraktIndexer
from lib.api_tvmaze.tvmaze_api import TvMaze from lib.api_tvmaze.tvmaze_api import TvMaze
from lib.api_tmdb.tmdb_api import TmdbIndexer from lib.api_tmdb.tmdb_api import TmdbIndexer
@ -23,8 +25,9 @@ tvinfo_config = {
api_url='https://api.thetvdb.com/', api_url='https://api.thetvdb.com/',
id=TVINFO_TVDB, id=TVINFO_TVDB,
name='TheTVDB', slug='tvdb', kodi_slug='tvdb', name='TheTVDB', slug='tvdb', kodi_slug='tvdb',
module=Tvdb, module=TvdbAPIv4,
api_params=dict(apikey='6cfd6399fd2bee018a8793da976f6522', language='en'), api_params=dict(apikey='6cfd6399fd2bee018a8793da976f6522',
apikey_v4=b'm5uaxWhrm56TlWTGm5Jkk5uYZW-ea5uOnmqcmWmXZmVtxp2a', language='en'),
active=True, active=True,
dupekey='', dupekey='',
mapped_only=False, mapped_only=False,
@ -260,3 +263,5 @@ tvinfo_config[src].update(dict(
show_url='%stv/%%d' % tvinfo_config[src]['main_url'], show_url='%stv/%%d' % tvinfo_config[src]['main_url'],
finder='%ssearch/tv?query=%s' % (tvinfo_config[src]['main_url'], '%s'), finder='%ssearch/tv?query=%s' % (tvinfo_config[src]['main_url'], '%s'),
)) ))
lib.api_tvdb.tvdb_api_v4.TVDB_API_CONFIG = tvinfo_config[TVINFO_TVDB]

View file

@ -73,7 +73,7 @@ class SBRotatingLogHandler(object):
self.console_logging = False # type: bool self.console_logging = False # type: bool
self.log_lock = threading.Lock() self.log_lock = threading.Lock()
self.log_types = ['sickgear', 'tornado.application', 'tornado.general', 'subliminal', 'adba', 'encodingKludge', self.log_types = ['sickgear', 'tornado.application', 'tornado.general', 'subliminal', 'adba', 'encodingKludge',
'tvdb.api', 'TVInfo'] 'tvdb.api', 'TVInfo', 'tvdb_v4.api']
self.external_loggers = ['sg.helper', 'api_trakt', 'api_trakt.api'] self.external_loggers = ['sg.helper', 'api_trakt', 'api_trakt.api']
self.log_types_null = ['tornado.access'] self.log_types_null = ['tornado.access']

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
@ -112,6 +113,7 @@ if False:
# from api_imdb.imdb_api import IMDbIndexer # from api_imdb.imdb_api import IMDbIndexer
from api_tmdb.tmdb_api import TmdbIndexer from api_tmdb.tmdb_api import TmdbIndexer
from api_trakt.indexerapiinterface import TraktIndexer from api_trakt.indexerapiinterface import TraktIndexer
from api_tvdb.tvdb_api_v4 import TvdbAPIv4 as TvdbIndexer
from api_tvmaze.tvmaze_api import TvMaze as TvmazeIndexer from api_tvmaze.tvmaze_api import TvMaze as TvmazeIndexer
@ -6036,6 +6038,155 @@ class AddShows(Home):
return self.new_show('|'.join(['', '', '', show_name]), use_show_name=True) return self.new_show('|'.join(['', '', '', show_name]), use_show_name=True)
def tvdb_default(self):
method = getattr(self, sickgear.TVDB_MRU, None)
if not callable(method) or not self.allow_browse_mru(sickgear.TVDB_MRU):
return self.tvdb_upcoming()
return method()
def tvdb_upcoming(self, **kwargs):
return self.browse_tvdb(
'Upcoming at TVDb', mode='upcoming', **kwargs)
def tvdb_toprated(self, **kwargs):
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'
mode = kwargs.get('mode', '')
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)
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 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)
base_url = sickgear.TVInfoAPI(TVINFO_TVDB).config['show_url']
for cur_show_info in items:
if cur_show_info.id in dedupe or not cur_show_info.seriesname:
continue
dedupe += [cur_show_info.id]
try:
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.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)
image = self._make_cache_image_url(tvid, cur_show_info)
images = {} if not image else dict(poster=dict(thumb=image))
ids = dict(tvdb=cur_show_info.id)
if cur_show_info.ids.imdb:
ids['imdb'] = cur_show_info.ids.imdb
network_name = cur_show_info.network
cc = 'US'
if network_name:
use_networks = True
cc = cur_show_info.network_country_code or cc
language = ((cur_show_info.language and 'jap' in cur_show_info.language.lower())
and 'jp' or 'en')
filtered.append(dict(
ord_premiered=ord_premiered,
str_premiered=str_premiered,
started_past=started_past,
episode_overview=self.clean_overview(cur_show_info),
episode_season=cur_show_info.season,
genres=(', '.join(cur_show_info.genre_list)
or (cur_show_info.genre and (cur_show_info.genre.strip('|').replace('|', ', ')) or '')
).lower(),
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),
country=cc,
country_img=sickgear.MEMCACHE_FLAG_IMAGES.get(cc.lower(), False),
network=network_name,
rating=False,
url_src_db=base_url % cur_show_info.id,
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, oldest_dt=oldest_dt, newest_dt=newest_dt, use_ratings=False, term_vote='Score'))
this_year = dt_date.today().year
years = [
(this_year - cur_y,
'tvdb_toprated?year=%s' % (this_year - cur_y),
'Top %s releases' % (this_year - cur_y))
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 and self.allow_browse_mru(mode):
func = 'tvdb_%s' % mode
if callable(getattr(self, func, None)):
sickgear.TVDB_MRU = func
sickgear.save_config()
return self.browse_shows(browse_type, browse_title, filtered, **kwargs)
# noinspection PyUnusedLocal
def info_tvdb(self, ids, show_name):
if not list(filter(lambda tvid_prodid: helpers.find_show_by_id(tvid_prodid), ids.split(' '))):
return self.new_show('|'.join(['', '', '', ' '.join([ids, show_name])]), use_show_name=True)
def tvm_default(self): def tvm_default(self):
method = getattr(self, sickgear.TVM_MRU, None) method = getattr(self, sickgear.TVM_MRU, None)
if not callable(method) or not self.allow_browse_mru(sickgear.TMDB_MRU): if not callable(method) or not self.allow_browse_mru(sickgear.TMDB_MRU):
@ -6065,13 +6216,16 @@ 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) and (tvid_prodid.startswith('tvmaze') or tvid_prodid.startswith('tvdb')):
tvid = TVINFO_TVMAZE tvid = TVINFO_TVMAZE
if tvid_prodid.startswith('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[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(int(re.sub('^[a-z]+?:', '', tvid_prodid)), 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, \
@ -6234,7 +6388,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)
@ -6283,7 +6437,7 @@ class AddShows(Home):
def browse_mru(browse_type, **kwargs): def browse_mru(browse_type, **kwargs):
save_config = False save_config = False
if browse_type in ('AniDB', 'IMDb', 'Metacritic', 'Trakt', 'TVCalendar', if browse_type in ('AniDB', 'IMDb', 'Metacritic', 'Trakt', 'TVCalendar',
'TMDB', 'TVmaze', 'Nextepisode'): 'TMDB', 'TVDb', 'TVmaze', 'Nextepisode'):
save_config = True save_config = True
if browse_type in ('TVmaze',) and kwargs.get('showfilter') and kwargs.get('showsort'): if browse_type in ('TVmaze',) and kwargs.get('showfilter') and kwargs.get('showsort'):
sickgear.BROWSELIST_MRU.setdefault(browse_type, dict()) \ sickgear.BROWSELIST_MRU.setdefault(browse_type, dict()) \

Some files were not shown because too many files have changed in this diff Show more