示例#1
0
    def _web_login(self):
        """ Executes a login and returns the JSON Web Token.
        :rtype str
        """
        # Yes, we have accepted the cookies
        util.SESSION.cookies.clear()
        util.SESSION.cookies.set('authId', str(uuid4()))

        # Start login flow
        util.http_get('https://vtm.be/vtmgo/aanmelden?redirectUrl=https://vtm.be/vtmgo')

        # Send login credentials
        try:
            response = util.http_post('https://login2.vtm.be/login?client_id=vtm-go-web', form={
                'userName': self._username,
                'password': self._password,
                'jsEnabled': 'true',
            })
        except HTTPError as exc:
            if exc.response.status_code == 400:
                raise InvalidLoginException()
            raise

        if 'errorBlock-OIDC-004' in response.text:  # E-mailadres is niet gekend.
            raise InvalidLoginException()

        if 'errorBlock-OIDC-003' in response.text:  # Wachtwoord is niet correct.
            raise InvalidLoginException()

        if 'OIDC-999' in response.text:  # Ongeldige login.
            raise InvalidLoginException()

        # Follow login
        response = util.http_get('https://login2.vtm.be/authorize/continue?client_id=vtm-go-web')

        # Extract state and code
        matches_state = re.search(r'name="state" value="([^"]+)', response.text)
        if matches_state:
            state = matches_state.group(1)
        else:
            raise LoginErrorException(code=101)  # Could not extract authentication code

        matches_code = re.search(r'name="code" value="([^"]+)', response.text)
        if matches_code:
            code = matches_code.group(1)
        else:
            raise LoginErrorException(code=101)  # Could not extract authentication code

        # Okay, final stage. We now need to POST our state and code to get a valid JWT.
        util.http_post('https://vtm.be/vtmgo/login-callback', form={
            'state': state,
            'code': code,
        })

        # Get JWT from cookies
        self._account.jwt_token = util.SESSION.cookies.get('lfvp_auth')

        self._save_cache()

        return self._account
示例#2
0
    def _anvato_get_anvacks(self, access_key):
        """ Get the anvacks from anvato. (not needed)
        :type access_key: string
        :rtype dict
        """
        url = 'https://access-prod.apis.anvato.net/anvacks/{key}'.format(
            key=access_key)
        _LOGGER.debug('Getting anvacks from %s', url)
        response = util.http_get(url,
                                 params={
                                     'apikey': self._ANVATO_API_KEY,
                                 },
                                 headers={
                                     'X-Anvato-User-Agent':
                                     self._ANVATO_USER_AGENT,
                                 })

        _LOGGER.debug('Got response (status=%s): %s', response.status_code,
                      response.text)

        if response.status_code != 200:
            raise Exception('Error %s in _anvato_get_anvacks.' %
                            response.status_code)

        info = json.loads(response.text)
        return info
示例#3
0
    def get_epgs(self, date=None):
        """ Load EPG information for the specified date.
        :type date: str
        :rtype: EpgChannel[]
        """
        date = self._parse_date(date)

        response = util.http_get(self.EPG_URL.format(date=date))
        epg = json.loads(response.text)

        # We get an EPG for all channels
        return [
            EpgChannel(
                name=epg_channel.get('name'),
                key=epg_channel.get('seoKey'),
                logo=epg_channel.get('channelLogoUrl'),
                uuid=epg_channel.get('uuid'),
                broadcasts=[
                    self._parse_broadcast(broadcast)
                    for broadcast in epg_channel.get('broadcasts', [])
                    if broadcast.get('title', '') != self.EPG_NO_BROADCAST
                ]
            )
            for epg_channel in epg.get('channels', [])
        ]
示例#4
0
    def _anvato_get_server_time(self, access_key):
        """ Get the server time from anvato. (not needed)
        :type access_key: string
        :rtype dict
        """
        url = 'https://tkx.apis.anvato.net/rest/v2/server_time'
        _LOGGER.debug('Getting servertime from %s with access_key %s', url,
                      access_key)
        response = util.http_get(url,
                                 params={
                                     'anvack': access_key,
                                     'anvtrid': self._generate_random_id(),
                                 },
                                 headers={
                                     'X-Anvato-User-Agent':
                                     self._ANVATO_USER_AGENT,
                                     'User-Agent': self._ANVATO_USER_AGENT,
                                 })

        _LOGGER.debug('Got response (status=%s): %s', response.status_code,
                      response.text)

        if response.status_code != 200:
            raise Exception('Error %s.' % response.status_code)

        info = json.loads(response.text)
        return info
