mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-30 13:02:19 +02:00
Fix get_manifest
This commit is contained in:
@@ -327,7 +327,8 @@ class Magenta2Authenticator(BaseOAuth2Authenticator):
|
||||
device_model: Optional[str] = None,
|
||||
sam3_client_id: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
device_id: Optional[str] = None):
|
||||
device_id: Optional[str] = None,
|
||||
provider_config: Optional[Any] = None):
|
||||
"""
|
||||
Enhanced authenticator with complete authentication flow support
|
||||
"""
|
||||
@@ -355,6 +356,7 @@ class Magenta2Authenticator(BaseOAuth2Authenticator):
|
||||
# Session and device management
|
||||
self._session_id = session_id or str(uuid.uuid4())
|
||||
self._device_id = device_id or str(uuid.uuid4())
|
||||
self.provider_config = provider_config
|
||||
|
||||
# NEW: Store MPX account info for persona token composition
|
||||
self._mpx_account_pid: Optional[str] = None
|
||||
@@ -512,7 +514,8 @@ class Magenta2Authenticator(BaseOAuth2Authenticator):
|
||||
sam3_client=self._sam3_client,
|
||||
taa_client=self._taa_client,
|
||||
provider_name=self.provider_name,
|
||||
country=self.country
|
||||
country=self.country,
|
||||
provider_config=self.provider_config
|
||||
)
|
||||
logger.debug("TokenFlowManager initialized")
|
||||
|
||||
|
||||
@@ -138,7 +138,8 @@ class Magenta2Provider(StreamingProvider):
|
||||
device_model=f"{platform.upper()}_FTV", # Use fallback initially
|
||||
sam3_client_id=fallback_client_id, # Use fallback initially
|
||||
session_id=self.session_id,
|
||||
device_id=self.device_id
|
||||
device_id=self.device_id,
|
||||
provider_config=None
|
||||
)
|
||||
|
||||
# 🚨 NOW PERFORM CONFIGURATION DISCOVERY (authenticator exists)
|
||||
@@ -151,6 +152,8 @@ class Magenta2Provider(StreamingProvider):
|
||||
# 🚨 UPDATE AUTHENTICATOR WITH DISCOVERED CONFIG
|
||||
if self.provider_config:
|
||||
# Update authenticator with discovered client_id and models
|
||||
self.authenticator.provider_config = self.provider_config # ✅ Store the config
|
||||
logger.info("✓ ProviderConfig stored in authenticator")
|
||||
if self.provider_config.bootstrap.sam3_client_id:
|
||||
# Use public method if available, otherwise update directly
|
||||
if hasattr(self.authenticator, 'update_sam3_client_id'):
|
||||
@@ -355,10 +358,9 @@ class Magenta2Provider(StreamingProvider):
|
||||
'x-dt-call-id': self._generate_call_id()
|
||||
}
|
||||
|
||||
def _get_api_headers(self, use_persona_token: bool = False,
|
||||
require_auth: bool = False) -> Dict[str, str]:
|
||||
def _get_api_headers(self, require_auth: bool = False) -> Dict[str, str]:
|
||||
"""
|
||||
FIXED: Get headers for API requests with proper token usage
|
||||
Get headers for API requests with persona_token Basic auth
|
||||
"""
|
||||
headers = {
|
||||
'User-Agent': self.platform_config['user_agent'],
|
||||
@@ -367,19 +369,20 @@ class Magenta2Provider(StreamingProvider):
|
||||
}
|
||||
|
||||
# Lazy authentication - only authenticate if required and not already done
|
||||
if require_auth and not self.bearer_token:
|
||||
if require_auth and not self.persona_token:
|
||||
try:
|
||||
self._ensure_authenticated()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not authenticate for API headers: {e}")
|
||||
|
||||
# CRITICAL FIX: Use COMPOSED persona token for API calls when requested
|
||||
if use_persona_token and self.persona_token:
|
||||
# Use persona token for Basic auth when available
|
||||
if require_auth and self.persona_token:
|
||||
headers['Authorization'] = f'Basic {self.persona_token}'
|
||||
logger.debug("Using composed persona token (Basic auth)")
|
||||
logger.debug("Using persona token (Basic auth) for API call")
|
||||
elif self.bearer_token:
|
||||
# Fallback to Bearer token for backward compatibility
|
||||
headers['Authorization'] = f'Bearer {self.bearer_token}'
|
||||
logger.debug("Using TAA bearer token")
|
||||
logger.debug("Using TAA bearer token (fallback)")
|
||||
|
||||
return headers
|
||||
|
||||
@@ -475,18 +478,15 @@ class Magenta2Provider(StreamingProvider):
|
||||
logger.error(f"Device registration failed: {e}")
|
||||
return False
|
||||
|
||||
def get_yo_digital_token(self, force_refresh: bool = False) -> str:
|
||||
"""Get yo_digital access token following complete hierarchy"""
|
||||
def get_persona_token(self, force_refresh: bool = False) -> str:
|
||||
"""Get persona token for ALL API calls"""
|
||||
persona_token = self.authenticator.get_persona_token()
|
||||
if persona_token:
|
||||
logger.info("✓ Got persona token")
|
||||
return persona_token
|
||||
|
||||
# Try TokenFlowManager via public API
|
||||
yo_digital_token = self.authenticator.get_yo_digital_token(force_refresh)
|
||||
|
||||
if yo_digital_token:
|
||||
logger.info("✓ Got yo_digital token")
|
||||
return yo_digital_token
|
||||
|
||||
# Fallback to existing authentication
|
||||
logger.info("Using legacy authentication flow")
|
||||
# Fallback to legacy authentication if persona token fails
|
||||
logger.info("Using legacy authentication flow as fallback")
|
||||
return self.authenticate(force_refresh=force_refresh)
|
||||
|
||||
def authenticate(self, **kwargs) -> str:
|
||||
@@ -545,32 +545,18 @@ class Magenta2Provider(StreamingProvider):
|
||||
return self.bearer_token
|
||||
|
||||
def _ensure_authenticated(self) -> None:
|
||||
"""Ensure we have valid authentication token"""
|
||||
|
||||
# Try TokenFlowManager first (gets yo_digital)
|
||||
if hasattr(self.authenticator, 'get_yo_digital_token'):
|
||||
yo_digital_token = self.authenticator.get_yo_digital_token()
|
||||
if yo_digital_token:
|
||||
self.bearer_token = yo_digital_token
|
||||
logger.info("✓ Lazy authentication via yo_digital")
|
||||
"""Ensure we have valid persona token"""
|
||||
if hasattr(self.authenticator, 'get_persona_token'):
|
||||
persona_token = self.authenticator.get_persona_token()
|
||||
if persona_token:
|
||||
self.persona_token = persona_token
|
||||
logger.info("✓ Lazy authentication via persona token")
|
||||
return
|
||||
|
||||
# Fallback to old flow
|
||||
# Fallback to old flow (for backward compatibility)
|
||||
logger.info("Using legacy authentication for lazy auth")
|
||||
self.bearer_token = self.authenticator.get_bearer_token()
|
||||
|
||||
# CRITICAL: Get composed persona token
|
||||
self.persona_token = self.authenticator.get_persona_token()
|
||||
|
||||
if not self.persona_token:
|
||||
# Debug information
|
||||
auth_state = self.authenticator.debug_authentication_state()
|
||||
logger.error(f"Authentication state: {json.dumps(auth_state, indent=2)}")
|
||||
raise Exception(
|
||||
"Lazy authentication succeeded but persona token composition failed. "
|
||||
"Check if TAA JWT contains dc_cts_persona_token and account_uri claims."
|
||||
)
|
||||
|
||||
logger.info("✓ Lazy authentication complete with persona token")
|
||||
# Note: Legacy flow might not set persona_token
|
||||
|
||||
def get_dynamic_manifest_params(self, channel: StreamingChannel, **kwargs) -> Optional[str]:
|
||||
return None
|
||||
@@ -806,13 +792,13 @@ class Magenta2Provider(StreamingProvider):
|
||||
|
||||
def get_entitlement_token(self, content_id: str, content_type: str = CONTENT_TYPE_LIVE) -> str:
|
||||
"""
|
||||
FIXED: Get entitlement token using COMPOSED persona token
|
||||
Get entitlement token using persona_token Basic auth
|
||||
"""
|
||||
# Ensure we're authenticated with persona token
|
||||
self._ensure_authenticated()
|
||||
|
||||
# CRITICAL: Use persona token (Basic auth) for entitlement
|
||||
headers = self._get_api_headers(use_persona_token=True, require_auth=True)
|
||||
# Use persona token (Basic auth) for entitlement
|
||||
headers = self._get_api_headers(require_auth=True)
|
||||
|
||||
payload = {
|
||||
"content_id": content_id,
|
||||
@@ -1064,7 +1050,7 @@ class Magenta2Provider(StreamingProvider):
|
||||
# Get required components for SMIL request
|
||||
selector_service = self.endpoint_manager.get_endpoint('mpx_selector')
|
||||
if not selector_service:
|
||||
logger.error(f"No mpx_basic_url_selector_service endpoint found for channel {channel_id}")
|
||||
logger.error(f"No mpx_selector endpoint found for channel {channel_id}")
|
||||
return None
|
||||
|
||||
account_pid = self.provider_config.manifest.mpx.account_pid
|
||||
@@ -1073,20 +1059,18 @@ class Magenta2Provider(StreamingProvider):
|
||||
return None
|
||||
|
||||
# Get composed persona token for Basic auth
|
||||
persona_token = self.authenticator.get_persona_token()
|
||||
persona_token = self.get_persona_token()
|
||||
if not persona_token:
|
||||
logger.error(f"No composed persona token available for channel {channel_id}")
|
||||
logger.error(f"No persona token available for channel {channel_id}")
|
||||
return None
|
||||
|
||||
# Fixed client ID (can be parameterized later if needed)
|
||||
# Fixed client ID
|
||||
client_id = "a8198f31-b406-4177-8dee-f6216c356c75"
|
||||
|
||||
# Construct SMIL URL
|
||||
smil_url = f"{selector_service}{account_pid}/media/{channel_id}?format=smil&formats=MPEG-DASH&tracking=true&clientId={client_id}"
|
||||
|
||||
logger.info(f"Requesting SMIL manifest for channel {channel_id}")
|
||||
logger.debug(f"SMIL URL: {smil_url}")
|
||||
logger.debug(f"Using account PID: {account_pid}, client ID: {client_id}")
|
||||
|
||||
# Make SMIL request with Basic auth
|
||||
headers = {
|
||||
@@ -1103,14 +1087,13 @@ class Magenta2Provider(StreamingProvider):
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
logger.info(f"✓ SMIL response received for channel {channel_id} (status: {response.status_code})")
|
||||
logger.info(f"✓ SMIL response received for channel {channel_id}")
|
||||
|
||||
# Parse SMIL to extract MPD URL
|
||||
mpd_url = self._parse_smil_for_mpd(response.text, channel_id)
|
||||
|
||||
if mpd_url:
|
||||
logger.info(f"✓ MPD manifest URL extracted for channel {channel_id}")
|
||||
logger.debug(f"MPD URL: {mpd_url}")
|
||||
else:
|
||||
logger.warning(f"✗ No MPD URL found in SMIL response for channel {channel_id}")
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ yo_digital → taa → tvhubs → line_auth/remote_login
|
||||
"""
|
||||
|
||||
import time
|
||||
import base64
|
||||
import json
|
||||
from typing import Optional, Dict, Any
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -26,6 +28,12 @@ class TokenFlowResult:
|
||||
error: Optional[str] = None
|
||||
flow_path: Optional[str] = None # For debugging which path was taken
|
||||
|
||||
@dataclass
|
||||
class PersonaResult:
|
||||
success: bool
|
||||
persona_token: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class TokenFlowManager:
|
||||
"""
|
||||
@@ -51,7 +59,8 @@ class TokenFlowManager:
|
||||
sam3_client: 'Sam3Client',
|
||||
taa_client: 'TaaClient',
|
||||
provider_name: str,
|
||||
country: Optional[str] = None):
|
||||
country: Optional[str] = None,
|
||||
provider_config: Optional[Any] = None):
|
||||
"""
|
||||
Initialize TokenFlowManager
|
||||
|
||||
@@ -67,10 +76,125 @@ class TokenFlowManager:
|
||||
self.taa_client = taa_client
|
||||
self.provider_name = provider_name
|
||||
self.country = country
|
||||
self.provider_config = provider_config
|
||||
|
||||
logger.debug(f"TokenFlowManager initialized for {provider_name}" +
|
||||
(f" ({country})" if country else ""))
|
||||
|
||||
@staticmethod
|
||||
def _extract_jwt_claims(jwt_token: str) -> Dict[str, Any]:
|
||||
"""Extract claims from JWT token"""
|
||||
try:
|
||||
parts = jwt_token.split('.')
|
||||
if len(parts) != 3:
|
||||
logger.warning("Invalid JWT format")
|
||||
return {}
|
||||
|
||||
# Decode payload
|
||||
payload_b64 = parts[1]
|
||||
padding = len(payload_b64) % 4
|
||||
if padding:
|
||||
payload_b64 += '=' * (4 - padding)
|
||||
|
||||
payload_json = base64.b64decode(payload_b64).decode('utf-8')
|
||||
claims = json.loads(payload_json)
|
||||
|
||||
logger.debug(f"JWT claims extracted: {list(claims.keys())}")
|
||||
return claims
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to extract JWT claims: {e}")
|
||||
return {}
|
||||
|
||||
def _compose_persona_token(self, access_token: str) -> Optional[str]:
|
||||
"""
|
||||
Compose persona token from access_token JWT claims
|
||||
"""
|
||||
try:
|
||||
# Import here to avoid circular imports
|
||||
from .config_models import ProviderConfig
|
||||
|
||||
# Extract dc_cts_persona_token from JWT
|
||||
claims = self._extract_jwt_claims(access_token)
|
||||
dc_cts_persona_token = claims.get('dc_cts_persona_token')
|
||||
|
||||
if not dc_cts_persona_token:
|
||||
logger.error("No dc_cts_persona_token found in JWT claims")
|
||||
return None
|
||||
|
||||
# Use the discovered account URI from ProviderConfig
|
||||
if not hasattr(self, 'provider_config') or not self.provider_config:
|
||||
logger.error("No provider_config available - cannot compose persona token")
|
||||
return None
|
||||
|
||||
# Type check to ensure it's a ProviderConfig
|
||||
if not isinstance(self.provider_config, ProviderConfig):
|
||||
logger.error(f"provider_config is not ProviderConfig: {type(self.provider_config)}")
|
||||
return None
|
||||
|
||||
# CORRECTED: Use get_mpx_account_uri() method from ProviderConfig
|
||||
account_uri = self.provider_config.get_mpx_account_uri()
|
||||
if not account_uri:
|
||||
logger.error("No account URI available from provider_config")
|
||||
return None
|
||||
|
||||
logger.debug(f"Using discovered account URI: {account_uri}")
|
||||
|
||||
# Compose: account_uri + ":" + dc_cts_persona_token
|
||||
raw_token = f"{account_uri}:{dc_cts_persona_token}"
|
||||
|
||||
# Base64 encode
|
||||
persona_token = base64.b64encode(raw_token.encode('utf-8')).decode('utf-8')
|
||||
|
||||
logger.info("✓ Persona token composed successfully")
|
||||
logger.debug(f"Account URI: {account_uri}")
|
||||
logger.debug(f"Persona token preview: {persona_token[:50]}...")
|
||||
|
||||
return persona_token
|
||||
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import ProviderConfig: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to compose persona token: {e}")
|
||||
return None
|
||||
|
||||
def get_persona_token(self, force_refresh: bool = False) -> PersonaResult:
|
||||
"""
|
||||
Get persona token - compose from successful yo_digital token
|
||||
"""
|
||||
# Get access_token using existing method
|
||||
token_result = self.get_yo_digital_token(force_refresh)
|
||||
|
||||
# If it failed, return the failure
|
||||
if not token_result.success:
|
||||
return PersonaResult(
|
||||
success=False,
|
||||
error=token_result.error,
|
||||
)
|
||||
|
||||
# We have success - extract access_token and compose persona_token
|
||||
access_token = token_result.access_token
|
||||
|
||||
if not access_token:
|
||||
return PersonaResult(
|
||||
success=False,
|
||||
error="Failed to get yo_digital access token"
|
||||
)
|
||||
|
||||
# Compose persona_token
|
||||
persona_token = self._compose_persona_token(access_token)
|
||||
if not persona_token:
|
||||
return PersonaResult(
|
||||
success=False,
|
||||
error="Failed to compose persona token"
|
||||
)
|
||||
|
||||
return PersonaResult(
|
||||
success=True,
|
||||
persona_token=persona_token
|
||||
)
|
||||
|
||||
def get_yo_digital_token(self, force_refresh: bool = False) -> TokenFlowResult:
|
||||
"""
|
||||
Get yo_digital access token following the complete hierarchy
|
||||
|
||||
Reference in New Issue
Block a user