diff --git a/client/components/modals/libraries/LibrarySettings.vue b/client/components/modals/libraries/LibrarySettings.vue index 53eb265081..4712d6a2cd 100644 --- a/client/components/modals/libraries/LibrarySettings.vue +++ b/client/components/modals/libraries/LibrarySettings.vue @@ -49,6 +49,9 @@ +
+ +
@@ -69,7 +72,8 @@ export default { skipMatchingMediaWithAsin: false, skipMatchingMediaWithIsbn: false, audiobooksOnly: false, - hideSingleBookSeries: false + hideSingleBookSeries: false, + podcastSearchRegion: 'us' } }, computed: { @@ -85,6 +89,9 @@ export default { isBookLibrary() { return this.mediaType === 'book' }, + isPodcastLibrary() { + return this.mediaType === 'podcast' + }, providers() { if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders return this.$store.state.scanners.providers @@ -99,7 +106,8 @@ export default { skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin, skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn, audiobooksOnly: !!this.audiobooksOnly, - hideSingleBookSeries: !!this.hideSingleBookSeries + hideSingleBookSeries: !!this.hideSingleBookSeries, + podcastSearchRegion: this.podcastSearchRegion } } }, @@ -113,6 +121,7 @@ export default { this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn this.audiobooksOnly = !!this.librarySettings.audiobooksOnly this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries + this.podcastSearchRegion = this.librarySettings.podcastSearchRegion || 'us' } }, mounted() { diff --git a/client/pages/library/_library/podcast/search.vue b/client/pages/library/_library/podcast/search.vue index 3be851ce73..4ca3fe7d1a 100644 --- a/client/pages/library/_library/podcast/search.vue +++ b/client/pages/library/_library/podcast/search.vue @@ -86,6 +86,9 @@ export default { }, streamLibraryItem() { return this.$store.state.streamLibraryItem + }, + librarySettings() { + return this.$store.getters['libraries/getCurrentLibrarySettings'] } }, methods: { @@ -151,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)}`).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 39eb7a09ee..d7fc972ef9 100644 --- a/client/plugins/i18n.js +++ b/client/plugins/i18n.js @@ -29,6 +29,18 @@ Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map(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: '中国' } +} +Vue.prototype.$podcastSearchRegionOptions = Object.keys(podcastSearchRegionMap).map(code => { + return { + text: podcastSearchRegionMap[code].label, + value: code + } +}) + Vue.prototype.$languageCodes = { default: defaultCode, current: defaultCode, diff --git a/client/strings/cs.json b/client/strings/cs.json index d7afc3e91f..3ab713efb6 100644 --- a/client/strings/cs.json +++ b/client/strings/cs.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Metoda přehrávání", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasty", + "LabelPodcastSearchRegion": "Oblast vyhledávání podcastu", "LabelPodcastType": "Typ podcastu", "LabelPort": "Port", "LabelPrefixesToIgnore": "Předpony, které se mají ignorovat (nerozlišují se malá a velká písmena)", diff --git a/client/strings/da.json b/client/strings/da.json index 5b6d826e52..d456cd51c3 100644 --- a/client/strings/da.json +++ b/client/strings/da.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Afspilningsmetode", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Podcast søgeområde", "LabelPodcastType": "Podcast type", "LabelPort": "Port", "LabelPrefixesToIgnore": "Præfikser der skal ignoreres (skal ikke skelne mellem store og små bogstaver)", diff --git a/client/strings/de.json b/client/strings/de.json index 47a2646737..ff8e991a87 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Abspielmethode", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Podcast-Suchregion", "LabelPodcastType": "Podcast Typ", "LabelPort": "Port", "LabelPrefixesToIgnore": "Zu ignorierende(s) Vorwort(e) (Groß- und Kleinschreibung wird nicht berücksichtigt)", diff --git a/client/strings/en-us.json b/client/strings/en-us.json index eb30c5191a..8c58093a36 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Play Method", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Podcast search region", "LabelPodcastType": "Podcast Type", "LabelPort": "Port", "LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)", diff --git a/client/strings/es.json b/client/strings/es.json index 5687608749..f2714c00cb 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Método de Reproducción", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Región de búsqueda de podcasts", "LabelPodcastType": "Tipo Podcast", "LabelPort": "Puerto", "LabelPrefixesToIgnore": "Prefijos para Ignorar (no distingue entre mayúsculas y minúsculas.)", diff --git a/client/strings/fr.json b/client/strings/fr.json index 7e1f358cd0..3f04fab8d1 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Méthode d’écoute", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Région de recherche de podcasts", "LabelPodcastType": "Type de Podcast", "LabelPort": "Port", "LabelPrefixesToIgnore": "Préfixes à Ignorer (Insensible à la Casse)", diff --git a/client/strings/gu.json b/client/strings/gu.json index 6f3cdbb944..daa923dbed 100644 --- a/client/strings/gu.json +++ b/client/strings/gu.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Play Method", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "પોડકાસ્ટ શોધ પ્રદેશ", "LabelPodcastType": "Podcast Type", "LabelPort": "Port", "LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)", diff --git a/client/strings/hi.json b/client/strings/hi.json index 25b8e7a596..a59b43ecd8 100644 --- a/client/strings/hi.json +++ b/client/strings/hi.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Play Method", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "पॉडकास्ट खोज क्षेत्र", "LabelPodcastType": "Podcast Type", "LabelPort": "Port", "LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)", diff --git a/client/strings/hr.json b/client/strings/hr.json index f15d2065e5..6e105ca298 100644 --- a/client/strings/hr.json +++ b/client/strings/hr.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Vrsta reprodukcije", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Područje pretrage podcasta", "LabelPodcastType": "Podcast Type", "LabelPort": "Port", "LabelPrefixesToIgnore": "Prefiksi za ignorirati (mala i velika slova nisu bitna)", diff --git a/client/strings/it.json b/client/strings/it.json index be12bb514e..53394481e5 100644 --- a/client/strings/it.json +++ b/client/strings/it.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Metodo di riproduzione", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Area di ricerca podcast", "LabelPodcastType": "Tipo di Podcast", "LabelPort": "Port", "LabelPrefixesToIgnore": "Suffissi da ignorare (specificando maiuscole e minuscole)", diff --git a/client/strings/lt.json b/client/strings/lt.json index add5c57529..f98d5aee40 100644 --- a/client/strings/lt.json +++ b/client/strings/lt.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Grojimo metodas", "LabelPodcast": "Tinklalaidė", "LabelPodcasts": "Tinklalaidės", + "LabelPodcastSearchRegion": "Podcast paieškos regionas", "LabelPodcastType": "Tinklalaidės tipas", "LabelPort": "Prievadas", "LabelPrefixesToIgnore": "Ignoruojami priešdėliai (didžiosios/mažosios nesvarbu)", diff --git a/client/strings/nl.json b/client/strings/nl.json index bc4974e392..1c1ffa1bc2 100644 --- a/client/strings/nl.json +++ b/client/strings/nl.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Afspeelwijze", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Podcast zoekregio", "LabelPodcastType": "Podcasttype", "LabelPort": "Poort", "LabelPrefixesToIgnore": "Te negeren voorzetsels (ongeacht hoofdlettergebruik)", diff --git a/client/strings/no.json b/client/strings/no.json index 72d3eed46c..432115dfb3 100644 --- a/client/strings/no.json +++ b/client/strings/no.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Avspillingsmetode", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcaster", + "LabelPodcastSearchRegion": "Podcast-søkeområde", "LabelPodcastType": "Podcast type", "LabelPort": "Port", "LabelPrefixesToIgnore": "Prefiks som skal ignoreres (skiller ikke mellom store og små bokstaver)", diff --git a/client/strings/pl.json b/client/strings/pl.json index 8db18059ae..7f0e44972b 100644 --- a/client/strings/pl.json +++ b/client/strings/pl.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Metoda odtwarzania", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasty", + "LabelPodcastSearchRegion": "Obszar wyszukiwania podcastów", "LabelPodcastType": "Podcast Type", "LabelPort": "Port", "LabelPrefixesToIgnore": "Ignorowane prefiksy (wielkość liter nie ma znaczenia)", diff --git a/client/strings/pt-br.json b/client/strings/pt-br.json index 148d53d51d..cd4e9cab08 100644 --- a/client/strings/pt-br.json +++ b/client/strings/pt-br.json @@ -40,8 +40,8 @@ "ButtonLookup": "Procurar", "ButtonManageTracks": "Gerenciar Faixas", "ButtonMapChapterTitles": "Designar Títulos de Capítulos", - "ButtonitensAllAuthors": "Consultar Todos os Autores", - "ButtonitensBooks": "Consultar Livros", + "ButtonMatchAllAuthors": "Consultar Todos os Autores", + "ButtonMatchBooks": "Consultar Livros", "ButtonNevermind": "Cancelar", "ButtonNextChapter": "Próximo Capítulo", "ButtonOk": "Ok", @@ -57,7 +57,7 @@ "ButtonPurgeMediaProgress": "Apagar o Progresso nas Mídias", "ButtonQueueAddItem": "Adicionar à Lista", "ButtonQueueRemoveItem": "Remover da Lista", - "ButtonQuickitens": "Consulta rápida", + "ButtonQuickMatch": "Consulta rápida", "ButtonRead": "Ler", "ButtonRemove": "Remover", "ButtonRemoveAll": "Remover Todos", @@ -133,9 +133,9 @@ "HeaderLogin": "Login", "HeaderLogs": "Logs", "HeaderManageGenres": "Gerenciar Gêneros", - "HeaderManageetiquetas": "Gerenciar Etiquetas", + "HeaderManageTags": "Gerenciar Etiquetas", "HeaderMapDetails": "Designar Detalhes", - "Headeritens": "Consultar", + "HeaderMatch": "Consultar", "HeaderMetadataOrderOfPrecedence": "Ordem de Prioridade dos Metadados", "HeaderMetadataToEmbed": "Metadados a Serem Incluídos", "HeaderNewAccount": "Nova Conta", @@ -396,6 +396,7 @@ "LabelPlayMethod": "Método de Reprodução", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Podcast search region", "LabelPodcastType": "Tipo de Podcast", "LabelPort": "Porta", "LabelPrefixesToIgnore": "Prefixos para Ignorar (sem distinção entre maiúsculas e minúsculas)", @@ -764,4 +765,4 @@ "ToastSocketFailedToConnect": "Falha na conexão do socket", "ToastUserDeleteFailed": "Falha ao apagar usuário", "ToastUserDeleteSuccess": "Usuário apagado" -} +} \ No newline at end of file diff --git a/client/strings/ru.json b/client/strings/ru.json index 49d67fbe38..3657b596e3 100644 --- a/client/strings/ru.json +++ b/client/strings/ru.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Метод воспроизведения", "LabelPodcast": "Подкаст", "LabelPodcasts": "Подкасты", + "LabelPodcastSearchRegion": "Регион поиска подкастов", "LabelPodcastType": "Тип подкаста", "LabelPort": "Порт", "LabelPrefixesToIgnore": "Игнорируемые префиксы (без учета регистра)", diff --git a/client/strings/sv.json b/client/strings/sv.json index 3cf645b5b5..986f2c4bda 100644 --- a/client/strings/sv.json +++ b/client/strings/sv.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "Spelläge", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", + "LabelPodcastSearchRegion": "Podcast-sökområde", "LabelPodcastType": "Podcasttyp", "LabelPort": "Port", "LabelPrefixesToIgnore": "Prefix att ignorera (skiftlägesokänsligt)", diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json index 17b3cd6a6a..fd7fe29034 100644 --- a/client/strings/zh-cn.json +++ b/client/strings/zh-cn.json @@ -396,6 +396,7 @@ "LabelPlayMethod": "播放方法", "LabelPodcast": "播客", "LabelPodcasts": "播客", + "LabelPodcastSearchRegion": "播客搜索地区", "LabelPodcastType": "播客类型", "LabelPort": "端口", "LabelPrefixesToIgnore": "忽略的前缀 (不区分大小写)", diff --git a/server/controllers/SearchController.js b/server/controllers/SearchController.js index 34d65e3cd2..213d23e16d 100644 --- a/server/controllers/SearchController.js +++ b/server/controllers/SearchController.js @@ -43,12 +43,15 @@ class SearchController { */ async findPodcasts(req, res) { const term = req.query.term + 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) + const results = await PodcastFinder.search(term, { + country + }) res.json(results) } diff --git a/server/finders/PodcastFinder.js b/server/finders/PodcastFinder.js index 52fec15cba..abaf02ac61 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/objects/settings/LibrarySettings.js b/server/objects/settings/LibrarySettings.js index 10ee19e02a..b070ff79cd 100644 --- a/server/objects/settings/LibrarySettings.js +++ b/server/objects/settings/LibrarySettings.js @@ -10,6 +10,7 @@ class LibrarySettings { this.audiobooksOnly = false this.hideSingleBookSeries = false // Do not show series that only have 1 book this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] + this.podcastSearchRegion = 'us' if (settings) { this.construct(settings) @@ -30,6 +31,7 @@ class LibrarySettings { // Added in v2.4.5 this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] } + this.podcastSearchRegion = settings.podcastSearchRegion || 'us' } toJSON() { @@ -41,7 +43,8 @@ class LibrarySettings { autoScanCronExpression: this.autoScanCronExpression, audiobooksOnly: this.audiobooksOnly, hideSingleBookSeries: this.hideSingleBookSeries, - metadataPrecedence: [...this.metadataPrecedence] + metadataPrecedence: [...this.metadataPrecedence], + podcastSearchRegion: this.podcastSearchRegion } } diff --git a/server/providers/iTunes.js b/server/providers/iTunes.js index 39f36ab29d..05a661b5ed 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))