diff --git a/client/pages/library/_library/podcast/search.vue b/client/pages/library/_library/podcast/search.vue index e786562c..4ca3fe7d 100644 --- a/client/pages/library/_library/podcast/search.vue +++ b/client/pages/library/_library/podcast/search.vue @@ -87,7 +87,7 @@ export default { streamLibraryItem() { return this.$store.state.streamLibraryItem }, - librarySetting() { + librarySettings() { return this.$store.getters['libraries/getCurrentLibrarySettings'] } }, @@ -154,7 +154,12 @@ export default { async submitSearch(term) { this.processing = true this.termSearched = '' - let results = await this.$axios.$get(`/api/search/podcast?term=${encodeURIComponent(term)}&country=${encodeURIComponent(this.librarySetting?.podcastSearchRegion)}`).catch((error) => { + + const searchParams = new URLSearchParams({ + term, + country: this.librarySettings?.podcastSearchRegion || 'us' + }) + let results = await this.$axios.$get(`/api/search/podcast?${searchParams.toString()}`).catch((error) => { console.error('Search request failed', error) return [] }) diff --git a/client/plugins/i18n.js b/client/plugins/i18n.js index 67857826..d7fc972e 100644 --- a/client/plugins/i18n.js +++ b/client/plugins/i18n.js @@ -28,9 +28,11 @@ Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map(code => { value: code } }) + +// iTunes search API uses ISO 3166 country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 const podcastSearchRegionMap = { 'us': { label: 'United States' }, - 'cn': { label: '中国' }, + 'cn': { label: '中国' } } Vue.prototype.$podcastSearchRegionOptions = Object.keys(podcastSearchRegionMap).map(code => { return { diff --git a/server/controllers/SearchController.js b/server/controllers/SearchController.js index 1acbe926..213d23e1 100644 --- a/server/controllers/SearchController.js +++ b/server/controllers/SearchController.js @@ -43,14 +43,14 @@ class SearchController { */ async findPodcasts(req, res) { const term = req.query.term - const country = req.query.country + const country = req.query.country || 'us' if (!term) { Logger.error('[SearchController] Invalid request query param "term" is required') return res.status(400).send('Invalid request query param "term" is required') } const results = await PodcastFinder.search(term, { - country: country + country }) res.json(results) } diff --git a/server/finders/PodcastFinder.js b/server/finders/PodcastFinder.js index 52fec15c..abaf02ac 100644 --- a/server/finders/PodcastFinder.js +++ b/server/finders/PodcastFinder.js @@ -6,10 +6,16 @@ class PodcastFinder { this.iTunesApi = new iTunes() } + /** + * + * @param {string} term + * @param {{country:string}} options + * @returns {Promise} + */ async search(term, options = {}) { if (!term) return null Logger.debug(`[iTunes] Searching for podcast with term "${term}"`) - var results = await this.iTunesApi.searchPodcasts(term, options) + const results = await this.iTunesApi.searchPodcasts(term, options) Logger.debug(`[iTunes] Podcast search for "${term}" returned ${results.length} results`) return results } diff --git a/server/providers/iTunes.js b/server/providers/iTunes.js index 39f36ab2..05a661b5 100644 --- a/server/providers/iTunes.js +++ b/server/providers/iTunes.js @@ -2,16 +2,46 @@ const axios = require('axios') const Logger = require('../Logger') const htmlSanitizer = require('../utils/htmlSanitizer') +/** + * @typedef iTunesSearchParams + * @property {string} term + * @property {string} country + * @property {string} media + * @property {string} entity + * @property {number} limit + */ + +/** + * @typedef iTunesPodcastSearchResult + * @property {string} id + * @property {string} artistId + * @property {string} title + * @property {string} artistName + * @property {string} description + * @property {string} descriptionPlain + * @property {string} releaseDate + * @property {string[]} genres + * @property {string} cover + * @property {string} feedUrl + * @property {string} pageUrl + * @property {boolean} explicit + */ + class iTunes { constructor() { } - // https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI/Searching.html + /** + * @see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI/Searching.html + * + * @param {iTunesSearchParams} options + * @returns {Promise} + */ search(options) { if (!options.term) { Logger.error('[iTunes] Invalid search options - no term') return [] } - var query = { + const query = { term: options.term, media: options.media, entity: options.entity, @@ -82,6 +112,11 @@ class iTunes { }) } + /** + * + * @param {Object} data + * @returns {iTunesPodcastSearchResult} + */ cleanPodcast(data) { return { id: data.collectionId, @@ -100,6 +135,12 @@ class iTunes { } } + /** + * + * @param {string} term + * @param {{country:string}} options + * @returns {Promise} + */ searchPodcasts(term, options = {}) { return this.search({ term, entity: 'podcast', media: 'podcast', ...options }).then((results) => { return results.map(this.cleanPodcast.bind(this))