Files
script.service.ultimate/lib/streaming_providers/providers/rtlplus/constants.py
T
2026-08-14 19:28:42 +02:00

610 lines
25 KiB
Python

# streaming_providers/providers/rtlplus/constants.py
"""
RTL+ provider constants and default configurations
"""
from ..globals import get_user_agent
import json
import urllib.request
from typing import Optional
from ...base.utils.logger import logger
class RTLPlusDefaults:
"""Default values for RTL+ provider"""
RTLPLUS_LOGO = "https://upload.wikimedia.org/wikipedia/commons/0/0f/RTL_DE_Logo_2021_pink_blau_gruen.png"
# Device information
# DEVICE_ID = "8c3f37cc-13a3-4141-bd0f-e4b3673fe5e4"
DEVICE_ID = "_luid_f4f52150-0645-43f8-a8f5-029341f49980"
DEVICE_NAME = "Linux Chrome"
PLAYREADY_DEVICE_NAME = "Windows Edge"
# Device screen size (used in headers)
DEVICE_PLAYER_SIZE_WIDTH = 3840
DEVICE_PLAYER_SIZE_HEIGHT = 2160
# User agents
USER_AGENT = get_user_agent("windows", "chrome")
PLAYREADY_USER_AGENT = get_user_agent("windows", "edge")
# Supported platform identifiers
PLATFORM_WEB = "web"
PLATFORM_DEFAULT = PLATFORM_WEB
# Client and version information
CLIENT_VERSION_FALLBACK = "2025.6.26.0"
CLIENT_ID = f"rtlplus-{PLATFORM_DEFAULT}"
BEDROCK_CLIENT_ID = "bedrock-m6group_web"
# Dedicated client for the OAuth2 Device Authorization Grant (RFC 8628).
# Must NOT be BEDROCK_CLIENT_ID -- /auth/device only accepts clients
# registered for the device-code flow. Confirmed against the working
# legacy addon (auth.py: DEVICE_CLIENT_ID = 'bedrock-androidtv').
DEVICE_CLIENT_ID = "bedrock-androidtv"
# API endpoints
AUTH_REALM_BASE = "https://auth.rtl.de/auth/realms/rtlplus"
AUTH_BASE_URL = f"{AUTH_REALM_BASE}/protocol/openid-connect"
AUTH_ENDPOINT = f"{AUTH_BASE_URL}/token"
# OAuth2 Device Authorization Grant endpoint (RFC 8628). Confirmed
# correct against the working legacy addon -- it lives under
# AUTH_BASE_URL (.../protocol/openid-connect/auth/device), NOT under
# AUTH_REALM_BASE directly.
DEVICE_AUTH_ENDPOINT = f"{AUTH_BASE_URL}/auth/device"
GRAPHQL_ENDPOINT = "https://cdn.gateway.now-plus-prod.aws-cbc.cloud/graphql"
BASE_WEBSITE = "https://plus.rtl.de/"
CONFIG_ENDPOINT = "https://plus.rtl.de/assets/config/config.json"
# Bedrock API endpoints (Linear TV)
BEDROCK_AUTH_URL = "https://front-auth.rtlde.bedrock.tech/v2/rtlde/platforms/m6group_web/token"
BEDROCK_LAYOUT_BASE = "https://layout.rtlde.bedrock.tech/front/v1/rtlde/m6group_web/main/token-web-31"
BEDROCK_DRM_UPFRONT_BASE = "https://drm.rtlde.bedrock.tech/v1/customers/rtlde/platforms/m6group_web/services/rtlplus_root/users/{uid}/live"
BEDROCK_HEARTBEAT_URL = "https://heartbeat-v2.rtlde.bedrock.tech/v2/platforms/m6group_web/notify/session_live"
TIME_ENDPOINT = "https://time.rtlde.bedrock.tech/"
USERS_ENDPOINT = "https://users.rtlde.bedrock.tech"
PROFILES_PATH = "/v2/platforms/m6group_web/users/{user_id}/profiles"
VERSION_ENDPOINT = "https://beta.plus.rtl.de/version.json"
# DRM Request Headers (common for all DRM types)
DRM_COMMON_HEADERS = {
"origin": BASE_WEBSITE.rstrip("/"),
"referer": BASE_WEBSITE,
}
# Direct CDN
ORIGIN_CDN_BASE = "https://origin.live.rtlde.bedrock.tech"
# Image CDN
IMAGE_BASE_URL = "https://images-fio.rtlde.bedrock.tech/v2/images"
IMAGE_PARAMS = "auto=webp&blur=0&fit=scale_crop&height=320&interlace=1&optimize=high&width=213"
IMAGE_PARAMS_LOGO = "auto=avif&blur=0&fit=scale_crop&height=1080&interlace=1&optimize=high&width=1080"
IMAGE_SIGNING_KEY = "x9vGg4RNeNBqV2nBfhqLV6cN4n"
# Anonymous credentials (fallback)
ANONYMOUS_CLIENT_ID = "anonymous-user"
ANONYMOUS_CLIENT_SECRET = "4bfeb73f-1c4a-4e9f-a7fa-96aa1ad3d94c"
# HTTP settings
DEFAULT_TIMEOUT = 30
# Custom header carrying the signed profile JWT
PROFILE_HEADER = "Rtlplus-Profile"
# requiredPermission values returned by the events API
PERMISSION_FREE_TV = "liveeventAccessToFreeTv"
PERMISSION_PAY_TV = "liveeventAccessToPayTv"
# GraphQL __typename values used during response parsing
TYPENAME_LIVE_EVENT_WIDGET = "LiveEventWidget"
TYPENAME_LIVE_EVENT = "LiveEvent"
# Linear TV Quality Presets
LINEAR_TV_PREFERRED_QUALITIES = ["hd", "sd"]
LINEAR_TV_PREFERRED_FORMATS = ["dashcenc", "hlsfp"]
# Deprecated: DRM type ordering is now handled by the scoring system in
# extract_best_manifest_url / get_drm_for_content. This list is retained
# only for backward-compatibility with existing config dicts.
LINEAR_TV_PREFERRED_DRM_TYPES = ["hardware", "software"]
# DRM variant selection
DRM_VARIANT_AUTO = "auto" # Prefer hardware DRM, fall back to software
DRM_VARIANT_SOFTWARE = "software" # Force software DRM only
DRM_VARIANT_HARDWARE = "hardware" # Force hardware DRM only
DEFAULT_DRM_VARIANT = DRM_VARIANT_AUTO
# All valid DRM variant values (used for input validation)
VALID_DRM_VARIANTS = frozenset([DRM_VARIANT_AUTO, DRM_VARIANT_SOFTWARE, DRM_VARIANT_HARDWARE])
# Layout path patterns
LAYOUT_PATH_LIVE = "/live/{id}/layout"
LAYOUT_PATH_VIDEO = "/video/{id}/layout"
LAYOUT_PATH_FOLDER = "/folder/{id}/layout"
LAYOUT_PATH_PROGRAM = "/program/{id}/layout"
LAYOUT_PATH_BLOCK = "/block/{id}"
# Default pagination values
DEFAULT_BLOCK_PAGE = 1
DEFAULT_NB_PAGES = 2
DEFAULT_BLOCK_NB_PAGES = 3
# Cache TTL for layouts (5 minutes)
LAYOUT_CACHE_TTL = 300
# ---------------------------------------------------------------------------
# VOD — stream config endpoints
# ---------------------------------------------------------------------------
VOD_PREFERRED_VARIANTS = ["dashhd", "dashsd", "hlsfairplayhd", "hlsfairplaysd"]
VOD_PLATFORM_GRAPHQL = PLATFORM_DEFAULT.upper()
VOD_WURSTLAND_CONFIG_URL = "https://wurstland.plus.rtl.de/config/{rrn}/{platform}"
VOD_WURSTLAND_PLATFORM = PLATFORM_DEFAULT.upper()
# ---------------------------------------------------------------------------
# VOD — root category definition
# ---------------------------------------------------------------------------
VOD_ROOT_SLUG = "topic-worlds"
VOD_MOVIES_ROOT_WATCH_PATH = "/video-tv/filme"
VOD_MOVIES_GENRE_WATCH_PATH = "/video-tv/filme/genre"
VOD_MOVIES_ROOT_ID = "/video-tv/filme"
VOD_MOVIE_GENRE_SLUGS = [
"action", "abenteuer", "animation", "comedy", "dokumentation",
"drama", "fantasy", "horror", "kinder", "krimi",
"liebesfilm", "science-fiction", "thriller",
]
VOD_SERIES_ROOT_WATCH_PATH = "/video-tv/serien"
VOD_SERIES_GENRE_WATCH_PATH = "/video-tv/serien/genre"
VOD_SERIES_ROOT_ID = "/video-tv/serien"
VOD_SERIES_GENRE_SLUGS = [
"action", "animation", "comedy", "crime", "dokumentation",
"drama", "fantasy", "horror", "kinder", "reality",
"romance", "science-fiction", "thriller",
]
VOD_SHOWS_ROOT_WATCH_PATH = "/video-tv/shows"
VOD_SHOWS_GENRE_WATCH_PATH = "/video-tv/shows/genre"
VOD_SHOWS_ROOT_ID = "/video-tv/shows"
VOD_SHOW_GENRE_SLUGS = [
"action", "comedy", "crime", "dokumentation",
"drama", "kinder", "reality", "romance", "thriller",
]
VOD_ELEMENT_TYPE_MOVIE = "MOVIE"
VOD_ELEMENT_TYPE_SERIES = "SERIES"
VOD_ELEMENT_TYPE_SHOW = "SHOW"
VOD_OVERVIEW_PAGE_LIMIT = 48
# Lazy-loaded client version
_CLIENT_VERSION_CACHE: Optional[str] = None
def _get_rtlplus_client_version() -> str:
"""Lazy fetch RTL+ client version from version.json endpoint."""
global _CLIENT_VERSION_CACHE
if _CLIENT_VERSION_CACHE is None:
try:
with urllib.request.urlopen(RTLPlusDefaults.VERSION_ENDPOINT, timeout=4) as resp:
data = json.loads(resp.read().decode())
_CLIENT_VERSION_CACHE = data["version"]
except Exception as exc:
logger.warning(
f"Could not fetch RTL+ client version ({exc}); using fallback {RTLPlusDefaults.CLIENT_VERSION_FALLBACK}")
_CLIENT_VERSION_CACHE = RTLPlusDefaults.CLIENT_VERSION_FALLBACK
return _CLIENT_VERSION_CACHE
class RTLPlusGraphQL:
"""Persisted-query hashes and parameter builders for all RTL+ GraphQL operations."""
HASHES: dict[str, str] = {
"LiveTvStations": "845cf56a2a78110a0f978c1a2af2bc7f9a1c937d0f324ffaf852a9a4414c8485",
"ExploreWidgetWatch": "724f21ab86aa3f8c57673a3b346cf119f6a155bf82bb0201fd3b18e28e44f1ed",
"TopicWorlds": "3dbde4c45532f4bdb0a1d7c210db43f0888f8a82f4aab43f7a90f3c4762d8ff7",
"Format": "d112638c0184ab5698af7b69532dfe2f12973f7af9cb137b9f70278130b1eafa",
"MRE": "0c77404637570adff548e329a48654498c54ce1c36a459d72586ff18999bebaa",
"Episode": "87dbde15a0d269b11606f5ff458d555e98eb493bb4fb6ddc150d812d5e9a9cf8",
"WatchPlayerConfigV3": "fea0311fb572b6fded60c5a1a9d652f97f55d182bc4cedbdad676354a8d2797c",
"SeoUrlData": "fcc4a812d6b93496f00c3068234db7722f553032bb760e09e5e6c74586c86f8d",
"OverviewPage": "28aad4e992bb63330bfcd40a6906af3119d8a2612fa9fd28dae9c19127e247ca",
"LiveEventsOverviewPage": "77a8f26d5de76daf801fcd7ae54bebd0ecabbeeb3ecb90889bc4a2e5590b7d20",
"SeasonWithFormatAndEpisodes": "cc0fbbe17143f549a35efa6f8665ceb9b1cfae44b590f0b2381a9a304304c584",
}
@classmethod
def _build(cls, operation: str, variables: str) -> dict:
return {
"operationName": operation,
"variables": variables,
"extensions": f'{{"persistedQuery":{{"version":1,"sha256Hash":"{cls.HASHES[operation]}"}}}}',
}
@classmethod
def live_tv_stations(cls, epg_count: int = 4) -> dict:
return cls._build("LiveTvStations",
f'{{"epgCount":{epg_count},"filter":{{"channelTypes":["BROADCAST","FAST"]}}}}')
@classmethod
def explore_widget_watch(cls, area: str = "home", offset: int = 0, take: int = 15) -> dict:
return cls._build("ExploreWidgetWatch", f'{{"area":"{area}","offset":{offset},"take":{take}}}')
@classmethod
def season_with_episodes(cls, season_rrn: str, offset: int = 0, limit: int = 48) -> dict:
return cls._build("SeasonWithFormatAndEpisodes",
f'{{"seasonId":"{season_rrn}","offset":{offset},"limit":{limit}}}')
@classmethod
def live_events_overview_page(cls) -> dict:
return cls._build("LiveEventsOverviewPage", "{}")
@classmethod
def topic_worlds(cls, take: int = 100, offset: int = 0) -> dict:
return cls._build("TopicWorlds", f'{{"take":{take},"offset":{offset},"filterForSearchGrid":true}}')
@classmethod
def format(cls, format_rrn: str) -> dict:
return cls._build("Format", f'{{"id":"{format_rrn}"}}')
@classmethod
def mre(cls, format_rrn: str) -> dict:
return cls._build("MRE", f'{{"id":"{format_rrn}"}}')
@classmethod
def episode(cls, episode_rrn: str) -> dict:
return cls._build("Episode", f'{{"rrn":"{episode_rrn}"}}')
@classmethod
def watch_player_config(cls, rrn: str) -> dict:
return cls._build("WatchPlayerConfigV3",
f'{{"platform":"{RTLPlusDefaults.VOD_PLATFORM_GRAPHQL}","id":"{rrn}"}}')
@classmethod
def seo_url_data(cls, watch_path: str) -> dict:
return cls._build("SeoUrlData", f'{{"watchPath":"{watch_path}"}}')
@classmethod
def overview_page(cls, element_type: str, genres: list = None, offset: int = 0, limit: int = None) -> dict:
if limit is None:
limit = RTLPlusDefaults.VOD_OVERVIEW_PAGE_LIMIT
filter_parts = [f'"elementType":"{element_type}"']
if genres:
genres_json = "[" + ",".join(f'"{g}"' for g in genres) + "]"
filter_parts.append(f'"genres":{genres_json}')
variables = f'{{"pagination":{{"offset":{offset},"limit":{limit}}},"filter":{{{",".join(filter_parts)}}}}}'
return cls._build("OverviewPage", variables)
class RTLPlusHeaders:
"""Standard header configurations for RTL+ requests"""
# Common headers shared across multiple request types
_COMMON_HEADERS = {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "de-DE,de;q=0.9",
"cache-control": "no-cache",
}
@staticmethod
def get_base_headers(user_agent: str = None) -> dict:
return {"User-Agent": user_agent or RTLPlusDefaults.USER_AGENT, "Accept": "application/json"}
@staticmethod
def get_auth_headers(user_agent: str = None) -> dict:
headers = RTLPlusHeaders.get_base_headers(user_agent)
headers.update({
"Content-Type": "application/x-www-form-urlencoded",
"Origin": RTLPlusDefaults.BASE_WEBSITE.rstrip("/"),
"Referer": RTLPlusDefaults.BASE_WEBSITE,
})
return headers
@staticmethod
def get_api_headers(access_token: str = None, device_id: str = None, client_version: str = None,
user_agent: str = None) -> dict:
headers = RTLPlusHeaders.get_base_headers(user_agent)
headers.update({
"Content-Type": "application/json",
"Rtlplus-Client-Id": f"rci:rtlplus:{RTLPlusDefaults.PLATFORM_DEFAULT}",
"Rtlplus-Referrer": "",
"Rtlplus-Client-Version": client_version or _get_rtlplus_client_version(),
})
if access_token:
headers["Authorization"] = f"Bearer {access_token}"
if device_id:
headers["X-Device-Id"] = device_id
return headers
@staticmethod
def get_events_headers(access_token: str, profile_token: str = None, device_id: str = None,
client_version: str = None, user_agent: str = None) -> dict:
headers = RTLPlusHeaders.get_api_headers(access_token=access_token, device_id=device_id,
client_version=client_version, user_agent=user_agent)
if profile_token:
headers[RTLPlusDefaults.PROFILE_HEADER] = profile_token
return headers
@staticmethod
def get_bedrock_token_headers(
oauth_token: str,
device_id: str,
client_version: str,
user_agent: str,
auth_token: str,
timestamp: int,
profile_id: str = None,
user_id: str = None, # Add user_id parameter
) -> dict:
"""Headers for obtaining Bedrock token."""
headers = dict(RTLPlusHeaders._COMMON_HEADERS)
headers.update({
"authorization": f"Bearer {oauth_token}",
"origin": RTLPlusDefaults.BASE_WEBSITE.rstrip("/"),
"referer": RTLPlusDefaults.BASE_WEBSITE,
"user-agent": user_agent,
"x-auth-device-id": device_id,
"x-auth-device-name": RTLPlusDefaults.DEVICE_NAME,
"x-auth-device-player-size-width": str(RTLPlusDefaults.DEVICE_PLAYER_SIZE_WIDTH),
"x-auth-device-player-size-height": str(RTLPlusDefaults.DEVICE_PLAYER_SIZE_HEIGHT),
"x-auth-token": auth_token,
"x-auth-token-timestamp": str(timestamp),
"x-client-release": client_version,
"x-customer-name": "rtlde",
})
# Add profile_id if available
if profile_id:
headers["x-auth-profile-id"] = profile_id
# Add user_id as x-auth-gigya-uid if available
if user_id:
headers["x-auth-gigya-uid"] = user_id
return headers
@staticmethod
def get_bedrock_layout_headers(
oauth_token: str,
bedrock_token: str,
device_id: str,
client_version: str,
user_agent: str,
location: str = None,
) -> dict:
"""Headers for Bedrock layout API calls."""
headers = dict(RTLPlusHeaders._COMMON_HEADERS)
headers.update({
"authorization": f"Bearer {oauth_token}",
"origin": RTLPlusDefaults.BASE_WEBSITE.rstrip("/"),
"referer": RTLPlusDefaults.BASE_WEBSITE,
"request-timeout": "10000",
"user-agent": user_agent,
"x-bedrock-token": bedrock_token,
"x-client-release": client_version,
"x-customer-name": "rtlde",
})
if location:
headers["x-location"] = location
return headers
@staticmethod
def get_upfront_token_headers(
oauth_token: str,
bedrock_token: str,
device_id: str,
client_version: str,
user_agent: str,
) -> dict:
"""Headers for upfront token request."""
headers = dict(RTLPlusHeaders._COMMON_HEADERS)
headers.update({
"authorization": f"Bearer {oauth_token}",
"origin": RTLPlusDefaults.BASE_WEBSITE.rstrip("/"),
"referer": RTLPlusDefaults.BASE_WEBSITE,
"user-agent": user_agent,
"x-bedrock-token": bedrock_token,
"x-auth-device-id": device_id,
"x-auth-device-name": RTLPlusDefaults.DEVICE_NAME,
"x-client-release": client_version,
"x-customer-name": "rtlde",
})
return headers
@staticmethod
def get_drm_headers(access_token: str, device_id: str = None, user_agent: str = None) -> dict:
return {
"Origin": RTLPlusDefaults.BASE_WEBSITE.rstrip("/"),
"Referer": RTLPlusDefaults.BASE_WEBSITE,
"User-Agent": user_agent or RTLPlusDefaults.USER_AGENT,
"X-Auth-Token": access_token,
"X-Device-Id": device_id or RTLPlusDefaults.DEVICE_ID,
"X-Device-Name": RTLPlusDefaults.DEVICE_NAME,
}
@staticmethod
def get_profiles_headers(
oauth_token: str,
bedrock_token: str,
client_version: str,
user_agent: str,
) -> dict:
"""Headers for fetching user profiles."""
headers = dict(RTLPlusHeaders._COMMON_HEADERS)
headers.update({
"authorization": f"Bearer {oauth_token}",
"origin": RTLPlusDefaults.BASE_WEBSITE.rstrip("/"),
"referer": RTLPlusDefaults.BASE_WEBSITE,
"user-agent": user_agent,
"x-bedrock-token": bedrock_token,
"x-client-release": client_version,
"x-customer-name": "rtlde",
})
return headers
class RTLPlusConfig:
"""Configuration class that can be customized per instance"""
def __init__(self, config_dict: dict = None):
config = config_dict or {}
self.logo = config.get("logo", RTLPlusDefaults.RTLPLUS_LOGO)
# Core settings
self.client_version = config.get("client_version", _get_rtlplus_client_version())
self.device_id = config.get("device_id", RTLPlusDefaults.DEVICE_ID)
self.user_agent = config.get("user_agent", RTLPlusDefaults.USER_AGENT)
self.platform = config.get("platform", RTLPlusDefaults.PLATFORM_DEFAULT)
# Screen size
self.screen_width = config.get("screen_width", RTLPlusDefaults.DEVICE_PLAYER_SIZE_WIDTH)
self.screen_height = config.get("screen_height", RTLPlusDefaults.DEVICE_PLAYER_SIZE_HEIGHT)
# API endpoints
self.auth_endpoint = config.get("auth_endpoint", RTLPlusDefaults.AUTH_ENDPOINT)
self.graphql_endpoint = config.get("graphql_endpoint", RTLPlusDefaults.GRAPHQL_ENDPOINT)
self.base_website = config.get("base_website", RTLPlusDefaults.BASE_WEBSITE)
self.config_endpoint = config.get("config_endpoint", RTLPlusDefaults.CONFIG_ENDPOINT)
# Bedrock endpoints (Linear TV)
self.bedrock_auth_url = config.get("bedrock_auth_url", RTLPlusDefaults.BEDROCK_AUTH_URL)
self.bedrock_layout_base = config.get("bedrock_layout_base", RTLPlusDefaults.BEDROCK_LAYOUT_BASE)
self.bedrock_drm_upfront_base = config.get("bedrock_drm_upfront_base", RTLPlusDefaults.BEDROCK_DRM_UPFRONT_BASE)
self.bedrock_heartbeat_url = config.get("bedrock_heartbeat_url", RTLPlusDefaults.BEDROCK_HEARTBEAT_URL)
self.time_endpoint = config.get("time_endpoint", RTLPlusDefaults.TIME_ENDPOINT)
self.users_endpoint = config.get("users_endpoint", RTLPlusDefaults.USERS_ENDPOINT)
self.profiles_path = config.get("profiles_path", RTLPlusDefaults.PROFILES_PATH)
# Quality presets for linear TV
self.preferred_qualities = config.get("preferred_qualities", RTLPlusDefaults.LINEAR_TV_PREFERRED_QUALITIES)
self.preferred_formats = config.get("preferred_formats", RTLPlusDefaults.LINEAR_TV_PREFERRED_FORMATS)
self.preferred_drm_types = config.get("preferred_drm_types", RTLPlusDefaults.LINEAR_TV_PREFERRED_DRM_TYPES)
# HTTP settings
self.timeout = config.get("timeout", RTLPlusDefaults.DEFAULT_TIMEOUT)
def get_bedrock_layout_url(self, channel_seo: str) -> str:
"""Get Bedrock layout URL for a specific channel"""
return f"{self.bedrock_layout_base}/live/{channel_seo}/layout"
def get_profiles_url(self, user_id: str) -> str:
"""Get profiles URL for a specific user"""
return f"{self.users_endpoint}{self.profiles_path.format(user_id=user_id)}"
def get_epg_grid_url(self) -> str:
"""Get EPG grid URL for channel listing"""
return f"{self.bedrock_layout_base}/epg_grid"
def get_upfront_token_url(self, uid: str, content_id: str) -> str:
"""Get upfront token URL for DRM"""
return f"{self.bedrock_drm_upfront_base.format(uid=uid)}/{content_id}/upfront-token"
def get_base_headers(self) -> dict:
return RTLPlusHeaders.get_base_headers(self.user_agent)
def get_auth_headers(self) -> dict:
return RTLPlusHeaders.get_auth_headers(self.user_agent)
def get_api_headers(self, access_token: str = None) -> dict:
return RTLPlusHeaders.get_api_headers(
access_token=access_token,
device_id=self.device_id,
client_version=self.client_version,
user_agent=self.user_agent,
)
def get_events_headers(self, access_token: str, profile_token: str = None) -> dict:
return RTLPlusHeaders.get_events_headers(
access_token=access_token,
profile_token=profile_token,
device_id=self.device_id,
client_version=self.client_version,
user_agent=self.user_agent,
)
def get_profiles_headers(self, oauth_token: str, bedrock_token: str) -> dict:
"""Get headers for profiles request."""
return RTLPlusHeaders.get_profiles_headers(
oauth_token=oauth_token,
bedrock_token=bedrock_token,
client_version=self.client_version,
user_agent=self.user_agent,
)
def get_bedrock_token_headers(self, oauth_token: str, auth_token: str, timestamp: int,
profile_id: str = None, user_id: str = None) -> dict:
"""Get headers for Bedrock token request."""
return RTLPlusHeaders.get_bedrock_token_headers(
oauth_token=oauth_token,
device_id=self.device_id,
client_version=self.client_version,
user_agent=self.user_agent,
auth_token=auth_token,
timestamp=timestamp,
profile_id=profile_id,
user_id=user_id, # Pass through
)
def get_bedrock_layout_headers(self, oauth_token: str, bedrock_token: str, location: str = None) -> dict:
return RTLPlusHeaders.get_bedrock_layout_headers(
oauth_token=oauth_token,
bedrock_token=bedrock_token,
device_id=self.device_id,
client_version=self.client_version,
user_agent=self.user_agent,
location=location,
)
def get_upfront_token_headers(self, oauth_token: str, bedrock_token: str) -> dict:
return RTLPlusHeaders.get_upfront_token_headers(
oauth_token=oauth_token,
bedrock_token=bedrock_token,
device_id=self.device_id,
client_version=self.client_version,
user_agent=self.user_agent,
)
def get_layout_url(self, layout_type: str, content_id: str) -> str:
"""Get layout URL by type: 'live', 'video', 'folder', 'program', 'block'"""
path_map = {
"live": RTLPlusDefaults.LAYOUT_PATH_LIVE,
"video": RTLPlusDefaults.LAYOUT_PATH_VIDEO,
"folder": RTLPlusDefaults.LAYOUT_PATH_FOLDER,
"program": RTLPlusDefaults.LAYOUT_PATH_PROGRAM,
"block": RTLPlusDefaults.LAYOUT_PATH_BLOCK,
}
if layout_type not in path_map:
raise ValueError(f"Unknown layout type: {layout_type}. Valid types: {list(path_map.keys())}")
path = path_map[layout_type]
# Ensure content_id doesn't have type prefixes for the URL
clean_id = content_id
if ":" in clean_id:
# If it has a colon, extract the part after the colon
clean_id = clean_id.split(":", 1)[-1]
return f"{self.bedrock_layout_base}{path.format(id=clean_id)}"
def get_layout_headers(self, oauth_token: str, bedrock_token: str, location: str = None) -> dict:
"""Common headers for all layout requests"""
return RTLPlusHeaders.get_bedrock_layout_headers(
oauth_token=oauth_token,
bedrock_token=bedrock_token,
device_id=self.device_id,
client_version=self.client_version,
user_agent=self.user_agent,
location=location,
)
@property
def users_base_url(self) -> str:
"""Base URL for user-related API endpoints (bookmarks, etc.)"""
return "https://users.rtlde.bedrock.tech/v4/rtlde/m6group_web"