From 2234a83cfe9e7cc8e7556531ca854c9be2858d20 Mon Sep 17 00:00:00 2001 From: Nirvana Date: Sat, 29 Nov 2025 13:39:25 +0100 Subject: [PATCH] Add hrti --- .../providers/hrti/auth.py | 133 ++++++++++++++---- .../providers/hrti/constants.py | 6 +- .../providers/hrti/provider.py | 29 ++-- 3 files changed, 125 insertions(+), 43 deletions(-) diff --git a/lib/streaming_providers/providers/hrti/auth.py b/lib/streaming_providers/providers/hrti/auth.py index 46e288a..cc5800e 100644 --- a/lib/streaming_providers/providers/hrti/auth.py +++ b/lib/streaming_providers/providers/hrti/auth.py @@ -4,6 +4,7 @@ import json import base64 import time +import uuid from typing import Dict, Any, Optional from datetime import datetime, timedelta @@ -39,7 +40,7 @@ class HRTiAuthenticator(BaseAuthenticator): super().__init__( provider_name='hrti', credentials=credentials, - country=None, + country=None, # Changed from 'HR' to None for flat credential structure config_dir=config_dir # NO proxy_config or http_manager passed to parent ) @@ -76,8 +77,53 @@ class HRTiAuthenticator(BaseAuthenticator): return self.config.api_endpoints['grant_access'] def _get_auth_headers(self) -> Dict[str, str]: - """Get headers for authentication requests""" - return self.config.get_base_headers() + """Get headers specifically for authentication endpoint""" + # Get IP address and device ID first + if not self._ip_address: + self._get_ip_address() + + device_id = self.get_device_id() + + # Special headers for authentication endpoint with /signin referer + headers = { + 'User-Agent': self.config.user_agent, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'deviceid': device_id, + 'devicetypeid': self.config.device_reference_id, + 'host': 'hrti.hrt.hr', + 'ipaddress': self._ip_address, + 'operatorreferenceid': self.config.operator_reference_id, + 'origin': self.config.base_website, + 'referer': f'{self.config.base_website}/signin' # Critical: Use /signin for auth + } + + return headers + + def _get_api_headers(self, bearer_token: str = None) -> Dict[str, str]: + """Get headers for regular API endpoints""" + if not self._ip_address: + self._get_ip_address() + + device_id = self.get_device_id() + + headers = { + 'User-Agent': self.config.user_agent, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'deviceid': device_id, + 'devicetypeid': self.config.device_reference_id, + 'host': 'hrti.hrt.hr', + 'ipaddress': self._ip_address, + 'operatorreferenceid': self.config.operator_reference_id, + 'origin': self.config.base_website, + 'referer': self.config.base_website # Regular referer for API calls + } + + if bearer_token: + headers['authorization'] = f'Client {bearer_token}' + + return headers def _build_auth_payload(self) -> Dict[str, Any]: """Build authentication payload from credentials""" @@ -148,7 +194,7 @@ class HRTiAuthenticator(BaseAuthenticator): operation='api' ) response.raise_for_status() - self._ip_address = response.text.strip() + self._ip_address = response.text.strip().strip('"') # Remove quotes if present logger.debug(f"Retrieved IP address: {self._ip_address}") return self._ip_address except Exception as e: @@ -183,28 +229,51 @@ class HRTiAuthenticator(BaseAuthenticator): logger.warning(f"Error loading HRTi environment config: {e}") def _perform_grant_access(self) -> Dict[str, Any]: - """Perform grant access authentication with better logging""" + """Perform grant access authentication with proper headers and debugging""" try: + # Ensure we have IP and device ID + if not self._ip_address: + self._get_ip_address() + + device_id = self.get_device_id() + + logger.debug(f"Performing grant access with username: {self.credentials.username}") + logger.debug(f"Using device ID: {device_id}") + logger.debug(f"Using IP address: {self._ip_address}") + + # Use auth-specific headers with /signin referer headers = self._get_auth_headers() payload = self._build_auth_payload() - # Log which credentials we're using - if hasattr(self.credentials, 'username'): - logger.debug(f"Performing grant access with username: {self.credentials.username}") + # Log the request details (safely) + safe_headers = headers.copy() + if 'deviceid' in safe_headers: + safe_headers['deviceid'] = f"{safe_headers['deviceid'][:8]}..." + logger.debug(f"HRTi Auth Headers: {safe_headers}") + + safe_payload = payload.copy() + if 'Password' in safe_payload: + safe_payload['Password'] = '***' + logger.debug(f"HRTi Auth Payload: {safe_payload}") response = self.http_manager.post( self.auth_endpoint, operation='auth', headers=headers, - data=json.dumps(payload) + data=json.dumps(payload), + retries=2, + retry_delay=1 ) + + logger.debug(f"HRTi Auth Response - Status: {response.status_code}") response.raise_for_status() result = response.json() if 'Result' not in result: raise Exception("No result in grant access response") - logger.debug(f"Grant access successful - has Result: {'Result' in result}") + customer_id = result.get('Result', {}).get('Customer', {}).get('CustomerId', 'unknown') + logger.debug(f"Grant access successful - user: {customer_id}") return result except Exception as e: @@ -218,13 +287,10 @@ class HRTiAuthenticator(BaseAuthenticator): raise e def _register_device(self): - """Register device with HRTi""" + """Register device with HRTi using API headers""" try: - headers = self.config.get_auth_headers( - device_id=self._device_id, - ip_address=self._ip_address, - token=self._current_token.access_token if self._current_token else '' - ) + bearer_token = self._current_token.access_token if self._current_token else '' + headers = self._get_api_headers(bearer_token) payload = { "DeviceSerial": self._device_id, @@ -250,13 +316,10 @@ class HRTiAuthenticator(BaseAuthenticator): logger.warning(f"HRTi device registration failed: {e}") def _get_initial_data(self): - """Get initial content rating and profiles""" + """Get initial content rating and profiles using API headers""" try: - headers = self.config.get_auth_headers( - device_id=self._device_id, - ip_address=self._ip_address, - token=self._current_token.access_token if self._current_token else '' - ) + bearer_token = self._current_token.access_token if self._current_token else '' + headers = self._get_api_headers(bearer_token) # Get content ratings content_response = self.http_manager.post( @@ -282,18 +345,31 @@ class HRTiAuthenticator(BaseAuthenticator): logger.debug(f"Error loading HRTi initial data: {e}") def _initialize_device(self): - """Initialize or load device ID""" + """Initialize or load device ID - ensure it's a proper UUID""" try: self._device_id = self.settings_manager.get_device_id(self.provider_name, self.country) if not self._device_id: - import uuid self._device_id = str(uuid.uuid4()) logger.debug(f"Generated new device ID: {self._device_id}") + + # Ensure it's a valid UUID format + if not self._validate_uuid(self._device_id): + logger.warning(f"Invalid device ID format, generating new one: {self._device_id}") + self._device_id = str(uuid.uuid4()) + except Exception as e: logger.error(f"Error initializing device ID: {e}") - import uuid self._device_id = str(uuid.uuid4()) + @staticmethod + def _validate_uuid(uuid_string): + """Validate UUID format""" + try: + uuid.UUID(uuid_string) + return True + except ValueError: + return False + def get_device_id(self) -> str: """Get device ID""" return self._device_id @@ -308,11 +384,8 @@ class HRTiAuthenticator(BaseAuthenticator): channel_id: str = None, **kwargs) -> Optional[Dict[str, Any]]: """Authorize a playback session""" try: - headers = self.config.get_auth_headers( - device_id=self._device_id, - ip_address=self._ip_address, - token=self._current_token.access_token if self._current_token else '' - ) + bearer_token = self._current_token.access_token if self._current_token else '' + headers = self._get_api_headers(bearer_token) payload = { "ContentType": content_type, diff --git a/lib/streaming_providers/providers/hrti/constants.py b/lib/streaming_providers/providers/hrti/constants.py index 121f574..c595b63 100644 --- a/lib/streaming_providers/providers/hrti/constants.py +++ b/lib/streaming_providers/providers/hrti/constants.py @@ -38,7 +38,7 @@ class HRTiDefaults: LICENSE_URL = 'https://lic.drmtoday.com/license-proxy-widevine/cenc/' # Device information - DEVICE_REFERENCE_ID = '6' + DEVICE_REFERENCE_ID = '6' # String '6' as required by headers OPERATOR_REFERENCE_ID = 'hrt' MERCHANT = 'aviion2' CONNECTION_TYPE = 'LAN/WiFi' @@ -147,10 +147,10 @@ class HRTiConfig: headers['authorization'] = f'Client {token}' headers.update({ + 'devicetypeid': self.device_reference_id, # Added devicetypeid 'operatorreferenceid': self.operator_reference_id, - 'devicetypeid': self.device_reference_id, 'origin': self.base_website, - 'referer': self.base_website + 'referer': self.base_website, }) return headers diff --git a/lib/streaming_providers/providers/hrti/provider.py b/lib/streaming_providers/providers/hrti/provider.py index cd5471d..2bb2b44 100644 --- a/lib/streaming_providers/providers/hrti/provider.py +++ b/lib/streaming_providers/providers/hrti/provider.py @@ -71,17 +71,29 @@ class HRTiProvider(StreamingProvider): def _get_authenticated_headers(self) -> Dict[str, str]: """ - Get headers with HRTi authentication + Get headers with HRTi authentication for API requests """ bearer_token = self.auth.get_bearer_token() device_id = self.auth.get_device_id() ip_address = self.auth.get_ip_address() - return self.hrti_config.get_auth_headers( - device_id=device_id, - ip_address=ip_address, - token=bearer_token - ) + headers = { + 'User-Agent': self.hrti_config.user_agent, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'deviceid': device_id, + 'devicetypeid': self.hrti_config.device_reference_id, + 'host': 'hrti.hrt.hr', + 'ipaddress': ip_address, + 'operatorreferenceid': self.hrti_config.operator_reference_id, + 'origin': self.hrti_config.base_website, + 'referer': self.hrti_config.base_website + } + + if bearer_token: + headers['authorization'] = f'Client {bearer_token}' + + return headers def get_channels(self, **kwargs) -> List[StreamingChannel]: """ @@ -248,8 +260,6 @@ class HRTiProvider(StreamingProvider): logger.error(f"Error getting manifest for channel {channel_id}: {e}") return None - # In provider.py - fix the get_drm method: - def get_drm(self, channel_id: str, **kwargs) -> List[DRMConfig]: """ Get DRM configurations for a channel @@ -280,7 +290,6 @@ class HRTiProvider(StreamingProvider): logger.error(f"Error getting DRM config for channel {channel_id}: {e}") return [] - def get_epg_data(self, channel_id: str, **kwargs) -> Optional[Dict]: """ Get EPG data for a channel @@ -298,7 +307,7 @@ class HRTiProvider(StreamingProvider): "EndTime": f"/Date({end_time})/" } - response = self.http_manager.post( # FIXED: Use http_manager + response = self.http_manager.post( self.hrti_config.api_endpoints['programme'], operation='api', headers=headers,