fix: podcast parser helpers not handling exception (#2861)
authorFabian Munkes <105975993+fmunkes@users.noreply.github.com>
Sat, 20 Dec 2025 10:12:18 +0000 (11:12 +0100)
committerGitHub <noreply@github.com>
Sat, 20 Dec 2025 10:12:18 +0000 (11:12 +0100)
music_assistant/helpers/podcast_parsers.py
music_assistant/providers/gpodder/__init__.py
music_assistant/providers/itunes_podcasts/__init__.py

index 886eb60183f53d3ed3c8b2098441178e3d0301ae..31b2a2f6ec32dba228de83e115540e70a84ace99 100644 (file)
@@ -6,7 +6,9 @@ from typing import Any
 
 import aiohttp
 import podcastparser
+from aiohttp.client import ClientError
 from music_assistant_models.enums import ContentType, ImageType, MediaType
+from music_assistant_models.errors import MediaNotFoundError
 from music_assistant_models.media_items import (
     AudioFormat,
     ItemMapping,
@@ -32,11 +34,24 @@ async def get_podcastparser_dict(
     # but, reports on discord show, that also the opposite may be true
     for headers in [{"User-Agent": "Mozilla/5.0"}, {}]:
         # raises ClientError on status failure
-        response = await session.get(feed_url, headers=headers, raise_for_status=True)
-    assert response is not None  # for type checking
+        # ClientError is the base class of all possible Error, i.e. not authorized,
+        # url doesn't exist etc.
+        try:
+            response = await session.get(feed_url, headers=headers, raise_for_status=True)
+        except ClientError:
+            continue
+        break
+    if response is None:
+        # we did not get a single acceptable response
+        raise MediaNotFoundError(
+            f"Did not get acceptable response while trying to access {feed_url}."
+        )
     feed_data = await response.read()
     feed_stream = BytesIO(feed_data)
-    return podcastparser.parse(feed_url, feed_stream, max_episodes=max_episodes)  # type: ignore[no-any-return]
+    try:
+        return podcastparser.parse(feed_url, feed_stream, max_episodes=max_episodes)  # type: ignore[no-any-return]
+    except podcastparser.FeedParseError:
+        raise MediaNotFoundError(f"The url at {feed_url} returns invalid RSS data.")
 
 
 def parse_podcast(
index 3aafc94cf6f7cc31ad1a762afb5bd8fa94ee088e..3918fa002e8bdeef5e99da1399768ac966b1b45e 100644 (file)
@@ -18,7 +18,6 @@ import time
 from collections.abc import AsyncGenerator
 from typing import TYPE_CHECKING, Any
 
-from aiohttp.client_exceptions import ClientError
 from music_assistant_models.config_entries import ConfigEntry, ConfigValueType, ProviderConfig
 from music_assistant_models.enums import (
     ConfigEntryType,
@@ -354,7 +353,7 @@ class GPodder(MusicProvider):
                     feed_url=feed_url,
                     max_episodes=self.max_episodes,
                 )
-            except ClientError:
+            except MediaNotFoundError:
                 self.logger.warning(f"Was unable to obtain podcast with feed {feed_url}")
                 continue
             await self._cache_set_podcast(feed_url, parsed_podcast)
@@ -612,6 +611,7 @@ class GPodder(MusicProvider):
             default=None,
         )
         if parsed_podcast is None:
+            # raises MediaNotFoundError
             parsed_podcast = await get_podcastparser_dict(
                 session=self.mass.http_session,
                 feed_url=prov_podcast_id,
index ddb976657785341d75b3693539a6ea5720b5b426..1352b0d4189b08d39250f9a61d3d6e6ad2ab7bd0 100644 (file)
@@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, Any
 
 import aiofiles
 import orjson
-from aiohttp.client_exceptions import ClientError
 from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption
 from music_assistant_models.enums import (
     ConfigEntryType,
@@ -334,14 +333,12 @@ class ITunesPodcastsProvider(MusicProvider):
             default=None,
         )
         if parsed_podcast is None:
-            try:
-                parsed_podcast = await get_podcastparser_dict(
-                    session=self.mass.http_session,
-                    feed_url=prov_podcast_id,
-                    max_episodes=self.max_episodes,
-                )
-            except ClientError as exc:
-                raise MediaNotFoundError from exc
+            # get_podcastparser_dict raises MediaNotFoundError if data is invalid
+            parsed_podcast = await get_podcastparser_dict(
+                session=self.mass.http_session,
+                feed_url=prov_podcast_id,
+                max_episodes=self.max_episodes,
+            )
             await self._cache_set_podcast(feed_url=prov_podcast_id, parsed_podcast=parsed_podcast)
 
         # this is a dictionary from podcastparser