Add movetv

This commit is contained in:
Nirvana
2026-03-26 13:43:31 +01:00
parent c9b3cb1488
commit bc53d862d1
2 changed files with 99 additions and 1 deletions
@@ -171,6 +171,99 @@ class MoveTVAuthenticator(BaseAuthenticator):
"""Concrete implementation of abstract method."""
return TokenAuthLevel.USER_AUTHENTICATED
# ------------------------------------------------------------------
# Token validation
# ------------------------------------------------------------------
def validate_token(self, token: MoveTVAuthToken) -> bool:
"""
Call the Move.tv token-validate endpoint to confirm the stored token
is still accepted by the server.
Returns True if the server reports "Token active.", False otherwise.
The token fields (dedicated_server etc.) are NOT updated here because
the validate response may contain fresh server URLs — callers that need
those values should read them from get_session_info() after a successful
validate, or trigger a full re-login on failure.
"""
if not token.access_token:
logger.debug("move.tv: validate_token called with empty access_token, skipping")
return False
headers = {
**MoveTVConfig.get_base_headers(),
"x-auth-token": token.access_token,
"x-refresh-token": token.refresh_token or "",
}
payload = {
"customerId": token.customer_id,
"appVersion": MoveTVConfig.APP_VERSION,
}
logger.debug(
f"move.tv: Validating token for customer_id={token.customer_id}"
)
try:
response = self.http_manager.post(
MoveTVConfig.validate_url(),
json=payload,
headers=headers,
operation="token_validate",
)
response.raise_for_status()
data = response.json()
except Exception as exc:
logger.debug(f"move.tv: Token validation request failed: {exc}")
return False
if data.get("message") == "Token active.":
logger.debug("move.tv: Token is active")
return True
# Token is not active — log the full response so we can diagnose
# what the API returned (new tokens, error codes, etc.).
logger.debug(f"move.tv: Token not active, server response: {data}")
return False
# ------------------------------------------------------------------
# Override authenticate() to bypass expiry when session data exists
# ------------------------------------------------------------------
def authenticate(self, force_refresh: bool = False) -> MoveTVAuthToken:
"""
Move.tv sessions do not carry an explicit server-side expiry we can
rely on. When we have a stored token we validate it with the API
instead of checking the local is_expired flag.
Flow:
1. No stored token → full login
2. Stored token, valid → return as-is
3. Stored token, invalid → full login (future: try refresh first)
4. force_refresh=True → full login unconditionally
"""
if force_refresh or not self._current_token:
return self._full_login()
token = cast(MoveTVAuthToken, self._current_token)
if self.validate_token(token):
return token
logger.info("move.tv: Stored token failed validation, performing full login")
return self._full_login()
def _full_login(self) -> MoveTVAuthToken:
"""Ensure credentials, run the login request, persist the session."""
if not self._ensure_credentials():
raise Exception("No valid credentials available for move.tv")
token = self._perform_authentication()
self._current_token = token
self._save_session()
logger.info("move.tv: Authentication successful")
return token
# ------------------------------------------------------------------
# Public helpers (unchanged interface)
# ------------------------------------------------------------------
@@ -183,7 +276,7 @@ class MoveTVAuthenticator(BaseAuthenticator):
Returns info required for playback.
Casts the token to MoveTVAuthToken to resolve attribute errors.
"""
if not self._current_token or self._current_token.is_expired:
if not self._current_token:
return None
t = cast(MoveTVAuthToken, self._current_token)
@@ -21,6 +21,7 @@ class MoveTVConfig:
# API endpoint paths (relative to API_BASE_URL)
# -------------------------------------------------------------------------
PATH_LOGIN: str = "/api/v2/login"
PATH_VALIDATE: str = "/api/v2/token/validate"
PATH_LIVE_CHANNELS: str = "/api/v2/content/live/all"
PATH_LIVE_SOURCE: str = "/api/v2/content/live/source/get"
@@ -95,6 +96,10 @@ class MoveTVConfig:
def login_url(cls) -> str:
return f"{cls.API_BASE_URL}{cls.PATH_LOGIN}"
@classmethod
def validate_url(cls) -> str:
return f"{cls.API_BASE_URL}{cls.PATH_VALIDATE}"
@classmethod
def channels_url(cls) -> str:
return f"{cls.API_BASE_URL}{cls.PATH_LIVE_CHANNELS}"