mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-10-21 07:12:38 +02:00
185 lines
7.1 KiB
Python
185 lines
7.1 KiB
Python
# streaming_providers/providers/magentaeu/vod_errors.py
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Typed exceptions for the MagentaEU VOD path.
|
|
|
|
Rebased on the shared hierarchy in base.errors. The class names are
|
|
unchanged so existing callers (except VodError: ...) keep working; the
|
|
only change is what these classes inherit from.
|
|
|
|
Rationale: the bifrost API returns 4xx for several distinct conditions --
|
|
expired token, geo-block, entitlement denial, content removed -- and they
|
|
need to be handled differently. `raise_for_status()` alone collapses them
|
|
into one opaque HTTPError. VODOperations and the Kodi player layer need
|
|
to distinguish "refresh and retry" from "tell the user they can't watch
|
|
this here" from "this title is gone" from "this is actually catch-up,
|
|
not VOD -- hand it to the channel/catchup pathway instead".
|
|
|
|
Hierarchy (now rebased on base.errors):
|
|
|
|
VodError (base, catch-all -- subclass of ProviderError)
|
|
├── VodAuthError (401 -- token expired/invalid)
|
|
├── VodGeoBlockError (403 -- not available in your region)
|
|
├── VodEntitlementError (403 -- not in your subscription)
|
|
│ └── VodAccountVodDisabledError (account-level VOD gate is off)
|
|
├── VodNotFoundError (404 -- content removed)
|
|
├── VodBadRequestError (400 -- routing signal)
|
|
├── VodRateLimitError (429)
|
|
├── VodServerError (5xx -- retryable)
|
|
├── VodCatchupRequiredError (item has no watch/trailer action but
|
|
│ DOES have schedules/catchup_schedules
|
|
│ -- it's a linear-catchup item, not a
|
|
│ playable VOD asset)
|
|
└── VodNotImplementedError (feature captured-but-not-yet-mapped)
|
|
|
|
Callers can now catch either the local name (VodAuthError) or the shared
|
|
base (AuthError) depending on their intent.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from ...base.errors import (
|
|
AuthError,
|
|
BadRequestError,
|
|
CatchupRequiredError,
|
|
EntitlementError,
|
|
GeoBlockError,
|
|
NotFoundError,
|
|
NotImplementedYetError,
|
|
ProviderError,
|
|
RateLimitError,
|
|
ServerError,
|
|
)
|
|
|
|
|
|
class VodError(ProviderError):
|
|
"""
|
|
Base class for all MagentaEU VOD errors.
|
|
|
|
Kept as a distinct name so existing `except VodError:` sites continue
|
|
to work; it is now a subclass of the shared ProviderError, so callers
|
|
that prefer the shared name (`except ProviderError:`) also catch it.
|
|
"""
|
|
|
|
|
|
class VodAuthError(AuthError, VodError):
|
|
"""
|
|
Raised on 401 or on a locally-detected expired Bff_token.
|
|
|
|
`MagentaEUVodManager._request()` catches this internally, refreshes
|
|
the token once via a forced re-authentication, and retries the
|
|
request exactly once. If the retry also fails, VodAuthError
|
|
propagates to the caller -- at that point re-auth is not going to
|
|
fix it (credentials are likely actually invalid).
|
|
"""
|
|
|
|
|
|
class VodGeoBlockError(GeoBlockError, VodError):
|
|
"""Content is geo-blocked for the current network egress."""
|
|
|
|
|
|
class VodEntitlementError(EntitlementError, VodError):
|
|
"""
|
|
The subscriber is authenticated but not entitled to this content.
|
|
|
|
Raised by the actions endpoint when `actions.watch` is empty and
|
|
`svod_subscription_message` is present, AND the item is not a
|
|
catchup-eligible linear item (see VodCatchupRequiredError -- that
|
|
takes priority when both watch/trailer are empty).
|
|
"""
|
|
|
|
|
|
class VodAccountVodDisabledError(VodEntitlementError):
|
|
"""
|
|
Account-level VOD gate is off.
|
|
|
|
This is distinct from a per-title entitlement error: the account's
|
|
`managed_settings["TVSOA-setting-VodEnabled"]` is "false", which
|
|
means *no* VOD can be played, regardless of package. The captured
|
|
HR test account ships in this state (`vod_enabled: false` at
|
|
/user/account and echoed in every X-Account-Details header).
|
|
Callers should surface this differently than "you need to add the
|
|
HBO package".
|
|
"""
|
|
|
|
|
|
class VodNotFoundError(NotFoundError, VodError):
|
|
"""Content has been removed or never existed."""
|
|
|
|
|
|
class VodBadRequestError(BadRequestError, VodError):
|
|
"""
|
|
400 -- malformed request for the endpoint called.
|
|
|
|
Confirmed in production (not just capture): passing a component id
|
|
to `/home/page/{id}` returns 400, not 404. `get_category_children()`'s
|
|
page-then-component dispatch guess relies on this to know when its
|
|
first guess was wrong, since content_id alone doesn't reveal
|
|
whether it names a page or a component.
|
|
"""
|
|
|
|
|
|
class VodRateLimitError(RateLimitError, VodError):
|
|
"""429 -- caller should back off."""
|
|
|
|
|
|
class VodServerError(ServerError, VodError):
|
|
"""5xx -- retryable."""
|
|
|
|
|
|
class VodCatchupRequiredError(CatchupRequiredError, VodError):
|
|
"""
|
|
This "VOD" list entry is actually a catch-up item from a linear
|
|
channel, not a TVOD/SVOD asset.
|
|
|
|
Evidence from capture: some rail/search items marked as VOD series
|
|
(e.g. "Nakon poplave, serija") return `actions.watch: []` and
|
|
`actions.trailer: []` -- same shape as a genuine entitlement denial
|
|
-- but ALSO carry a non-empty `actions.schedules` / a `station_id`
|
|
/`channel_number`, and the episode list includes populated
|
|
`catchup_schedules[]` entries with their own `pid` and
|
|
`catchup_start_utc`/`catchup_end_utc` window. Treating this as a
|
|
plain VodEntitlementError is wrong: the content IS watchable, just
|
|
via the existing live-channel catchup path
|
|
(`provider.get_catchup_manifest()`), not via
|
|
`MagentaEUVodManager.get_manifest()`.
|
|
|
|
This exception carries the raw schedule/catchup data so a caller
|
|
that wants to bridge into the catchup flow can do so without a
|
|
second round-trip. Bridging itself (mapping station_id +
|
|
catchup_start_utc/catchup_end_utc into a get_catchup_manifest()
|
|
call) is NOT implemented here -- it needs the channel/EPG manager,
|
|
which this module deliberately has no dependency on. Treat this as
|
|
a routing signal, not a playback failure.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
*,
|
|
station_id: Optional[str] = None,
|
|
schedules: Optional[List[Dict[str, Any]]] = None,
|
|
catchup_schedules: Optional[List[Dict[str, Any]]] = None,
|
|
status: Optional[int] = None,
|
|
url: Optional[str] = None,
|
|
) -> None:
|
|
super().__init__(message, status=status, url=url)
|
|
self.station_id = station_id
|
|
self.schedules = schedules or []
|
|
self.catchup_schedules = catchup_schedules or []
|
|
|
|
|
|
class VodNotImplementedError(NotImplementedYetError, VodError):
|
|
"""
|
|
Raised by methods whose endpoint shape has not been captured yet.
|
|
|
|
Current cases:
|
|
* pricing resolution (needs a non-empty rent[]/purchase[] sample)
|
|
* recommendations rails (needs the recommendations endpoint)
|
|
* related-content parsing beyond the raw pass-through in
|
|
get_related_content() (field shapes only partially captured)
|
|
These raise rather than returning empty so callers can't silently
|
|
ship with a bug they don't know about.
|
|
""" |