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.streamz.Movie | resources.lib.streamz.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
def get_recommendations(self, storefront): """ Returns the config for the dashboard """ 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
def get_config(self): """ Returns the config for the app. """ 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 Streamz about the playing progress return info
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, )
def _get_video_info(self, strtype, stream_id, player_token): """ Get the stream info for the specified stream. :param str strtype: :param str stream_id: :param str player_token: :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, # 'x-dpg-correlation-id': '', 'Popcorn-SDK-Version': '4', 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 6.0.1; MotoG3 Build/MPIS24.107-55-2-17)', 'Authorization': 'Bearer ' + player_token, }) info = json.loads(response.text) return info
def get_swimlane(self, swimlane=None): """ Returns the contents of My List """ response = util.http_get(API_ENDPOINT + '/%s/main/swimlane/%s' % (self._mode(), swimlane), token=self._tokens.jwt_token, profile=self._tokens.profile) # 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: items.append(self._parse_movie_teaser(item)) elif item.get('target', {}).get('type') == CONTENT_TYPE_PROGRAM: items.append(self._parse_program_teaser(item)) elif item.get('target', {}).get('type') == CONTENT_TYPE_EPISODE: items.append(self._parse_episode_teaser(item)) return items
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.access_token, profile=self._tokens.profile) 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)) elif item.get('target', {}).get('type') == CONTENT_TYPE_EPISODE: items.append(self._parse_episode_teaser(item)) return Category(category_id=category, title=result.get('row', {}).get('title'), content=items)
def get_items(self, category=None): """ Get a list of all the items in a category. :type category: str :rtype list[Union[Movie, 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: 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
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.access_token, profile=self._tokens.profile) 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
def check_status(self): """ Check customer status """ response = util.http_get( 'https://customer-api.streamz.be/onboarding/customer-status', headers={ 'authorization': 'Bearer ' + self._account.login_token, }) status = json.loads(response.text) if status.get('customerStatus') == 'NOT_AUTHORIZED': raise NoStreamzSubscriptionException() if status.get('customerStatus') == 'NOT_AUTHORIZED_TELENET': raise NoTelenetSubscriptionException()
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
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, profile=self._tokens.profile) 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
def _get_video_info(self, strtype, stream_id, player_token): """ Get the stream info for the specified stream. :type strtype: str :type stream_id: str :type player_token: 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={ 'Accept': 'application/json', 'x-api-key': self._API_KEY, # 'x-dpg-correlation-id': '', 'Popcorn-SDK-Version': '4', 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 6.0.1; MotoG3 Build/MPIS24.107-55-2-17)', 'Authorization': 'Bearer ' + player_token, }) _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
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
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
def get_profiles(self, products='STREAMZ,STREAMZ_KIDS'): """ Returns the available profiles """ response = util.http_get(API_ENDPOINT + '/profiles', {'products': products}, token=self._account.access_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
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.access_token, profile=self._tokens.profile) 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')), # my_list=program.get('addedToMyList'), # Don't use addedToMyList, since we might have cached this info available=movie.get('blockedFor') != 'SUBSCRIPTION', )
def _get_stream_tokens(self, strtype, stream_id): """ Get the stream info for the specified stream. :param str strtype: :param str stream_id: :rtype: dict """ if strtype == 'movies': url = API_ENDPOINT + '/%s/play/movie/%s' % (self._mode(), stream_id) elif strtype == 'episodes': url = API_ENDPOINT + '/%s/play/episode/%s' % (self._mode(), stream_id) else: raise Exception('Unknown stream type: %s' % strtype) _LOGGER.debug('Getting stream tokens from %s', url) response = util.http_get(url, token=self._tokens.jwt_token, profile=self._tokens.profile) return json.loads(response.text)
def _get_stream_tokens(self, strtype, stream_id): """ Get the stream info for the specified stream. :type strtype: str :type stream_id: str :rtype: dict """ if strtype == 'movies': url = API_ENDPOINT + '/%s/play/movie/%s' % (self._mode(), stream_id) elif strtype == 'episodes': url = API_ENDPOINT + '/%s/play/episode/%s' % (self._mode(), stream_id) else: raise Exception('Unknown stream type: %s' % strtype) _LOGGER.debug('Getting stream info from %s', url) response = util.http_get(url, token=self._tokens.jwt_token, profile=self._tokens.profile) _LOGGER.debug('Got response (status=%s): %s', response.status_code, response.text) # TODO: handle errors # 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() return json.loads(response.text)
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, profile=self._tokens.profile) 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'), cover=movie.get('bigPhotoUrl'), image=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')), )
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 = [] 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
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.access_token, profile=self._tokens.profile) # 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
def _web_login(self): """ Executes a login and returns the JSON Web Token. :rtype str """ # Start login flow util.http_get('https://account.streamz.be/login') # Generate random state and nonce parameters import random import string state = ''.join( random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(32)) nonce = ''.join( random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(32)) if self._loginprovider == LOGIN_STREAMZ: # Send login credentials response = util.http_post( 'https://login.streamz.be/co/authenticate', data={ "client_id": self.CLIENT_ID, "username": self._username, "password": self._password, "realm": "Username-Password-Authentication", "credential_type": "http://auth0.com/oauth/grant-type/password-realm" }, headers={ 'Origin': 'https://account.streamz.be', 'Referer': 'https://account.streamz.be', }) login_data = json.loads(response.text) # Obtain authorization response = util.http_get( 'https://login.streamz.be/authorize', params={ 'audience': 'https://streamz.eu.auth0.com/api/v2/', 'domain': 'login.streamz.be', 'client_id': self.CLIENT_ID, 'response_type': 'id_token token', 'redirect_uri': 'https://account.streamz.be/callback', 'scope': 'read:current_user profile email openid', 'state': state, 'nonce': nonce, 'auth0Client': 'eyJuYW1lIjoiYXV0aDAuanMiLCJ2ZXJzaW9uIjoiOS4xMy4yIn0=', # base64 encoded {"name":"auth0.js","version":"9.13.2"} 'realm': 'Username-Password-Authentication', 'login_ticket': login_data.get('login_ticket'), }) elif self._loginprovider == LOGIN_TELENET: # Obtain authorization util.http_get( 'https://login.streamz.be/authorize', params={ 'audience': 'https://streamz.eu.auth0.com/api/v2/', 'domain': 'login.streamz.be', 'client_id': self.CLIENT_ID, 'response_type': 'id_token token', 'redirect_uri': 'https://account.streamz.be/callback', 'scope': 'read:current_user profile email openid', 'connection': 'TN', 'state': state, 'nonce': nonce, 'auth0Client': 'eyJuYW1lIjoiYXV0aDAuanMiLCJ2ZXJzaW9uIjoiOS4xMy4yIn0=', # base64 encoded {"name":"auth0.js","version":"9.13.2"} }) # Send login credentials response = util.http_post( 'https://login.prd.telenet.be/openid/login.do', form={ 'j_username': self._username, 'j_password': self._password, 'rememberme': 'true', }) if 'Je gebruikersnaam en/of wachtwoord zijn verkeerd' in response.text: raise InvalidLoginException else: raise Exception('Unsupported login method: %s' % self._loginprovider) # Extract login_token params = parse_qs(urlsplit(response.url).fragment) if params: self._account.login_token = params.get('access_token')[0] else: raise LoginErrorException(code=103) # Could not extract parameter # Check login token self.check_status() # Login to the actual app response = util.http_get('https://www.streamz.be/streamz/aanmelden') # 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 state matches_code = re.search(r'name="code" value="([^"]+)', response.text) if matches_code: code = matches_code.group(1) else: raise LoginErrorException( code=102) # 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://www.streamz.be/streamz/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
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')) 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), ) 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'), )