# -*- 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 . # 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)