594 lines
20 KiB
Python
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
|