From 0c1e86325bc1ee4bf18a72ac55f77954d17fc711 Mon Sep 17 00:00:00 2001 From: Nirvana Date: Sun, 15 Feb 2026 18:16:41 +0100 Subject: [PATCH] Add m3u playlists --- lib/streaming_providers/base/manager.py | 49 +++++++++++++------- routes/providers.py | 59 +++++++++++++++++++++---- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/lib/streaming_providers/base/manager.py b/lib/streaming_providers/base/manager.py index 8da3cab..f31aeae 100644 --- a/lib/streaming_providers/base/manager.py +++ b/lib/streaming_providers/base/manager.py @@ -56,6 +56,21 @@ class ProviderManager: self.registry.discover_all_providers(country) return self.registry.get_enabled_providers() + def rediscover_providers(self, country: str = "DE") -> List[str]: + """ + Re-scan for new providers, particularly useful for dynamically added M3U playlists. + + Args: + country: Country code for provider discovery + + Returns: + List of all discovered provider names + """ + logger.info(f"ProviderManager: Re-discovering providers for country '{country}'...") + discovered = self.registry.discover_all_providers(country) + logger.info(f"ProviderManager: Re-discovery complete. Found {len(discovered)} providers") + return discovered + def get_provider(self, provider_name: str): return self.registry.get_provider(provider_name) @@ -103,7 +118,7 @@ class ProviderManager: # ========================================================================== def get_channels( - self, provider_name: str, fetch_manifests: bool = False, **kwargs + self, provider_name: str, fetch_manifests: bool = False, **kwargs ) -> List[StreamingChannel]: return self.channel_ops.get_channels(provider_name, fetch_manifests, **kwargs) @@ -111,7 +126,7 @@ class ProviderManager: return self.channel_ops.get_channel_manifest(provider_name, channel_id, **kwargs) def get_all_channels( - self, fetch_manifests: bool = True, **kwargs + self, fetch_manifests: bool = True, **kwargs ) -> Dict[str, List[StreamingChannel]]: return self.channel_ops.get_all_channels(fetch_manifests, **kwargs) @@ -158,26 +173,26 @@ class ProviderManager: # ========================================================================== def get_catchup_manifest( - self, - provider_name: str, - channel_id: str, - start_time: int, - end_time: int, - epg_id: Optional[str] = None, - country: Optional[str] = None, + self, + provider_name: str, + channel_id: str, + start_time: int, + end_time: int, + epg_id: Optional[str] = None, + country: Optional[str] = None, ) -> Optional[str]: return self.catchup_ops.get_catchup_manifest( provider_name, channel_id, start_time, end_time, epg_id, country ) def get_catchup_drm_configs( - self, - provider_name: str, - channel_id: str, - start_time: int, - end_time: int, - epg_id: Optional[str] = None, - country: Optional[str] = None, + self, + provider_name: str, + channel_id: str, + start_time: int, + end_time: int, + epg_id: Optional[str] = None, + country: Optional[str] = None, ) -> List: return self.catchup_ops.get_catchup_drm_configs( provider_name, channel_id, start_time, end_time, epg_id, country @@ -291,4 +306,4 @@ class ProviderManager: Extract PSSH data from a manifest URL. Kept for backward compatibility but delegated to DRMOperations. """ - return self.drm_ops._extract_pssh_from_manifest(manifest_url) + return self.drm_ops._extract_pssh_from_manifest(manifest_url) \ No newline at end of file diff --git a/routes/providers.py b/routes/providers.py index 88c5e5a..4b4aff7 100644 --- a/routes/providers.py +++ b/routes/providers.py @@ -36,7 +36,7 @@ def setup_provider_routes(app, manager, service): # Check specific auth type needs needs_user_creds = "user_credentials" in supported_auth_types needs_client_creds = ( - "client_credentials" in supported_auth_types + "client_credentials" in supported_auth_types ) is_network_based = "network_based" in supported_auth_types is_anonymous = "anonymous" in supported_auth_types @@ -63,10 +63,10 @@ def setup_provider_routes(app, manager, service): # Derived summary for UI "needs_user_input": needs_user_creds or uses_device_reg, "needs_configuration": needs_user_creds - or needs_client_creds, + or needs_client_creds, "is_automatic": is_network_based - or is_anonymous - or uses_embedded, + or is_anonymous + or uses_embedded, }, # Token properties "primary_token_scope": getattr( @@ -98,7 +98,7 @@ def setup_provider_routes(app, manager, service): channels = manager.get_channels( provider_name=provider, fetch_manifests=request.query.get("fetch_manifests", "false").lower() - == "true", + == "true", country=request.query.get("country"), ) @@ -342,8 +342,8 @@ def setup_provider_routes(app, manager, service): if "not registered" in message.lower(): response.status = 404 elif ( - "invalid" in message.lower() - or "validation failed" in message.lower() + "invalid" in message.lower() + or "validation failed" in message.lower() ): response.status = 400 else: @@ -514,8 +514,8 @@ def setup_provider_routes(app, manager, service): if "not registered" in message.lower(): response.status = 404 elif ( - "invalid" in message.lower() - or "validation failed" in message.lower() + "invalid" in message.lower() + or "validation failed" in message.lower() ): response.status = 400 else: @@ -810,3 +810,44 @@ def setup_provider_routes(app, manager, service): logger.error(f"Error setting enabled status for {provider}: {e}") response.status = 500 return {"error": str(e)} + + @app.route("/api/providers/rediscover", method="POST") + def rediscover_providers(): + """ + Re-scan for new providers (e.g., newly added M3U playlists). + + Accepts optional query parameter: + - country: Country code for provider discovery (default: service default_country) + + Example: POST /api/providers/rediscover + Example: POST /api/providers/rediscover?country=US + + Returns: + JSON with list of all discovered providers and count + """ + try: + # Get country from query param or use service default + country = request.query.get("country", service.default_country) + + logger.info(f"API: Rediscovering providers for country '{country}'") + + # Trigger provider rediscovery + discovered = manager.rediscover_providers(country) + + # Get updated metadata + all_metadata = manager.get_all_providers_metadata() + enabled_count = sum(1 for m in all_metadata if m["enabled"]) + + return { + "success": True, + "message": f"Re-discovered {len(discovered)} providers ({enabled_count} enabled)", + "providers": discovered, + "count": len(discovered), + "enabled_count": enabled_count, + "country": country, + } + + except Exception as api_err: + logger.error(f"API Error in POST /api/providers/rediscover: {str(api_err)}") + response.status = 500 + return {"error": f"Failed to rediscover providers: {str(api_err)}"} \ No newline at end of file