示例#5
0
    def get_episode(self, episode_id):
        """ Get some details of the specified episode.
        :type episode_id: str
        :rtype Episode
        """
        response = util.http_get(API_ENDPOINT + '/%s/play/episode/%s' % (self._mode(), episode_id),
                                 token=self._tokens.jwt_token,
                                 profile=self._tokens.profile)
        episode = json.loads(response.text)

        # Extract next episode info if available
        next_playable = episode.get('nextPlayable')
        if next_playable:
            next_episode = Episode(
                episode_id=next_playable['id'],
                program_name=next_playable['title'],
                name=next_playable['subtitle'],
                description=next_playable['description'],
                cover=next_playable['imageUrl'],
            )
        else:
            next_episode = None

        return Episode(
            episode_id=episode.get('id'),
            name=episode.get('title'),
            cover=episode.get('posterImageUrl'),
            progress=episode.get('playerPositionSeconds'),
            next_episode=next_episode,
        )
示例#6
0
    def get_items(self, category=None, content_filter=None, cache=CACHE_ONLY):
        """ Get a list of all the items in a category.

        :type category: str
        :type content_filter: class
        :type cache: int
        :rtype list[resources.lib.vtmgo.Movie | resources.lib.vtmgo.Program]
        """
        # Fetch from API
        response = util.http_get(API_ENDPOINT + '/%s/catalog' % self._mode(),
                                 params={'pageSize': 2000, 'filter': quote(category) if category else None},
                                 token=self._tokens.jwt_token,
                                 profile=self._tokens.profile)
        info = json.loads(response.text)
        content = info.get('pagedTeasers', {}).get('content', [])

        items = []
        for item in content:
            if item.get('target', {}).get('type') == CONTENT_TYPE_MOVIE and content_filter in [None, Movie]:
                items.append(self._parse_movie_teaser(item, cache=cache))

            elif item.get('target', {}).get('type') == CONTENT_TYPE_PROGRAM and content_filter in [None, Program]:
                items.append(self._parse_program_teaser(item, cache=cache))

        return items
示例#7
0
    def get_catalog_ids(self):
        """ Returns the IDs of the contents of the Catalog """
        # Try to fetch from cache
        items = kodiutils.get_cache(['catalog_id'], 300)  # 5 minutes ttl
        if items:
            return items

        # Fetch from API
        response = util.http_get(
            API_ENDPOINT + '/%s/catalog' % self._mode(),
            params={
                'pageSize': 2000,
                'filter': None
            },
            token=self._tokens.jwt_token if self._tokens else None,
            profile=self._tokens.profile if self._tokens else None)
        info = json.loads(response.text)

        items = [
            item.get('target', {}).get('id')
            for item in info.get('pagedTeasers', {}).get('content', [])
        ]

        kodiutils.set_cache(['catalog_id'], items)
        return items
示例#8
0
    def get_storefront_category(self, storefront, category):
        """ Returns a storefront.

         :param str storefront:         The ID of the storefront.
         :param str category:           The ID of the category.
         :rtype: Category
         """
        response = util.http_get(
            API_ENDPOINT + '/%s/storefronts/%s/detail/%s' %
            (self._mode(), storefront, category),
            token=self._tokens.jwt_token if self._tokens else None,
            profile=self._tokens.profile if self._tokens else None)
        result = json.loads(response.text)

        items = []
        for item in result.get('row', {}).get('teasers'):
            if item.get('target', {}).get('type') == CONTENT_TYPE_MOVIE:
                items.append(self._parse_movie_teaser(item))

            elif item.get('target', {}).get('type') == CONTENT_TYPE_PROGRAM:
                items.append(self._parse_program_teaser(item))

        return Category(category_id=category,
                        title=result.get('row', {}).get('title'),
                        content=items)
