mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-23 01:22:17 +02:00
604 lines
22 KiB
Python
604 lines
22 KiB
Python
# streaming_providers/base/settings/models/provider_settings.py
|
|
from typing import Dict, List, Optional, Any, Set
|
|
from dataclasses import dataclass, field
|
|
from .settings_models import (
|
|
SettingValue,
|
|
string_setting, password_setting, integer_setting, boolean_setting,
|
|
select_setting, port_setting
|
|
)
|
|
from ...utils.logger import logger
|
|
|
|
|
|
@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
|