Files
DevOps/Kodi/Lenovo/addons/metadata.tvshows.thetvdb.com.v4.python/resources/lib/tvdb.py

594 lines
20 KiB
Python

import enum
import urllib.parse
from resources.lib import simple_requests as requests
from resources.lib.constants import LANGUAGES_MAP
from resources.lib.utils import logger, ADDON
apikey = "edae60dc-1b44-4bac-8db7-65c0aaf5258b"
apikey_with_pin = "51bdbd35-bcd5-40d9-9bc3-788e24454baf"
USER_AGENT = 'TheTVDB v.4 TV Scraper for Kodi'
class ArtworkType(enum.IntEnum):
BANNER = 1
POSTER = 2
BACKGROUND = 3
ICON = 5
SEASONPOSTER = 7
CLEARART = 22
CLEARLOGO = 23
class SeasonType(enum.IntEnum):
DEFAULT = 1
ABSOLUTE = 2
DVD = 3
ALTERNATE = 4
REGIONAL = 5
ALTDVD = 6
class Auth:
logger.debug("logging in")
def __init__(self, url, apikey, pin="", **kwargs):
loginInfo = {"apikey": apikey}
if pin:
loginInfo["pin"] = pin
loginInfo["apikey"] = apikey_with_pin
loginInfo.update(kwargs)
logger.debug("body in auth call")
logger.debug(loginInfo)
headers = {
'User-Agent': USER_AGENT,
'Accept': 'application/json',
}
response = requests.post(url, headers=headers, json=loginInfo)
if not response.ok:
response.raise_for_status()
self.token = response.json()['data']['token']
def get_token(self):
return self.token
class Request:
def __init__(self, auth_token):
self.auth_token = auth_token
self.cache = {}
def make_api_request(self, url):
logger.debug(f"about to make request to url {url}")
logger.debug(url)
data = self.cache.get(url, None)
if data:
return data
headers = {
'User-Agent': USER_AGENT,
'Accept': 'application/json',
'Authorization': f'Bearer {self.auth_token}'
}
response = requests.get(url, headers=headers)
if not response.ok:
response.raise_for_status()
data = response.json()['data']
self.cache[url] = data
return data
@staticmethod
def make_web_request(url):
logger.debug(f"about to make request to url {url}")
headers = {
'User-Agent': USER_AGENT,
'Accept': 'text/html',
}
response = requests.get(url, headers=headers)
if not response.ok:
response.raise_for_status()
return response.text
class Url:
def __init__(self):
self.base_url = "https://api4.thetvdb.com/v4"
def login_url(self):
return "{}/login".format(self.base_url)
def artwork_status_url(self):
return "{}/artwork/statuses".format(self.base_url)
def artwork_types_url(self):
return "{}/artwork/types".format(self.base_url)
def artwork_url(self, id, extended=False):
url = "{}/artwork/{}".format(self.base_url, id)
if extended:
url = "{}/extended".format(url)
return url
def awards_url(self, page):
if page < 0:
page = 0
url = "{}/awards?page={}".format(self.base_url, page)
return url
def award_url(self, id, extended=False):
url = "{}/awards/{}".format(self.base_url, id)
if extended:
url = "{}/extended".format(url)
return url
def awards_categories_url(self):
url = "{}/awards/categories".format(self.base_url)
return url
def award_category_url(self, id, extended=False):
url = "{}/awards/categories/{}".format(self.base_url, id)
if extended:
url = "{}/extended".format(url)
return url
def content_ratings_url(self):
url = "{}/content/ratings".format(self.base_url)
return url
def countries_url(self):
url = "{}/countries".format(self.base_url)
return url
def companies_url(self, page=0):
url = "{}/companies?page={}".format(self.base_url, page)
return url
def company_url(self, id):
url = "{}/companies/{}".format(self.base_url, id)
return url
def all_series_url(self, page=0):
url = "{}/series".format(self.base_url)
return url
def series_url(self, id, extended=False):
url = "{}/series/{}".format(self.base_url, id)
if extended:
url = "{}/extended".format(url)
return url
def movies_url(self, page=0):
url = "{}/movies".format(self.base_url, id)
return url
def movie_url(self, id, extended=False):
url = "{}/movies/{}".format(self.base_url, id)
if extended:
url = "{}/extended".format(url)
return url
def season_url(self, id, extended=False):
url = "{}/seasons/{}".format(self.base_url, id)
if extended:
url = "{}/extended?meta=episodes".format(url)
return url
def episode_url(self, id, extended=False):
url = "{}/episodes/{}".format(self.base_url, id)
if extended:
url = "{}/extended".format(url)
return url
def episode_translation_url(self, id: int, language: str = "eng"):
url = "{}/episodes/{}/translations/{}".format(
self.base_url, id, language)
return url
def person_url(self, id, extended=False):
url = "{}/people/{}".format(self.base_url, id)
if extended:
url = "{}/extended".format(url)
return url
def character_url(self, id):
url = "{}/characters/{}".format(self.base_url, id)
return url
def people_types_url(self, id):
url = "{}/people/types".format(self.base_url)
return url
def source_types_url(self):
url = "{}/sources/types".format(self.base_url)
return url
def updates_url(self, since=0):
url = "{}/updates?since={}".format(self.base_url, since)
return url
def tag_options_url(self):
url = "{}/tags/options".format(self.base_url)
return url
def tag_option_url(self, id):
url = "{}/tags/options/{}".format(self.base_url, id)
return url
def search_url(self, query, filters):
filters["query"] = query
qs = urllib.parse.urlencode(filters)
url = "{}/search?{}".format(self.base_url, qs)
return url
def series_translation_url(self, id: int, language="eng"):
url = "{}/series/{}/translations/{}".format(
self.base_url, id, language)
return url
def series_season_episodes_url(self, id: int, season_type_number: int = 1, page: int = 0):
season_type = SeasonType(season_type_number).name.lower()
url = "{}/series/{}/episodes/{}?page={}".format(
self.base_url, id, season_type, page)
return url
class TVDB:
def __init__(self, apikey: str, pin="", **kwargs):
self.url = Url()
login_url = self.url.login_url()
self.auth = Auth(login_url, apikey, pin, **kwargs)
auth_token = self.auth.get_token()
self.request = Request(auth_token)
def get_artwork_statuses(self) -> list:
"""Returns a list of artwork statuses"""
url = self.url.artwork_status_url()
return self.request.make_api_request(url)
def get_artwork_types(self) -> list:
"""Returns a list of artwork types"""
url = self.url.artwork_types_url()
return self.request.make_api_request(url)
def get_artwork(self, id: int) -> dict:
"""Returns an artwork dictionary"""
url = self.url.artwork_url(id)
return self.request.make_api_request(url)
def get_artwork_extended(self, id: int) -> dict:
"""Returns an artwork extended dictionary"""
url = self.url.artwork_url(id, True)
return self.request.make_api_request(url)
def get_all_awards(self, page=0) -> list:
"""Returns a list of awards"""
url = self.url.awards_url(page)
return self.request.make_api_request(url)
def get_award(self, id: int) -> dict:
"""Returns an award dictionary"""
url = self.url.award_url(id, False)
return self.request.make_api_request(url)
def get_award_extended(self, id: int) -> dict:
"""Returns an award extended dictionary"""
url = self.url.award_url(id, True)
return self.request.make_api_request(url)
def get_all_award_categories(self) -> list:
"""Returns a list of award categories"""
url = self.url.awards_categories_url()
return self.request.make_api_request(url)
def get_award_category(self, id: int) -> dict:
"""Returns an artwork category dictionary"""
url = self.url.award_category_url(id, False)
return self.request.make_api_request(url)
def get_award_category_extended(self, id: int) -> dict:
"""Returns an award category extended dictionary"""
url = self.url.award_category_url(id, True)
return self.request.make_api_request(url)
def get_content_ratings(self) -> list:
"""Returns a list of content ratings"""
url = self.url.content_ratings_url()
return self.request.make_api_request(url)
def get_countries(self) -> list:
"""Returns a list of countries"""
url = self.url.countries_url()
return self.request.make_api_request(url)
def get_all_companies(self, page=0) -> list:
"""Returns a list of companies"""
url = self.url.companies_url(page)
return self.request.make_api_request(url)
def get_company(self, id: int) -> dict:
"""Returns a company dictionary"""
url = self.url.company_url(id)
return self.request.make_api_request(url)
def get_all_series(self, page=0) -> list:
"""Returns a list of series"""
url = self.url.all_series_url(page)
return self.request.make_api_request(url)
def get_series(self, id: int) -> dict:
"""Returns a series dictionary"""
url = self.url.series_url(id, False)
return self.request.make_api_request(url)
def get_series_extended(self, id: int) -> dict:
"""Returns an series extended dictionary"""
url = self.url.series_url(id, True)
return self.request.make_api_request(url)
def get_series_translation(self, id: int, lang: str) -> dict:
"""Returns a series translation dictionary"""
url = self.url.series_translation_url(id, lang)
return self.request.make_api_request(url)
def get_all_movies(self, page=0) -> list:
"""Returns a list of movies"""
url = self.url.movies_url(page)
return self.request.make_api_request(url)
def get_movie(self, id: int) -> dict:
"""Returns a movie dictionary"""
url = self.url.movie_url(id, False)
return self.request.make_api_request(url)
def get_movie_extended(self, id: int) -> dict:
"""Returns a movie extended dictionary"""
url = self.url.movie_url(id, True)
return self.request.make_api_request(url)
def get_movie_translation(self, lang: str) -> dict:
"""Returns a movie translation dictionary"""
url = self.url.movie_translation_url(id, lang)
return self.request.make_api_request(url)
def get_season(self, id: int) -> dict:
"""Returns a season dictionary"""
url = self.url.season_url(id, False)
return self.request.make_api_request(url)
def get_season_extended(self, id: int) -> dict:
"""Returns a season extended dictionary"""
url = self.url.season_url(id, True)
return self.request.make_api_request(url)
def get_episode(self, id: int) -> dict:
"""Returns an episode dictionary"""
url = self.url.episode_url(id, False)
return self.request.make_api_request(url)
def get_episode_translation(self, id: int, lang: str) -> dict:
"""Returns an episode translation dictionary"""
url = self.url.episode_translation_url(id, lang)
return self.request.make_api_request(url)
def get_episode_extended(self, id: int) -> dict:
"""Returns an episode extended dictionary"""
url = self.url.episode_url(id, True)
return self.request.make_api_request(url)
def get_person(self, id: int) -> dict:
"""Returns a person dictionary"""
url = self.url.person_url(id, False)
return self.request.make_api_request(url)
def get_person_extended(self, id: int) -> dict:
"""Returns a person extended dictionary"""
url = self.url.person_url(id, True)
return self.request.make_api_request(url)
def get_character(self, id: int) -> dict:
"""Returns a character dictionary"""
url = self.url.character_url(id)
return self.request.make_api_request(url)
def get_all_people_types(self) -> list:
"""Returns a list of people types"""
url = self.url.people_types_url()
return self.request.make_api_request(url)
def get_all_sourcetypes(self) -> list:
"""Returns a list of sourcetypes"""
url = self.url.source_types_url()
return self.request.make_api_request(url)
def get_updates(self, since: int) -> list:
"""Returns a list of updates"""
url = self.url.updates_url(since)
return self.request.make_api_request(url)
def get_all_tag_options(self, page=0) -> list:
"""Returns a list of tag options"""
url = self.url.tag_options_url()
return self.request.make_api_request(url)
def get_tag_option(self, id: int) -> dict:
"""Returns a tag option dictionary"""
url = self.url.tag_option_url(id)
return self.request.make_api_request(url)
def search(self, query, **kwargs) -> list:
"""Returns a list of search results"""
url = self.url.search_url(query, kwargs)
return self.request.make_api_request(url)
def get_series_season_episodes(self, id: int, season_type: int = 1):
page = 0
episodes = []
while True:
url = self.url.series_season_episodes_url(id, season_type, page)
res = self.request.make_api_request(url).get("episodes", [])
page += 1
if not res:
break
episodes.extend(res)
return episodes
def get_series_details_api(self, id, settings=None) -> dict:
settings = settings or {}
series = self.get_series_extended(id)
language = get_language(settings)
try:
translation = self.get_series_translation(id, language)
except requests.HTTPError as exc:
logger.warning(f'{language} translation is not available: {exc}')
translation = {}
overview = translation.get("overview") or ''
name = translation.get("name") or ''
if not (overview or name) and translation.get('language') != 'eng':
try:
english_info = self.get_series_translation(id, 'eng')
except requests.HTTPError as exc:
logger.warning(f'eng info is not available: {exc}')
english_info = {}
if not overview:
overview = english_info.get('overview') or ''
if not name:
name = english_info.get('name') or ''
if name:
series["name"] = name
series["overview"] = overview
return series
def get_series_episodes_api(self, id, settings):
season_type = get_season_type(settings)
result = self.get_series_season_episodes(id, season_type)
if not result:
season_type_name = SeasonType(season_type).name.lower()
logger.warning(
f'No episodes returned for show {id}, season type "{season_type_name}"')
return result
def get_episode_details_api(self, id, settings):
try:
ep = self.get_episode_extended(id)
except requests.HTTPError as e:
logger.warning(f'No episode found with id={id}. [error: {e}]')
return None
trans = None
primary_language = get_language(settings)
language_attempties = [primary_language] if primary_language == "eng" else [
primary_language, "eng"]
for language in language_attempties:
try:
trans = self.get_episode_translation(id, language)
break
except requests.HTTPError as e:
logger.warning(
f'No episode found with id={id} and language={language}. [error: {e}]')
if not trans:
return None
overview = trans.get("overview") or ''
name = trans.get("name") or ''
if not (overview and name) and trans['language'] != 'eng':
try:
english_info = self.get_episode_translation(id, 'eng')
if not overview:
overview = english_info.get('overview') or ''
if not name:
name = english_info.get('name') or ''
except requests.HTTPError as e:
logger.warning(
f'No episode found with id={id} and language=eng . [error: {e}]')
ep["overview"] = overview
ep["name"] = name
return ep
def get_language(path_settings):
language = path_settings.get('language')
if language is None:
language = ADDON.getSetting('language') or 'English'
language_code = LANGUAGES_MAP.get(language, 'eng')
return language_code
def get_season_type(settings):
season_type_str = settings.get("season_type", "1")
return int(season_type_str)
class Client(object):
_instance = None
def __new__(cls, settings=None):
settings = settings or {}
if cls._instance is None:
pin = settings.get("pin", "")
gender = settings.get("gender", "Other")
uuid = settings.get("uuid", "")
birth_year = settings.get("year", "")
cls._instance = TVDB(apikey, pin=pin, gender=gender,
birthYear=birth_year, uuid=uuid)
return cls._instance
def get_artworks_from_show(show: dict, language: str = 'eng'):
def sorter(item):
item_language = item.get('language')
score = item.get('score') or 0
if item_language == language:
return 3, score
if item_language is None:
return 2, score
if item_language == 'eng':
return 1, score
return 0, score
artworks = show.get("artworks", [{}])
seasons = show.get("seasons", [{}])
banners = []
posters = []
fanarts = []
icons = []
cleararts = []
clearlogos = []
season_posters = []
for art in artworks:
art_type = art.get('type')
if art_type == ArtworkType.BANNER:
banners.append(art)
elif art_type == ArtworkType.POSTER:
posters.append(art)
elif art_type == ArtworkType.BACKGROUND:
fanarts.append(art)
elif art_type == ArtworkType.ICON:
icons.append(art)
elif art_type == ArtworkType.CLEARART:
cleararts.append(art)
elif art_type == ArtworkType.CLEARLOGO:
clearlogos.append(art)
elif art_type == ArtworkType.SEASONPOSTER:
season_id = art.get("seasonId", -1)
season = next((season for season in seasons if season.get("id", -2) == season_id), None)
if season:
season_posters.append( (art.get("image", ""), season.get("number", 0) ) )
banners.sort(key=sorter, reverse=True)
posters.sort(key=sorter, reverse=True)
fanarts.sort(key=sorter, reverse=True)
artwork_dict = {
'banner': banners,
'poster': posters,
'icon': icons,
'clearart': cleararts,
'clearlogo': clearlogos,
'fanarts': fanarts,
'season_posters': season_posters,
}
return artwork_dict