From 9e39a54a680e1986a68c4ce09c65a647e03c3ca8 Mon Sep 17 00:00:00 2001 From: Nirvana Date: Fri, 27 Feb 2026 21:09:54 +0100 Subject: [PATCH] Add discovery --- .../providers/discovery/__init__.py | 2 + .../providers/discovery/constants.py | 11 ++-- .../providers/discovery/provider.py | 65 ++++++++++++++++++- .../providers/rtlplus/constants.py | 3 +- 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/lib/streaming_providers/providers/discovery/__init__.py b/lib/streaming_providers/providers/discovery/__init__.py index 4c119a2..ac3a461 100644 --- a/lib/streaming_providers/providers/discovery/__init__.py +++ b/lib/streaming_providers/providers/discovery/__init__.py @@ -21,6 +21,7 @@ from .constants import ( SUPPORTED_COUNTRIES, CHANNEL_COLLECTIONS, CHANNEL_ITEM_TYPES, + DISCOVERY_USERS_ME_URL, StreamingMode, CDMMode, VideoQuality, @@ -75,6 +76,7 @@ __all__ = [ "SUPPORTED_COUNTRIES", "CHANNEL_COLLECTIONS", "CHANNEL_ITEM_TYPES", + "DISCOVERY_USERS_ME_URL", # Constants - Enums "StreamingMode", diff --git a/lib/streaming_providers/providers/discovery/constants.py b/lib/streaming_providers/providers/discovery/constants.py index 5d3b4ed..eff9595 100644 --- a/lib/streaming_providers/providers/discovery/constants.py +++ b/lib/streaming_providers/providers/discovery/constants.py @@ -72,6 +72,10 @@ DISCOVERY_LOGO: Final[ DISCOVERY_BOOTSTRAP_URL: Final[ str] = "https://default.any-any.prd.api.discoveryplus.com/session-context/headwaiter/v1/bootstrap" +# User profile endpoint — returns currentLocationTerritory for country detection +DISCOVERY_USERS_ME_URL: Final[ + str] = "https://default.dplus-emea.prd.api.discoveryplus.com/users/me" + # Default realm DEFAULT_REALM: Final[str] = "bolt" @@ -89,10 +93,9 @@ SUPPORTED_AUTH_TYPES: Final[List[str]] = [ # Country/Region Configuration # ============================================================================ -# Supported countries (EMEA region) -SUPPORTED_COUNTRIES: Final[List[str]] = [ - "de", "at", "ch", "dk", "fi", "no", "se", "it", "nl", "es", "uk", "ie", -] +# Wildcard sentinel — Discovery+ accepts any country; the user's actual country +# is determined at runtime via the /users/me endpoint (currentLocationTerritory). +SUPPORTED_COUNTRIES: Final[str] = "*" # Home market mapping (immutable) HOME_MARKET_MAPPING: Final[Dict[str, str]] = { diff --git a/lib/streaming_providers/providers/discovery/provider.py b/lib/streaming_providers/providers/discovery/provider.py index 138e3cf..eac2668 100644 --- a/lib/streaming_providers/providers/discovery/provider.py +++ b/lib/streaming_providers/providers/discovery/provider.py @@ -28,6 +28,7 @@ from .constants import ( DEFAULT_TENANT, DEFAULT_PLATFORM_OS, DISCOVERY_LOGO, + DISCOVERY_USERS_ME_URL, DRMSystem as DiscoveryDRMSystem, DRM_SYSTEM_PLAYREADY, PlatformOS, @@ -71,7 +72,7 @@ class DiscoveryProvider(StreamingProvider): PROVIDER_LABEL: ClassVar[str] = "Discovery+" SUPPORTED_AUTH_TYPES: ClassVar[List[str]] = SUPPORTED_AUTH_TYPES PROVIDER_LOGO: ClassVar[str] = DISCOVERY_LOGO - SUPPORTED_COUNTRIES: ClassVar[List[str]] = SUPPORTED_COUNTRIES + SUPPORTED_COUNTRIES: ClassVar[str] = SUPPORTED_COUNTRIES # "*" = all countries def __init__( self, @@ -106,9 +107,9 @@ class DiscoveryProvider(StreamingProvider): ValueError: If country not supported or credentials missing """ if not self.validate_country(country): - supported = ", ".join(self.SUPPORTED_COUNTRIES) raise ValueError( - f"Unsupported country: {country}. Discovery+ supports: {supported}" + f"Unsupported country: {country}. " + f"Provide a valid ISO-3166-1 alpha-2 country code (e.g. 'de', 'uk')." ) super().__init__(country=country) @@ -300,6 +301,64 @@ class DiscoveryProvider(StreamingProvider): return self.token_info.get("anonymous", True) return True + def validate_country(self, country: str) -> bool: + """ + Override base validation. + + Discovery+ supports all countries (SUPPORTED_COUNTRIES == "*"), + so any non-empty lowercase alpha-2 code is accepted. + The user's actual serving country is determined at runtime via + get_user_country(). + """ + if self.SUPPORTED_COUNTRIES == "*": + return bool(country and country.isalpha() and len(country) == 2) + # Fallback to standard list-based check if constant is ever changed back + return country.lower() in [c.lower() for c in self.SUPPORTED_COUNTRIES] + + def get_user_country(self) -> Optional[str]: + """ + Detect the user's current country by calling the /users/me endpoint. + + The API returns ``currentLocationTerritory`` (e.g. ``"DE"``) inside + ``data.attributes``. This method normalises the value to lowercase + so it is consistent with the rest of the codebase (e.g. ``"de"``). + + Returns: + Two-letter lowercase country code (e.g. ``"de"``, ``"uk"``) or + ``None`` if the request fails or the field is absent. + + Example:: + + provider = DiscoveryProvider(country="de") + country = provider.get_user_country() + # → "de" (or whatever territory the server reports) + """ + try: + headers = self._get_auth_headers() + response = self.http_manager.get( + DISCOVERY_USERS_ME_URL, + operation="users_me", + headers=headers, + ) + response.raise_for_status() + data = response.json() + territory = ( + data.get("data", {}) + .get("attributes", {}) + .get("currentLocationTerritory") + ) + if territory: + country_code = territory.lower() + logger.debug( + f"Detected user country from /users/me: {country_code}" + ) + return country_code + logger.warning("/users/me response missing currentLocationTerritory") + return None + except Exception as e: + logger.error(f"Failed to fetch user country from /users/me: {e}") + return None + def get_channels( self, fetch_manifests: bool = False, diff --git a/lib/streaming_providers/providers/rtlplus/constants.py b/lib/streaming_providers/providers/rtlplus/constants.py index 3338804..5cdb3d8 100644 --- a/lib/streaming_providers/providers/rtlplus/constants.py +++ b/lib/streaming_providers/providers/rtlplus/constants.py @@ -17,6 +17,7 @@ class RTLPlusDefaults: # Device information DEVICE_ID = "8c3f37cc-13a3-4141-bd0f-e4b3673fe5e4" DEVICE_NAME = "Linux Chrome" + PLAYREADY_DEVICE_NAME = "Windows Edge" # User Agent components USER_AGENT = f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{CHROME_VERSION} Safari/537.36" @@ -132,7 +133,7 @@ class RTLPlusHeaders: "User-Agent": RTLPlusDefaults.PLAYREADY_USER_AGENT, "X-Auth-Token": access_token, "X-Device-Id": device_id or RTLPlusDefaults.DEVICE_ID, - "X-Device-Name": RTLPlusDefaults.DEVICE_NAME, + "X-Device-Name": RTLPlusDefaults.PLAYREADY_DEVICE_NAME, }