示例#9
0
    def get_epg(self, channel, date=None):
        """ Load EPG information for the specified channel and date.
        :type channel: str
        :type date: str
        :rtype: EpgChannel
        """
        date = self._parse_date(date)

        response = util.http_get(self.EPG_URL.format(date=date))
        epg = json.loads(response.text)

        # We get an EPG for all channels, but we only return the requested channel.
        for epg_channel in epg.get('channels', []):
            if epg_channel.get('seoKey') == channel:
                return EpgChannel(
                    name=epg_channel.get('name'),
                    key=epg_channel.get('seoKey'),
                    logo=epg_channel.get('channelLogoUrl'),
                    uuid=epg_channel.get('uuid'),
                    broadcasts=[
                        self._parse_broadcast(broadcast)
                        for broadcast in epg_channel.get('broadcasts', [])
                        if broadcast.get('title', '') != self.EPG_NO_BROADCAST
                    ])

        raise Exception('Channel %s not found in the EPG' % channel)
示例#10
0
    def get_live_channels(self):
        """ Get a list of all the live tv channels.
        :rtype list[LiveChannel]
        """
        import dateutil.parser
        response = util.http_get(API_ENDPOINT + '/%s/live' % self._mode(),
                                 token=self._tokens.jwt_token,
                                 profile=self._tokens.profile)
        info = json.loads(response.text)

        channels = []
        for item in info.get('channels'):
            epg = []
            for item_epg in item.get('broadcasts', []):
                epg.append(LiveChannelEpg(
                    title=item_epg.get('name'),
                    start=dateutil.parser.parse(item_epg.get('startsAt')),
                    end=dateutil.parser.parse(item_epg.get('endsAt')),
                ))
            channels.append(LiveChannel(
                key=item.get('seoKey'),
                channel_id=item.get('channelId'),
                logo=item.get('channelLogoUrl'),
                background=item.get('channelPosterUrl'),
                name=item.get('name'),
                epg=epg,
            ))

        return channels
示例#11
0
    def get_config(self):
        """ Returns the config for the app """
        # This is currently not used
        response = util.http_get(API_ENDPOINT + '/config', token=self._tokens.jwt_token)
        info = json.loads(response.text)

        # This contains a player.updateIntervalSeconds that could be used to notify VTM GO about the playing progress
        return info
示例#12
0
    def _download_text(url):
        """ Download a file as text.
        :type url: str
        :rtype str
        """
        _LOGGER.debug('Downloading text from %s', url)
        response = util.http_get(url)
        if response.status_code != 200:
            raise Exception('Error %s.' % response.status_code)

        return response.text
示例#13
0
    def get_storefront(self, storefront):
        """ Returns a storefront.

         :param str storefront:         The ID of the storefront.
         :rtype: list[Category|Program|Movie]
         """
        response = util.http_get(
            API_ENDPOINT + '/%s/storefronts/%s' % (self._mode(), storefront),
            token=self._tokens.jwt_token if self._tokens else None,
            profile=self._tokens.profile if self._tokens else None)
        result = json.loads(response.text)

        items = []
        for row in result.get('rows', []):
            if row.get('rowType') in [
                    'SWIMLANE_DEFAULT', 'SWIMLANE_PORTRAIT',
                    'SWIMLANE_LANDSCAPE'
            ]:
                items.append(
                    Category(
                        category_id=row.get('id'),
                        title=row.get('title'),
                    ))
                continue

            if row.get('rowType') == 'CAROUSEL':
                for item in row.get('teasers'):
                    if item.get('target',
                                {}).get('type') == CONTENT_TYPE_MOVIE:
                        items.append(self._parse_movie_teaser(item))

                    elif item.get('target',
                                  {}).get('type') == CONTENT_TYPE_PROGRAM:
                        items.append(self._parse_program_teaser(item))
                continue

            if row.get('rowType') in ['TOP_BANNER', 'MARKETING_BLOCK']:
                item = row.get('teaser')
                if item.get('target', {}).get('type') == CONTENT_TYPE_MOVIE:
                    items.append(self._parse_movie_teaser(item))

                elif item.get('target',
                              {}).get('type') == CONTENT_TYPE_PROGRAM:
                    items.append(self._parse_program_teaser(item))
                continue

            _LOGGER.debug('Skipping recommendation %s with type %s',
                          row.get('title'), row.get('rowType'))

        return items
