diff --git a/build.gradle.kts b/build.gradle.kts index 25f2da3..c79e1dd 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } group = "ee.bjarn" -version = "0.1" +version = "0.1.1" repositories { mavenCentral() diff --git a/src/main/kotlin/ee/bjarn/ktify/model/Episode.kt b/src/main/kotlin/ee/bjarn/ktify/model/Episode.kt index 57622bf..302290e 100644 --- a/src/main/kotlin/ee/bjarn/ktify/model/Episode.kt +++ b/src/main/kotlin/ee/bjarn/ktify/model/Episode.kt @@ -40,17 +40,6 @@ data class Episode( val uri: String, ) : KtifyObject() -@Serializable -data class EpisodePagingObject( - val href: String, - val items: List, - val limit: Int, - val next: String? = null, - val offset: Int, - val previous: String? = null, - val total: Int, -) - @Serializable data class SavedEpisodeObject( @SerialName("added_at") diff --git a/src/main/kotlin/ee/bjarn/ktify/model/KtifyObject.kt b/src/main/kotlin/ee/bjarn/ktify/model/KtifyObject.kt index 4f2045c..2e2aa70 100644 --- a/src/main/kotlin/ee/bjarn/ktify/model/KtifyObject.kt +++ b/src/main/kotlin/ee/bjarn/ktify/model/KtifyObject.kt @@ -20,7 +20,7 @@ data class RawKtifyObject( ) : KtifyObject() object KtifyObjectSerializer : JsonContentPolymorphicSerializer(KtifyObject::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { return when (element.jsonObject["item"]?.jsonObject?.get("type")?.jsonPrimitive?.content) { "track" -> Track.serializer() "album" -> Album.serializer() diff --git a/src/main/kotlin/ee/bjarn/ktify/model/PaginationObject.kt b/src/main/kotlin/ee/bjarn/ktify/model/PaginationObject.kt new file mode 100644 index 0000000..169313b --- /dev/null +++ b/src/main/kotlin/ee/bjarn/ktify/model/PaginationObject.kt @@ -0,0 +1,14 @@ +package ee.bjarn.ktify.model + +import kotlinx.serialization.Serializable + +@Serializable +data class PaginationObject( + val href: String, + val limit: Int, + val offset: Int, + val total: Int, + val items: List, + val previous: String? = null, + val next: String? = null, +) diff --git a/src/main/kotlin/ee/bjarn/ktify/model/Playlist.kt b/src/main/kotlin/ee/bjarn/ktify/model/Playlist.kt index 701ec18..65bd6cd 100755 --- a/src/main/kotlin/ee/bjarn/ktify/model/Playlist.kt +++ b/src/main/kotlin/ee/bjarn/ktify/model/Playlist.kt @@ -64,7 +64,7 @@ sealed class PlaylistTrackObject { } object PlaylistTrackObjectSerializer : JsonContentPolymorphicSerializer(PlaylistTrackObject::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { return if (element.jsonObject["track"] != null) PlaylistTrack.serializer() else PlaylistTrackRef.serializer() } } diff --git a/src/main/kotlin/ee/bjarn/ktify/model/Track.kt b/src/main/kotlin/ee/bjarn/ktify/model/Track.kt old mode 100755 new mode 100644 index 8ff646b..2de97cb --- a/src/main/kotlin/ee/bjarn/ktify/model/Track.kt +++ b/src/main/kotlin/ee/bjarn/ktify/model/Track.kt @@ -2,6 +2,8 @@ package ee.bjarn.ktify.model import ee.bjarn.ktify.model.external.ExternalId import ee.bjarn.ktify.model.external.ExternalUrl +import ee.bjarn.ktify.model.track.LinkedTrack +import ee.bjarn.ktify.model.track.TrackRestriction import ee.bjarn.ktify.model.util.ObjectType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -42,70 +44,4 @@ data class Track( val trackNumber: Int, override val type: ObjectType = ObjectType.TRACK, val uri: String, -) : KtifyObject() - -@Serializable -data class LinkedTrack( - @SerialName("external_urls") - val externalUrls: ExternalUrl, - val href: String, - val id: String, - val type: ObjectType = ObjectType.TRACK, - val uri: String, -) - -@Serializable -data class TuneableTrack( - val acousticness: Float, - val danceability: Float, - @SerialName("duration_ms") - val durationMs: Int, - val energy: Float, - val instrumentalness: Float, - val key: Int, - val liveness: Float, - val loudness: Float, - val mode: Int, - val popularity: Float, - val speechiness: Float, - val tempo: Float, - @SerialName("time_signature") - val timeSignature: Int, - val valence: Float -) - -@Serializable -data class SavedTrack( - @SerialName("added_at") - val addedAt: String, - val track: Track, -) - -@Serializable -data class TrackPagingObject( - val href: String, - val items: List, - val limit: Int, - val next: String? = null, - val offset: Int, - val previous: String? = null, - val total: Int, -) - -@Serializable -data class TrackActions( - @SerialName("is_playing") - val isPlaying: Boolean? = null, - val disallows: TrackActionsDisallows -) - -@Serializable -data class TrackActionsDisallows( - val pausing: Boolean? = null, - val resuming: Boolean? = null -) - -@Serializable -data class TrackRestriction( - val reason: RestrictionType -) +) : KtifyObject() \ No newline at end of file diff --git a/src/main/kotlin/ee/bjarn/ktify/model/User.kt b/src/main/kotlin/ee/bjarn/ktify/model/User.kt index ed50845..9d54ed9 100755 --- a/src/main/kotlin/ee/bjarn/ktify/model/User.kt +++ b/src/main/kotlin/ee/bjarn/ktify/model/User.kt @@ -39,17 +39,6 @@ data class PublicUser( val uri: String ) : KtifyObject() -@Serializable -data class UserPagingObject( - val href: String, - val items: List, - val limit: Int, - val next: String? = null, - val offset: Int, - val previous: String? = null, - val total: Int, -) - @Serializable data class ExplicitContentSettings( @SerialName("filter_enabled") diff --git a/src/main/kotlin/ee/bjarn/ktify/model/player/CurrentPlayback.kt b/src/main/kotlin/ee/bjarn/ktify/model/player/CurrentPlayback.kt index e98242e..98c1235 100755 --- a/src/main/kotlin/ee/bjarn/ktify/model/player/CurrentPlayback.kt +++ b/src/main/kotlin/ee/bjarn/ktify/model/player/CurrentPlayback.kt @@ -2,7 +2,7 @@ package ee.bjarn.ktify.model.player import ee.bjarn.ktify.model.Episode import ee.bjarn.ktify.model.Track -import ee.bjarn.ktify.model.TrackActions +import ee.bjarn.ktify.model.track.TrackActions import ee.bjarn.ktify.model.util.Context import kotlinx.serialization.* import kotlinx.serialization.descriptors.PrimitiveKind @@ -94,7 +94,7 @@ class CurrentPlaybackNull( ) : CurrentPlayback() object CurrentPlaybackSerializer : JsonContentPolymorphicSerializer(CurrentPlayback::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { return when (element.jsonObject["item"]?.jsonObject?.get("type")?.jsonPrimitive?.content) { "track" -> CurrentPlayingTrack.serializer() "episode" -> CurrentPlayingEpisode.serializer() diff --git a/src/main/kotlin/ee/bjarn/ktify/model/search/SearchResult.kt b/src/main/kotlin/ee/bjarn/ktify/model/search/SearchResult.kt index 4453bae..59c806f 100644 --- a/src/main/kotlin/ee/bjarn/ktify/model/search/SearchResult.kt +++ b/src/main/kotlin/ee/bjarn/ktify/model/search/SearchResult.kt @@ -1,14 +1,15 @@ package ee.bjarn.ktify.model.search import ee.bjarn.ktify.model.* +import ee.bjarn.ktify.model.Track import kotlinx.serialization.Serializable @Serializable data class SearchResult( - val tracks: TrackPagingObject? = null, - val episodes: EpisodePagingObject? = null, - val albums: AlbumPagingObject? = null, - val artists: ArtistPagingObject? = null, - val shows: ShowPagingObject? = null, - val users: UserPagingObject? = null, + val tracks: PaginationObject? = null, + val episodes: PaginationObject? = null, + val albums: PaginationObject? = null, + val artists: PaginationObject? = null, + val shows: PaginationObject? = null, + val users: PaginationObject? = null, ) diff --git a/src/main/kotlin/ee/bjarn/ktify/model/track/Audio.kt b/src/main/kotlin/ee/bjarn/ktify/model/track/Audio.kt new file mode 100644 index 0000000..db05970 --- /dev/null +++ b/src/main/kotlin/ee/bjarn/ktify/model/track/Audio.kt @@ -0,0 +1,163 @@ +package ee.bjarn.ktify.model.track + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AudioFeatures( + val acousticness: Float, + @SerialName("analysis_url") + val analysisUrl: String, + val danceability: Float, + @SerialName("duration_ms") + val durationMs: Int, + val energy: Float, + val id: String, + val instrumentalness: Float, + val key: Int, + val liveness: Float, + val loudness: Float, + val mode: Int, + val speechiness: Float, + val tempo: Float, + @SerialName("time_signature") + val timeSignature: Int, + @SerialName("track_href") + val trackHref: String, + val type: String, + val url: String, + val valence: Float +) + +@Serializable +data class AudioAnalysis( + val meta: AudioAnalysisMeta, + val track: AudioAnalysisTrack, + val bars: List, + val beats: List, + val sections: List, + val segments: List, + val tatums: List, +) + +@Serializable +data class AudioAnalysisMeta( + @SerialName("analyzer_version") + val analyzerVersion: String, + val platform: String, + @SerialName("detailed_status") + val detailedStatus: String, + @SerialName("status_code") + val statusCode: Int, + val timestamp: Int, + @SerialName("analysis_time") + val analysisTime: Double, + @SerialName("input_process") + val inputProcess: String, +) + +@Serializable +data class AudioAnalysisTrack( + @SerialName("num_samples") + val numSamples: Int, + val duration: Double, + @SerialName("sample_md5") + val sampleMd5: String = "", + @SerialName("offset_seconds") + val offsetSeconds: Int, + @SerialName("window_seconds") + val windowSeconds: Int, + @SerialName("analysis_sample_rate") + val analysisSampleRate: Int, + @SerialName("analysis_channel") + val analysisChannel: Int, + @SerialName("end_of_fade_in") + val endOfFaseIn: Int, + @SerialName("start_of_fade_out") + val startOfFadeOut: Int, + val loudness: Float, + val tempo: Float, + @SerialName("tempo_confidence") + val tempoConfidence: Double, + @SerialName("time_signature") + val timeSignature: Int, + @SerialName("time_signature_confidence") + val timeSignatureConfidence: Double, + val key: Int, + @SerialName("key_confidence") + val keyConfidence: Double, + val mode: Int, + @SerialName("mode_confidence") + val modeConfidence: Double, + val codestring: String, + @SerialName("code_version") + val codeVersion: Double, + val echoprintstring: String, + @SerialName("echoprint_version") + val echoprintVersion: Double, + val synchstring: String, + @SerialName("synch_version") + val synchVersion: Double, + val rhythmstring: String, + @SerialName("rhythm_version") + val rhythmVersion: Double, +) + +@Serializable +data class AudioAnalysisBar( + val start: Double, + val duration: Double, + val confidence: Double, +) + +@Serializable +data class AudioAnalysisBeat( + val start: Double, + val duration: Double, + val confidence: Double, +) + +@Serializable +data class AudioAnalysisSection( + val start: Double, + val duration: Double, + val confidence: Double, + val loudness: Double, + val tempo: Double, + @SerialName("tempo_confidence") + val tempoConfidence: Double, + val key: Int, + @SerialName("key_confidence") + val keyConfidence: Double, + val mode: Double, + @SerialName("mode_confidence") + val modeConfidence: Double, + @SerialName("time_signature") + val timeSignature: Int, + @SerialName("time_signature_confidence") + val timeSignatureConfidence: Double, +) + +@Serializable +data class AudioAnalysisSegment( + val start: Double, + val duration: Double, + val confidence: Double, + @SerialName("loudness_start") + val loudnessStart: Double, + @SerialName("loudness_max") + val loudnessMax: Double, + @SerialName("loudness_max_time") + val loudnessMaxTime: Double, + @SerialName("loudness_end") + val loudnessEnd: Double, + val pitches: List, + val timbre: List, +) + +@Serializable +data class AudioAnalysisTatum( + val start: Double, + val duration: Double, + val confidence: Double, +) diff --git a/src/main/kotlin/ee/bjarn/ktify/model/track/Recommendation.kt b/src/main/kotlin/ee/bjarn/ktify/model/track/Recommendation.kt new file mode 100644 index 0000000..95a3db2 --- /dev/null +++ b/src/main/kotlin/ee/bjarn/ktify/model/track/Recommendation.kt @@ -0,0 +1,20 @@ +package ee.bjarn.ktify.model.track + +import ee.bjarn.ktify.model.Track +import kotlinx.serialization.Serializable + +@Serializable +data class RecommendationSeed( + val afterFilteringSize: Int, + val afterRelinkingSize: Int, + val href: String, + val id: String, + val initialPoolSize: Int, + val type: String, +) + +@Serializable +data class Recommendations( + val seeds: List, + val tracks: List, +) diff --git a/src/main/kotlin/ee/bjarn/ktify/model/track/Track.kt b/src/main/kotlin/ee/bjarn/ktify/model/track/Track.kt new file mode 100755 index 0000000..b732148 --- /dev/null +++ b/src/main/kotlin/ee/bjarn/ktify/model/track/Track.kt @@ -0,0 +1,67 @@ +package ee.bjarn.ktify.model.track + +import ee.bjarn.ktify.model.* +import ee.bjarn.ktify.model.external.ExternalUrl +import ee.bjarn.ktify.model.util.ObjectType +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LinkedTrack( + @SerialName("external_urls") + val externalUrls: ExternalUrl, + val href: String, + val id: String, + val type: ObjectType = ObjectType.TRACK, + val uri: String, +) + +@Serializable +data class TuneableTrack( + val acousticness: Float, + val danceability: Float, + @SerialName("duration_ms") + val durationMs: Int, + val energy: Float, + val instrumentalness: Float, + val key: Int, + val liveness: Float, + val loudness: Float, + val mode: Int, + val popularity: Float, + val speechiness: Float, + val tempo: Float, + @SerialName("time_signature") + val timeSignature: Int, + val valence: Float +) + +@Serializable +data class SavedTrack( + @SerialName("added_at") + val addedAt: String, + val track: Track, +) + +@Serializable +data class TrackActions( + @SerialName("is_playing") + val isPlaying: Boolean? = null, + val disallows: TrackActionsDisallows +) + +@Serializable +data class TrackActionsDisallows( + val pausing: Boolean? = null, + val resuming: Boolean? = null +) + +@Serializable +data class TrackRestriction( + val reason: RestrictionType +) + +@Serializable +data class TracksResponse( + val tracks: List +) diff --git a/src/main/kotlin/ee/bjarn/ktify/player/KtifyPlayer.kt b/src/main/kotlin/ee/bjarn/ktify/player/KtifyPlayer.kt index aec41f8..3c44517 100755 --- a/src/main/kotlin/ee/bjarn/ktify/player/KtifyPlayer.kt +++ b/src/main/kotlin/ee/bjarn/ktify/player/KtifyPlayer.kt @@ -2,7 +2,7 @@ package ee.bjarn.ktify.player import ee.bjarn.ktify.Ktify import ee.bjarn.ktify.model.Episode -import ee.bjarn.ktify.model.LinkedTrack +import ee.bjarn.ktify.model.track.LinkedTrack import ee.bjarn.ktify.model.Track import ee.bjarn.ktify.model.auth.Scope import ee.bjarn.ktify.model.player.* diff --git a/src/main/kotlin/ee/bjarn/ktify/search/Search.kt b/src/main/kotlin/ee/bjarn/ktify/search/Search.kt index 3aa0729..90b8776 100644 --- a/src/main/kotlin/ee/bjarn/ktify/search/Search.kt +++ b/src/main/kotlin/ee/bjarn/ktify/search/Search.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.json.Json * @param types The types of the searched items * @param limit The limit of search results per category, must be between 1 and 50, otherwise it will be 20 * @param offset The offset, maximum is 1000 (including the limit) - * @param includeExternal Weather to include external hosted audio or not + * @param includeExternal Whether to include external hosted audio or not * @param market (Optional) [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code of a country. Can also be 'from_token', equivalent to the current users country. * @param queue The queue for searching items * @return The search results as a SearchResult @@ -124,7 +124,7 @@ class SearchQueueBuilder { /** * The class representing a part of a search queue * @param value The keyword - * @param explicit Weather the keyword should be an exact match + * @param explicit Whether the keyword should be an exact match */ open class Phrase(val value: String, private val explicit: Boolean = false) { /** diff --git a/src/main/kotlin/ee/bjarn/ktify/tracks/Tracks.kt b/src/main/kotlin/ee/bjarn/ktify/tracks/Tracks.kt new file mode 100644 index 0000000..8703e30 --- /dev/null +++ b/src/main/kotlin/ee/bjarn/ktify/tracks/Tracks.kt @@ -0,0 +1,295 @@ +package ee.bjarn.ktify.tracks + +import io.ktor.http.* +import ee.bjarn.ktify.Ktify +import ee.bjarn.ktify.model.* +import ee.bjarn.ktify.model.auth.Scope +import ee.bjarn.ktify.model.Track +import ee.bjarn.ktify.model.track.* +import ee.bjarn.ktify.utils.InputException +import io.ktor.client.request.* + +/** + * @param id Spotify ID of the track + * @param market The market to search in + * @return The corresponding track object + */ +suspend fun Ktify.getTrack( + id: String, + market: String? = null +): Track { + return requestHelper.makeRequest( + requiresAuthentication = true + ) { + method = HttpMethod.Get + url.takeFrom(requestHelper.baseUrl + "tracks/$id") + if (market != null) { + parameter("market", market) + } + } +} + +/** + * @param ids List of Spotify IDs of the tracks + * @param market The market to search in + */ +suspend fun Ktify.getSeveralTracks( + ids: List, + market: String? = null +): TracksResponse { + return requestHelper.makeRequest( + requiresAuthentication = true + ) { + method = HttpMethod.Get + url.takeFrom(requestHelper.baseUrl + "tracks") + parameter("ids", ids.joinToString(",")) + if (market != null) { + parameter("market", market) + } + } +} + +/** + * @param market The market to search in + * @param limit The maximum number of items return + * @param offset The index of the first item to return + * @return A pagination object of SavedTracks + */ +suspend fun Ktify.getSavedTracks( + market: String? = null, + limit: Int? = null, + offset: Int? = null +): PaginationObject { + return requestHelper.makeRequest( + requiresAuthentication = true, + requiresScope = Scope.USER_LIBRARY_READ + ) { + method = HttpMethod.Get + url.takeFrom(requestHelper.baseUrl + "me/tracks") + if (market != null) { + parameter("market", market) + } + if (limit != null) { + parameter("limit", limit) + } + if (offset != null) { + parameter("offset", offset) + } + } +} + +/** + * Save tracks in the user's library. Maximum of 50 IDs, following IDs will be ignored. + * @param ids List of track IDs to save + */ +suspend fun Ktify.saveTracks( + ids: List +) { + requestHelper.makeRequest( + requiresAuthentication = true, + requiresScope = Scope.USER_LIBRARY_MODIFY + ) { + method = HttpMethod.Put + url.takeFrom(requestHelper.baseUrl + "me/tracks") + parameter("ids", (if (ids.size > 50) ids.subList(0, 50) else ids).joinToString(",")) + } +} + +/** + * Delete tracks in the user's library. Maximum of 50 IDs, following IDs will be ignored. + * @param ids List of track IDs to remove from the user's library + */ +suspend fun Ktify.removeSavedTracks( + ids: List +) { + requestHelper.makeRequest( + requiresAuthentication = true, + requiresScope = Scope.USER_LIBRARY_MODIFY + ) { + method = HttpMethod.Delete + url.takeFrom(requestHelper.baseUrl + "me/tracks") + parameter("ids", (if (ids.size > 50) ids.subList(0, 50) else ids).joinToString(",")) + } +} + +/* + * Check if tracks are already saved in the user's library. + * @param ids List of track IDs to check, Maximum: 50, following will be ingnored + * @return List of booleans that indicate whether the track is already saved or not + */ +suspend fun Ktify.containsSavedTracks( + ids: List +) : List { + return requestHelper.makeRequest( + requiresAuthentication = true, + requiresScope = Scope.USER_LIBRARY_READ + ) { + method = HttpMethod.Get + url.takeFrom(requestHelper.baseUrl + "me/tracks/contains") + parameter("ids", (if (ids.size > 50) ids.subList(0, 50) else ids).joinToString(",")) + } +} + +/** + * Fetch audio features for several tracks. + * @param ids List of track IDs, Maximum: 100, following will be ignored + * @return Array of Audio Features object + */ +suspend fun Ktify.getSeveralAudioFeatures( + ids: List +) : List { + return requestHelper.makeRequest( + requiresAuthentication = true + ) { + method = HttpMethod.Get + url.takeFrom(requestHelper.baseUrl + "audio-features") + parameter("ids", (if (ids.size > 100) ids.subList(0, 100) else ids).joinToString(",")) + } +} + +/** + * Fetch audio features. + * @param id Spotify track ID + * @return Audio Features object + */ +suspend fun Ktify.getAudioFeatures( + id: String +) : AudioFeatures { + return requestHelper.makeRequest( + requiresAuthentication = true + ) { + method = HttpMethod.Get + url.takeFrom(requestHelper.baseUrl + "audio-features/$id") + } +} + +/** + * Get a detailed audio analysis of a track + * @param id Spotify track ID + * @return Audio Analysis object + */ +suspend fun Ktify.getAudioAnalysis( + id: String +) : AudioAnalysis { + return requestHelper.makeRequest( + requiresAuthentication = true + ) { + method = HttpMethod.Get + url.takeFrom(requestHelper.baseUrl + "audio-analysis/$id") + } +} + +/** + * Get recommendations + * A minimum of one seed in either artists, genres or tracks has to be provided, a maximum of 5 in combination of all are allowed + * For parameter documentation. refer to [Spotify Documentation](https://developer.spotify.com/documentation/web-api/reference/get-recommendations) + * @return Recommendations object + */ +suspend fun Ktify.getRecommendations( + seedArtists: List = listOf(), + seedGenres: List = listOf(), + seedTracks: List = listOf(), + limit: Int? = null, + market: String? = null, + minAcousticness: Double? = null, + maxAcousticness: Double? = null, + targetAcousticness: Double? = null, + minDanceability: Double? = null, + maxDancability: Double? = null, + targetDancability: Double? = null, + minDurationMs: Int? = null, + maxDurationMs: Int? = null, + targetDurationMs: Int? = null, + minEnergy: Double? = null, + maxEnergy: Double? = null, + targetEnergy: Double? = null, + minInstrumentalness: Double? = null, + maxInstrumentalness: Double? = null, + targetInstrumentalness: Double? = null, + minKey: Int? = null, + maxKey: Int? = null, + targetKey: Int? = null, + minLiveness: Double? = null, + maxLiveness: Double? = null, + targetLiveness: Double? = null, + minLoudness: Double? = null, + maxLoudness: Double? = null, + targetLoudness: Double? = null, + minMode: Int? = null, + maxMode: Int? = null, + targetMode: Int? = null, + minPopularity: Int? = null, + maxPopularity: Int? = null, + targetPopularity: Int? = null, + minSpeechiness: Double? = null, + maxSpeechiness: Double? = null, + targetSpeechiness: Double? = null, + minTempo: Double? = null, + maxTempo: Double? = null, + targetTempo: Double? = null, + minTimeSignature: Int? = null, + maxTimeSignature: Int? = null, + targetTimeSignature: Int? = null, + minValence: Double? = null, + maxValence: Double? = null, + targetValence: Double? = null, +) : Recommendations { + return requestHelper.makeRequest( + requiresAuthentication = true + ) { + method = HttpMethod.Get + url.takeFrom(requestHelper.baseUrl + "recommendations") + val seedSize = seedArtists.size + seedGenres.size + seedTracks.size + if (seedSize < 1 || seedSize > 5) { + throw InputException(listOf("seedArtists", "seedGenres", "seedTracks")) + } + if (seedArtists.isNotEmpty()) parameter("seed_artists", seedArtists.joinToString(",") { it.id }) + if (seedGenres.isNotEmpty()) parameter("seed_genres", seedGenres.joinToString(",")) + if (seedTracks.isNotEmpty()) parameter("seed_tracks", seedTracks.joinToString(",") { it.id }) + + if (limit != null) parameter("limit", limit) + if (market != null) parameter("market", market) + if (minAcousticness != null) parameter("min_acousticness", minAcousticness) + if (maxAcousticness != null) parameter("max_acousticness", maxAcousticness) + if (targetAcousticness != null) parameter("target_acousticness", targetAcousticness) + if (minDanceability != null) parameter("min_danceability", minDanceability) + if (maxDancability != null) parameter("max_danceability", maxDancability) + if (targetDancability != null) parameter("target_danceability", targetDancability) + if (minDurationMs != null) parameter("min_duration_ms", minDurationMs) + if (maxDurationMs != null) parameter("max_duration_ms", maxDurationMs) + if (targetDurationMs != null) parameter("target_duration_ms", targetDurationMs) + if (minEnergy != null) parameter("min_energy", minEnergy) + if (maxEnergy != null) parameter("max_energy", maxEnergy) + if (targetEnergy != null) parameter("target_energy", targetEnergy) + if (minInstrumentalness != null) parameter("min_instrumentalness", minInstrumentalness) + if (maxInstrumentalness != null) parameter("max_instrumentalness", maxInstrumentalness) + if (targetInstrumentalness != null) parameter("target_instrumentalness", targetInstrumentalness) + if (minKey != null) parameter("min_key", minKey) + if (maxKey != null) parameter("max_key", maxKey) + if (targetKey != null) parameter("target_key", targetKey) + if (minLiveness != null) parameter("min_liveness", minLiveness) + if (maxLiveness != null) parameter("max_liveness", maxLiveness) + if (targetLiveness != null) parameter("target_liveness", targetLiveness) + if (minLoudness != null) parameter("min_loudness", minLoudness) + if (maxLoudness != null) parameter("max_loudness", maxLoudness) + if (targetLoudness != null) parameter("target_loudness", targetLoudness) + if (minMode != null) parameter("min_mode", minMode) + if (maxMode != null) parameter("max_mode", maxMode) + if (targetMode != null) parameter("target_mode", targetMode) + if (minPopularity != null) parameter("min_popularity", minPopularity) + if (maxPopularity != null) parameter("max_popularity", maxPopularity) + if (targetPopularity != null) parameter("target_popularity", targetPopularity) + if (minSpeechiness != null) parameter("min_speechiness", minSpeechiness) + if (maxSpeechiness != null) parameter("max_speechiness", maxSpeechiness) + if (targetSpeechiness != null) parameter("target_speechiness", targetSpeechiness) + if (minTempo != null) parameter("min_tempo", minTempo) + if (maxTempo != null) parameter("max_tempo", maxTempo) + if (targetTempo != null) parameter("target_tempo", targetTempo) + if (minTimeSignature != null) parameter("min_time_signature", minTimeSignature) + if (maxTimeSignature != null) parameter("max_time_signature", maxTimeSignature) + if (targetTimeSignature != null) parameter("target_time_signature", targetTimeSignature) + if (minValence != null) parameter("min_valence", minValence) + if (maxValence != null) parameter("max_valence", maxValence) + if (targetValence != null) parameter("target_valence", targetValence) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/bjarn/ktify/utils/Exceptions.kt b/src/main/kotlin/ee/bjarn/ktify/utils/Exceptions.kt index f873f70..ae6dba7 100755 --- a/src/main/kotlin/ee/bjarn/ktify/utils/Exceptions.kt +++ b/src/main/kotlin/ee/bjarn/ktify/utils/Exceptions.kt @@ -8,7 +8,9 @@ import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import java.lang.Exception import java.lang.RuntimeException +import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec class RequestException(override val message: String = "Unauthorized", val error: ErrorObject) : RuntimeException(message) @@ -18,6 +20,8 @@ class AuthenticationException(override val message: String, val error: Authentic class RateLimitException(override val message: String = "Too many requests", val retryAfterMs: Long) : RuntimeException(message) +class InputException(val parameters: List, override val message: String = "Provided parameter input is not valid") : Exception(message) + @Serializable data class ErrorObject( @Serializable(with = HttpStatusCodeSerializer::class)