mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-22 09:02:23 +02:00
Add discovery
This commit is contained in:
@@ -297,4 +297,15 @@ VOD_PREFIX_MOVIE_SH = "GN_SH"
|
||||
|
||||
# Name of the personal-bar tile that leads to the VOD StructuredGrid.
|
||||
# Looked up by exact title match in the primary.tiles list returned by homeUrl.
|
||||
VOD_STREAMING_TILE_TITLE = "Streaming"
|
||||
# Ordered list of personal-bar tile titles that lead to the VOD StructuredGrid.
|
||||
# Tried in order — first match wins. Different user subscriptions/locales
|
||||
# may show different tile names (e.g. "MagentaTV+" instead of "Streaming").
|
||||
VOD_STREAMING_TILE_TITLES = [
|
||||
"Streaming", # Default / standard subscription
|
||||
"MagentaTV+", # Alternative name observed in some accounts
|
||||
"Heimkino", # Fallback seen in basic-tier bars
|
||||
]
|
||||
|
||||
# Keep the singular alias for backwards compatibility with any external code
|
||||
# that still imports VOD_STREAMING_TILE_TITLE directly.
|
||||
VOD_STREAMING_TILE_TITLE = VOD_STREAMING_TILE_TITLES[0]
|
||||
@@ -75,9 +75,13 @@ class Magenta2Provider(StreamingProvider):
|
||||
)
|
||||
self.terminal_type = self.platform_config["terminal_type"]
|
||||
|
||||
# Generate session ID and device ID
|
||||
# Generate session ID, device ID, and serial number.
|
||||
# serial_number is stable for the lifetime of this provider instance —
|
||||
# it mimics the hardware serial that a real device would send and must
|
||||
# be consistent across all requests in the same session.
|
||||
self.session_id = self._generate_uuid()
|
||||
self.device_id = self._generate_device_id()
|
||||
self.serial_number = self._generate_uuid()
|
||||
|
||||
# Setup proxy configuration
|
||||
self.proxy_config = (
|
||||
@@ -277,6 +281,7 @@ class Magenta2Provider(StreamingProvider):
|
||||
bootstrap=self.endpoint_manager.config.bootstrap,
|
||||
provider_config=self.endpoint_manager.config,
|
||||
session_id=self.session_id,
|
||||
serial_number=self.serial_number,
|
||||
auth_headers_callback=self._vod_auth_headers,
|
||||
)
|
||||
logger.info("✓ VodManager initialized with discovered config")
|
||||
@@ -1116,37 +1121,81 @@ class Magenta2Provider(StreamingProvider):
|
||||
|
||||
def _vod_auth_headers(self) -> Dict[str, str]:
|
||||
"""
|
||||
Build authentication headers required by authenticated VOD endpoints:
|
||||
vodproductinformation (wcps.t-online.de/uspip/...)
|
||||
VodPlayer (wcps.t-online.de/vops/...)
|
||||
Build authentication headers required by authenticated VOD endpoints
|
||||
(vodproductinformation, VodPlayer, PersonalBar).
|
||||
|
||||
Required headers (observed from browser traffic):
|
||||
Authorization: Bearer <persona_jwt>
|
||||
x-mpx-authorization: Basic <persona_token> (account_uri:jwt b64)
|
||||
x-dt-session-id: <session_id>
|
||||
x-dt-call-id: <fresh uuid per request>
|
||||
origin / referer: magenta.tv
|
||||
Headers are platform-dependent:
|
||||
|
||||
ftv-web
|
||||
-------
|
||||
Authorization: Bearer <persona_jwt>
|
||||
x-mpx-authorization: Basic <persona_token>
|
||||
x-dt-session-id: <session_id>
|
||||
x-dt-call-id: <fresh uuid per request>
|
||||
origin: https://www.magenta.tv
|
||||
referer: https://www.magenta.tv/
|
||||
user-agent: <web UA>
|
||||
x-permissionflagpersonalizeduireco: false
|
||||
|
||||
ftv-android / ftv-androidtv (and variants)
|
||||
-------------------------------------------
|
||||
Authorization: Bearer <persona_jwt>
|
||||
x-mpx-authorization: Basic <persona_token>
|
||||
x-stbserialnumber: <stable serial UUID>
|
||||
dt-session-id: <session_id> (no x- prefix)
|
||||
dt-call-id: <fresh uuid per request> (no x- prefix)
|
||||
user-agent: <android UA>
|
||||
accept-encoding: gzip
|
||||
|
||||
Note: _get_with_serial() in VodManager may further override dt-call-id
|
||||
and x-stbserialnumber per-request; the values set here act as a
|
||||
sensible default for plain _get() / _get_auth() calls.
|
||||
"""
|
||||
persona_token = self._ensure_authenticated()
|
||||
# The persona_token is already Base64(account_uri:jwt) — used as-is
|
||||
# for x-mpx-authorization. For Authorization we need just the raw JWT.
|
||||
# persona_token is Base64(account_uri:jwt) — used as-is for
|
||||
# x-mpx-authorization. Strip to just the raw JWT for Authorization.
|
||||
persona_jwt = self._extract_persona_jwt_from_token(persona_token)
|
||||
|
||||
headers = {
|
||||
"x-mpx-authorization": f"Basic {persona_token}",
|
||||
"x-dt-session-id": self.session_id,
|
||||
"x-dt-call-id": self._generate_call_id(),
|
||||
"origin": "https://www.magenta.tv",
|
||||
"referer": "https://www.magenta.tv/",
|
||||
"User-Agent": self.platform_config["user_agent"],
|
||||
"Accept": "application/json",
|
||||
"x-permissionflagpersonalizeduireco": "false",
|
||||
}
|
||||
if persona_jwt:
|
||||
headers["Authorization"] = f"Bearer {persona_jwt}"
|
||||
auth_value = (
|
||||
f"Bearer {persona_jwt}" if persona_jwt else f"Basic {persona_token}"
|
||||
)
|
||||
|
||||
# Determine which header flavour to use based on the client_model
|
||||
# resolved at init time (ftv-web vs everything else).
|
||||
client_model: str = (
|
||||
self.provider_config.bootstrap.client_model
|
||||
if self.provider_config and self.provider_config.bootstrap
|
||||
else f"ftv-{self.platform}"
|
||||
)
|
||||
is_web = client_model == "ftv-web"
|
||||
|
||||
if is_web:
|
||||
headers: Dict[str, str] = {
|
||||
"Authorization": auth_value,
|
||||
"x-mpx-authorization": f"Basic {persona_token}",
|
||||
"x-dt-session-id": self.session_id,
|
||||
"x-dt-call-id": self._generate_call_id(),
|
||||
"origin": "https://www.magenta.tv",
|
||||
"referer": "https://www.magenta.tv/",
|
||||
"user-agent": self.platform_config["user_agent"],
|
||||
"accept": "*/*",
|
||||
"accept-encoding": "gzip, deflate, br, zstd",
|
||||
"accept-language": "de-DE,de;q=0.9",
|
||||
"x-permissionflagpersonalizeduireco": "false",
|
||||
}
|
||||
else:
|
||||
# Fallback: some endpoints accept Basic auth too
|
||||
headers["Authorization"] = f"Basic {persona_token}"
|
||||
# Android TV / Android Mobile / ATV-Launcher / iOS all use the
|
||||
# non-prefixed dt-* header names and expose the serial number.
|
||||
headers = {
|
||||
"Authorization": auth_value,
|
||||
"x-mpx-authorization": f"Basic {persona_token}",
|
||||
"x-stbserialnumber": self.serial_number,
|
||||
"dt-session-id": self.session_id,
|
||||
"dt-call-id": self._generate_call_id(),
|
||||
"user-agent": self.platform_config["user_agent"],
|
||||
"accept-encoding": "gzip",
|
||||
}
|
||||
|
||||
return headers
|
||||
|
||||
def get_vod_category(self, category_path, **kwargs):
|
||||
|
||||
@@ -57,7 +57,7 @@ from .constants import (
|
||||
VOD_PREFIX_EPISODE,
|
||||
VOD_PREFIX_SEASON,
|
||||
VOD_PREFIX_SERIES,
|
||||
VOD_STREAMING_TILE_TITLE,
|
||||
VOD_STREAMING_TILE_TITLES,
|
||||
)
|
||||
|
||||
class VodManager:
|
||||
@@ -94,6 +94,7 @@ class VodManager:
|
||||
bootstrap=None,
|
||||
provider_config=None,
|
||||
session_id: Optional[str] = None,
|
||||
serial_number: Optional[str] = None,
|
||||
preferred_quality: str = "HD",
|
||||
auth_headers_callback=None,
|
||||
):
|
||||
@@ -101,6 +102,12 @@ class VodManager:
|
||||
self._provider = provider_name
|
||||
self._provider_config = provider_config
|
||||
self._session_id: str = session_id or ""
|
||||
# Stable serial number for the lifetime of this manager instance.
|
||||
# Passed in from the provider so it stays consistent across all
|
||||
# requests (auth headers callback, _get_with_serial calls, etc.).
|
||||
# Falls back to a fresh UUID only when not supplied (e.g. unit tests).
|
||||
import uuid as _uuid_mod
|
||||
self._serial_number: str = serial_number or str(_uuid_mod.uuid4())
|
||||
# Normalise to uppercase; fall back to "HD" for unknown values.
|
||||
_q = (preferred_quality or "HD").upper()
|
||||
self._preferred_quality: str = _q if _q in self._QUALITY_FALLBACK else "HD"
|
||||
@@ -356,9 +363,10 @@ class VodManager:
|
||||
dt_call_id_1 = str(_uuid.uuid4())
|
||||
cid = f"{dt_session_id}::{dt_call_id_1}"
|
||||
|
||||
# Random serial number UUID — the real device sends its hardware serial,
|
||||
# but any stable UUID is accepted.
|
||||
serial_number = str(_uuid.uuid4())
|
||||
# Use the stable serial number that was set at construction time.
|
||||
# Generating a new UUID here on every call would cause the server to
|
||||
# treat each request as a different device, breaking session correlation.
|
||||
serial_number = self._serial_number
|
||||
|
||||
# Params mirror the real Android TV DocumentGroupRedirect request exactly.
|
||||
# Note: $subscriberType and $reloadAfterChange are NOT sent by real devices;
|
||||
@@ -457,15 +465,23 @@ class VodManager:
|
||||
f"{self._provider}: Personal bar tiles: "
|
||||
f"{[t.get('title') for t in tiles]}"
|
||||
)
|
||||
for tile in tiles:
|
||||
if tile.get("title") == VOD_STREAMING_TILE_TITLE:
|
||||
# Build a lookup so we can find the first matching tile by title
|
||||
# regardless of which subscription/locale variant the user has.
|
||||
tile_by_title = {t.get("title"): t for t in tiles}
|
||||
for candidate in VOD_STREAMING_TILE_TITLES:
|
||||
tile = tile_by_title.get(candidate)
|
||||
if tile:
|
||||
href = tile.get("onFocus", {}).get("screen", {}).get("href")
|
||||
if href:
|
||||
logger.debug(f"{self._provider}: Found Streaming grid URL: {href}")
|
||||
logger.debug(
|
||||
f"{self._provider}: Found VOD grid tile '{candidate}': {href}"
|
||||
)
|
||||
return href
|
||||
|
||||
logger.warning(
|
||||
f"{self._provider}: '{VOD_STREAMING_TILE_TITLE}' tile not found in personal bar"
|
||||
f"{self._provider}: No VOD tile found in personal bar "
|
||||
f"(tried: {VOD_STREAMING_TILE_TITLES}); "
|
||||
f"available: {list(tile_by_title.keys())}"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user