示例#14
0
    def _download_text(self, url):
        """ Download a file as text.
        :type url: str
        :rtype str
        """
        _LOGGER.debug('Downloading text from %s', url)
        response = util.http_get(url,
                                 headers={
                                     'X-Anvato-User-Agent': self._ANVATO_USER_AGENT,
                                     'User-Agent': self._ANVATO_USER_AGENT,
                                 })
        if response.status_code != 200:
            raise Exception('Error %s.' % response.status_code)

        return response.text
示例#15
0
    def get_categories(self):
        """ Get a list of all the categories.
        :rtype list[Category]
        """
        response = util.http_get(API_ENDPOINT + '/%s/catalog/filters' % self._mode(),
                                 token=self._tokens.jwt_token,
                                 profile=self._tokens.profile)
        info = json.loads(response.text)

        categories = []
        for item in info.get('catalogFilters', []):
            categories.append(Category(
                category_id=item.get('id'),
                title=item.get('title'),
            ))

        return categories
示例#16
0
    def _delay_subtitles(self, subtitles, json_manifest):
        """ Modify the subtitles timings to account for ad breaks.
        :type subtitles: list[string]
        :type json_manifest: dict
        :rtype list[str]
        """
        # Clean up old subtitles
        temp_dir = os.path.join(kodiutils.addon_profile(), 'temp', '')
        _, files = kodiutils.listdir(temp_dir)
        if files:
            for item in files:
                if kodiutils.to_unicode(item).endswith('.vtt'):
                    kodiutils.delete(temp_dir + kodiutils.to_unicode(item))

        # Return if there are no subtitles available
        if not subtitles:
            return None

        import re
        if not kodiutils.exists(temp_dir):
            kodiutils.mkdirs(temp_dir)

        ad_breaks = list()
        delayed_subtitles = list()
        webvtt_timing_regex = re.compile(
            r'\n(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})\s')

        # Get advertising breaks info from json manifest
        cues = json_manifest.get('interstitials').get('cues')
        for cue in cues:
            ad_breaks.append(
                dict(start=cue.get('start'),
                     duration=cue.get('break_duration')))

        for subtitle in subtitles:
            output_file = temp_dir + subtitle.get('name') + '.' + subtitle.get(
                'url').split('.')[-1]
            webvtt_content = util.http_get(subtitle.get('url')).text
            webvtt_content = webvtt_timing_regex.sub(
                lambda match: self._delay_webvtt_timing(match, ad_breaks),
                webvtt_content)
            with kodiutils.open_file(output_file, 'w') as webvtt_output:
                webvtt_output.write(kodiutils.from_unicode(webvtt_content))
            delayed_subtitles.append(output_file)
        return delayed_subtitles
示例#17
0
    def get_mylist_ids(self):
        """ Returns the IDs of the contents of My List """
        # Try to fetch from cache
        items = kodiutils.get_cache(['mylist_id'], 300)  # 5 minutes ttl
        if items:
            return items

        # Fetch from API
        response = util.http_get(API_ENDPOINT + '/%s/main/swimlane/%s' % (self._mode(), 'my-list'),
                                 token=self._tokens.jwt_token,
                                 profile=self._tokens.profile)

        # Result can be empty
        result = json.loads(response.text) if response.text else []

        items = [item.get('target', {}).get('id') for item in result.get('teasers', [])]

        kodiutils.set_cache(['mylist_id'], items)
        return items
