Example #1
0
    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()
Example #2
0
    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()
Example #3
0
def resolveProviderURL(providerURL):
    '''
    Tries to resolve a provider URL into a stream.
    '''
    try:
        temp = None
        requestHelper.delayBegin()
        r = requestHelper.GET(providerURL)
        if r.ok:
            html = r.text
            if 'var video_links' in html:
                # Try the generic videozoo \ play44 resolve first:
                temp = re.findall(
                    r'''var video_links.*?['"]link['"]\s*?:\s*?['"](.*?)['"]''',
                    html, re.DOTALL)
            else:
                # Try variants (found sometimes in Playpanda.net etc.):
                temp = re.findall(r'''{\s*?url\s*?:\s*?['"](.*?)['"]''', html,
                                  re.DOTALL)
                if not temp:
                    temp = re.findall(r'''file\s*?:\s*?['"](.*?)['"]''', html,
                                      re.DOTALL)
            requestHelper.delayEnd(
                1000
            )  # Sleep this thread a little before the next request, if necessary.
        if temp:
            return temp[0].replace(
                r'\/', r'/')  # Unescape any potential escaped JS slashes.
    except:
        pass

    return None
Example #4
0
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 + ')')
Example #5
0
    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()
Example #6
0
    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
Example #7
0
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 + ')')
Example #8
0
def getEpisodeProviders(api, episodeID):
    '''
    :returns: A dict where each key is a provider name and each value is a list of
    lists of stream URLs from that same provider.
    
    Usually each provider list has a single stream list, but in some rare cases there's more than
    one set of streams for the same provider.
    '''
    requestHelper.setAPISource(api)
    requestHelper.delayBegin()
    jsonData = requestHelper.routeGET('/GetVideos/' + episodeID)
    requestHelper.delayEnd(1000)

    if not jsonData:
        return None

    if isinstance(jsonData[0], dict):
        # Special-case for animeplus, URLs might come inside one dictionary each provider.
        providerURLs = (providerURL['url'] for providerURL in jsonData)
    elif isinstance(jsonData[0], list):
        # Or the JSON data is a list of lists (sometimes w/ mixed providers in the same list...).
        providerURLs = (url for urlList in jsonData for url in urlList)
    else:
        providerURLs = jsonData  # A list of single providers?

    # Assume the video parts of the same provider will be in order (eg. easyvideo part 1, easyvideo part 2 , 3 etc.).

    providers = {}  # Parent dict of providers, this is what's returned.
    lastURLs = []
    lastProvider = ''

    for url in providerURLs:
        # Try to get the provider name from the URL to see if we support resolving it.
        if url.startswith('http://'):
            tempURL = url.replace('http://', '')
        elif url.startswith('https://'):
            tempURL = url.replace('https://', '')

        providerName = next((key for word in tempURL.split('.')
                             for key in _SUPPORTED_PROVIDERS.iterkeys()
                             if word in _SUPPORTED_PROVIDERS[key]), None)
        if not providerName:
            continue  # It's not a supported provider (or we failed finding its name).

        # Accummulate consecutive URLs from the same provider in a list, until the provider name changes.
        if providerName != lastProvider:
            if lastURLs:  # Store the list in the dict, if it has any items.
                if lastProvider in providers:
                    providers[lastProvider].append(
                        lastURLs
                    )  # In case of non-consecutive lists of the same provider.
                else:
                    providers[lastProvider] = [lastURLs]
                lastURLs = []
            lastProvider = providerName

        lastURLs.append(url)

    # Flatten the 'providers' dict with a new key for each URL-list of the same provider.
    # providers[videozoo] = [[...], [...], [...]] becomes providers[videozoo][...], providers[videozoo2][...], etc.
    allProviders = {(providerName if index == 1 else providerName + ' (' +
                     str(index) + ')'): urlList
                    for providerName in providers.iterkeys()
                    for index, urlList in enumerate(providers[providerName], 1)
                    }
    return allProviders if allProviders else None