diff --git a/src/__tests__/__snapshots__/rss2.spec.ts.snap b/src/__tests__/__snapshots__/rss2.spec.ts.snap index 6cb59bc..a2e9280 100644 --- a/src/__tests__/__snapshots__/rss2.spec.ts.snap +++ b/src/__tests__/__snapshots__/rss2.spec.ts.snap @@ -261,3 +261,61 @@ exports[`rss 2.0 should generate a valid feed with video 1`] = ` " `; + +exports[`rss 2.0 should generate a valid podcast feed with audio 1`] = ` +" + + + Feed Title + http://example.com/ + This is my personnal feed! + Sat, 13 Jul 2013 23:00:00 GMT + https://validator.w3.org/feed/docs/rss2.html + https://github.com/jpmonette/feed + en + 60 + + Feed Title + http://example.com/image.png + http://example.com/ + + All rights reserved 2013, John Doe + Technology + + + <![CDATA[Hello World]]> + https://example.com/hello-world?link=sanitized&value=2 + https://example.com/hello-world?id=this&that=true + Wed, 10 Jul 2013 23:00:00 GMT + + + janedoe@example.com (Jane Doe) + joesmith@example.com (Joe Smith) + Grateful Dead + MSFT + + + + <![CDATA[Hello World]]> + https://example.com/hello-world3 + https://example.com/hello-world3 + Wed, 10 Jul 2013 23:00:00 GMT + + + janedoe@example.com (Jane Doe) + joesmith@example.com (Joe Smith) + Grateful Dead + MSFT + + 50:00 + + johndoe@example.com + + johndoe@example.com + + John Doe + John Doe + + +" +`; diff --git a/src/__tests__/rss2.spec.ts b/src/__tests__/rss2.spec.ts index df6b5d2..275113d 100644 --- a/src/__tests__/rss2.spec.ts +++ b/src/__tests__/rss2.spec.ts @@ -1,5 +1,5 @@ import { Feed } from "../feed"; -import { published, sampleFeed, updated } from "./setup"; +import { createSampleFeed, published, sampleFeed, updated } from "./setup"; describe("rss 2.0", () => { it("should generate a valid feed", () => { @@ -158,6 +158,60 @@ describe("rss 2.0", () => { const actual = sampleFeed.rss2(); expect(actual).toMatchSnapshot(); }); + it("should generate a valid podcast feed with audio", () => { + var podcastFeed = createSampleFeed(); + podcastFeed.options.podcast = true; + + podcastFeed.addItem({ + title: "Hello World", + link: "https://example.com/hello-world3", + description: "This is an article about Hello World.", + content: "Content of my item", + author: [ + { + name: "Jane Doe", + email: "janedoe@example.com", + link: "https://example.com/janedoe", + }, + { + name: "Joe Smith", + email: "joesmith@example.com", + link: "https://example.com/joesmith", + }, + ], + extensions: [ + { + name: "_item_extension_1", + objects: { + about: "just an item extension example", + dummy1: "example", + }, + }, + { + name: "_item_extension_2", + objects: { + about: "just a second item extension example", + dummy1: "example", + }, + }, + ], + category: [ + { + name: "Grateful Dead", + }, + { + name: "MSFT", + domain: "http://www.fool.com/cusips", + }, + ], + date: updated, + audio: { url: "https://example.com/hello-world.mp3", length: 12665, type: "audio/mpeg", duration: 3000 }, + published, + }); + + const actual = podcastFeed.rss2(); + expect(actual).toMatchSnapshot(); + }); it("should generate a valid feed with video", () => { const sampleFeed = new Feed({ title: "Feed Title", diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts index 79d77f7..0236af1 100644 --- a/src/__tests__/setup.ts +++ b/src/__tests__/setup.ts @@ -3,106 +3,112 @@ import { Feed } from "../feed"; export const updated = new Date("Sat, 13 Jul 2013 23:00:00 GMT"); export const published = new Date("Sat, 10 Jul 2013 23:00:00 GMT"); -export const sampleFeed = new Feed({ - title: "Feed Title", - description: "This is my personnal feed!", - link: "http://example.com/", - id: "http://example.com/", - feed: "http://example.com/sampleFeed.rss", - feedLinks: { - json: "http://example.com/sampleFeed.json", - }, - language: "en", - ttl: 60, - image: "http://example.com/image.png", - favicon: "http://example.com/image.ico", - copyright: "All rights reserved 2013, John Doe", - hub: "wss://example.com/", - updated, // optional, default = today +export const createSampleFeed = () => { + var feed = new Feed({ + title: "Feed Title", + description: "This is my personnal feed!", + link: "http://example.com/", + id: "http://example.com/", + feed: "http://example.com/sampleFeed.rss", + feedLinks: { + json: "http://example.com/sampleFeed.json", + }, + language: "en", + ttl: 60, + image: "http://example.com/image.png", + favicon: "http://example.com/image.ico", + copyright: "All rights reserved 2013, John Doe", + hub: "wss://example.com/", + updated, // optional, default = today - author: { - name: "John Doe", - email: "johndoe@example.com", - link: "https://example.com/johndoe?link=sanitized&value=2" - } -}); + author: { + name: "John Doe", + email: "johndoe@example.com", + link: "https://example.com/johndoe?link=sanitized&value=2" + } + }); -sampleFeed.addCategory("Technology"); + feed.addCategory("Technology"); -sampleFeed.addContributor({ - name: "Johan Cruyff", - email: "johancruyff@example.com", - link: "https://example.com/johancruyff", -}); + feed.addContributor({ + name: "Johan Cruyff", + email: "johancruyff@example.com", + link: "https://example.com/johancruyff", + }); -sampleFeed.addItem({ - title: "Hello World", - id: "https://example.com/hello-world?id=this&that=true", - link: "https://example.com/hello-world?link=sanitized&value=2", - description: "This is an article about Hello World.", - content: "Content of my item", - author: [ - { - name: "Jane Doe", - email: "janedoe@example.com", - link: "https://example.com/janedoe?link=sanitized&value=2", - }, - { - name: "Joe Smith", - email: "joesmith@example.com", - link: "https://example.com/joesmith", - }, - { - name: "Joe Smith, Name Only", - } - ], - contributor: [ - { - name: "Shawn Kemp", - email: "shawnkemp@example.com", - link: "https://example.com/shawnkemp", - }, - { - name: "Reggie Miller", - email: "reggiemiller@example.com", - link: "https://example.com/reggiemiller", - }, - ], - extensions: [ - { - name: "_item_extension_1", - objects: { - about: "just an item extension example", - dummy1: "example", + feed.addItem({ + title: "Hello World", + id: "https://example.com/hello-world?id=this&that=true", + link: "https://example.com/hello-world?link=sanitized&value=2", + description: "This is an article about Hello World.", + content: "Content of my item", + author: [ + { + name: "Jane Doe", + email: "janedoe@example.com", + link: "https://example.com/janedoe?link=sanitized&value=2", }, - }, - { - name: "_item_extension_2", - objects: { - about: "just a second item extension example", - dummy1: "example", + { + name: "Joe Smith", + email: "joesmith@example.com", + link: "https://example.com/joesmith", }, + { + name: "Joe Smith, Name Only", + } + ], + contributor: [ + { + name: "Shawn Kemp", + email: "shawnkemp@example.com", + link: "https://example.com/shawnkemp", + }, + { + name: "Reggie Miller", + email: "reggiemiller@example.com", + link: "https://example.com/reggiemiller", + }, + ], + extensions: [ + { + name: "_item_extension_1", + objects: { + about: "just an item extension example", + dummy1: "example", + }, + }, + { + name: "_item_extension_2", + objects: { + about: "just a second item extension example", + dummy1: "example", + }, + }, + ], + category: [ + { + name: "Grateful Dead", + }, + { + name: "MSFT", + domain: "http://www.fool.com/cusips", + }, + ], + date: updated, + image: "https://example.com/hello-world.jpg", + enclosure: { url: "https://example.com/hello-world.jpg", length: 12665, type: "image/jpeg" }, + published, + }); + + feed.addExtension({ + name: "_example_extension", + objects: { + about: "just an extension example", + dummy: "example", }, - ], - category: [ - { - name: "Grateful Dead", - }, - { - name: "MSFT", - domain: "http://www.fool.com/cusips", - }, - ], - date: updated, - image: "https://example.com/hello-world.jpg", - enclosure: { url: "https://example.com/hello-world.jpg", length: 12665, type: "image/jpeg" }, - published, -}); + }); + + return feed; +} -sampleFeed.addExtension({ - name: "_example_extension", - objects: { - about: "just an extension example", - dummy: "example", - }, -}); +export const sampleFeed = createSampleFeed(); \ No newline at end of file diff --git a/src/rss2.ts b/src/rss2.ts index cd940a1..9fc78a4 100644 --- a/src/rss2.ts +++ b/src/rss2.ts @@ -186,7 +186,16 @@ export default (ins: Feed) => { } if (entry.audio) { + let duration = undefined; + if (options.podcast && typeof entry.audio !== 'string' && entry.audio.duration) { + duration = entry.audio.duration; + entry.audio.duration = undefined; + } item.enclosure = formatEnclosure(entry.audio, "audio"); + + if (duration) { + item["itunes:duration"] = formatDuration(duration); + } } if (entry.video) { @@ -204,6 +213,35 @@ export default (ins: Feed) => { if (isAtom) { base.rss._attributes["xmlns:atom"] = "http://www.w3.org/2005/Atom"; } + + /** + * Podcast extensions + * https://support.google.com/podcast-publishers/answer/9889544?hl=en + */ + if (options.podcast) { + base.rss._attributes["xmlns:googleplay"] = "http://www.google.com/schemas/play-podcasts/1.0"; + base.rss._attributes["xmlns:itunes"] = "http://www.itunes.com/dtds/podcast-1.0.dtd"; + if (options.category) { + base.rss.channel["googleplay:category"] = options.category; + base.rss.channel["itunes:category"] = options.category; + } + if (options.author?.email) { + base.rss.channel["googleplay:owner"] = options.author.email; + base.rss.channel["itunes:owner"] = { + 'itunes:email': options.author.email + }; + } + if (options.author?.name) { + base.rss.channel["googleplay:author"] = options.author.name; + base.rss.channel["itunes:author"] = options.author.name; + } + if (options.image) { + base.rss.channel["googleplay:image"] = { + _attributes: { href: sanitize(options.image) } + }; + } + } + return convert.js2xml(base, { compact: true, ignoreComment: true, spaces: 4 }); }; @@ -235,3 +273,16 @@ const formatCategory = (category: Category) => { }, }; }; + +/** + * Returns a formated duration from seconds + * @param duration + */ +const formatDuration = (duration: number) => { + const seconds = duration % 60; + const totalMinutes = Math.floor(duration / 60); + const minutes = totalMinutes % 60; + const hours = Math.floor(totalMinutes / 60); + const notHours = ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2); + return hours > 0 ? hours + ":" + notHours : notHours; +} diff --git a/src/typings/index.ts b/src/typings/index.ts index ac7e8c3..f44374f 100644 --- a/src/typings/index.ts +++ b/src/typings/index.ts @@ -58,6 +58,9 @@ export interface FeedOptions { hub?: string; docs?: string; + podcast?: boolean; + category?: string; + author?: Author; link?: string; description?: string;