mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-10-13 19:32:43 +02:00
Add web config
This commit is contained in:
@@ -446,6 +446,16 @@ class StreamingProvider(ABC):
|
||||
"""
|
||||
return self.catchup_window > 0
|
||||
|
||||
@property
|
||||
def requires_user_credentials(self) -> bool:
|
||||
"""
|
||||
Some providers do not need to authenticate
|
||||
|
||||
Returns:
|
||||
bool: True if user credentials are required
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_epg(self, channel_id: str,
|
||||
start_time: Optional[datetime] = None,
|
||||
end_time: Optional[datetime] = None,
|
||||
|
||||
@@ -405,6 +405,10 @@ class Magenta2Provider(StreamingProvider):
|
||||
# This provider offers 4 hours of catchup
|
||||
return 4
|
||||
|
||||
@property
|
||||
def requires_user_credentials(self) -> bool:
|
||||
return False
|
||||
|
||||
def get_discovery_status(self) -> Dict[str, Any]:
|
||||
"""Get discovery and configuration status"""
|
||||
if not self.discovery_service:
|
||||
|
||||
+89
-46
@@ -142,6 +142,7 @@
|
||||
padding: 20px;
|
||||
border: 1px solid #dee2e6;
|
||||
transition: var(--transition);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.provider-card:hover {
|
||||
@@ -160,10 +161,11 @@
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
background: white;
|
||||
padding: 5px;
|
||||
padding: 8px;
|
||||
border: 2px solid var(--primary-color);
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.provider-info h3 {
|
||||
@@ -214,6 +216,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@@ -255,6 +258,7 @@
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
@@ -386,6 +390,27 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Special styling for provider types */
|
||||
.client-credentials-only .form-group:has(label:contains("Username")),
|
||||
.client-credentials-only .form-group:has(label:contains("Password")),
|
||||
.client-credentials-only .form-group:has(label:contains("Client ID")),
|
||||
.client-credentials-only .form-group:has(label:contains("Client Secret")) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.client-credentials-only .no-credentials-required {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
background: rgba(76, 201, 240, 0.1);
|
||||
border-radius: var(--border-radius);
|
||||
border-left: 4px solid var(--success-color);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.no-credentials-required {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.providers-grid {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -402,6 +427,14 @@
|
||||
.tab {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -444,7 +477,7 @@
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<div>
|
||||
<strong>Credentials Setup</strong>
|
||||
<p>Enter your login credentials for each streaming provider. Some providers may also require client ID/secret for API access.</p>
|
||||
<p>Enter your login credentials for each streaming provider. Some providers like magenta2 don't require manual credentials.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -653,8 +686,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
credentialsContainer.innerHTML = providers.map(provider => `
|
||||
<div class="provider-card" data-provider="${provider.name}">
|
||||
credentialsContainer.innerHTML = providers.map(provider => {
|
||||
// Check if this provider requires user credentials
|
||||
const isClientOnly = !provider.requires_user_credentials;
|
||||
const providerCardClass = isClientOnly ? 'provider-card client-credentials-only' : 'provider-card';
|
||||
|
||||
return `
|
||||
<div class="${providerCardClass}" data-provider="${provider.name}">
|
||||
<div class="provider-header">
|
||||
${provider.logo ? `<img src="${provider.logo}" alt="${provider.label}" class="provider-logo">` : ''}
|
||||
<div class="provider-info">
|
||||
@@ -667,6 +705,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${isClientOnly ? `
|
||||
<div class="no-credentials-required">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>No manual credentials required</strong>
|
||||
<p>This provider uses client credentials that are hardcoded in the application.</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username-${provider.name}">
|
||||
<i class="fas fa-user"></i> Username/Email
|
||||
@@ -689,28 +735,6 @@
|
||||
value="">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="client-id-${provider.name}">
|
||||
<i class="fas fa-id-card"></i> Client ID (Optional)
|
||||
</label>
|
||||
<input type="text"
|
||||
id="client-id-${provider.name}"
|
||||
class="form-control"
|
||||
placeholder="Client ID"
|
||||
value="${authStatus[provider.name]?.client_id || ''}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="client-secret-${provider.name}">
|
||||
<i class="fas fa-key"></i> Client Secret (Optional)
|
||||
</label>
|
||||
<input type="password"
|
||||
id="client-secret-${provider.name}"
|
||||
class="form-control"
|
||||
placeholder="••••••••"
|
||||
value="">
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button onclick="saveCredentials('${provider.name}')" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Save
|
||||
@@ -723,7 +747,8 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Load proxy forms
|
||||
@@ -838,16 +863,24 @@
|
||||
|
||||
// API Functions
|
||||
async function saveCredentials(providerName) {
|
||||
// Check if provider is client-only
|
||||
if (!provider.requires_user_credentials) {
|
||||
showAlert('info', `${providerName} uses hardcoded client credentials - no manual setup needed`);
|
||||
return;
|
||||
}
|
||||
|
||||
const username = document.getElementById(`username-${providerName}`).value;
|
||||
const password = document.getElementById(`password-${providerName}`).value;
|
||||
const clientId = document.getElementById(`client-id-${providerName}`).value;
|
||||
const clientSecret = document.getElementById(`client-secret-${providerName}`).value;
|
||||
|
||||
const credentials = {};
|
||||
if (username) credentials.username = username;
|
||||
if (password) credentials.password = password;
|
||||
if (clientId) credentials.client_id = clientId;
|
||||
if (clientSecret) credentials.client_secret = clientSecret;
|
||||
if (!username || !password) {
|
||||
showAlert('error', 'Please enter both username and password');
|
||||
return;
|
||||
}
|
||||
|
||||
const credentials = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
|
||||
const statusEl = document.getElementById(`status-${providerName}`);
|
||||
statusEl.innerHTML = '<span class="loader"></span> Saving...';
|
||||
@@ -867,7 +900,6 @@
|
||||
|
||||
// Clear password field
|
||||
document.getElementById(`password-${providerName}`).value = '';
|
||||
document.getElementById(`client-secret-${providerName}`).value = '';
|
||||
|
||||
// Reload auth status
|
||||
await loadAuthStatus();
|
||||
@@ -889,6 +921,12 @@
|
||||
}
|
||||
|
||||
async function deleteCredentials(providerName) {
|
||||
// Check if provider is client-only
|
||||
if (!provider.requires_user_credentials) {
|
||||
showAlert('info', `${providerName} uses hardcoded client credentials - nothing to delete`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Delete credentials for ${providerName}?`)) return;
|
||||
|
||||
const statusEl = document.getElementById(`status-${providerName}`);
|
||||
@@ -906,8 +944,6 @@
|
||||
// Clear fields
|
||||
document.getElementById(`username-${providerName}`).value = '';
|
||||
document.getElementById(`password-${providerName}`).value = '';
|
||||
document.getElementById(`client-id-${providerName}`).value = '';
|
||||
document.getElementById(`client-secret-${providerName}`).value = '';
|
||||
|
||||
// Reload auth status
|
||||
await loadAuthStatus();
|
||||
@@ -937,14 +973,14 @@
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (result.is_authenticated) {
|
||||
if (result.is_ready) {
|
||||
statusEl.className = 'status-indicator status-success';
|
||||
statusEl.innerHTML = '<i class="fas fa-check-circle"></i> Authentication successful';
|
||||
showAlert('success', `Authentication successful for ${providerName}`);
|
||||
statusEl.innerHTML = '<i class="fas fa-check-circle"></i> Ready to use';
|
||||
showAlert('success', `${providerName} is ready to use`);
|
||||
} else {
|
||||
statusEl.className = 'status-indicator status-warning';
|
||||
statusEl.innerHTML = `<i class="fas fa-exclamation-triangle"></i> ${result.auth_state}`;
|
||||
showAlert('warning', `Authentication status: ${result.auth_state}`);
|
||||
showAlert('warning', `${providerName} status: ${result.auth_state}`);
|
||||
}
|
||||
} else {
|
||||
statusEl.className = 'status-indicator status-error';
|
||||
@@ -1179,11 +1215,18 @@
|
||||
}
|
||||
|
||||
try {
|
||||
// Get all providers first
|
||||
const response = await fetch(`${API_BASE}/api/providers`);
|
||||
const data = await response.json();
|
||||
|
||||
// Delete credentials for all providers
|
||||
for (const provider of providers) {
|
||||
await fetch(`${API_BASE}/api/providers/${provider.name}/credentials`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
for (const provider of data.providers) {
|
||||
// Skip client-only providers
|
||||
if (provider.requires_user_credentials) {
|
||||
await fetch(`${API_BASE}/api/providers/${provider.name}/credentials`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
}
|
||||
|
||||
await fetch(`${API_BASE}/api/providers/${provider.name}/proxy`, {
|
||||
method: 'DELETE'
|
||||
|
||||
+37
-9
@@ -541,6 +541,23 @@ class UltimateService:
|
||||
response.content_type = 'application/json'
|
||||
return json.dumps({'error': f'Failed to fetch manifest: {str(fetch_err)}'})
|
||||
|
||||
@staticmethod
|
||||
def _get_settings_manager():
|
||||
"""Simple helper to get SettingsManager"""
|
||||
try:
|
||||
from streaming_providers.base.settings.settings_manager import SettingsManager
|
||||
return SettingsManager()
|
||||
except ImportError:
|
||||
# Try alternative path
|
||||
try:
|
||||
from base.settings.settings_manager import SettingsManager
|
||||
return SettingsManager()
|
||||
except ImportError as e:
|
||||
logger.error(f"Cannot import SettingsManager: {e}")
|
||||
# Re-raise with a clearer message
|
||||
raise ImportError(
|
||||
f"SettingsManager not available. Ensure streaming_providers module is installed. Error: {e}")
|
||||
|
||||
def setup_routes(self):
|
||||
@self.app.route('/api/providers')
|
||||
def list_providers():
|
||||
@@ -556,12 +573,18 @@ class UltimateService:
|
||||
provider_label = getattr(provider_instance, 'provider_label', provider_name)
|
||||
country = getattr(provider_instance, 'country', default_country)
|
||||
provider_logo = getattr(provider_instance, 'provider_logo', '')
|
||||
requires_user_credentials = getattr(
|
||||
provider_instance,
|
||||
'requires_user_credentials',
|
||||
True # Default to True if property doesn't exist (for backward compatibility)
|
||||
)
|
||||
|
||||
providers_details.append({
|
||||
'name': provider_name,
|
||||
'label': provider_label,
|
||||
'logo': provider_logo,
|
||||
'country': country
|
||||
'country': country,
|
||||
'requires_user_credentials': requires_user_credentials
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -1201,7 +1224,8 @@ class UltimateService:
|
||||
Example: GET /api/providers/joyn_de/auth/status
|
||||
"""
|
||||
try:
|
||||
status = self.manager.settings_manager.get_auth_status(provider)
|
||||
settings_manager = self._get_settings_manager()
|
||||
status = settings_manager.get_auth_status(provider)
|
||||
|
||||
response.content_type = 'application/json; charset=utf-8'
|
||||
return status
|
||||
@@ -1217,8 +1241,7 @@ class UltimateService:
|
||||
Save credentials for a provider via API
|
||||
|
||||
Accepts JSON body with credentials:
|
||||
- User/password: {"username": "...", "password": "...", "client_id": "..." (optional)}
|
||||
- Client credentials: {"client_id": "...", "client_secret": "..."}
|
||||
- User/password: {"username": "...", "password": "..."}
|
||||
|
||||
Example: POST /api/providers/joyn_de/credentials
|
||||
Body: {"username": "user@example.com", "password": "secret123"}
|
||||
@@ -1241,8 +1264,11 @@ class UltimateService:
|
||||
response.status = 400
|
||||
return {'error': 'Credentials data must be a JSON object'}
|
||||
|
||||
# Get settings manager
|
||||
settings_manager = self._get_settings_manager()
|
||||
|
||||
# Save credentials
|
||||
success, message = self.manager.settings_manager.save_provider_credentials_from_api(
|
||||
success, message = settings_manager.save_provider_credentials_from_api(
|
||||
provider, credentials_data
|
||||
)
|
||||
|
||||
@@ -1278,7 +1304,8 @@ class UltimateService:
|
||||
Example: DELETE /api/providers/joyn_de/credentials
|
||||
"""
|
||||
try:
|
||||
success, message = self.manager.settings_manager.delete_provider_credentials_from_api(provider)
|
||||
settings_manager = self._get_settings_manager()
|
||||
success, message = settings_manager.delete_provider_credentials_from_api(provider)
|
||||
|
||||
if success:
|
||||
response.status = 200
|
||||
@@ -1345,8 +1372,8 @@ class UltimateService:
|
||||
response.status = 400
|
||||
return {'error': 'Proxy data must be a JSON object'}
|
||||
|
||||
# Save proxy configuration
|
||||
success, message = self.manager.settings_manager.save_provider_proxy_from_api(
|
||||
settings_manager = self._get_settings_manager()
|
||||
success, message = settings_manager.save_provider_proxy_from_api(
|
||||
provider, proxy_data
|
||||
)
|
||||
|
||||
@@ -1382,7 +1409,8 @@ class UltimateService:
|
||||
Example: DELETE /api/providers/joyn_de/proxy
|
||||
"""
|
||||
try:
|
||||
success, message = self.manager.settings_manager.delete_provider_proxy_from_api(provider)
|
||||
settings_manager = self._get_settings_manager()
|
||||
success, message = settings_manager.delete_provider_proxy_from_api(provider)
|
||||
|
||||
if success:
|
||||
response.status = 200
|
||||
|
||||
Reference in New Issue
Block a user