From 198e84303d3032746c4de1a45cb4964d9c93f1d3 Mon Sep 17 00:00:00 2001 From: Nirvana Date: Thu, 30 Apr 2026 13:31:09 +0200 Subject: [PATCH] Prepare RTLPlus for new streaming prov --- .../providers/rtlplus/auth.py | 45 ++++++++++++++++--- .../providers/rtlplus/channel_manager.py | 11 +++-- .../providers/rtlplus/constants.py | 19 ++++++-- .../providers/rtlplus/provider.py | 8 ++-- 4 files changed, 65 insertions(+), 18 deletions(-) diff --git a/lib/streaming_providers/providers/rtlplus/auth.py b/lib/streaming_providers/providers/rtlplus/auth.py index bdab9e0..8ccea0d 100644 --- a/lib/streaming_providers/providers/rtlplus/auth.py +++ b/lib/streaming_providers/providers/rtlplus/auth.py @@ -283,25 +283,50 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator): data = f"{device_id}{ts_str}" token = hashlib.sha1(data.encode()).hexdigest() - logger.debug(f"Generated auth token: {token} for device {device_id[:8]}... at {timestamp}") + # Alternative: Try with colon separator + # data = f"{device_id}:{ts_str}" + # token = hashlib.sha1(data.encode()).hexdigest() + + logger.debug(f"Generated auth token for device {device_id} at {timestamp}: {token}") return token def get_bedrock_token(self, force_refresh: bool = False) -> str: """Get or refresh Bedrock token, including profile ID if available.""" if not force_refresh and self._bedrock_token and self._bedrock_token_expiry > time.time() + 300: + logger.debug(f"Using cached bedrock token (expires at {self._bedrock_token_expiry})") return self._bedrock_token oauth_token = self.get_bearer_token() + if not oauth_token: + raise ValueError("No OAuth token available for Bedrock token request") + + # Get user ID (Gigya UID) + user_id = self.get_user_id_from_token() + if not user_id: + logger.warning("No user ID available for Bedrock token request") + # Continue without user_id - might still work for anonymous? # Get timestamp and generate auth token timestamp = self._get_server_timestamp() auth_token = self._generate_auth_token(self.config.device_id, timestamp) + logger.debug(f"Timestamp: {timestamp}, Auth token: {auth_token}") + # Get profile ID if available profile_id = self.get_selected_profile_id() + logger.debug(f"Using profile_id: {profile_id}") - # Get headers with profile_id - headers = self.config.get_bedrock_token_headers(oauth_token, auth_token, timestamp, profile_id) + # Get headers with profile_id and user_id + headers = self.config.get_bedrock_token_headers( + oauth_token=oauth_token, + auth_token=auth_token, + timestamp=timestamp, + profile_id=profile_id, + user_id=user_id, # Add user_id parameter + ) + + logger.debug( + f"Requesting Bedrock token with headers: { {k: v[:20] if 'token' in k.lower() or 'authorization' in k.lower() else v for k, v in headers.items()} }") response = self.http_manager.get( self.config.bedrock_auth_url, @@ -312,6 +337,7 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator): data = response.json() self._bedrock_token = data.get("token") + logger.debug(f"Bedrock token response: {data.keys() if data else 'empty'}") if self._bedrock_token: try: @@ -323,9 +349,12 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator): payload += "=" * padding decoded = json.loads(base64.b64decode(payload)) self._bedrock_token_expiry = decoded.get("exp", 0) - logger.debug(f"Bedrock token obtained with profile: {decoded.get('profileid', 'none')}") + logger.debug( + f"Bedrock token obtained with profile: {decoded.get('profileid', 'none')}, expires: {self._bedrock_token_expiry}") except Exception as e: logger.debug(f"Could not decode Bedrock token expiry: {e}") + # Set default expiry (1 hour) + self._bedrock_token_expiry = time.time() + 3600 logger.debug("RTL+ Bedrock token obtained successfully") return self._bedrock_token @@ -499,14 +528,17 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator): try: profiles = self.get_user_profiles(user_id) - # Select first adult profile + logger.debug(f"Fetched profiles: {profiles}") + + # Select first adult profile (not kid profile) adult_profiles = [p for p in profiles if p.get("profile_type") == "adult"] if not adult_profiles: logger.error("No adult profiles found") return False profile_id = adult_profiles[0].get("uid") - logger.info(f"Selected profile: {adult_profiles[0].get('username')} (ID: {profile_id})") + profile_username = adult_profiles[0].get("username") + logger.info(f"Selected profile: {profile_username} (ID: {profile_id})") except Exception as e: logger.error(f"Failed to fetch/select profile: {e}") return False @@ -522,6 +554,7 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator): # Invalidate Bedrock token so it gets re-issued with the profile ID self.invalidate_bedrock_token() + logger.info(f"Profile selected successfully: {profile_id}") return True def get_selected_profile_id(self) -> Optional[str]: diff --git a/lib/streaming_providers/providers/rtlplus/channel_manager.py b/lib/streaming_providers/providers/rtlplus/channel_manager.py index 511cb60..cff1281 100644 --- a/lib/streaming_providers/providers/rtlplus/channel_manager.py +++ b/lib/streaming_providers/providers/rtlplus/channel_manager.py @@ -216,18 +216,17 @@ class RTLPlusChannelManager: ) -> Optional[str]: """ Get the best manifest URL for a channel based on preferences. - - Args: - channel_id: Can be either slug (rtlde_rtl) or seo (rtl) - preferred_quality - preferred_format - preferred_drm_type """ token = self._provider.get_user_bearer_token() if not token: logger.error("User authentication required for linear TV stream, not authenticated") return None + # Ensure profile is selected + if not self.auth.ensure_profile_selected(): + logger.error("Failed to select profile for channel stream") + return None + channel_seo = self._normalize_channel_identifier(channel_id) # Use provider's common layout fetching diff --git a/lib/streaming_providers/providers/rtlplus/constants.py b/lib/streaming_providers/providers/rtlplus/constants.py index cb34a7e..687ebc4 100644 --- a/lib/streaming_providers/providers/rtlplus/constants.py +++ b/lib/streaming_providers/providers/rtlplus/constants.py @@ -313,6 +313,7 @@ class RTLPlusHeaders: auth_token: str, timestamp: int, profile_id: str = None, + user_id: str = None, # Add user_id parameter ) -> dict: """Headers for obtaining Bedrock token.""" headers = dict(RTLPlusHeaders._COMMON_HEADERS) @@ -320,8 +321,9 @@ class RTLPlusHeaders: "authorization": f"Bearer {oauth_token}", "origin": RTLPlusDefaults.BETA_WEBSITE.rstrip("/"), "referer": RTLPlusDefaults.BETA_WEBSITE, - "request-timeout": "10000", "user-agent": user_agent, + "x-auth-device-id": device_id, + "x-auth-device-name": RTLPlusDefaults.DEVICE_NAME, "x-auth-device-player-size-width": str(RTLPlusDefaults.DEVICE_PLAYER_SIZE_WIDTH), "x-auth-device-player-size-height": str(RTLPlusDefaults.DEVICE_PLAYER_SIZE_HEIGHT), "x-auth-token": auth_token, @@ -329,8 +331,17 @@ class RTLPlusHeaders: "x-client-release": client_version, "x-customer-name": "rtlde", }) + + # Add profile_id if available if profile_id: headers["x-auth-profile-id"] = profile_id + logger.debug(f"Adding x-auth-profile-id header: {profile_id}") + + # Add user_id as x-auth-gigya-uid if available + if user_id: + headers["x-auth-gigya-uid"] = user_id + logger.debug(f"Adding x-auth-gigya-uid header: {user_id}") + return headers @staticmethod @@ -372,9 +383,10 @@ class RTLPlusHeaders: "authorization": f"Bearer {oauth_token}", "origin": RTLPlusDefaults.BETA_WEBSITE.rstrip("/"), "referer": RTLPlusDefaults.BETA_WEBSITE, - "request-timeout": "10000", "user-agent": user_agent, "x-bedrock-token": bedrock_token, + "x-auth-device-id": device_id, + "x-auth-device-name": RTLPlusDefaults.DEVICE_NAME, "x-client-release": client_version, "x-customer-name": "rtlde", }) @@ -503,7 +515,7 @@ class RTLPlusConfig: ) def get_bedrock_token_headers(self, oauth_token: str, auth_token: str, timestamp: int, - profile_id: str = None) -> dict: + profile_id: str = None, user_id: str = None) -> dict: """Get headers for Bedrock token request.""" return RTLPlusHeaders.get_bedrock_token_headers( oauth_token=oauth_token, @@ -513,6 +525,7 @@ class RTLPlusConfig: auth_token=auth_token, timestamp=timestamp, profile_id=profile_id, + user_id=user_id, # Pass through ) def get_bedrock_layout_headers(self, oauth_token: str, bedrock_token: str, location: str = None) -> dict: diff --git a/lib/streaming_providers/providers/rtlplus/provider.py b/lib/streaming_providers/providers/rtlplus/provider.py index 0a68487..5b6361a 100644 --- a/lib/streaming_providers/providers/rtlplus/provider.py +++ b/lib/streaming_providers/providers/rtlplus/provider.py @@ -521,10 +521,12 @@ class RTLPlusProvider(StreamingProvider): def get_drm_for_content(self, layout_data: Dict) -> List[DRMConfig]: """ Extract DRM configuration from layout data. - - This method works for any layout type (live channel, event folder, VOD) - that contains video assets with DRM configuration. """ + # Ensure profile is selected for user-authenticated sessions + if self.authenticator.has_user_credentials(): + if not self.authenticator.ensure_profile_selected(): + logger.warning("Failed to select profile for DRM request") + assets = self.extract_video_assets(layout_data) if not assets: