diff --git a/CHANGELOG.md b/CHANGELOG.md index 7688f9f..9d6d74c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,21 @@ +## 0.7.0 + +- Add support for PC2.0 `` tag (at channel level). + ## 0.6.9 - Bug fix: handle empty fields from iTunes and PodcastIndex API call results. ## 0.6.8 -- BREAKING CHANGE: Language is now a text parameter (2-3 letter code) rather than enum. This is because iTunes largely ignores language, but PodcastIndex can use it for trending podcasts. +- BREAKING CHANGE: Language is now a text parameter (2-3 letter code) rather than enum. This is because iTunes largely + ignores language, but PodcastIndex can use it for trending podcasts. ## 0.6.7 - Update example to handle null feed. -- Default podcast image to take iTunes version if available and fallback to channel version to be consistent with episode image handling. +- Default podcast image to take iTunes version if available and fallback to channel version to be consistent with + episode image handling. ## 0.6.6 @@ -63,7 +69,8 @@ ## 0.5.1 -- Breaking change: Search provider is now passed when instantiating a Search object, rather than passing one at search time. +- Breaking change: Search provider is now passed when instantiating a Search object, rather than passing one at search + time. - Support for genres across iTunes & PodcastIndex. ## 0.5.0 @@ -128,15 +135,15 @@ ## 0.3.1 -- Add support for returning podcast chart. +- Add support for returning podcast chart. ## 0.3.0 -- Fix formatting. +- Fix formatting. ## 0.2.9 -- Fix lints for newer version of Pedantic. +- Fix lints for newer version of Pedantic. ## 0.2.8 diff --git a/lib/src/model/podcast.dart b/lib/src/model/podcast.dart index 81eb107..b8aa795 100644 --- a/lib/src/model/podcast.dart +++ b/lib/src/model/podcast.dart @@ -5,19 +5,20 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:podcast_search/src/model/block.dart'; -import 'package:podcast_search/src/model/value_recipient.dart'; -import 'package:rss_dart/dart_rss.dart'; import 'package:dio/dio.dart'; import 'package:podcast_search/podcast_search.dart'; +import 'package:podcast_search/src/model/block.dart'; import 'package:podcast_search/src/model/chapter.dart'; import 'package:podcast_search/src/model/chapter_headers.dart'; import 'package:podcast_search/src/model/locked.dart'; import 'package:podcast_search/src/model/person.dart'; +import 'package:podcast_search/src/model/remote_item.dart'; +import 'package:podcast_search/src/model/value_recipient.dart'; import 'package:podcast_search/src/search/base_search.dart'; import 'package:podcast_search/src/utils/json_parser.dart'; import 'package:podcast_search/src/utils/srt_parser.dart'; import 'package:podcast_search/src/utils/utils.dart'; +import 'package:rss_dart/dart_rss.dart'; /// This class represents a podcast and its episodes. The Podcast is instantiated with a feed URL which is /// then parsed and the episode list generated. @@ -64,6 +65,9 @@ class Podcast { /// A list of current episodes. final List episodes; + /// A list of remote items at the channel level + final List remoteItems; + Podcast._({ this.guid, this.url, @@ -78,6 +82,7 @@ class Podcast { this.value = const [], this.block = const [], this.episodes = const [], + this.remoteItems = const [], }); /// This method takes a Url pointing to an RSS feed containing the Podcast details and episodes. You @@ -167,6 +172,7 @@ class Podcast { static Podcast _loadFeed(RssFeed rssFeed, String url) { // Parse the episodes var episodes = []; + var remoteItems = []; var author = rssFeed.itunes!.author; var locked = Locked( locked: rssFeed.podcastIndex!.locked?.locked ?? false, @@ -189,6 +195,19 @@ class Podcast { } } + if (rssFeed.podcastIndex!.remoteItem != null) { + for (var r in rssFeed.podcastIndex!.remoteItem!) { + if (r != null) { + remoteItems.add(RemoteItem( + feedGuid: r.feedGuid, + itemGuid: r.itemGuid, + feedUrl: r.feedUrl, + medium: r.medium, + )); + } + } + } + if (rssFeed.podcastIndex!.persons != null) { for (var p in rssFeed.podcastIndex!.persons!) { persons.add(Person( @@ -253,6 +272,7 @@ class Podcast { block: block, value: value, episodes: episodes, + remoteItems: remoteItems, ); } diff --git a/lib/src/model/remote_item.dart b/lib/src/model/remote_item.dart new file mode 100644 index 0000000..3ea7fb7 --- /dev/null +++ b/lib/src/model/remote_item.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2019 Ben Hills and the project contributors. Use of this source +// code is governed by a MIT license that can be found in the LICENSE file. + +/// This class represents a PC2.0 [remote item](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#remote-item) +class RemoteItem { + String feedGuid; + String? itemGuid; + String? feedUrl; + String? medium; + + RemoteItem({ + required this.feedGuid, + this.itemGuid, + this.feedUrl, + this.medium, + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index e325d82..db4c35b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: podcast_search description: A library for searching for podcasts and parsing podcast RSS feeds. Supports iTunes and PodcastIndex directories, and newer features such as chapters and transcripts. -version: 0.6.9 +version: 0.7.0 homepage: https://github.com/amugofjava/podcast_search environment: @@ -12,7 +12,7 @@ dependencies: convert: ^3.0.1 crypto: ^3.0.1 dio: ^5.4.3+1 - rss_dart: ^1.0.6 + rss_dart: ^1.0.7 meta: ^1.15.0 dev_dependencies: diff --git a/test/podcast_load_test.dart b/test/podcast_load_test.dart index 75d1b9a..642db83 100644 --- a/test/podcast_load_test.dart +++ b/test/podcast_load_test.dart @@ -69,10 +69,46 @@ void main() { }); test('Load podcast with no block tags', () async { - var podcast = - await Podcast.loadFeedFile(file: 'test_resources/podcast-no-block.rss'); + var podcast = await Podcast.loadFeedFile( + file: 'test_resources/podcast-no-block.rss'); expect(podcast.block.length, 0); }); }); + + group('Remote item test', () { + test('No remote items', () async { + var podcast = await Podcast.loadFeedFile( + file: 'test_resources/podcast-no-block.rss'); + + expect(podcast.remoteItems.length, 0); + }); + + test('Load podcast 3 remote items', () async { + var podcast = await Podcast.loadFeedFile( + file: 'test_resources/podcast-remote-item.rss'); + + expect(podcast.remoteItems.length, 3); + + var item1 = podcast.remoteItems[0]; + var item2 = podcast.remoteItems[1]; + var item3 = podcast.remoteItems[2]; + + expect(item1.feedGuid, '917393e3-1b1e-5cef-ace4-edaa54e1f810'); + expect(item1.itemGuid, null); + expect(item1.feedUrl, null); + expect(item1.medium, null); + + expect(item2.feedGuid, '917393e3-1b1e-5cef-ace4-edaa54e1f811'); + expect(item2.itemGuid, 'asdf089j0-ep240-20230510'); + expect(item2.feedUrl, null); + expect(item2.medium, null); + + expect(item3.feedGuid, '917393e3-1b1e-5cef-ace4-edaa54e1f812'); + expect(item3.itemGuid, 'asdf089j0-ep240-20230511'); + expect(item3.feedUrl, + 'https://feeds.example.org/917393e3-1b1e-5cef-ace4-edaa54e1f811/rss.xml'); + expect(item3.medium, 'music'); + }); + }); } diff --git a/test_resources/podcast-remote-item.rss b/test_resources/podcast-remote-item.rss new file mode 100644 index 0000000..65e3aee --- /dev/null +++ b/test_resources/podcast-remote-item.rss @@ -0,0 +1,79 @@ + + + +Podcast Load Test 2 +Unit test podcast test 1 +https://nowhere.com/podcastsearchtest1 +Thu, 24 Jun 2021 18:00:20 +0000 +en + + https://nowhere.com/podcastsearchtest1/image1.png + Podcast Load Test 1 + https://nowhere.com/podcastsearchtest1 + + + + + +no +Unit test podcast test 1 +Podcast Search Author + + Ben Hills + anytime@amugofjava.me.uk + +podcast1.rss +episodic + + + + + + + + + + + + + Episode 001 + https://nowhere.com/podcastsearchtest1/podcast1 + + 1200 + yes + full + Test of episode 001]]> + Episode summary 001 + Thu, 24 Jun 2021 18:00:00 +0000 + + Ben Hills + Ben Hills + Podcast, NowPlaying, Listen, Test + Podcast, NowPlaying, Listen, Test + + + + + Episode 002 + https://nowhere.com/podcastsearchtest1/podcast2 + + 1200 + no + full + Test of episode 002]]> + Episode summary 002 + + Ben Hills + Ben Hills + Podcast, NowPlaying, Listen, Test + Podcast, NowPlaying, Listen, Test + + + +