262 lines
9.7 KiB
Python
262 lines
9.7 KiB
Python
# -*- coding: UTF-8 -*-
|
|
#
|
|
# Copyright (C) 2020, Team Kodi
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
# pylint: disable=missing-docstring
|
|
#
|
|
# This is based on the metadata.tvmaze scrapper by Roman Miroshnychenko aka Roman V.M.
|
|
|
|
"""Plugin route actions"""
|
|
|
|
from __future__ import absolute_import, unicode_literals
|
|
|
|
import sys
|
|
import json
|
|
import urllib.parse
|
|
import xbmcgui
|
|
import xbmcplugin
|
|
from . import tmdb, data_utils
|
|
from .utils import logger, safe_get
|
|
try:
|
|
from typing import Optional, Text, Union, ByteString # pylint: disable=unused-import
|
|
except ImportError:
|
|
pass
|
|
|
|
HANDLE = int(sys.argv[1]) # type: int
|
|
|
|
|
|
def find_show(title, year=None):
|
|
# type: (Union[Text, bytes], Optional[Text]) -> None
|
|
"""Find a show by title"""
|
|
if not isinstance(title, str):
|
|
title = title.decode('utf-8')
|
|
logger.debug('Searching for TV show {} ({})'.format(title, year))
|
|
search_results = tmdb.search_show(title, year)
|
|
for search_result in search_results:
|
|
show_name = search_result['name']
|
|
if safe_get(search_result, 'first_air_date') is not None:
|
|
show_name += ' ({})'.format(search_result['first_air_date'][:4])
|
|
list_item = xbmcgui.ListItem(show_name, offscreen=True)
|
|
show_info = search_result
|
|
list_item = data_utils.add_main_show_info(
|
|
list_item, show_info, full_info=False)
|
|
# Below "url" is some unique ID string (may be an actual URL to a show page)
|
|
# that is used to get information about a specific TV show.
|
|
xbmcplugin.addDirectoryItem(
|
|
HANDLE,
|
|
url=str(search_result['id']),
|
|
listitem=list_item,
|
|
isFolder=True
|
|
)
|
|
|
|
|
|
def get_show_id_from_nfo(nfo):
|
|
# type: (Text) -> None
|
|
"""
|
|
Get show ID by NFO file contents
|
|
|
|
This function is called first instead of find_show
|
|
if a NFO file is found in a TV show folder.
|
|
|
|
:param nfo: the contents of a NFO file
|
|
"""
|
|
if isinstance(nfo, bytes):
|
|
nfo = nfo.decode('utf-8', 'replace')
|
|
logger.debug('Parsing NFO file:\n{}'.format(nfo))
|
|
parse_result, named_seasons = data_utils.parse_nfo_url(nfo)
|
|
if parse_result:
|
|
if parse_result.provider == 'tmdb':
|
|
show_info = tmdb.load_show_info(
|
|
parse_result.show_id, ep_grouping=parse_result.ep_grouping, named_seasons=named_seasons)
|
|
else:
|
|
show_info = None
|
|
if show_info is not None:
|
|
list_item = xbmcgui.ListItem(show_info['name'], offscreen=True)
|
|
# "url" is some string that unique identifies a show.
|
|
# It may be an actual URL of a TV show page.
|
|
xbmcplugin.addDirectoryItem(
|
|
HANDLE,
|
|
url=str(show_info['id']),
|
|
listitem=list_item,
|
|
isFolder=True
|
|
)
|
|
|
|
|
|
def get_show_id(unique_ids):
|
|
"""
|
|
Get show ID by unique IDs
|
|
|
|
In case there is a tmdb identifier in the unique IDs, use that.
|
|
Else use the find_by_id method to get the show ID by an external ID.
|
|
|
|
:param unique_ids: dictionary of unique IDs
|
|
"""
|
|
if unique_ids.get('tmdb'):
|
|
return unique_ids['tmdb']
|
|
else:
|
|
res = tmdb.find_by_id(unique_ids)
|
|
if len(res) > 0:
|
|
return res[0].get('id')
|
|
return None
|
|
|
|
|
|
def get_details(show_id):
|
|
# type: (Text) -> None
|
|
"""Get details about a specific show"""
|
|
logger.debug('Getting details for show id {}'.format(show_id))
|
|
show_info = tmdb.load_show_info(show_id)
|
|
if show_info is not None:
|
|
list_item = xbmcgui.ListItem(show_info['name'], offscreen=True)
|
|
list_item = data_utils.add_main_show_info(
|
|
list_item, show_info, full_info=True)
|
|
xbmcplugin.setResolvedUrl(HANDLE, True, list_item)
|
|
else:
|
|
xbmcplugin.setResolvedUrl(
|
|
HANDLE, False, xbmcgui.ListItem(offscreen=True))
|
|
|
|
|
|
def get_episode_list(show_ids): # pylint: disable=missing-docstring
|
|
# type: (Text) -> None
|
|
# Kodi has a bug: when a show directory contains an XML NFO file with
|
|
# episodeguide URL, that URL is always passed here regardless of
|
|
# the actual parsing result in get_show_from_nfo()
|
|
# so much of this weird logic is to deal with that
|
|
try:
|
|
all_ids = json.loads(show_ids)
|
|
show_id = all_ids.get('tmdb')
|
|
if not show_id:
|
|
for key, value in all_ids.items():
|
|
show_id = data_utils._convert_ext_id(key, value)
|
|
if show_id:
|
|
show_id = str(show_id)
|
|
break
|
|
if not show_id:
|
|
show_id = str(show_ids)
|
|
except (ValueError, AttributeError):
|
|
show_id = str(show_ids)
|
|
if show_id.isdigit():
|
|
logger.error(
|
|
'using deprecated episodeguide format, this show should be refreshed or rescraped')
|
|
if not show_id:
|
|
raise RuntimeError(
|
|
'No TMDb TV show id found in episode guide, this show should be refreshed or rescraped')
|
|
elif not show_id.isdigit():
|
|
parse_result, named_seasons = data_utils.parse_nfo_url(show_id)
|
|
if parse_result:
|
|
show_id = parse_result.show_id
|
|
else:
|
|
raise RuntimeError(
|
|
'No TMDb TV show id found in episode guide, this show should be refreshed or rescraped')
|
|
logger.debug('Getting episode list for show id {}'.format(show_id))
|
|
show_info = tmdb.load_show_info(show_id)
|
|
if show_info is not None:
|
|
theindex = 0
|
|
for episode in show_info['episodes']:
|
|
epname = episode.get('name', 'Episode ' +
|
|
str(episode['episode_number']))
|
|
list_item = xbmcgui.ListItem(epname, offscreen=True)
|
|
list_item = data_utils.add_episode_info(
|
|
list_item, episode, full_info=False)
|
|
encoded_ids = urllib.parse.urlencode(
|
|
{'show_id': str(show_info['id']), 'episode_id': str(theindex)}
|
|
)
|
|
theindex = theindex + 1
|
|
# Below "url" is some unique ID string (may be an actual URL to an episode page)
|
|
# that allows to retrieve information about a specific episode.
|
|
url = urllib.parse.quote(encoded_ids)
|
|
xbmcplugin.addDirectoryItem(
|
|
HANDLE,
|
|
url=url,
|
|
listitem=list_item,
|
|
isFolder=True
|
|
)
|
|
else:
|
|
logger.error(
|
|
'unable to get show information using show id {}'.format(show_id))
|
|
logger.error('you may need to refresh the show to get a valid show id')
|
|
|
|
|
|
def get_episode_details(encoded_ids): # pylint: disable=missing-docstring
|
|
# type: (Text) -> None
|
|
encoded_ids = urllib.parse.unquote(encoded_ids)
|
|
decoded_ids = dict(urllib.parse.parse_qsl(encoded_ids))
|
|
logger.debug('Getting episode details for {}'.format(decoded_ids))
|
|
episode_info = tmdb.load_episode_info(
|
|
decoded_ids['show_id'], decoded_ids['episode_id']
|
|
)
|
|
if episode_info:
|
|
list_item = xbmcgui.ListItem(episode_info['name'], offscreen=True)
|
|
list_item = data_utils.add_episode_info(
|
|
list_item, episode_info, full_info=True)
|
|
xbmcplugin.setResolvedUrl(HANDLE, True, list_item)
|
|
else:
|
|
xbmcplugin.setResolvedUrl(
|
|
HANDLE, False, xbmcgui.ListItem(offscreen=True))
|
|
|
|
|
|
def get_artwork(show_id):
|
|
# type: (Text) -> None
|
|
"""
|
|
Get available artwork for a show
|
|
|
|
:param show_id: default unique ID set by setUniqueIDs() method
|
|
"""
|
|
if not show_id:
|
|
return
|
|
logger.debug('Getting artwork for show ID {}'.format(show_id))
|
|
show_info = tmdb.load_show_info(show_id)
|
|
if show_info is not None:
|
|
list_item = xbmcgui.ListItem(show_info['name'], offscreen=True)
|
|
list_item = data_utils.set_show_artwork(show_info, list_item)
|
|
xbmcplugin.setResolvedUrl(HANDLE, True, list_item)
|
|
else:
|
|
xbmcplugin.setResolvedUrl(
|
|
HANDLE, False, xbmcgui.ListItem(offscreen=True))
|
|
|
|
|
|
def router(paramstring):
|
|
# type: (Text) -> None
|
|
"""
|
|
Route addon calls
|
|
|
|
:param paramstring: url-encoded query string
|
|
:raises RuntimeError: on unknown call action
|
|
"""
|
|
params = dict(urllib.parse.parse_qsl(paramstring))
|
|
logger.debug('Called addon with params: {}'.format(sys.argv))
|
|
if params['action'] == 'find':
|
|
logger.debug('performing find action')
|
|
find_show(params['title'], params.get('year'))
|
|
elif params['action'].lower() == 'nfourl':
|
|
logger.debug('performing nfourl action')
|
|
get_show_id_from_nfo(params['nfo'])
|
|
elif params['action'] == 'getdetails':
|
|
logger.debug('performing getdetails action')
|
|
show_id = params.get('url') or get_show_id(json.loads(params.get('uniqueIDs')))
|
|
if show_id:
|
|
get_details(show_id)
|
|
elif params['action'] == 'getepisodelist':
|
|
logger.debug('performing getepisodelist action')
|
|
get_episode_list(params['url'])
|
|
elif params['action'] == 'getepisodedetails':
|
|
logger.debug('performing getepisodedetails action')
|
|
get_episode_details(params['url'])
|
|
elif params['action'] == 'getartwork':
|
|
logger.debug('performing getartwork action')
|
|
get_artwork(params.get('id'))
|
|
else:
|
|
raise RuntimeError('Invalid addon call: {}'.format(sys.argv))
|
|
xbmcplugin.endOfDirectory(HANDLE)
|