Files
script.service.ultimate/lib/streaming_providers/base/models/subscription.py
T
2026-01-06 16:41:03 +01:00

134 lines
4.4 KiB
Python

"""
Subscription models for provider packages and user entitlements.
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, List, Optional, Set
@dataclass
class SubscriptionPackage:
"""Represents a subscription package with provider-specific naming"""
package_id: str
"""Internal ID (e.g., 'sports_package_2024', 'joyn_plus')"""
name: str
"""Display name (e.g., 'Sports Package', 'Sky Sport', 'Joyn Plus')"""
description: Optional[str] = None
"""Optional description of the package"""
price_info: Optional[str] = None
"""Optional price information (e.g., '€9.99/month', 'included')"""
channel_ids: List[str] = field(default_factory=list)
"""List of channel IDs included in this package"""
metadata: Dict[str, any] = field(default_factory=dict)
"""Provider-specific metadata (e.g., {'sky_id': 'SPORT1', 'category': 'sports'})"""
@property
def channel_count(self) -> int:
"""Number of channels in this package"""
return len(self.channel_ids)
@dataclass
class UserSubscription:
"""User's subscription status for a provider"""
provider: str
"""Provider name (e.g., 'joyn', 'magenta')"""
country: str
"""Country code (e.g., 'DE', 'AT')"""
active: bool = False
"""Whether the subscription is currently active"""
packages: List[SubscriptionPackage] = field(default_factory=list)
"""All subscription packages the user has access to"""
accessible_channel_ids: Set[str] = field(default_factory=set)
"""Set of all channel IDs the user can access (derived from packages)"""
valid_from: Optional[datetime] = None
"""When the subscription becomes/starts valid"""
valid_until: Optional[datetime] = None
"""When the subscription expires"""
status_message: Optional[str] = None
"""Human-readable status message (e.g., 'Active until 2024-12-31')"""
billing_status: Optional[str] = None
"""Billing status (e.g., 'paid', 'trial', 'expired', 'cancelled')"""
def __post_init__(self):
"""Populate derived fields after initialization"""
self._update_derived_fields()
def _update_derived_fields(self):
"""Update derived fields like accessible_channel_ids"""
self.accessible_channel_ids.clear()
for package in self.packages:
self.accessible_channel_ids.update(package.channel_ids)
@property
def has_packages(self) -> bool:
"""Check if user has any packages"""
return len(self.packages) > 0
@property
def package_count(self) -> int:
"""Number of packages"""
return len(self.packages)
@property
def accessible_channel_count(self) -> int:
"""Number of accessible channels"""
return len(self.accessible_channel_ids)
@property
def package_names(self) -> List[str]:
"""List of package names for display"""
return [pkg.name for pkg in self.packages]
def to_dict(self) -> Dict:
"""Convert to dictionary for JSON serialization"""
return {
"provider": self.provider,
"country": self.country,
"active": self.active,
"has_packages": self.has_packages,
"package_count": self.package_count,
"package_names": self.package_names,
"accessible_channel_count": self.accessible_channel_count,
"valid_from": self.valid_from.isoformat() if self.valid_from else None,
"valid_until": self.valid_until.isoformat() if self.valid_until else None,
"status_message": self.status_message,
"billing_status": self.billing_status,
"packages": [
{
"package_id": pkg.package_id,
"name": pkg.name,
"description": pkg.description,
"price_info": pkg.price_info,
"channel_count": pkg.channel_count,
"metadata": pkg.metadata,
}
for pkg in self.packages
],
}
def add_package(self, package: SubscriptionPackage):
"""Add a package and update derived fields"""
self.packages.append(package)
self._update_derived_fields()
def can_access_channel(self, channel_id: str) -> bool:
"""Check if user can access a specific channel"""
return channel_id in self.accessible_channel_ids