示例#18
0
    def _get_stream_info(self, strtype, stream_id):
        """ Get the stream info for the specified stream.
        :type strtype: str
        :type stream_id: str
        :rtype: dict
        """
        url = 'https://videoplayer-service.api.persgroep.cloud/config/%s/%s' % (
            strtype, stream_id)
        _LOGGER.debug('Getting stream info from %s', url)
        response = util.http_get(
            url,
            params={
                'startPosition': '0.0',
                'autoPlay': 'true',
            },
            headers={
                'x-api-key':
                self._VTM_API_KEY,
                'Popcorn-SDK-Version':
                '2',
                'User-Agent':
                'Dalvik/2.1.0 (Linux; U; Android 6.0.1; Nexus 5 Build/M4B30Z)',
            },
            proxies=kodiutils.get_proxies())

        _LOGGER.debug('Got response (status=%s): %s', response.status_code,
                      response.text)

        if response.status_code == 403:
            error = json.loads(response.text)
            if error['type'] == 'videoPlaybackGeoblocked':
                raise StreamGeoblockedException()
            if error['type'] == 'serviceError':
                raise StreamUnavailableException()

        if response.status_code == 404:
            raise StreamUnavailableException()

        if response.status_code != 200:
            raise StreamUnavailableException()

        info = json.loads(response.text)
        return info
示例#19
0
    def get_profiles(self, products='VTM_GO,VTM_GO_KIDS'):
        """ Returns the available profiles """
        response = util.http_get(API_ENDPOINT + '/profiles', {'products': products}, token=self._account.jwt_token)
        result = json.loads(response.text)

        profiles = [
            Profile(
                key=profile.get('id'),
                product=profile.get('product'),
                name=profile.get('name'),
                gender=profile.get('gender'),
                birthdate=profile.get('birthDate'),
                color=profile.get('color', {}).get('start'),
                color2=profile.get('color', {}).get('end'),
            )
            for profile in result
        ]

        return profiles
示例#20
0
    def do_search(self, search):
        """ Do a search in the full catalog.
        :type search: str
        :rtype list[Union[Movie, Program]]
        """
        response = util.http_get(API_ENDPOINT + '/%s/search/?query=%s' % (self._mode(),
                                                                          kodiutils.to_unicode(quote(kodiutils.from_unicode(search)))),
                                 token=self._tokens.jwt_token,
                                 profile=self._tokens.profile)
        results = json.loads(response.text)

        items = []
        for category in results.get('results', []):
            for item in category.get('teasers'):
                if item.get('target', {}).get('type') == CONTENT_TYPE_MOVIE:
                    items.append(self._parse_movie_teaser(item))

                elif item.get('target', {}).get('type') == CONTENT_TYPE_PROGRAM:
                    items.append(self._parse_program_teaser(item))
        return items
示例#21
0
    def _get_video_info(self, strtype, stream_id):
        """ Get the stream info for the specified stream.
        :param str strtype:
        :param str stream_id:
        :rtype: dict
        """
        url = 'https://videoplayer-service.api.persgroep.cloud/config/%s/%s' % (strtype, stream_id)
        _LOGGER.debug('Getting video info from %s', url)
        response = util.http_get(url,
                                 params={
                                     'startPosition': '0.0',
                                     'autoPlay': 'true',
                                 },
                                 headers={
                                     'Accept': 'application/json',
                                     'x-api-key': self._API_KEY,
                                     'Popcorn-SDK-Version': '4',
                                 })

        info = json.loads(response.text)
        return info
示例#22
0
    def get_movie(self, movie_id, cache=CACHE_AUTO):
        """ Get the details of the specified movie.
        :type movie_id: str
        :type cache: int
        :rtype Movie
        """
        if cache in [CACHE_AUTO, CACHE_ONLY]:
            # Try to fetch from cache
            movie = kodiutils.get_cache(['movie', movie_id])
            if movie is None and cache == CACHE_ONLY:
                return None
        else:
            movie = None

        if movie is None:
            # Fetch from API
            response = util.http_get(
                API_ENDPOINT + '/%s/movies/%s' % (self._mode(), movie_id),
                token=self._tokens.jwt_token if self._tokens else None,
                profile=self._tokens.profile if self._tokens else None)
            info = json.loads(response.text)
            movie = info.get('movie', {})
            kodiutils.set_cache(['movie', movie_id], movie)

        return Movie(
            movie_id=movie.get('id'),
            name=movie.get('name'),
            description=movie.get('description'),
            duration=movie.get('durationSeconds'),
            thumb=movie.get('teaserImageUrl'),
            fanart=movie.get('bigPhotoUrl'),
            year=movie.get('productionYear'),
            geoblocked=movie.get('geoBlocked'),
            remaining=movie.get('remainingDaysAvailable'),
            legal=movie.get('legalIcons'),
            # aired=movie.get('broadcastTimestamp'),
            channel=self._parse_channel(movie.get('channelLogoUrl')),
        )
