def latestUpdatesCatalog(self, params): ''' Returns a catalog for the Latest Updates category. This route is cached memory-only to make sure it's always fresh. This memory cache lasts one Kodi session. Closing Kodi and reopening it will do a new web request. ''' api = params['api'] if api == requestHelper.API_ANIMETOON: _PROPERTY_LAST_UPDATES = 'tmania2.prop.toonUpdates' else: _PROPERTY_LAST_UPDATES = 'tmania2.prop.plusUpdates' # Memory-only cache of the latest updates IDs. latestIDs = cache.getCacheProperty(_PROPERTY_LAST_UPDATES, readFromDisk = False) if not latestIDs: requestHelper.setAPISource(api) requestHelper.delayBegin() jsonData = requestHelper.routeGET(params['route']) if jsonData: latestIDs = tuple(entry['id'] for entry in jsonData.get('updates', [ ])) cache.setCacheProperty(_PROPERTY_LAST_UPDATES, latestIDs, saveToDisk = False) requestHelper.delayEnd(1000) if latestIDs: # Latest Updates needs all items of the current API loaded, to compare IDs with. # Make a dictionary to map item IDs to the items themselves. allData = self._getMainRoutesData(api) idDict = {item[0]: item for item in chain.from_iterable(allData)} # Find the entries based on their IDs from the latest updates list. return self.catalogFromIterable(idDict[entryID] for entryID in latestIDs if entryID in idDict) else: return self.getEmptyCatalog()
def genericCatalog(self, params): ''' Returns a catalog for either the '/GetNew(...)' or '/GetPopular(...)' routes. These routes are cached memory-only, so they last one Kodi session. Closing and reopening Kodi will make a new web request for fresh data. ''' api = params['api'] route = params['route'] # Get the corresponding '/GetAll(...)' route with the data that will be needed. allRoute = route.replace('New', 'All') if '/GetNew' in route else route.replace('Popular', 'All') genericIDs = cache.getCacheProperty(api+route, readFromDisk = False) if not genericIDs: requestHelper.setAPISource(api) requestHelper.delayBegin() jsonData = requestHelper.routeGET(route) if jsonData: genericIDs = tuple(entry['id'] for entry in jsonData) cache.setCacheProperty(api+route, genericIDs, saveToDisk = False) requestHelper.delayEnd(500) if genericIDs: # Load all the main routes of the API (they are disk-cached), to compare IDs with. allData = self._getMainRoutesData(api, allRoute) idDict = {item[0]: item for item in chain.from_iterable(allData)} return self.catalogFromIterable(idDict[entryID] for entryID in genericIDs if entryID in idDict) else: return self.getEmptyCatalog()
def viewSearchGenre(params): ''' Directory for a sub menu that lists the available genres to filter items by. The genre list is cached memory-only to last a Kodi session. This is done because if you hit "Back" Kodi will reload this directory and do a redundant web request... ''' xbmcplugin.setContent(int(sys.argv[1]), 'tvshows') # Optional, influences the skin layout. api = params['api'] route = params['route'] if api == requestHelper.API_ANIMETOON: _PROPERTY_GENRE_NAMES = 'tmania2.prop.toonGenres' else: _PROPERTY_GENRE_NAMES = 'tmania2.prop.plusGenres' genreList = cache.getCacheProperty(_PROPERTY_GENRE_NAMES, readFromDisk=True) if not genreList: # The data from the '/GetGenres/' route is a dict with a list of genre names like "Action", # "Comedy" etc., but it also has some weird texts in the list probably from data entry errors. requestHelper.setAPISource(api) requestHelper.delayBegin() genreList = requestHelper.routeGET(route).get('genres', []) requestHelper.delayEnd() # Store the current API genres in a disk-persistent property with 1 week of lifetime. cache.setCacheProperty(_PROPERTY_GENRE_NAMES, genreList, saveToDisk=True, lifetime=cache.LIFETIME_ONE_WEEK) listItems = ((buildURL({ 'view': 'CATALOG_MENU', 'api': api, 'route': route, 'genreName': genreName }), xbmcgui.ListItem(genreName), True) for genreName in genreList) xbmcplugin.addDirectoryItems(int(sys.argv[1]), tuple(listItems)) xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=False) useCatalogLayout, layoutType = ADDON_SETTINGS['layoutCatalog'] if useCatalogLayout: xbmc.executebuiltin('Container.SetViewMode(' + layoutType + ')')
def getCatalog(self, params): ''' Retrieves the catalog from a persistent window property between different add-on states, or recreates the catalog if needed. The catalog is a dictionary of lists of entries, used to store data between add-on states to make xbmcgui.ListItems: { (1. Sections, as in alphabet sections for list items: #, A, B, C, D, E, F etc., each section holds a tuple of items.) A: ( item, item, item, ... (2. Items, each item is a tuple with the format seen in makeCatalogEntry().) ) B: (...) C: (...) } ''' def _buildCatalog(): catalog = self.catalogFunctions.get(params['route'], self.genericCatalog)(params) cache.setCacheProperty(self.PROPERTY_CATALOG, catalog, saveToDisk = False) return catalog # If these properties are empty (like when coming in from a favourites menu), or if a different # route (website area) or a different API was stored in this property, then reload it. lastAPI = cache.getRawProperty(self.PROPERTY_CATALOG_API) lastRoute = cache.getRawProperty(self.PROPERTY_CATALOG_ROUTE) if ( params['api'] != lastAPI or params['route'] != lastRoute or 'genreName' in params # Special-case for the genre search, same api\route but 'genreName' changes. or 'query' in params # Special-case for the name search, same api\route but 'query' changes. or 'searchData' in params # Special-case for search results, we might be coming in from Kodi Favourites. ): cache.setRawProperty(self.PROPERTY_CATALOG_API, params['api']) cache.setRawProperty(self.PROPERTY_CATALOG_ROUTE, params['route']) catalog = _buildCatalog() else: catalog = cache.getCacheProperty(self.PROPERTY_CATALOG, readFromDisk = False) if not catalog: catalog = _buildCatalog() return catalog
def allRouteCatalog(self, params): ''' Returns a catalog for one of the main '/GetAll(...)' routes. ''' api = params['api'] route = params['route'] propName = self._diskFriendlyPropName(api, route) jsonData = cache.getCacheProperty(propName, readFromDisk = True) if not jsonData: requestHelper.setAPISource(api) requestHelper.delayBegin() jsonData = requestHelper.routeGET(route) if jsonData: cache.setCacheProperty(propName, jsonData, saveToDisk = True) # Defaults to 3 days cache lifetime. requestHelper.delayEnd(500) if jsonData: return self.catalogFromIterable(self.makeCatalogEntry(entry) for entry in jsonData) else: return self.getEmptyCatalog()
def _getMainRoutesData(self, api, customRoute=None): ''' Loads all the main routes from one of the APIs to be used in the "filtering" catalog functions, like the latestUpdatesCatalog, genreSearchCatalog etc. that need all the items loaded at once. :returns: A dict of the '/GetAll(...)' routes of the API, each route key holds a list of catalog entries. ''' requestHelper.setAPISource(api) if customRoute: routeAlls = (customRoute,) else: if api == requestHelper.API_ANIMETOON: routeAlls = ('/GetAllCartoon', '/GetAllMovies', '/GetAllDubbed') # Animetoon 'All' routes. else: routeAlls = ('/GetAllMovies', '/GetAllShows') # Animeplus 'All' routes. routesData = [ ] newProperties = [ ] for route in routeAlls: # Try to get the cached property first. jsonData = cache.getCacheProperty( self._diskFriendlyPropName(api, route), readFromDisk = True ) if not jsonData: requestHelper.delayBegin() jsonData = requestHelper.routeGET(route) if jsonData: newProperties.append( (cache.diskFriendlyPropName(api + route), jsonData, cache.LIFETIME_THREE_DAYS) ) requestHelper.delayEnd(1000) # Always delay between requests so we don't abuse the source. routesData.append(tuple(self.makeCatalogEntry(entry) for entry in jsonData)) if newProperties: cache.setCacheProperties(newProperties, saveToDisk=True) return routesData
def viewListEpisodes(params): ''' Directory for the list of episodes from a show. This is the last directory before playing a video. Pages aren't necessary in here even if the thumbnails setting is on, because for now all episodes use the same thumb and plot, inherited from the show. ''' xbmcplugin.setContent(int(sys.argv[1]), 'episodes') # Sort episode list by labels. In some rare cases the episode date is set wrong, # so Kodi lists them out of order. xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) api = params['api'] jsonData = None # Cache (memory-only) the episode list of the show, as the user might watch more than one in sequence. _PROPERTY_LAST_SHOW_DETAILS = 'tmania2.prop.lastShow' lastShowDetails = cache.getCacheProperty(_PROPERTY_LAST_SHOW_DETAILS, readFromDisk=False) showKey = params['id'] + api if lastShowDetails and showKey in lastShowDetails: jsonData = lastShowDetails[ showKey] # Use the show ID + API name as the unique identifier. if not jsonData: requestHelper.setAPISource(api) requestHelper.delayBegin() jsonData = requestHelper.routeGET('/GetDetails/' + params['id']) if jsonData: cache.setCacheProperty(_PROPERTY_LAST_SHOW_DETAILS, {showKey: jsonData}, saveToDisk=False) requestHelper.delayEnd() # Genres, thumb and plot are taken from the parent show \ movie. # But the date of the parent show \ movie will only be used if the individual episode doesn't have a date itself. showTitle = jsonData.get('name', '') showGenres = params['genres'].split(',') showThumb = params.get( 'thumb', '') # Might be empty in case ADDON_SETTINGS['showThumbs'] is off. showPlot = params['plot'] showDate = params.get('date', '') # If the JSON data for this show has more than one episode, list all the episodes as usual. if len(jsonData['episode']) > 1: xbmcplugin.addDirectoryItems( int(sys.argv[1]), tuple( _makeEpisodeItems(api, jsonData['episode'], showTitle, showGenres, showThumb, showPlot, showDate))) else: # For a single episode, this is probably a movie, OVA or special (considered as a "1 episode show"). # Try to get the direct stream links to see if it's a movie (which are always multi-part). episodeEntry = jsonData['episode'][0] episodeProviders = None # Cache (memory-only) this request, as it might be for a multi part video (a movie) # where the user would go back and forth a lot on this view to watch all the movie parts. _PROPERTY_LAST_EPISODE_DETAILS = 'tmania2.prop.lastEpisode' lastEpisodeDetails = cache.getCacheProperty( _PROPERTY_LAST_EPISODE_DETAILS, readFromDisk=False) episodeKey = episodeEntry['id'] + api if lastEpisodeDetails and episodeKey in lastEpisodeDetails: episodeProviders = lastEpisodeDetails[episodeKey] else: episodeProviders = getEpisodeProviders(api, episodeEntry['id']) if episodeProviders: cache.setCacheProperty(_PROPERTY_LAST_EPISODE_DETAILS, {episodeKey: episodeProviders}, saveToDisk=False) if episodeProviders: if len(next(episodeProviders.itervalues()) ) > 1: # If it's a multi-video-part media. xbmcplugin.addDirectoryItems( int(sys.argv[1]), tuple( _makeEpisodePartItems(episodeEntry, episodeProviders, showTitle, showGenres, showThumb, showPlot, showDate))) else: # The item doesn't have multiple video parts. List the single item as usual and send its providers # as a URL parameter so they don't have to be retrieved again. dirItem = next( _makeEpisodeItems(api, (episodeEntry, ), showTitle, showGenres, showThumb, showPlot, showDate)) newUrl = dirItem[0] + '&' + urlencode( {'providers': str(episodeProviders)}) xbmcplugin.addDirectoryItem(int(sys.argv[1]), newUrl, dirItem[1], False) else: logStreamError(params['api'], api, episodeEntry['id']) xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=False) # Custom layout. useEpisodeLayout, layoutType = ADDON_SETTINGS['layoutEpisodes'] if useEpisodeLayout: xbmc.executebuiltin('Container.SetViewMode(' + layoutType + ')')