# streaming_providers/providers/rtlplus/models.py from dataclasses import dataclass from typing import Any, Dict, Optional from ...base.auth.base_auth import BaseAuthToken from ...base.auth.credentials import ClientCredentials, UserPasswordCredentials from .constants import RTLPlusDefaults @dataclass class RTLPlusUserCredentials(UserPasswordCredentials): """ RTL+ specific username/password credentials """ def __init__(self, username: str, password: str, client_id: Optional[str] = None): super().__init__( username=username, password=password, client_id=client_id or RTLPlusDefaults.CLIENT_ID, grant_type="password", ) def to_auth_payload(self) -> Dict[str, Any]: """Convert to authentication payload for RTL+""" payload = { "grant_type": self.grant_type, "username": self.username, "password": self.password, } # RTL+ might need client_id for user auth - adjust based on API requirements if self.client_id: payload["client_id"] = self.client_id return payload @dataclass class RTLPlusClientCredentials(ClientCredentials): """ RTL+ specific client credentials (anonymous access) """ def __init__(self, client_id: Optional[str] = None, client_secret: Optional[str] = None): super().__init__( client_id=client_id or RTLPlusDefaults.ANONYMOUS_CLIENT_ID, client_secret=client_secret or RTLPlusDefaults.ANONYMOUS_CLIENT_SECRET, grant_type="client_credentials", ) class RTLPlusAuthToken(BaseAuthToken): """ RTL+ specific authentication token """ def __init__( self, access_token: str, token_type: str, expires_in: int, issued_at: float, refresh_token: Optional[str] = None, refresh_expires_in: int = 0, not_before_policy: Optional[int] = None, scope: str = "", ): # Initialize parent class with refresh_expires_in super().__init__( access_token=access_token, token_type=token_type, expires_in=expires_in, issued_at=issued_at, refresh_token=refresh_token, refresh_expires_in=refresh_expires_in, ) # RTL+ specific fields self.refresh_expires_in = refresh_expires_in self.not_before_policy = not_before_policy self.scope = scope def to_dict(self) -> Dict[str, Any]: """Convert token to dictionary""" return { "access_token": self.access_token, "token_type": self.token_type, "expires_in": self.expires_in, "issued_at": self.issued_at, "refresh_token": self.refresh_token, "refresh_expires_in": self.refresh_expires_in, "not_before_policy": self.not_before_policy, "scope": self.scope, } @classmethod def from_dict(cls, data: Dict[str, Any], issued_at: float) -> "RTLPlusAuthToken": """Create token from dictionary response""" return cls( access_token=data["access_token"], token_type=data.get("token_type", "Bearer"), expires_in=data.get("expires_in", 0), issued_at=issued_at, refresh_token=data.get("refresh_token"), refresh_expires_in=data.get("refresh_expires_in", 0), not_before_policy=data.get("not-before-policy"), scope=data.get("scope", ""), ) def is_valid(self) -> bool: """Check if token is still valid""" import time if not self.access_token: return False current_time = time.time() # Add small buffer (30 seconds) to account for network delays buffer_time = 30 return current_time < (self.issued_at + self.expires_in - buffer_time) # Add this method to the RTLPlusAuthToken class in models.py def is_anonymous_token(self) -> bool: """Check if this token was obtained via anonymous authentication""" if not self.access_token: return True try: # Simple check without full JWT decoding - look for anonymous client ID pattern return "anonymous-user" in self.access_token except: return False @dataclass class RTLPlusChannel: """ RTL+ channel information """ id: str name: str slug: str logo_url: Optional[str] = None description: Optional[str] = None is_live: bool = True channel_type: str = "BROADCAST" sort_order: int = 0 def __post_init__(self): """Validate channel data after initialization""" if not self.id or not self.name: raise ValueError("Channel must have both id and name") @classmethod def from_api_response(cls, data: Dict[str, Any]) -> "RTLPlusChannel": """Create channel from API response data""" return cls( id=data["id"], name=data["name"], slug=data.get("slug", ""), logo_url=data.get("logoUrl"), description=data.get("description"), is_live=data.get("isLive", True), channel_type=data.get("channelType", "BROADCAST"), sort_order=data.get("sortOrder", 0), ) def to_dict(self) -> Dict[str, Any]: """Convert channel to dictionary""" return { "id": self.id, "name": self.name, "slug": self.slug, "logo_url": self.logo_url, "description": self.description, "is_live": self.is_live, "channel_type": self.channel_type, "sort_order": self.sort_order, } @dataclass class RTLPlusStreamInfo: """ RTL+ stream information """ manifest_url: str channel_id: str drm_license_url: Optional[str] = None drm_key_id: Optional[str] = None stream_type: str = "HLS" quality: str = "auto" def __post_init__(self): """Validate stream info after initialization""" if not self.manifest_url or not self.channel_id: raise ValueError("Stream must have both manifest_url and channel_id") @classmethod def from_manifest_response(cls, data: Dict[str, Any], channel_id: str) -> "RTLPlusStreamInfo": """Create stream info from manifest API response""" return cls( manifest_url=data["url"], channel_id=channel_id, drm_license_url=data.get("drmLicenseUrl"), drm_key_id=data.get("drmKeyId"), stream_type=data.get("type", "HLS"), quality=data.get("quality", "auto"), ) def to_dict(self) -> Dict[str, Any]: """Convert stream info to dictionary""" return { "manifest_url": self.manifest_url, "channel_id": self.channel_id, "drm_license_url": self.drm_license_url, "drm_key_id": self.drm_key_id, "stream_type": self.stream_type, "quality": self.quality, } def has_drm(self) -> bool: """Check if stream has DRM protection""" return bool(self.drm_license_url and self.drm_key_id)