From b917512551913d1bc2e3d6c350a0a9b31ff556ba Mon Sep 17 00:00:00 2001 From: Nirvana Date: Wed, 25 Mar 2026 15:22:43 +0100 Subject: [PATCH] Add movetv --- .../providers/movetv/auth.py | 71 ++++++++++++++----- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/lib/streaming_providers/providers/movetv/auth.py b/lib/streaming_providers/providers/movetv/auth.py index b01eaa7..05b5e35 100644 --- a/lib/streaming_providers/providers/movetv/auth.py +++ b/lib/streaming_providers/providers/movetv/auth.py @@ -1,5 +1,6 @@ # streaming_providers/providers/movetv/auth.py import time +import uuid from dataclasses import dataclass from typing import Any, Dict, Optional @@ -24,6 +25,7 @@ class MoveTVAuthToken(BaseAuthToken): - customer_profile_id active profile id (needed in manifest requests) - dedicated_server CDN base URL (e.g. https://edge-mts-si-2.mts-si.tv) - device_id server-assigned device identifier + - uid client-generated unique identifier (UUID) - widevine_url Widevine license server URL (from drm_server block) - playready_url PlayReady license server URL (from drm_server block) """ @@ -33,6 +35,7 @@ class MoveTVAuthToken(BaseAuthToken): customer_profile_id: int = 0 dedicated_server: str = "" device_id: int = 0 + uid: str = "" widevine_url: str = "" playready_url: str = "" @@ -51,6 +54,7 @@ class MoveTVAuthToken(BaseAuthToken): "customer_profile_id": self.customer_profile_id, "dedicated_server": self.dedicated_server, "device_id": self.device_id, + "uid": self.uid, "widevine_url": self.widevine_url, "playready_url": self.playready_url, } @@ -72,6 +76,7 @@ class MoveTVAuthToken(BaseAuthToken): customer_profile_id=data.get("customer_profile_id", 0), dedicated_server=data.get("dedicated_server", ""), device_id=data.get("device_id", 0), + uid=data.get("uid", ""), widevine_url=data.get("widevine_url", ""), playready_url=data.get("playready_url", ""), ) @@ -93,19 +98,20 @@ class MoveTVAuthenticator(BaseAuthenticator): - refresh_token → used to renew the session without re-entering credentials - dedicated_server → CDN base URL; manifest URLs are derived from it - customer_id / device_id / profile.id → required for manifest source requests + - t → token lifetime in seconds (32400 = 9 hours) 3. All of the above is stored on MoveTVAuthToken and persisted via the base-class settings_manager. """ def __init__( - self, - proxy_config=None, - http_manager=None, - settings_manager=None, - credentials: Optional[UserPasswordCredentials] = None, - country: Optional[str] = None, - config_dir: Optional[str] = None, - enable_kodi_integration: bool = True, + self, + proxy_config=None, + http_manager=None, + settings_manager=None, + credentials: Optional[UserPasswordCredentials] = None, + country: Optional[str] = None, + config_dir: Optional[str] = None, + enable_kodi_integration: bool = True, ): super().__init__( provider_name="movetv", @@ -117,6 +123,30 @@ class MoveTVAuthenticator(BaseAuthenticator): ) self._proxy_config = proxy_config self._http_manager = http_manager + self._uid = None # Will be loaded from stored token or generated + + # ------------------------------------------------------------------ + # UID management + # ------------------------------------------------------------------ + + def _get_or_generate_uid(self) -> str: + """ + Get existing UID from stored token or generate a new UUID. + The UID is persisted across sessions via the token storage. + """ + if self._uid: + return self._uid + + # Try to load from existing token if available + if self._current_token and hasattr(self._current_token, 'uid'): + self._uid = self._current_token.uid + if self._uid: + return self._uid + + # Generate new UUID + self._uid = str(uuid.uuid4()) + logger.debug(f"move.tv: Generated new UID: {self._uid}") + return self._uid # ------------------------------------------------------------------ # BaseAuthenticator abstract contract @@ -137,7 +167,7 @@ class MoveTVAuthenticator(BaseAuthenticator): Called by the base class only when credentials are already validated, so self.credentials is safe to access here. """ - uid = self.get_device_id() + uid = self._get_or_generate_uid() return { "username": self.credentials.username, "password": self.credentials.password, @@ -158,8 +188,8 @@ class MoveTVAuthenticator(BaseAuthenticator): def has_user_credentials(self) -> bool: return ( - isinstance(self.credentials, UserPasswordCredentials) - and self.credentials.validate() + isinstance(self.credentials, UserPasswordCredentials) + and self.credentials.validate() ) def get_current_token_level(self) -> TokenAuthLevel: @@ -232,16 +262,18 @@ class MoveTVAuthenticator(BaseAuthenticator): """ Map the raw /api/v2/login JSON response onto a MoveTVAuthToken. - The API does not return a numeric expires_in; the auth_token contains - an 'expires-' component in the protection header of manifest - responses (typically ~24 h). We default to 86 400 s (24 h) so the - base-class expiry logic behaves sensibly. + The API returns token lifetime in the 't' field (in seconds). + If not present, defaults to 32400 (9 hours) based on observed responses. """ auth_token: str = data.get("auth_token", "") refresh_token: str = data.get("refresh_token", "") customer_id: int = data.get("customer_id", 0) device_id: int = data.get("device_id", 0) dedicated_server: str = data.get("dedicated_server", "") + uid: str = data.get("uid", "") + + # Get token expiry from 't' field (in seconds) + token_expiry: int = data.get("t", 32400) # Default to 9 hours if not present profile: Dict = data.get("profile", {}) customer_profile_id: int = profile.get("id", 0) @@ -252,14 +284,15 @@ class MoveTVAuthenticator(BaseAuthenticator): logger.info( f"move.tv: Login successful – customer_id={customer_id}, " - f"device_id={device_id}, dedicated_server={dedicated_server}" + f"device_id={device_id}, uid={uid}, dedicated_server={dedicated_server}, " + f"token_expires_in={token_expiry}s" ) return MoveTVAuthToken( # BaseAuthToken fields - access_token=auth_token, # used as bearer / X-Auth-Token + access_token=auth_token, # used as bearer / X-Auth-Token token_type="token", - expires_in=86400, # 24 h default; no numeric TTL in response + expires_in=token_expiry, # Use actual token lifetime from API issued_at=time.time(), refresh_token=refresh_token if refresh_token else None, refresh_expires_in=0, @@ -271,6 +304,7 @@ class MoveTVAuthenticator(BaseAuthenticator): customer_profile_id=customer_profile_id, dedicated_server=dedicated_server, device_id=device_id, + uid=uid, widevine_url=widevine_url, playready_url=playready_url, ) @@ -299,6 +333,7 @@ class MoveTVAuthenticator(BaseAuthenticator): "customer_id": t.customer_id, "customer_profile_id": t.customer_profile_id, "device_id": t.device_id, + "uid": t.uid, "dedicated_server": t.dedicated_server, "auth_token": t.auth_token, } \ No newline at end of file