mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-19 15:42:13 +02:00
Prepare RTLPlus for new streaming prov
This commit is contained in:
@@ -3,7 +3,7 @@ import base64
|
||||
import json
|
||||
import time
|
||||
import hashlib
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, Optional, List
|
||||
|
||||
from ...base.auth.base_auth import BaseAuthToken, TokenAuthLevel
|
||||
from ...base.auth.base_oauth2_auth import BaseOAuth2Authenticator
|
||||
@@ -35,6 +35,7 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator):
|
||||
self._bedrock_token: Optional[str] = None
|
||||
self._bedrock_token_expiry: float = 0
|
||||
self._cached_user_id: Optional[str] = None
|
||||
self._selected_profile_id: Optional[str] = None
|
||||
|
||||
if proxy_config is None:
|
||||
from ...base.network import ProxyConfigManager
|
||||
@@ -286,7 +287,7 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator):
|
||||
return token
|
||||
|
||||
def get_bedrock_token(self, force_refresh: bool = False) -> str:
|
||||
"""Get or refresh Bedrock token."""
|
||||
"""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:
|
||||
return self._bedrock_token
|
||||
|
||||
@@ -296,8 +297,11 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator):
|
||||
timestamp = self._get_server_timestamp()
|
||||
auth_token = self._generate_auth_token(self.config.device_id, timestamp)
|
||||
|
||||
# Use headers from config
|
||||
headers = self.config.get_bedrock_token_headers(oauth_token, auth_token, timestamp)
|
||||
# Get profile ID if available
|
||||
profile_id = self.get_selected_profile_id()
|
||||
|
||||
# Get headers with profile_id
|
||||
headers = self.config.get_bedrock_token_headers(oauth_token, auth_token, timestamp, profile_id)
|
||||
|
||||
response = self.http_manager.get(
|
||||
self.config.bedrock_auth_url,
|
||||
@@ -319,6 +323,7 @@ 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')}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not decode Bedrock token expiry: {e}")
|
||||
|
||||
@@ -460,4 +465,82 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator):
|
||||
})
|
||||
if self.has_user_credentials() and hasattr(self.credentials, "username"):
|
||||
status["username"] = self.credentials.username
|
||||
return status
|
||||
return status
|
||||
|
||||
def get_user_profiles(self, user_id: str = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Fetch available profiles for a user.
|
||||
"""
|
||||
if not user_id:
|
||||
user_id = self.get_user_id_from_token()
|
||||
if not user_id:
|
||||
raise ValueError("No user ID available")
|
||||
|
||||
oauth_token = self.get_bearer_token()
|
||||
bedrock_token = self.get_bedrock_token()
|
||||
|
||||
url = self.config.get_profiles_url(user_id)
|
||||
headers = self.config.get_profiles_headers(oauth_token, bedrock_token)
|
||||
|
||||
response = self.http_manager.get(url, headers=headers, operation="api")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def select_profile(self, profile_id: str = None) -> bool:
|
||||
"""
|
||||
Select a profile to use for this session.
|
||||
If no profile_id provided, fetches profiles and selects the first adult profile.
|
||||
"""
|
||||
if not profile_id:
|
||||
user_id = self.get_user_id_from_token()
|
||||
if not user_id:
|
||||
logger.error("Cannot select profile: No user ID available")
|
||||
return False
|
||||
|
||||
try:
|
||||
profiles = self.get_user_profiles(user_id)
|
||||
# Select first adult 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})")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch/select profile: {e}")
|
||||
return False
|
||||
|
||||
# Store the selected profile ID
|
||||
self._selected_profile_id = profile_id
|
||||
|
||||
# Save to credentials for persistence
|
||||
if hasattr(self.credentials, "profile_id"):
|
||||
self.credentials.profile_id = profile_id
|
||||
self.save_credentials(self.credentials)
|
||||
|
||||
# Invalidate Bedrock token so it gets re-issued with the profile ID
|
||||
self.invalidate_bedrock_token()
|
||||
|
||||
return True
|
||||
|
||||
def get_selected_profile_id(self) -> Optional[str]:
|
||||
"""Get the currently selected profile ID."""
|
||||
if hasattr(self, "_selected_profile_id") and self._selected_profile_id:
|
||||
return self._selected_profile_id
|
||||
|
||||
# Try to load from stored credentials
|
||||
if hasattr(self.credentials, "profile_id") and self.credentials.profile_id:
|
||||
return self.credentials.profile_id
|
||||
|
||||
return None
|
||||
|
||||
def ensure_profile_selected(self) -> bool:
|
||||
"""Ensure a profile is selected, auto-selecting if needed."""
|
||||
if self.get_selected_profile_id():
|
||||
return True
|
||||
|
||||
if self.has_user_credentials():
|
||||
return self.select_profile()
|
||||
|
||||
return False
|
||||
@@ -56,6 +56,8 @@ class RTLPlusDefaults:
|
||||
BEDROCK_DRM_UPFRONT_BASE = "https://drm.rtlde.bedrock.tech/v1/customers/rtlde/platforms/m6group_web/services/rtlplus_root/users/{uid}/live"
|
||||
BEDROCK_HEARTBEAT_URL = "https://heartbeat-v2.rtlde.bedrock.tech/v2/platforms/m6group_web/notify/session_live"
|
||||
TIME_ENDPOINT = "https://time.rtlde.bedrock.tech/"
|
||||
USERS_ENDPOINT = "https://users.rtlde.bedrock.tech"
|
||||
PROFILES_PATH = "/v2/platforms/m6group_web/users/{user_id}/profiles"
|
||||
|
||||
# DRM license server
|
||||
DRMTODAY_LICENSE_URL = "https://lic.drmtoday.com/license-proxy-widevine/cenc/"
|
||||
@@ -295,6 +297,7 @@ class RTLPlusHeaders:
|
||||
user_agent: str,
|
||||
auth_token: str,
|
||||
timestamp: int,
|
||||
profile_id: str = None,
|
||||
) -> dict:
|
||||
"""Headers for obtaining Bedrock token."""
|
||||
headers = dict(RTLPlusHeaders._COMMON_HEADERS)
|
||||
@@ -312,6 +315,8 @@ class RTLPlusHeaders:
|
||||
"x-client-release": client_version,
|
||||
"x-customer-name": "rtlde",
|
||||
})
|
||||
if profile_id:
|
||||
headers["x-auth-profile-id"] = profile_id
|
||||
return headers
|
||||
|
||||
@staticmethod
|
||||
@@ -400,6 +405,26 @@ class RTLPlusHeaders:
|
||||
"X-Device-Name": RTLPlusDefaults.PLAYREADY_DEVICE_NAME,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_profiles_headers(
|
||||
oauth_token: str,
|
||||
bedrock_token: str,
|
||||
client_version: str,
|
||||
user_agent: str,
|
||||
) -> dict:
|
||||
"""Headers for fetching user profiles."""
|
||||
headers = dict(RTLPlusHeaders._COMMON_HEADERS)
|
||||
headers.update({
|
||||
"authorization": f"Bearer {oauth_token}",
|
||||
"origin": RTLPlusDefaults.BETA_WEBSITE.rstrip("/"),
|
||||
"referer": RTLPlusDefaults.BETA_WEBSITE,
|
||||
"user-agent": user_agent,
|
||||
"x-bedrock-token": bedrock_token,
|
||||
"x-client-release": client_version,
|
||||
"x-customer-name": "rtlde",
|
||||
})
|
||||
return headers
|
||||
|
||||
|
||||
class RTLPlusConfig:
|
||||
"""Configuration class that can be customized per instance"""
|
||||
@@ -432,6 +457,8 @@ class RTLPlusConfig:
|
||||
self.bedrock_drm_upfront_base = config.get("bedrock_drm_upfront_base", RTLPlusDefaults.BEDROCK_DRM_UPFRONT_BASE)
|
||||
self.bedrock_heartbeat_url = config.get("bedrock_heartbeat_url", RTLPlusDefaults.BEDROCK_HEARTBEAT_URL)
|
||||
self.time_endpoint = config.get("time_endpoint", RTLPlusDefaults.TIME_ENDPOINT)
|
||||
self.users_endpoint = config.get("users_endpoint", RTLPlusDefaults.USERS_ENDPOINT)
|
||||
self.profiles_path = config.get("profiles_path", RTLPlusDefaults.PROFILES_PATH)
|
||||
|
||||
# DRM license server
|
||||
self.drmtoday_license_url = config.get("drmtoday_license_url", RTLPlusDefaults.DRMTODAY_LICENSE_URL)
|
||||
@@ -448,6 +475,10 @@ class RTLPlusConfig:
|
||||
"""Get Bedrock layout URL for a specific channel"""
|
||||
return f"{self.bedrock_layout_base}/live/{channel_seo}/layout"
|
||||
|
||||
def get_profiles_url(self, user_id: str) -> str:
|
||||
"""Get profiles URL for a specific user"""
|
||||
return f"{self.users_endpoint}{self.profiles_path.format(user_id=user_id)}"
|
||||
|
||||
def get_epg_grid_url(self) -> str:
|
||||
"""Get EPG grid URL for channel listing"""
|
||||
return f"{self.bedrock_layout_base}/epg_grid"
|
||||
@@ -479,7 +510,17 @@ class RTLPlusConfig:
|
||||
user_agent=self.user_agent,
|
||||
)
|
||||
|
||||
def get_bedrock_token_headers(self, oauth_token: str, auth_token: str, timestamp: int) -> dict:
|
||||
def get_profiles_headers(self, oauth_token: str, bedrock_token: str) -> dict:
|
||||
"""Get headers for profiles request."""
|
||||
return RTLPlusHeaders.get_profiles_headers(
|
||||
oauth_token=oauth_token,
|
||||
bedrock_token=bedrock_token,
|
||||
client_version=self.client_version,
|
||||
user_agent=self.user_agent,
|
||||
)
|
||||
|
||||
def get_bedrock_token_headers(self, oauth_token: str, auth_token: str, timestamp: int,
|
||||
profile_id: str = None) -> dict:
|
||||
"""Get headers for Bedrock token request."""
|
||||
return RTLPlusHeaders.get_bedrock_token_headers(
|
||||
oauth_token=oauth_token,
|
||||
@@ -488,6 +529,7 @@ class RTLPlusConfig:
|
||||
user_agent=self.user_agent,
|
||||
auth_token=auth_token,
|
||||
timestamp=timestamp,
|
||||
profile_id=profile_id,
|
||||
)
|
||||
|
||||
def get_bedrock_layout_headers(self, oauth_token: str, bedrock_token: str, location: str = None) -> dict:
|
||||
|
||||
@@ -14,13 +14,14 @@ class RTLPlusUserCredentials(UserPasswordCredentials):
|
||||
RTL+ specific username/password credentials
|
||||
"""
|
||||
|
||||
def __init__(self, username: str, password: str, client_id: Optional[str] = None):
|
||||
def __init__(self, username: str, password: str, client_id: Optional[str] = None, profile_id: Optional[str] = None):
|
||||
super().__init__(
|
||||
username=username,
|
||||
password=password,
|
||||
client_id=client_id or RTLPlusDefaults.CLIENT_ID,
|
||||
grant_type="password",
|
||||
)
|
||||
self.profile_id = profile_id # Add this attribute
|
||||
|
||||
def to_auth_payload(self) -> Dict[str, Any]:
|
||||
"""Convert to authentication payload for RTL+"""
|
||||
|
||||
@@ -61,6 +61,11 @@ class RTLPlusProvider(StreamingProvider):
|
||||
try:
|
||||
self.bearer_token = self.authenticator.get_bearer_token()
|
||||
logger.debug("RTL+ authentication successful during initialization")
|
||||
|
||||
# Ensure a profile is selected for user-authenticated sessions
|
||||
if self.authenticator.has_user_credentials():
|
||||
self.authenticator.ensure_profile_selected()
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"RTL+ could not authenticate during initialization: {e}")
|
||||
self.bearer_token = None
|
||||
@@ -363,13 +368,20 @@ class RTLPlusProvider(StreamingProvider):
|
||||
def get_user_bearer_token(self) -> Optional[str]:
|
||||
"""Get a user-authenticated bearer token, upgrading if necessary. Returns None if impossible."""
|
||||
from ...base.auth.base_auth import TokenAuthLevel
|
||||
|
||||
current_level = self.authenticator.get_current_token_level()
|
||||
logger.debug(
|
||||
f"get_user_bearer_token: current_level={current_level}, has_user_credentials={self.authenticator.has_user_credentials()}")
|
||||
|
||||
if current_level == TokenAuthLevel.USER_AUTHENTICATED:
|
||||
# Ensure profile is selected
|
||||
self.authenticator.ensure_profile_selected()
|
||||
return self.authenticator.get_bearer_token()
|
||||
|
||||
if self.authenticator.has_user_credentials():
|
||||
token = self.authenticator.get_bearer_token(force_upgrade=True)
|
||||
if self.authenticator.get_current_token_level() == TokenAuthLevel.USER_AUTHENTICATED:
|
||||
self.authenticator.ensure_profile_selected()
|
||||
return token
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user