2022-03-06 23:32:04 +01:00
const axios = require ( 'axios' )
const Logger = require ( '../Logger' )
2022-05-28 02:41:40 +02:00
const htmlSanitizer = require ( '../utils/htmlSanitizer' )
2024-02-17 20:24:49 +01:00
/ * *
* @ 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
* /
2022-03-06 23:32:04 +01:00
class iTunes {
2024-05-25 23:32:02 +02:00
# responseTimeout = 30000
constructor ( ) { }
2022-03-06 23:32:04 +01:00
2024-02-17 20:24:49 +01:00
/ * *
* @ see https : //developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI/Searching.html
2024-05-25 23:32:02 +02:00
*
* @ param { iTunesSearchParams } options
* @ param { number } [ timeout ] response timeout in ms
2024-02-17 20:24:49 +01:00
* @ returns { Promise < Object [ ] > }
* /
2024-05-25 23:32:02 +02:00
search ( options , timeout = this . # responseTimeout ) {
2022-03-06 23:32:04 +01:00
if ( ! options . term ) {
Logger . error ( '[iTunes] Invalid search options - no term' )
return [ ]
}
2024-05-25 23:32:02 +02:00
if ( ! timeout || isNaN ( timeout ) ) timeout = this . # responseTimeout
2024-02-17 20:24:49 +01:00
const query = {
2022-03-06 23:32:04 +01:00
term : options . term ,
media : options . media ,
entity : options . entity ,
lang : options . lang ,
limit : options . limit ,
2024-01-05 07:45:35 +01:00
country : options . country
2022-03-06 23:32:04 +01:00
}
2024-05-25 23:32:02 +02:00
return axios
. get ( 'https://itunes.apple.com/search' , {
params : query ,
timeout
} )
. then ( ( response ) => {
return response . data . results || [ ]
} )
. catch ( ( error ) => {
Logger . error ( ` [iTunes] search request error ` , error )
return [ ]
} )
2022-03-06 23:32:04 +01:00
}
2022-03-07 00:26:35 +01:00
2022-03-07 02:02:06 +01:00
// Example cover art: https://is1-ssl.mzstatic.com/image/thumb/Music118/v4/cb/ea/73/cbea739b-ff3b-11c4-fb93-7889fbec7390/9781598874983_cover.jpg/100x100bb.jpg
// 100x100bb can be replaced by other values https://github.com/bendodson/itunes-artwork-finder
// Target size 600 or larger
getCoverArtwork ( data ) {
if ( data . artworkUrl600 ) {
return data . artworkUrl600
}
// Should already be sorted from small to large
2024-05-25 23:32:02 +02:00
var artworkSizes = Object . keys ( data )
. filter ( ( key ) => key . startsWith ( 'artworkUrl' ) )
. map ( ( key ) => {
return {
url : data [ key ] ,
size : Number ( key . replace ( 'artworkUrl' , '' ) )
}
} )
2022-03-07 02:02:06 +01:00
if ( ! artworkSizes . length ) return null
// Return next biggest size > 600
2024-05-25 23:32:02 +02:00
var nextBestSize = artworkSizes . find ( ( size ) => size . size > 600 )
2022-03-07 02:02:06 +01:00
if ( nextBestSize ) return nextBestSize . url
// Find square artwork
2024-05-25 23:32:02 +02:00
var squareArtwork = artworkSizes . find ( ( size ) => size . url . includes ( ` ${ size . size } x ${ size . size } bb ` ) )
2022-03-07 02:02:06 +01:00
// Square cover replace with 600x600bb
if ( squareArtwork ) {
return squareArtwork . url . replace ( ` ${ squareArtwork . size } x ${ squareArtwork . size } bb ` , '600x600bb' )
}
// Last resort just return biggest size
return artworkSizes [ artworkSizes . length - 1 ] . url
}
2022-03-07 00:26:35 +01:00
cleanAudiobook ( data ) {
2022-10-05 00:41:26 +02:00
// artistName can be "Name1, Name2 & Name3" so we refactor this to "Name1, Name2, Name3"
// see: https://github.com/advplyr/audiobookshelf/issues/1022
const author = ( data . artistName || '' ) . split ( ' & ' ) . join ( ', ' )
2022-03-07 00:26:35 +01:00
return {
id : data . collectionId ,
artistId : data . artistId ,
title : data . collectionName ,
2022-10-05 00:41:26 +02:00
author ,
2022-05-28 02:41:40 +02:00
description : htmlSanitizer . stripAllTags ( data . description || '' ) ,
2022-03-14 01:34:31 +01:00
publishedYear : data . releaseDate ? data . releaseDate . split ( '-' ) [ 0 ] : null ,
2022-10-01 23:51:22 +02:00
genres : data . primaryGenreName ? [ data . primaryGenreName ] : null ,
2022-03-07 02:02:06 +01:00
cover : this . getCoverArtwork ( data )
2022-03-07 00:26:35 +01:00
}
}
2024-05-25 23:32:02 +02:00
/ * *
*
* @ param { string } term
* @ param { number } [ timeout ] response timeout in ms
* @ returns { Promise < Object [ ] > }
* /
searchAudiobooks ( term , timeout = this . # responseTimeout ) {
return this . search ( { term , entity : 'audiobook' , media : 'audiobook' } , timeout ) . then ( ( results ) => {
2022-03-07 02:02:06 +01:00
return results . map ( this . cleanAudiobook . bind ( this ) )
} )
}
2024-02-17 20:24:49 +01:00
/ * *
2024-05-25 23:32:02 +02:00
*
* @ param { Object } data
2024-02-17 20:24:49 +01:00
* @ returns { iTunesPodcastSearchResult }
* /
2022-03-07 02:02:06 +01:00
cleanPodcast ( data ) {
return {
id : data . collectionId ,
2022-04-14 01:13:39 +02:00
artistId : data . artistId || null ,
2022-03-07 02:02:06 +01:00
title : data . collectionName ,
artistName : data . artistName ,
2022-05-28 02:41:40 +02:00
description : htmlSanitizer . sanitize ( data . description || '' ) ,
descriptionPlain : htmlSanitizer . stripAllTags ( data . description || '' ) ,
2022-03-07 02:02:06 +01:00
releaseDate : data . releaseDate ,
genres : data . genres || [ ] ,
cover : this . getCoverArtwork ( data ) ,
trackCount : data . trackCount ,
feedUrl : data . feedUrl ,
2023-02-22 13:48:12 +01:00
pageUrl : data . collectionViewUrl ,
explicit : data . trackExplicitness === 'explicit'
2022-03-07 02:02:06 +01:00
}
}
2024-02-17 20:24:49 +01:00
/ * *
2024-05-25 23:32:02 +02:00
*
* @ param { string } term
* @ param { { country : string } } options
* @ param { number } [ timeout ] response timeout in ms
2024-02-17 20:24:49 +01:00
* @ returns { Promise < iTunesPodcastSearchResult [ ] > }
* /
2024-05-25 23:32:02 +02:00
searchPodcasts ( term , options = { } , timeout = this . # responseTimeout ) {
return this . search ( { term , entity : 'podcast' , media : 'podcast' , ... options } , timeout ) . then ( ( results ) => {
2022-03-07 02:02:06 +01:00
return results . map ( this . cleanPodcast . bind ( this ) )
2022-03-07 00:26:35 +01:00
} )
}
2022-03-06 23:32:04 +01:00
}
2023-02-22 13:48:12 +01:00
module . exports = iTunes