mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-20 08:02:28 +02:00
628 lines
22 KiB
Python
628 lines
22 KiB
Python
# streaming_providers/base/settings/models/provider_settings.py
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Dict, List, Optional, Set
|
|
|
|
from ...utils.logger import logger
|
|
from .settings_models import (
|
|
SettingValue,
|
|
boolean_setting,
|
|
integer_setting,
|
|
password_setting,
|
|
port_setting,
|
|
select_setting,
|
|
string_setting,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class ProviderSettingsSchema:
|
|
"""Schema definition for a provider's settings"""
|
|
|
|
provider_name: str
|
|
_settings: Dict[str, SettingValue] = field(default_factory=dict)
|
|
_categories: Dict[str, Set[str]] = field(default_factory=dict)
|
|
_kodi_mapping: Dict[str, str] = field(default_factory=dict)
|
|
|
|
def __post_init__(self):
|
|
"""Initialize schema with default settings"""
|
|
self._build_default_schema()
|
|
|
|
def _build_default_schema(self) -> None:
|
|
"""Build the complete settings schema for this provider"""
|
|
# Build all setting categories
|
|
credential_settings = self.define_credential_settings()
|
|
proxy_settings = self.define_proxy_settings()
|
|
video_settings = self.define_video_settings()
|
|
drm_settings = self.define_drm_settings()
|
|
network_settings = self.define_network_settings()
|
|
|
|
# Merge all settings
|
|
self._settings.update(credential_settings)
|
|
self._settings.update(proxy_settings)
|
|
self._settings.update(video_settings)
|
|
self._settings.update(drm_settings)
|
|
self._settings.update(network_settings)
|
|
|
|
# Build category mappings
|
|
self._categories = {
|
|
"credentials": set(credential_settings.keys()),
|
|
"proxy": set(proxy_settings.keys()),
|
|
"video": set(video_settings.keys()),
|
|
"drm": set(drm_settings.keys()),
|
|
"network": set(network_settings.keys()),
|
|
}
|
|
|
|
# Build Kodi mapping
|
|
self._build_kodi_mapping()
|
|
|
|
def _build_kodi_mapping(self) -> None:
|
|
"""Build mapping from internal setting names to Kodi setting IDs"""
|
|
for setting_name, setting in self._settings.items():
|
|
if setting.kodi_setting_id:
|
|
self._kodi_mapping[setting_name] = setting.kodi_setting_id
|
|
|
|
def define_credential_settings(self) -> Dict[str, SettingValue]:
|
|
"""Define credential-related settings for this provider"""
|
|
settings = {}
|
|
|
|
# Common credential settings - can be overridden in subclasses
|
|
settings["username"] = (
|
|
string_setting()
|
|
.required()
|
|
.min_length(1)
|
|
.display_name("Username")
|
|
.description("Account username or email address")
|
|
.kodi_setting(f"{self.provider_name}_username")
|
|
.build()
|
|
)
|
|
|
|
settings["password"] = (
|
|
password_setting()
|
|
.display_name("Password")
|
|
.description("Account password")
|
|
.kodi_setting(f"{self.provider_name}_password")
|
|
.build()
|
|
)
|
|
|
|
# Optional client ID for OAuth providers
|
|
settings["client_id"] = (
|
|
string_setting()
|
|
.display_name("Client ID")
|
|
.description("OAuth client identifier (if required)")
|
|
.kodi_setting(f"{self.provider_name}_client_id")
|
|
.build()
|
|
)
|
|
|
|
# Optional client secret for OAuth providers
|
|
settings["client_secret"] = (
|
|
password_setting(required=False)
|
|
.display_name("Client Secret")
|
|
.description("OAuth client secret (if required)")
|
|
.kodi_setting(f"{self.provider_name}_client_secret")
|
|
.build()
|
|
)
|
|
|
|
return settings
|
|
|
|
def define_proxy_settings(self) -> Dict[str, SettingValue]:
|
|
"""Define proxy-related settings for this provider"""
|
|
settings = {}
|
|
|
|
# Enable proxy for this provider
|
|
settings["proxy_enabled"] = (
|
|
boolean_setting(False)
|
|
.display_name("Enable Proxy")
|
|
.description("Use proxy for this provider")
|
|
.kodi_setting(f"{self.provider_name}_proxy_enabled")
|
|
.build()
|
|
)
|
|
|
|
# Proxy host
|
|
settings["proxy_host"] = (
|
|
string_setting()
|
|
.display_name("Proxy Host")
|
|
.description("Proxy server hostname or IP address")
|
|
.kodi_setting(f"{self.provider_name}_proxy_host")
|
|
.build()
|
|
)
|
|
|
|
# Proxy port
|
|
settings["proxy_port"] = (
|
|
port_setting(8080)
|
|
.display_name("Proxy Port")
|
|
.description("Proxy server port")
|
|
.kodi_setting(f"{self.provider_name}_proxy_port")
|
|
.build()
|
|
)
|
|
|
|
# Proxy type
|
|
settings["proxy_type"] = (
|
|
select_setting(["http", "https", "socks4", "socks5"], "http")
|
|
.display_name("Proxy Type")
|
|
.description("Type of proxy server")
|
|
.kodi_setting(f"{self.provider_name}_proxy_type")
|
|
.build()
|
|
)
|
|
|
|
# Proxy authentication
|
|
settings["proxy_auth_enabled"] = (
|
|
boolean_setting(False)
|
|
.display_name("Proxy Authentication")
|
|
.description("Proxy server requires authentication")
|
|
.kodi_setting(f"{self.provider_name}_proxy_auth_enabled")
|
|
.build()
|
|
)
|
|
|
|
# Proxy username
|
|
settings["proxy_username"] = (
|
|
string_setting()
|
|
.display_name("Proxy Username")
|
|
.description("Username for proxy authentication")
|
|
.kodi_setting(f"{self.provider_name}_proxy_username")
|
|
.build()
|
|
)
|
|
|
|
# Proxy password
|
|
settings["proxy_password"] = (
|
|
password_setting(required=False)
|
|
.display_name("Proxy Password")
|
|
.description("Password for proxy authentication")
|
|
.kodi_setting(f"{self.provider_name}_proxy_password")
|
|
.build()
|
|
)
|
|
|
|
# Proxy scope settings
|
|
settings["proxy_scope_api"] = (
|
|
boolean_setting(True)
|
|
.display_name("Proxy API Calls")
|
|
.description("Use proxy for API requests")
|
|
.kodi_setting(f"{self.provider_name}_proxy_scope_api")
|
|
.build()
|
|
)
|
|
|
|
settings["proxy_scope_auth"] = (
|
|
boolean_setting(True)
|
|
.display_name("Proxy Authentication")
|
|
.description("Use proxy for authentication requests")
|
|
.kodi_setting(f"{self.provider_name}_proxy_scope_auth")
|
|
.build()
|
|
)
|
|
|
|
settings["proxy_scope_manifest"] = (
|
|
boolean_setting(True)
|
|
.display_name("Proxy Manifests")
|
|
.description("Use proxy for manifest downloads")
|
|
.kodi_setting(f"{self.provider_name}_proxy_scope_manifest")
|
|
.build()
|
|
)
|
|
|
|
settings["proxy_scope_license"] = (
|
|
boolean_setting(True)
|
|
.display_name("Proxy DRM Licenses")
|
|
.description("Use proxy for DRM license requests")
|
|
.kodi_setting(f"{self.provider_name}_proxy_scope_license")
|
|
.build()
|
|
)
|
|
|
|
return settings
|
|
|
|
def define_video_settings(self) -> Dict[str, SettingValue]:
|
|
"""Define video quality/format settings for this provider"""
|
|
settings = {}
|
|
|
|
# Video quality preference
|
|
settings["video_quality"] = (
|
|
select_setting(["best", "worst", "720p", "1080p", "4k"], "best")
|
|
.display_name("Video Quality")
|
|
.description("Preferred video quality")
|
|
.kodi_setting(f"{self.provider_name}_video_quality")
|
|
.build()
|
|
)
|
|
|
|
# Audio language preference
|
|
settings["audio_language"] = (
|
|
select_setting(["de", "en", "original"], "de")
|
|
.display_name("Audio Language")
|
|
.description("Preferred audio language")
|
|
.kodi_setting(f"{self.provider_name}_audio_language")
|
|
.build()
|
|
)
|
|
|
|
# Subtitle language preference
|
|
settings["subtitle_language"] = (
|
|
select_setting(["none", "de", "en", "auto"], "none")
|
|
.display_name("Subtitle Language")
|
|
.description("Preferred subtitle language")
|
|
.kodi_setting(f"{self.provider_name}_subtitle_language")
|
|
.build()
|
|
)
|
|
|
|
# Speed up streams
|
|
settings["speed_up"] = (
|
|
boolean_setting(True)
|
|
.display_name("Speed Up Playback")
|
|
.description("Enable faster stream startup")
|
|
.kodi_setting(f"{self.provider_name}_speed_up")
|
|
.build()
|
|
)
|
|
|
|
return settings
|
|
|
|
def define_drm_settings(self) -> Dict[str, SettingValue]:
|
|
"""Define DRM/CDM related settings for this provider"""
|
|
settings = {}
|
|
|
|
# CDM usage
|
|
settings["use_cdm"] = (
|
|
boolean_setting(True)
|
|
.display_name("Use CDM")
|
|
.description("Enable Content Decryption Module for DRM")
|
|
.kodi_setting(f"{self.provider_name}_use_cdm")
|
|
.build()
|
|
)
|
|
|
|
# CDM type
|
|
settings["cdm_type"] = (
|
|
select_setting(["widevine", "playready", "clearkey"], "widevine")
|
|
.display_name("CDM Type")
|
|
.description("Type of Content Decryption Module")
|
|
.kodi_setting(f"{self.provider_name}_cdm_type")
|
|
.build()
|
|
)
|
|
|
|
# CDM mode
|
|
settings["cdm_mode"] = (
|
|
select_setting(["external", "internal", "auto"], "external")
|
|
.display_name("CDM Mode")
|
|
.description("How to handle CDM operations")
|
|
.kodi_setting(f"{self.provider_name}_cdm_mode")
|
|
.build()
|
|
)
|
|
|
|
return settings
|
|
|
|
def define_network_settings(self) -> Dict[str, SettingValue]:
|
|
"""Define network timeout/retry settings for this provider"""
|
|
settings = {}
|
|
|
|
# Request timeout
|
|
settings["request_timeout"] = (
|
|
integer_setting(30, 5, 120)
|
|
.display_name("Request Timeout")
|
|
.description("Timeout for network requests in seconds")
|
|
.kodi_setting(f"{self.provider_name}_request_timeout")
|
|
.build()
|
|
)
|
|
|
|
# Max retries
|
|
settings["max_retries"] = (
|
|
integer_setting(3, 0, 10)
|
|
.display_name("Max Retries")
|
|
.description("Maximum number of retry attempts")
|
|
.kodi_setting(f"{self.provider_name}_max_retries")
|
|
.build()
|
|
)
|
|
|
|
# Retry delay
|
|
settings["retry_delay"] = (
|
|
integer_setting(1, 0, 10)
|
|
.display_name("Retry Delay")
|
|
.description("Delay between retry attempts in seconds")
|
|
.kodi_setting(f"{self.provider_name}_retry_delay")
|
|
.build()
|
|
)
|
|
|
|
# Verify SSL
|
|
settings["verify_ssl"] = (
|
|
boolean_setting(True)
|
|
.display_name("Verify SSL")
|
|
.description("Verify SSL certificates for HTTPS requests")
|
|
.kodi_setting(f"{self.provider_name}_verify_ssl")
|
|
.build()
|
|
)
|
|
|
|
return settings
|
|
|
|
def get_all_settings(self) -> Dict[str, SettingValue]:
|
|
"""Get complete settings schema for this provider"""
|
|
return self._settings.copy()
|
|
|
|
def get_setting(self, setting_name: str) -> Optional[SettingValue]:
|
|
"""Get a specific setting by name"""
|
|
return self._settings.get(setting_name)
|
|
|
|
def get_settings_by_category(self, category: str) -> Dict[str, SettingValue]:
|
|
"""Get all settings in a specific category"""
|
|
if category not in self._categories:
|
|
return {}
|
|
|
|
category_settings = {}
|
|
for setting_name in self._categories[category]:
|
|
if setting_name in self._settings:
|
|
category_settings[setting_name] = self._settings[setting_name]
|
|
|
|
return category_settings
|
|
|
|
def get_categories(self) -> List[str]:
|
|
"""Get list of all setting categories"""
|
|
return list(self._categories.keys())
|
|
|
|
def get_kodi_setting_mapping(self) -> Dict[str, str]:
|
|
"""Get mapping from internal setting names to Kodi setting IDs"""
|
|
return self._kodi_mapping.copy()
|
|
|
|
def get_reverse_kodi_mapping(self) -> Dict[str, str]:
|
|
"""Get mapping from Kodi setting IDs to internal setting names"""
|
|
return {v: k for k, v in self._kodi_mapping.items()}
|
|
|
|
def validate_all_settings(self) -> Dict[str, tuple[bool, List[str]]]:
|
|
"""Validate all settings in the schema"""
|
|
results = {}
|
|
for setting_name, setting in self._settings.items():
|
|
results[setting_name] = setting.validate()
|
|
return results
|
|
|
|
def get_required_settings(self) -> List[str]:
|
|
"""Get list of setting names that are required"""
|
|
required = []
|
|
for setting_name, setting in self._settings.items():
|
|
# Check if setting has a "not_empty" validation rule
|
|
has_required_rule = any(rule.name == "not_empty" for rule in setting.validation_rules)
|
|
if has_required_rule:
|
|
required.append(setting_name)
|
|
return required
|
|
|
|
def get_incomplete_settings(self) -> List[str]:
|
|
"""Get list of required settings that don't have values"""
|
|
incomplete = []
|
|
required_settings = self.get_required_settings()
|
|
|
|
for setting_name in required_settings:
|
|
setting = self._settings[setting_name]
|
|
if not setting.has_value():
|
|
incomplete.append(setting_name)
|
|
|
|
return incomplete
|
|
|
|
def is_configuration_complete(self) -> bool:
|
|
"""Check if all required settings have values"""
|
|
return len(self.get_incomplete_settings()) == 0
|
|
|
|
def get_configuration_completeness(self) -> Dict[str, Any]:
|
|
"""Get detailed information about configuration completeness"""
|
|
required_settings = self.get_required_settings()
|
|
incomplete_settings = self.get_incomplete_settings()
|
|
|
|
completeness_by_category = {}
|
|
for category in self.get_categories():
|
|
category_settings = self.get_settings_by_category(category)
|
|
category_required = [
|
|
name for name in category_settings.keys() if name in required_settings
|
|
]
|
|
category_incomplete = [
|
|
name for name in category_settings.keys() if name in incomplete_settings
|
|
]
|
|
|
|
completeness_by_category[category] = {
|
|
"total_settings": len(category_settings),
|
|
"required_settings": len(category_required),
|
|
"incomplete_settings": len(category_incomplete),
|
|
"is_complete": len(category_incomplete) == 0,
|
|
"completion_percentage": (
|
|
100.0
|
|
if len(category_required) == 0
|
|
else (
|
|
(len(category_required) - len(category_incomplete))
|
|
/ len(category_required)
|
|
* 100
|
|
)
|
|
),
|
|
}
|
|
|
|
return {
|
|
"is_complete": self.is_configuration_complete(),
|
|
"total_settings": len(self._settings),
|
|
"required_settings": len(required_settings),
|
|
"incomplete_settings": len(incomplete_settings),
|
|
"completion_percentage": (
|
|
100.0
|
|
if len(required_settings) == 0
|
|
else (
|
|
(len(required_settings) - len(incomplete_settings))
|
|
/ len(required_settings)
|
|
* 100
|
|
)
|
|
),
|
|
"categories": completeness_by_category,
|
|
"incomplete_setting_names": incomplete_settings,
|
|
}
|
|
|
|
def add_custom_setting(
|
|
self, setting_name: str, setting: SettingValue, category: str = "custom"
|
|
) -> None:
|
|
"""Add a custom setting to the schema"""
|
|
self._settings[setting_name] = setting
|
|
|
|
if category not in self._categories:
|
|
self._categories[category] = set()
|
|
self._categories[category].add(setting_name)
|
|
|
|
if setting.kodi_setting_id:
|
|
self._kodi_mapping[setting_name] = setting.kodi_setting_id
|
|
|
|
def remove_setting(self, setting_name: str) -> bool:
|
|
"""Remove a setting from the schema"""
|
|
if setting_name not in self._settings:
|
|
return False
|
|
|
|
# Remove from settings
|
|
del self._settings[setting_name]
|
|
|
|
# Remove from categories
|
|
for category_settings in self._categories.values():
|
|
category_settings.discard(setting_name)
|
|
|
|
# Remove from Kodi mapping
|
|
if setting_name in self._kodi_mapping:
|
|
del self._kodi_mapping[setting_name]
|
|
|
|
return True
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert schema to dictionary representation"""
|
|
return {
|
|
"provider_name": self.provider_name,
|
|
"settings": {name: setting.to_dict() for name, setting in self._settings.items()},
|
|
"categories": {
|
|
category: list(settings) for category, settings in self._categories.items()
|
|
},
|
|
"kodi_mapping": self._kodi_mapping.copy(),
|
|
"configuration_status": self.get_configuration_completeness(),
|
|
}
|
|
|
|
|
|
class StandardProviderSettings:
|
|
"""Standard settings schemas for common providers"""
|
|
|
|
_registered_schemas: Dict[str, ProviderSettingsSchema] = {}
|
|
|
|
@classmethod
|
|
def get_rtlplus_schema(cls) -> ProviderSettingsSchema:
|
|
"""Get RTL Plus settings schema"""
|
|
if "rtlplus" not in cls._registered_schemas:
|
|
schema = ProviderSettingsSchema("rtlplus")
|
|
|
|
# RTL Plus specific customizations
|
|
# Override default credential settings for RTL Plus specifics
|
|
schema._settings["username"].description = "RTL Plus account email address"
|
|
|
|
# Add RTL Plus specific settings
|
|
schema.add_custom_setting(
|
|
"device_id",
|
|
string_setting()
|
|
.display_name("Device ID")
|
|
.description("Unique device identifier for RTL Plus")
|
|
.kodi_setting("rtlplus_device_id")
|
|
.build(),
|
|
"credentials",
|
|
)
|
|
|
|
cls._registered_schemas["rtlplus"] = schema
|
|
|
|
return cls._registered_schemas["rtlplus"]
|
|
|
|
@classmethod
|
|
def get_joyn_schema(cls) -> ProviderSettingsSchema:
|
|
"""Get Joyn settings schema"""
|
|
if "joyn" not in cls._registered_schemas:
|
|
schema = ProviderSettingsSchema("joyn")
|
|
|
|
# Joyn doesn't require authentication for basic content
|
|
schema._settings["username"].validation_rules = [
|
|
rule
|
|
for rule in schema._settings["username"].validation_rules
|
|
if rule.name != "not_empty"
|
|
]
|
|
schema._settings["password"].validation_rules = [
|
|
rule
|
|
for rule in schema._settings["password"].validation_rules
|
|
if rule.name != "not_empty"
|
|
]
|
|
|
|
cls._registered_schemas["joyn"] = schema
|
|
|
|
return cls._registered_schemas["joyn"]
|
|
|
|
@classmethod
|
|
def get_zdf_schema(cls) -> ProviderSettingsSchema:
|
|
"""Get ZDF settings schema"""
|
|
if "zdf" not in cls._registered_schemas:
|
|
schema = ProviderSettingsSchema("zdf")
|
|
|
|
# ZDF is free, no authentication required
|
|
schema.remove_setting("username")
|
|
schema.remove_setting("password")
|
|
schema.remove_setting("client_id")
|
|
schema.remove_setting("client_secret")
|
|
|
|
# Add ZDF specific settings
|
|
schema.add_custom_setting(
|
|
"geo_location",
|
|
select_setting(["DE", "AT", "CH"], "DE")
|
|
.display_name("Geographic Location")
|
|
.description("Your geographic location for content filtering")
|
|
.kodi_setting("zdf_geo_location")
|
|
.build(),
|
|
"network",
|
|
)
|
|
|
|
cls._registered_schemas["zdf"] = schema
|
|
|
|
return cls._registered_schemas["zdf"]
|
|
|
|
@classmethod
|
|
def get_ard_schema(cls) -> ProviderSettingsSchema:
|
|
"""Get ARD settings schema"""
|
|
if "ard" not in cls._registered_schemas:
|
|
schema = ProviderSettingsSchema("ard")
|
|
|
|
# ARD is free, no authentication required
|
|
schema.remove_setting("username")
|
|
schema.remove_setting("password")
|
|
schema.remove_setting("client_id")
|
|
schema.remove_setting("client_secret")
|
|
|
|
cls._registered_schemas["ard"] = schema
|
|
|
|
return cls._registered_schemas["ard"]
|
|
|
|
@classmethod
|
|
def register_provider_schema(cls, provider_name: str, schema: ProviderSettingsSchema) -> None:
|
|
"""Register a custom provider schema"""
|
|
cls._registered_schemas[provider_name] = schema
|
|
logger.info(f"Registered custom settings schema for provider: {provider_name}")
|
|
|
|
@classmethod
|
|
def get_provider_schema(cls, provider_name: str) -> Optional[ProviderSettingsSchema]:
|
|
"""Get schema for a provider, creating default if not found"""
|
|
# Check if we have a specific schema method
|
|
method_name = f"get_{provider_name}_schema"
|
|
if hasattr(cls, method_name):
|
|
return getattr(cls, method_name)()
|
|
|
|
# Check registered schemas
|
|
if provider_name in cls._registered_schemas:
|
|
return cls._registered_schemas[provider_name]
|
|
|
|
# Create default schema
|
|
logger.info(f"Creating default settings schema for provider: {provider_name}")
|
|
schema = ProviderSettingsSchema(provider_name)
|
|
cls._registered_schemas[provider_name] = schema
|
|
return schema
|
|
|
|
@classmethod
|
|
def list_registered_providers(cls) -> List[str]:
|
|
"""Get list of all registered provider schemas"""
|
|
# Include both registered schemas and built-in methods
|
|
builtin_providers = []
|
|
for attr_name in dir(cls):
|
|
if (
|
|
attr_name.startswith("get_")
|
|
and attr_name.endswith("_schema")
|
|
and attr_name != "get_provider_schema"
|
|
):
|
|
provider_name = attr_name[4:-7] # Remove 'get_' and '_schema'
|
|
builtin_providers.append(provider_name)
|
|
|
|
all_providers = list(set(builtin_providers + list(cls._registered_schemas.keys())))
|
|
return sorted(all_providers)
|
|
|
|
@classmethod
|
|
def unregister_provider_schema(cls, provider_name: str) -> bool:
|
|
"""Unregister a provider schema"""
|
|
if provider_name in cls._registered_schemas:
|
|
del cls._registered_schemas[provider_name]
|
|
logger.info(f"Unregistered settings schema for provider: {provider_name}")
|
|
return True
|
|
return False
|