diff --git a/plugins/feed-discovery/src/feed-discovery.test.ts b/plugins/feed-discovery/src/feed-discovery.test.ts index f2c4383..1f2fb58 100644 --- a/plugins/feed-discovery/src/feed-discovery.test.ts +++ b/plugins/feed-discovery/src/feed-discovery.test.ts @@ -36,6 +36,26 @@ describe("feed parsing", () => { expect(atom.ok).toBe(true); expect(json.ok).toBe(true); }); + + it("classifies RSS feeds with audio enclosures as podcasts", () => { + const result = parseFeedDocument( + "https://example.com/podcast.xml", + ` + + + Example Podcast + Example Host + + Episode 1 + + + + ` + ); + + expect(result.ok).toBe(true); + expect(result.kind).toBe("podcast"); + }); }); describe("site probing helpers", () => { diff --git a/plugins/feed-discovery/src/feed-parsing.ts b/plugins/feed-discovery/src/feed-parsing.ts index 92fd4d7..627b382 100644 --- a/plugins/feed-discovery/src/feed-parsing.ts +++ b/plugins/feed-discovery/src/feed-parsing.ts @@ -96,7 +96,7 @@ function parseRss(feedUrl: string, rss: Record): ValidationResu title: title || "Untitled RSS Feed", description: stringValue(channel.description), homepageUrl: linkValue(channel.link), - kind: detectRssKind(channel), + kind: detectRssKind(channel, items), language: stringValue(channel.language), imageUrl: imageValue(channel.image), lastPublishedAt: newestDate([stringValue(channel.lastBuildDate), stringValue(channel.pubDate), ...sampleItems.map((item) => item.publishedAt)]), @@ -132,10 +132,21 @@ function parseAtom(feedUrl: string, feed: Record): ValidationRe }; } -function detectRssKind(channel: Record): FeedKind { +function detectRssKind(channel: Record, items: Record[]): FeedKind { if (channel.itunes || channel["itunes:author"] || channel.enclosure) { return "podcast"; } + const hasMediaEnclosure = items.some((item) => + asArray(item.enclosure) + .filter(isRecord) + .some((enclosure) => { + const type = stringValue(enclosure.type)?.toLowerCase(); + return type?.startsWith("audio/") || type?.startsWith("video/"); + }) + ); + if (hasMediaEnclosure) { + return "podcast"; + } return "blog"; }