movetv: update version

This commit is contained in:
Nirvana
2026-10-26 18:08:31 +01:00
parent 3f55b6ba0b
commit 2f84b6627b
4 changed files with 41 additions and 25 deletions
@@ -109,7 +109,7 @@ def api_post(
response: Any = None
for attempt in (1, 2):
info = session.info(need_profile=need_profile) # raises typed AuthError & co.
headers = MoveTVConfig.get_api_headers(info["auth_token"])
headers = MoveTVConfig.get_api_headers(info["auth_token"], dev_id=info.get("uid"))
try:
response = http_manager.post(
url, operation=operation, json=build_payload(info), headers=headers
@@ -96,7 +96,7 @@ class MoveTVAuthenticator(BaseAuthenticator):
def _get_auth_headers(self) -> Dict[str, str]:
"""Headers for the authentication request."""
return MoveTVConfig.get_base_headers()
return MoveTVConfig.get_base_headers(dev_id=self.get_device_id())
def _build_auth_payload(self) -> Dict[str, Any]:
"""Build the login payload from current credentials."""
@@ -110,6 +110,7 @@ class MoveTVAuthenticator(BaseAuthenticator):
"deviceName": MoveTVConfig.DEVICE_NAME,
"deviceModelId": MoveTVConfig.DEVICE_MODEL_ID,
"uid": uid,
"debugMac": uid,
"appVersion": MoveTVConfig.APP_VERSION,
}
@@ -217,8 +218,10 @@ class MoveTVAuthenticator(BaseAuthenticator):
status, f"move.tv: login failed (HTTP {status})", url=self.auth_endpoint
) from exc
if isinstance(status, int) and status >= 400:
body = (getattr(response, "text", "") or "")[:300]
logger.warning(f"move.tv: login failed (HTTP {status}): {body}")
raise default_error_for_status_heuristic(
status, f"move.tv: login failed (HTTP {status})", url=self.auth_endpoint
status, f"move.tv: login failed (HTTP {status}): {body}", url=self.auth_endpoint
)
try:
data = response.json()
@@ -255,13 +258,11 @@ class MoveTVAuthenticator(BaseAuthenticator):
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 "",
}
headers = MoveTVConfig.get_base_headers(token.access_token, dev_id=token.device_id)
headers[MoveTVConfig.HEADER_REFRESH_TOKEN] = token.refresh_token or ""
payload = {
"customerId": token.customer_id,
"debugMac": token.device_id,
"appVersion": MoveTVConfig.APP_VERSION,
}
@@ -279,7 +280,8 @@ class MoveTVAuthenticator(BaseAuthenticator):
response.raise_for_status()
data = response.json()
except Exception as exc:
logger.debug(f"move.tv: Token validation request failed: {exc}")
status = getattr(getattr(exc, "response", None), "status_code", None)
logger.warning(f"move.tv: Token validation request failed (HTTP {status}): {exc}")
return False
if data.get("message") == "Token active.":
@@ -312,10 +314,8 @@ class MoveTVAuthenticator(BaseAuthenticator):
logger.debug("move.tv: refresh_token called but no refresh_token stored, skipping")
return None
headers = {
**MoveTVConfig.get_base_headers(),
"x-refresh-token": token.refresh_token,
}
headers = MoveTVConfig.get_base_headers(dev_id=token.device_id)
headers[MoveTVConfig.HEADER_REFRESH_TOKEN] = token.refresh_token
payload = {
"customerProfileId": token.customer_profile_id,
"appVersion": MoveTVConfig.APP_VERSION,
@@ -58,8 +58,8 @@ class MoveTVConfig:
# -------------------------------------------------------------------------
PARTNER_ID: int = 2
DEVICE_MODEL_ID: int = 10
DEVICE_NAME: str = "Chrome 146"
APP_VERSION: str = "3.4.8"
DEVICE_NAME: str = "Chrome 155"
APP_VERSION: str = "3.5.41"
# -------------------------------------------------------------------------
# Language / locale defaults
@@ -70,9 +70,9 @@ class MoveTVConfig:
# HTTP / request defaults
# -------------------------------------------------------------------------
USER_AGENT: str = (
"Mozilla/5.0 (X11; Linux x86_64) "
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/146.0.0.0 Safari/537.36"
"Chrome/155.0.0.0 Safari/537.36"
)
TIMEOUT: int = 30
@@ -96,6 +96,8 @@ class MoveTVConfig:
PLAY_AUTH_EARLY_EXPIRY_SECONDS: int = 60
PLAY_AUTH_EXPIRY_PATTERN: str = r"expires-(\d+)"
HEADER_PLAY_AUTH: str = "X-Play-Auth"
HEADER_DEV_ID: str = "X-Dev-Id"
HEADER_REFRESH_TOKEN: str = "X-Refresh-Token"
# VOD: browse tag per content-type NAME (as in v1)
TAG_ID_BY_CONTENT_TYPE_NAME: Dict[str, int] = {"Film": 1, "Serija": 2}
@@ -135,27 +137,36 @@ class MoveTVConfig:
# -------------------------------------------------------------------------
@classmethod
def get_base_headers(cls, auth_token: Optional[str] = None) -> Dict[str, str]:
def get_base_headers(
cls, auth_token: Optional[str] = None, dev_id: Optional[str] = None
) -> Dict[str, str]:
"""
Returns headers shared by every request.
When *auth_token* is None the unauthenticated sentinel value is used
(as observed in the login request capture).
(as observed in the login request capture). *dev_id* is the local
device uid; the web client sends it as X-Dev-Id on every call.
"""
return {
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br, zstd",
# Only encodings the HTTP stack is sure to decode (no br / zstd).
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/json",
"Origin": cls.WEB_ORIGIN,
"Referer": cls.WEB_REFERER,
"User-Agent": cls.USER_AGENT,
"X-Auth-Token": auth_token if auth_token else cls.UNAUTHENTICATED_TOKEN,
}
if dev_id:
headers[cls.HEADER_DEV_ID] = dev_id
return headers
@classmethod
def get_api_headers(cls, auth_token: Optional[str] = None) -> Dict[str, str]:
def get_api_headers(
cls, auth_token: Optional[str] = None, dev_id: Optional[str] = None
) -> Dict[str, str]:
"""Headers for authenticated JSON API calls (channels, manifest source, VOD)."""
return cls.get_base_headers(auth_token)
return cls.get_base_headers(auth_token, dev_id=dev_id)
@classmethod
def stream_headers(cls, play_auth: Optional[str] = None) -> Dict[str, str]:
@@ -67,10 +67,13 @@ class MoveTvSession:
return self.authenticator.get_current_token()
def _ready(self) -> bool:
# No local is_expired check: the token's real lifetime is decided by
# the server (401 -> recover()). A locally "expired" but server-valid
# token would otherwise be re-validated on every call.
token = self._token()
return bool(
self._logged_in and token is not None
and getattr(token, "access_token", None) and not token.is_expired
and getattr(token, "access_token", None)
)
# ---- failure bookkeeping ---------------------------------------------
@@ -165,7 +168,9 @@ class MoveTvSession:
return self._token().access_token
def build_headers(self) -> Dict[str, str]:
return self.config.get_api_headers(self.get_access_token())
access_token = self.get_access_token()
dev_id = getattr(self._token(), "device_id", None)
return self.config.get_api_headers(access_token, dev_id=dev_id)
def invalidate(self) -> None:
self.authenticator.invalidate_token() # memory AND persisted storage