示例#23
0
    def _download_subtitles(subtitles):
        # Clean up old subtitles
        temp_dir = os.path.join(kodiutils.addon_profile(), 'temp', '')
        _, files = kodiutils.listdir(temp_dir)
        if files:
            for item in files:
                kodiutils.delete(temp_dir + kodiutils.to_unicode(item))

        # Return if there are no subtitles available
        if not subtitles:
            return None

        if not kodiutils.exists(temp_dir):
            kodiutils.mkdirs(temp_dir)

        downloaded_subtitles = list()
        for subtitle in subtitles:
            output_file = temp_dir + subtitle.get('name')
            webvtt_content = util.http_get(subtitle.get('url')).text
            with kodiutils.open_file(output_file, 'w') as webvtt_output:
                webvtt_output.write(kodiutils.from_unicode(webvtt_content))
            downloaded_subtitles.append(output_file)
        return downloaded_subtitles
示例#24
0
    def get_mylist(self, content_filter=None, cache=CACHE_ONLY):
        """ Returns the contents of My List """
        response = util.http_get(
            API_ENDPOINT + '/%s/my-list' % (self._mode()),
            token=self._tokens.jwt_token if self._tokens else None,
            profile=self._tokens.profile if self._tokens else None)

        # Result can be empty
        if not response.text:
            return []

        result = json.loads(response.text)

        items = []
        for item in result.get('teasers'):
            if item.get(
                    'target',
                {}).get('type') == CONTENT_TYPE_MOVIE and content_filter in [
                    None, Movie
                ]:
                items.append(self._parse_movie_teaser(item, cache=cache))

            elif item.get(
                    'target',
                {}).get('type') == CONTENT_TYPE_PROGRAM and content_filter in [
                    None, Program
                ]:
                items.append(self._parse_program_teaser(item, cache=cache))

            elif item.get(
                    'target',
                {}).get('type') == CONTENT_TYPE_EPISODE and content_filter in [
                    None, Episode
                ]:
                items.append(self._parse_episode_teaser(item, cache=cache))

        return items
示例#25
0
    def get_recommendations(self, storefront):
        """ Returns the config for the dashboard.

         :param str storefront:         The ID of the listing.
         :rtype: list[Category]
         """
        response = util.http_get(API_ENDPOINT + '/%s/storefronts/%s' %
                                 (self._mode(), storefront),
                                 token=self._tokens.jwt_token,
                                 profile=self._tokens.profile)
        recommendations = json.loads(response.text)

        categories = []
        for cat in recommendations.get('rows', []):
            if cat.get('rowType') not in ['SWIMLANE_DEFAULT']:
                _LOGGER.debug('Skipping recommendation %s with type %s',
                              cat.get('title'), cat.get('rowType'))
                continue

            items = []
            for item in cat.get('teasers'):
                if item.get('target', {}).get('type') == CONTENT_TYPE_MOVIE:
                    items.append(self._parse_movie_teaser(item))

                elif item.get('target',
                              {}).get('type') == CONTENT_TYPE_PROGRAM:
                    items.append(self._parse_program_teaser(item))

            categories.append(
                Category(
                    category_id=cat.get('id'),
                    title=cat.get('title'),
                    content=items,
                ))

        return categories
