Skip to content

Commit

Permalink
Add support for Genres across iTunes & PodcastIndex.
Browse files Browse the repository at this point in the history
  • Loading branch information
amugofjava committed Mar 20, 2022
1 parent 6fe9864 commit 026b5b7
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 41 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.5.1

- 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

- Stable null safe release
Expand Down
2 changes: 1 addition & 1 deletion example/podcast_search_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void main() async {
});

/// Find the top 10 comedy podcasts in the UK.
var charts = await search.charts(genre: Genre.COMEDY, limit: 10, country: Country.UNITED_KINGDOM);
var charts = await search.charts(genre: 'Education', limit: 10, country: Country.UNITED_KINGDOM);

/// List the name of each podcast found.
charts.items.forEach((result) {
Expand Down
22 changes: 0 additions & 22 deletions lib/src/model/genre.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,6 @@ class Genre {

Genre(this._id, this._name);

static const Genre NONE = Genre._(-1, 'None');

static const Genre ARTS = Genre._(1301, 'Arts');
static const Genre BUSINESS = Genre._(1321, 'Business');
static const Genre COMEDY = Genre._(1303, 'Comedy');
static const Genre EDUCATION = Genre._(1304, 'Education');
static const Genre FICTION = Genre._(1483, 'Fiction');
static const Genre GOVERNMENT = Genre._(1511, 'Government');
static const Genre HEALTH_FITNESS = Genre._(1512, 'Health & Fitness');
static const Genre HISTORY = Genre._(1487, 'History');
static const Genre KIDS_FAMILY = Genre._(1305, 'Kids & Family');
static const Genre LEISURE = Genre._(1502, 'Leisure');
static const Genre MUSIC = Genre._(1301, 'Music');
static const Genre NEWS = Genre._(1489, 'News');
static const Genre RELIGION_SPIRITUALITY = Genre._(1314, 'Religion & Spirituality');
static const Genre SCIENCE = Genre._(1533, 'Science');
static const Genre SOCIETY_CULTURE = Genre._(1324, 'Society & Culture');
static const Genre SPORTS = Genre._(1545, 'Sports');
static const Genre TV_FILM = Genre._(1309, 'TV & Film');
static const Genre TECHNOLOGY = Genre._(1318, 'Technology');
static const Genre TRUE_CRIME = Genre._(1488, 'True Crime');

const Genre._(int id, String name)
: _id = id,
_name = name;
Expand Down
6 changes: 5 additions & 1 deletion lib/src/search/base_search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ abstract class BaseSearch {
Map<String, dynamic>? queryParams,
});

Future<SearchResult> charts();
Future<SearchResult> charts({
String genre,
});

List<String> genres();

/// If an error occurs during an HTTP GET request this method is called to
/// determine the error and set two variables which can then be included
Expand Down
37 changes: 33 additions & 4 deletions lib/src/search/itunes_search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ class ITunesSearch extends BaseSearch {
static String FEED_API_ENDPOINT = 'https://itunes.apple.com';
static String SEARCH_API_ENDPOINT = 'https://itunes.apple.com/search';

static const _genres = <String, int>{
'': -1,
'Arts': 1301,
'Business': 1321,
'Comedy': 1303,
'Education': 1304,
'Fiction': 1483,
'Government': 1511,
'Health & Fitness': 1512,
'History': 1487,
'Kids & Family': 1305,
'Leisure': 1502,
'Music': 1301,
'News': 1489,
'Religion & Spirituality': 1314,
'Science': 1533,
'Society & Culture': 1324,
'Sports': 1545,
'TV & Film': 1309,
'Technology': 1318,
'True Crime': 1488,
};
final Dio _client;

/// The search term keyword(s)
Expand All @@ -27,7 +49,7 @@ class ITunesSearch extends BaseSearch {
Country _country = Country.NONE;

/// If this property is not-null search results will be limited to this genre
Genre? _genre;
String _genre = '';

/// By default, searches will be performed against keyword(s) in [_term].
/// Set this property to search against a different attribute.
Expand Down Expand Up @@ -113,7 +135,7 @@ class ITunesSearch extends BaseSearch {
Country country = Country.UNITED_KINGDOM,
int limit = 20,
bool explicit = false,
Genre? genre,
String genre = '',
}) async {
_country = country;
_limit = limit;
Expand All @@ -135,6 +157,9 @@ class ITunesSearch extends BaseSearch {
return SearchResult.fromError(lastError: lastError ?? '', lastErrorType: lastErrorType);
}

@override
List<String> genres() => _genres.keys.toList(growable: false);

Future<SearchResult> _chartsToResults(dynamic jsonInput) async {
var entries = jsonInput['feed']['entry'];

Expand Down Expand Up @@ -196,8 +221,12 @@ class ITunesSearch extends BaseSearch {
buf.write('/rss/toppodcasts/limit=');
buf.write(_limit);

if (_genre != null) {
buf.write('/genre=${_genre!.id}');
if (_genre != '') {
var g = _genres[_genre];

if (g != null) {
buf.write('/genre=$g');
}
}

buf.write('/explicit=');
Expand Down
123 changes: 122 additions & 1 deletion lib/src/search/podcast_index_search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,122 @@ import 'package:podcast_search/src/search/base_search.dart';
class PodcastIndexSearch extends BaseSearch {
static String SEARCH_API_ENDPOINT = 'https://api.podcastindex.org/api/1.0/search';
static String TRENDING_API_ENDPOINT = 'https://api.podcastindex.org/api/1.0/podcasts/trending';
static String GENRE_API_ENDPOINT = 'https://api.podcastindex.org/api/1.0/categories/list';

static const _genres = <String>[
'After-Shows',
'Alternative',
'Animals',
'Animation',
'Arts',
'Astronomy',
'Automotive',
'Aviation',
'Baseball',
'Basketball',
'Beauty',
'Books',
'Buddhism',
'Business',
'Careers',
'Chemistry',
'Christianity',
'Climate',
'Comedy',
'Commentary',
'Courses',
'Crafts',
'Cricket',
'Cryptocurrency',
'Culture',
'Daily',
'Design',
'Documentary',
'Drama',
'Earth',
'Education',
'Entertainment',
'Entrepreneurship',
'Family',
'Fantasy',
'Fashion',
'Fiction',
'Film',
'Fitness',
'Food',
'Football',
'Games',
'Garden',
'Golf',
'Government',
'Health',
'Hinduism',
'History',
'Hobbies',
'Hockey',
'Home',
'How-To',
'Improv',
'Interviews',
'Investing',
'Islam',
'Journals',
'Judaism',
'Kids',
'Language',
'Learning',
'Leisure',
'Life',
'Management',
'Manga',
'Marketing',
'Mathematics',
'Medicine',
'Mental',
'Music',
'Natural',
'Nature',
'News',
'Non-Profit',
'Nutrition',
'Parenting',
'Performing',
'Personal',
'Pets',
'Philosophy',
'Physics',
'Places',
'Politics',
'Relationships',
'Religion',
'Reviews',
'Role-Playing',
'Rugby',
'Running',
'Science',
'Self-Improvement',
'Sexuality',
'Soccer',
'Social',
'Society',
'Spirituality',
'Sports',
'Stand-Up',
'Stories',
'Swimming',
'TV',
'Tabletop',
'Technology',
'Tennis',
'Travel',
'True Crime',
'Video-Games',
'Visual',
'Volleyball',
'Weather',
'Wilderness',
'Wrestling',
];

PodcastIndexProvider podcastIndexProvider;

Expand Down Expand Up @@ -123,12 +239,14 @@ class PodcastIndexSearch extends BaseSearch {
{Country country = Country.UNITED_KINGDOM,
int limit = 20,
bool explicit = false,
Genre? genre,
String genre = '',
Map<String, dynamic> queryParams = const {}}) async {
try {
var response = await _client.get(TRENDING_API_ENDPOINT,
queryParameters: {
'since': -1 * 3600 * 24 * 7,
'cat': genre,
'max': limit,
}..addAll(queryParams));

return SearchResult.fromJson(json: response.data, type: ResultType.podcastIndex);
Expand All @@ -139,6 +257,9 @@ class PodcastIndexSearch extends BaseSearch {
return SearchResult.fromError(lastError: _lastError ?? '', lastErrorType: _lastErrorType);
}

@override
List<String> genres() => _genres;

/// This internal method constructs a correctly encoded URL which is then
/// used to perform the search.
String _buildSearchUrl(Map<String, dynamic> queryParams) {
Expand Down
26 changes: 21 additions & 5 deletions lib/src/search/search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Search {
Country _country = Country.NONE;

/// If this property is not-null search results will be limited to this genre
Genre _genre = Genre.NONE;
String _genre = '';

/// By default, searches will be performed against keyword(s) in [_term].
/// Set this property to search against a different attribute.
Expand All @@ -44,7 +44,10 @@ class Search {
/// If this property is non-null, it will be prepended to the User Agent header.
String userAgent = '';

final SearchProvider searchProvider;

Search({
this.searchProvider = const ITunesProvider(),
this.userAgent = '',
});

Expand All @@ -54,7 +57,6 @@ class Search {
/// value to search by a different attribute such as Author, genre etc.
Future<SearchResult> search(
String term, {
SearchProvider searchProvider = const ITunesProvider(),
Country country = Country.NONE,
Attribute attribute = Attribute.NONE,
Language language = Language.NONE,
Expand Down Expand Up @@ -103,11 +105,10 @@ class Search {
/// the infrequent update of the chart feed it is recommended that clients
/// cache the results.
Future<SearchResult> charts({
SearchProvider searchProvider = const ITunesProvider(),
Country country = Country.UNITED_KINGDOM,
int limit = 20,
bool explicit = false,
Genre genre = Genre.NONE,
String genre = '',
Map<String, dynamic> queryParams = const {},
}) async {
_country = country;
Expand All @@ -119,7 +120,7 @@ class Search {
return PodcastIndexSearch(
userAgent: userAgent,
timeout: timeout,
podcastIndexProvider: searchProvider,
podcastIndexProvider: searchProvider as PodcastIndexProvider,
).charts(
country: _country,
limit: _limit,
Expand All @@ -139,6 +140,21 @@ class Search {
);
}

List<String> genres() {
if (searchProvider is PodcastIndexProvider) {
return PodcastIndexSearch(
userAgent: userAgent,
timeout: timeout,
podcastIndexProvider: searchProvider as PodcastIndexProvider,
).genres();
} else {
return ITunesSearch(
userAgent: userAgent,
timeout: timeout,
).genres();
}
}

/// Returns the search term.
String get term => _term;
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: podcast_search
description: A library for searching for podcasts, parsing podcast RSS feeds and obtaining episodes details. Supports searching via iTunes and PodcastIndex (preview).

version: 0.5.0
version: 0.5.1
homepage: https://github.com/amugofjava/podcast_search

environment:
Expand Down
11 changes: 5 additions & 6 deletions test/podcast_search_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ void main() {
late Search search;

setUp(() {
search = Search();
search = Search(
searchProvider:
PodcastIndexProvider(key: 'XXWQEGULBJABVHZUM8NF', secret: 'KZ2uy4upvq4t3e\$m\$3r2TeFS2fEpFTAaF92xcNdX'),
);
});

test('Podcast index trending', () async {
final result = await search.charts(
searchProvider: PodcastIndexProvider(
key: 'XXWQEGULBJABVHZUM8NF',
secret: 'KZ2uy4upvq4t3e\$m\$3r2TeFS2fEpFTAaF92xcNdX'),
queryParams: {'val': 'lightning'});
final result = await search.charts(queryParams: {'val': 'lightning'});

expect(result.resultCount > 0, true);
});
Expand Down

0 comments on commit 026b5b7

Please sign in to comment.