Prepare RTLPlus for new streaming prov

This commit is contained in:
Nirvana
2026-04-30 13:31:09 +02:00
parent c90bc37d06
commit 198e84303d
4 changed files with 65 additions and 18 deletions
@@ -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]:
@@ -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
@@ -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:
@@ -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: