Files
script.service.ultimate/lib/streaming_providers/providers/eon/provider.py
T
2026-10-19 14:28:32 +02:00

138 lines
5.1 KiB
Python

# streaming_providers/providers/eon/provider.py
"""
Eon provider -- orchestrator only.
The provider owns the SHARED resources (config, http_manager, endpoints,
auth, api, catalog, caches) and BUILDS the managers. Everything identical
across providers is inherited from ManagedProvider.
Eon is a white-label platform: `config["brand"]` (vivacom, nova, sbb,
telemach, telemach-hr, telemach-si) picks the operator, `config["platform"]`
("web" / "androidtv") the client identity. Geo-unblocking users also need
Kodi's global proxy: the CDN requests are made by inputstream.adaptive, which
does not use the backend's ProxyConfig.
"""
from typing import ClassVar, Dict, List, Optional
from ...base.managed_provider import ManagedProvider
from ...base.models.proxy_models import ProxyConfig
from .api import EonApi
from .auth import EonAuth, EonStore
from .bootstrap import EonCatalog, EonEndpoints
from .catchup_manager import EonCatchupManager
from .channel_manager import EonChannelManager
from .constants import EonConfig, EonDefaults
from .epg_manager import EonEpgManager
__all__ = ["EonProvider"]
class EonProvider(ManagedProvider):
PROVIDER_LABEL: ClassVar[str] = EonDefaults.PROVIDER_LABEL
PROVIDER_LOGO: ClassVar[str] = EonDefaults.PROVIDER_LOGO
SUPPORTED_AUTH_TYPES: ClassVar[List[str]] = ["user_credentials"]
SUPPORTED_COUNTRIES: ClassVar[List[str]] = list(EonDefaults.SUPPORTED_COUNTRIES)
# Eon streams are clear HLS: no DRM anywhere.
DRM_IN_MANAGERS: ClassVar[bool] = False
# CDN requests need the browser / smart-TV UA the channel manager returns.
HEADERS_FROM_MANAGERS: ClassVar[bool] = True
# The backend's clear-HLS check fetches the first variant playlist of the
# master. The addon resolves variant URIs WITHOUT inheriting the master's
# query (it carries the encrypted payload), and plays fine, so variant URIs
# are self-sufficient: do not append the master query to them.
inherit_manifest_query_on_segments: ClassVar[bool] = False
@property
def provider_name(self) -> str:
return EonDefaults.PROVIDER_NAME
def __init__(
self,
country: str = "BG",
config: Optional[Dict] = None,
proxy_config: Optional[ProxyConfig] = None,
store: Optional[EonStore] = None,
):
super().__init__(country=country)
self.country = self.country.lower()
# 1. ONE config object, shared by every layer.
self.provider_config = EonConfig(config or {})
# 2. HTTP manager.
self.http_manager = self._setup_http_manager(
provider_name=EonDefaults.PROVIDER_NAME,
proxy_config=proxy_config,
user_agent=self.provider_config.user_agent,
timeout=self.provider_config.timeout,
)
# 3. Lazy collaborators: no network in __init__.
self.endpoints = EonEndpoints(
http_manager=self.http_manager, config=self.provider_config
)
self.auth = EonAuth(
http_manager=self.http_manager, config=self.provider_config,
endpoints=self.endpoints, store=store,
)
self.api = EonApi(
http_manager=self.http_manager, auth=self.auth,
endpoints=self.endpoints, config=self.provider_config,
)
self.catalog = EonCatalog(api=self.api, config=self.provider_config)
# 4. Provider-owned caches, borrowed by managers by reference.
self._url_cache: Dict = {}
# 5. Managers. NOTE: self.channels is now the ChannelManager.
self._init_managers()
def _build_channels(self):
return EonChannelManager(
http_manager=self.http_manager,
auth=self.auth,
country=self.country,
config=self.provider_config,
api=self.api,
endpoints=self.endpoints,
catalog=self.catalog,
url_cache=self._url_cache,
)
def _build_epg(self):
return EonEpgManager(
http_manager=self.http_manager,
auth=self.auth,
country=self.country,
config=self.provider_config,
api=self.api,
endpoints=self.endpoints,
)
def _build_catchup(self):
# Needs the channel manager (it mints the signed URL): built after it.
return EonCatchupManager(
http_manager=self.http_manager,
auth=self.auth,
country=self.country,
config=self.provider_config,
channels=self.channels,
)
# ------------------------------------------------------------------
# Credentials API for the settings UI
# ------------------------------------------------------------------
def set_user_credentials(self, username: str, password: str) -> bool:
"""Store credentials and log in once; True on success."""
from types import SimpleNamespace
self.auth.set_credentials(SimpleNamespace(username=username, password=password))
self._url_cache.clear()
self.catalog.invalidate()
try:
self.auth.get_access_token()
except Exception:
return False
return True