示例#26
0
    def _android_login(self):
        """ Executes an android login and returns the JSON Web Token.
        :rtype str
        """
        # We should start fresh
        util.SESSION.cookies.clear()

        # Start login flow
        util.http_get('https://login2.vtm.be/authorize', params={
            'client_id': 'vtm-go-android',
            'response_type': 'id_token',
            'scope': 'openid email profile address phone',
            'nonce': 1550073732654,
            'sdkVersion': '0.13.1',
            'state': 'dnRtLWdvLWFuZHJvaWQ=',  # vtm-go-android
            'redirect_uri': 'https://login2.vtm.be/continue',
        })

        # Send login credentials
        try:
            response = util.http_post('https://login2.vtm.be/login',
                                      params={
                                          'client_id': 'vtm-go-android',
                                      },
                                      form={
                                          'userName': self._username,
                                          'password': self._password,
                                      })
        except HTTPError as exc:
            if exc.response.status_code == 400:
                raise InvalidLoginException()
            raise

        if 'errorBlock-OIDC-004' in response.text:  # E-mailadres is niet gekend.
            raise InvalidLoginException()

        if 'errorBlock-OIDC-003' in response.text:  # Wachtwoord is niet correct.
            raise InvalidLoginException()

        if 'OIDC-999' in response.text:  # Ongeldige login.
            raise InvalidLoginException()

        # Extract redirect
        match = re.search(r"window.location.href = '([^']+)'", response.text)
        if not match:
            raise LoginErrorException(code=103)
        redirect_url = match.group(1)

        # Follow login
        response = util.http_get(redirect_url)

        # We are redirected and our id_token is in the fragment of the redirected url
        params = parse_qs(urlparse(response.url).fragment)
        id_token = params['id_token'][0]

        # Okay, final stage. We now need to authorize our id_token so we get a valid JWT.
        response = util.http_post('https://lfvp-api.dpgmedia.net/vtmgo/tokens', data={
            'idToken': id_token,
        })

        # Get JWT from reply
        self._account.jwt_token = json.loads(response.text).get('lfvpToken')

        self._save_cache()

        return self._account
示例#27
0
    def get_program(self, program_id, cache=CACHE_AUTO):
        """ Get the details of the specified program.
        :type program_id: str
        :type cache: int
        :rtype Program
        """
        if cache in [CACHE_AUTO, CACHE_ONLY]:
            # Try to fetch from cache
            program = kodiutils.get_cache(['program', program_id])
            if program is None and cache == CACHE_ONLY:
                return None
        else:
            program = None

        if program is None:
            # Fetch from API
            response = util.http_get(API_ENDPOINT + '/%s/programs/%s' % (self._mode(), program_id),
                                     token=self._tokens.jwt_token,
                                     profile=self._tokens.profile)
            info = json.loads(response.text)
            program = info.get('program', {})
            kodiutils.set_cache(['program', program_id], program)

        channel = self._parse_channel(program.get('channelLogoUrl'))

        # Calculate a hash value of the ids of all episodes
        program_hash = hashlib.md5()
        program_hash.update(program.get('id').encode())

        seasons = {}
        for item_season in program.get('seasons', []):
            episodes = {}

            for item_episode in item_season.get('episodes', []):
                episodes[item_episode.get('index')] = Episode(
                    episode_id=item_episode.get('id'),
                    program_id=program_id,
                    program_name=program.get('name'),
                    number=item_episode.get('index'),
                    season=item_season.get('index'),
                    name=item_episode.get('name'),
                    description=item_episode.get('description'),
                    duration=item_episode.get('durationSeconds'),
                    cover=item_episode.get('bigPhotoUrl'),
                    geoblocked=program.get('geoBlocked'),
                    remaining=item_episode.get('remainingDaysAvailable'),
                    channel=channel,
                    legal=program.get('legalIcons'),
                    aired=item_episode.get('broadcastTimestamp'),
                    progress=item_episode.get('playerPositionSeconds', 0),
                    watched=item_episode.get('doneWatching', False),
                )
                program_hash.update(item_episode.get('id').encode())

            seasons[item_season.get('index')] = Season(
                number=item_season.get('index'),
                episodes=episodes,
                cover=item_season.get('episodes', [{}])[0].get('bigPhotoUrl')
                if episodes else program.get('bigPhotoUrl'),
                geoblocked=program.get('geoBlocked'),
                channel=channel,
                legal=program.get('legalIcons'),
            )

        return Program(
            program_id=program.get('id'),
            name=program.get('name'),
            description=program.get('description'),
            cover=program.get('bigPhotoUrl'),
            image=program.get('bigPhotoUrl'),
            geoblocked=program.get('geoBlocked'),
            seasons=seasons,
            channel=channel,
            legal=program.get('legalIcons'),
            content_hash=program_hash.hexdigest().upper(),
            # my_list=program.get('addedToMyList'),  # Don't use addedToMyList, since we might have cached this info
        )