Skip to content

Commit b6a6e29

Browse files
committed
document and test track removal endpoints
Signed-off-by: Adam Ratzman <adam@adamratzman.com>
1 parent 5fb43e0 commit b6a6e29

File tree

3 files changed

+118
-50
lines changed

3 files changed

+118
-50
lines changed

src/main/kotlin/com/adamratzman/spotify/endpoints/client/ClientPlaylistAPI.kt

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ class ClientPlaylistAPI(api: SpotifyAPI) : PlaylistsAPI(api) {
6969
})
7070
}
7171

72+
73+
/**
74+
* Add a track to a user’s playlist.
75+
*
76+
* @param playlist The Spotify ID for the playlist.
77+
* @param tracks Spotify track id
78+
* @param position The position to insert the tracks, a zero-based index. For example, to insert the tracks in the
79+
* first position: position=0; to insert the tracks in the third position: position=2 . If omitted, the tracks will
80+
* be appended to the playlist. Tracks are added in the order they are listed in the query string or request body.
81+
*
82+
* @throws BadRequestException if any invalid track ids is provided or the playlist is not found
83+
*/
84+
85+
fun addTrackToPlaylist(playlist: String,track: String,position: Int?=null)
86+
= addTracksToPlaylist(playlist,track,position = position)
87+
7288
/**
7389
* Add one or more tracks to a user’s playlist.
7490
*
@@ -185,7 +201,7 @@ class ClientPlaylistAPI(api: SpotifyAPI) : PlaylistsAPI(api) {
185201
*
186202
* @throws BadRequestException if the playlist is not found or illegal filters are applied
187203
*/
188-
fun reorderTracks(
204+
fun reorderPlaylistTracks(
189205
playlist: String,
190206
reorderRangeStart: Int,
191207
reorderRangeLength: Int? = null,
@@ -273,26 +289,55 @@ class ClientPlaylistAPI(api: SpotifyAPI) : PlaylistsAPI(api) {
273289
})
274290
}
275291

276-
fun removePlaylistTrack(
292+
/**
293+
* Remove a track in the specified positions (zero-based) from the specified playlist.
294+
*
295+
* @param playlist the playlist id
296+
* @param track the track id
297+
* @param positions the positions at which the track is located in the playlist
298+
* @param snapshotId the playlist snapshot against which to apply the track removals. **recommended to have**
299+
*/
300+
fun removeTrackFromPlaylist(
277301
playlist: String,
278302
track: String,
279303
positions: SpotifyTrackPositions,
280304
snapshotId: String? = null
281-
) = removePlaylistTracks(playlist, track to positions, snapshotId = snapshotId)
305+
) = removeTracksFromPlaylist(playlist, track to positions, snapshotId = snapshotId)
282306

283-
fun removePlaylistTrack(
307+
/**
308+
* Remove all occurrences of a track from the specified playlist.
309+
*
310+
* @param playlist the playlist id
311+
* @param track the track id
312+
* @param snapshotId the playlist snapshot against which to apply the track removals. **recommended to have**
313+
*/
314+
fun removeTrackFromPlaylist(
284315
playlist: String,
285316
track: String,
286317
snapshotId: String? = null
287-
) = removePlaylistTracks(playlist, track, snapshotId = snapshotId)
318+
) = removeTracksFromPlaylist(playlist, track, snapshotId = snapshotId)
288319

289-
fun removePlaylistTracks(
320+
/**
321+
* Remove all occurrences of the specified tracks from the given playlist.
322+
*
323+
* @param playlist the playlist id
324+
* @param tracks an array of track ids
325+
* @param snapshotId the playlist snapshot against which to apply the track removals. **recommended to have**
326+
*/
327+
fun removeTracksFromPlaylist(
290328
playlist: String,
291329
vararg tracks: String,
292330
snapshotId: String? = null
293331
) = removePlaylistTracksImpl(playlist, tracks.map { it to null }.toTypedArray(), snapshotId)
294332

295-
fun removePlaylistTracks(
333+
/**
334+
* Remove tracks (each with their own positions) from the given playlist.
335+
*
336+
* @param playlist the playlist id
337+
* @param tracks an array of [Pair]s of track ids *and* track positions (zero-based)
338+
* @param snapshotId the playlist snapshot against which to apply the track removals. **recommended to have**
339+
*/
340+
fun removeTracksFromPlaylist(
296341
playlist: String,
297342
vararg tracks: Pair<String, SpotifyTrackPositions>,
298343
snapshotId: String? = null
@@ -302,7 +347,7 @@ class ClientPlaylistAPI(api: SpotifyAPI) : PlaylistsAPI(api) {
302347
playlist: String,
303348
tracks: Array<Pair<String, SpotifyTrackPositions?>>,
304349
snapshotId: String?
305-
): SpotifyRestAction<String> {
350+
): SpotifyRestAction<Snapshot> {
306351
return toAction(Supplier {
307352
if (tracks.isEmpty()) throw IllegalArgumentException("You need to provide at least one track to remove")
308353

@@ -311,29 +356,15 @@ class ClientPlaylistAPI(api: SpotifyAPI) : PlaylistsAPI(api) {
311356
tracks.map { (track, positions) ->
312357
JsonObject().apply {
313358
this["uri"] = TrackURI(track).uri
314-
if (positions?.positions?.isNotEmpty() == true) this.put("positions", positions.positions)
315-
}.also { if (positions?.positions?.isNotEmpty() == true) it["positions"] = positions.positions }
359+
if (positions?.positions?.isNotEmpty() == true) this["positions"] = positions.positions
360+
}.also { if (positions?.positions?.isNotEmpty() == true) it["positions"] = positions }
316361
}.let { json.put("tracks", JsonArray(it)) }
317362
delete(
318363
EndpointBuilder("/playlists/${PlaylistURI(playlist).id}/tracks").toString(), body = json.toJsonString()
319-
)
364+
).toObject<Snapshot>(api)
320365
})
321366
}
322367

323-
/*
324-
fun removeAllOccurances(user: String, playlist: String, vararg tracks: String): SpotifyRestAction<Unit> {
325-
if (tracks.isEmpty()) throw IllegalArgumentException("Tracks to remove must not be empty")
326-
return toAction(Supplier {
327-
val json = JSONObject()
328-
json.put("tracks", tracks.map { JSONObject().put("uri", TrackURI(it).uri) })
329-
println(json.toString())
330-
val userURI = UserURI(user)
331-
delete("https://api.spotify.com/v1/users/${userURI.id}/playlists/${userURI.PlaylistURI(playlist).id}/tracks",
332-
body = json.toString(), contentType = "application/json")
333-
Unit
334-
})
335-
}
336-
*/
337368
private fun encode(image: BufferedImage): String {
338369
val bos = ByteArrayOutputStream()
339370
ImageIO.write(image, "jpg", bos)
@@ -344,4 +375,4 @@ class ClientPlaylistAPI(api: SpotifyAPI) : PlaylistsAPI(api) {
344375
data class Snapshot(val snapshot_id: String)
345376
}
346377

347-
class SpotifyTrackPositions(vararg val positions: Int)
378+
class SpotifyTrackPositions(vararg val positions: Int) : ArrayList<Int>(positions.toList())

src/main/kotlin/com/adamratzman/spotify/utils/Helpers.kt

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,17 @@ internal inline fun <reified T> String.toObjectNullable(o: SpotifyAPI?): T? = tr
5353
}
5454

5555
internal inline fun <reified T> String.toObject(o: SpotifyAPI?): T {
56-
try {
57-
val klaxon = o?.klaxon ?: Klaxon()
58-
val obj = klaxon.parse<T>(this) ?: throw SpotifyException(
59-
"Unable to parse $this",
60-
IllegalArgumentException("$this not found")
61-
)
62-
o?.let {
63-
if (obj is Linkable) obj.api = o
64-
if (obj is AbstractPagingObject<*>) obj.endpoint = o.tracks
65-
obj.instantiatePagingObjects(o)
66-
}
67-
return obj
68-
} catch (e: java.lang.Exception) {
69-
println(this)
70-
throw e
56+
val klaxon = o?.klaxon ?: Klaxon()
57+
val obj = klaxon.parse<T>(this) ?: throw SpotifyException(
58+
"Unable to parse $this",
59+
IllegalArgumentException("$this not found")
60+
)
61+
o?.let {
62+
if (obj is Linkable) obj.api = o
63+
if (obj is AbstractPagingObject<*>) obj.endpoint = o.tracks
64+
obj.instantiatePagingObjects(o)
7165
}
66+
return obj
7267
}
7368

7469
internal inline fun <reified T> String.toArray(o: SpotifyAPI?): List<T> {

src/test/kotlin/com/adamratzman/spotify/private/ClientPlaylistAPITest.kt

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
package com.adamratzman.spotify.private
33

44
import com.adamratzman.spotify.api
5+
import com.adamratzman.spotify.endpoints.client.SpotifyTrackPositions
56
import com.adamratzman.spotify.main.SpotifyClientAPI
7+
import com.adamratzman.spotify.utils.BadRequestException
68
import org.junit.jupiter.api.Assertions.assertEquals
79
import org.junit.jupiter.api.Assertions.assertTrue
10+
import org.junit.jupiter.api.assertThrows
811
import org.spekframework.spek2.Spek
912
import org.spekframework.spek2.style.specification.describe
1013

@@ -13,29 +16,36 @@ class ClientPlaylistAPITest : Spek({
1316
val cp = (api as? SpotifyClientAPI)?.playlists
1417
val playlistsBefore = cp?.getClientPlaylists()?.complete()
1518
val createdPlaylist = cp?.createPlaylist("this is a test playlist", "description")
16-
?.complete()
19+
?.complete()
1720

1821
createdPlaylist ?: return@describe
1922
it("get playlists for user, then see if we can create/delete playlists") {
2023
assertTrue(cp.getClientPlaylists().complete().size - 1 == playlistsBefore?.size)
2124
}
2225
it("edit playlists") {
23-
cp.changePlaylistDescription(createdPlaylist.id, "test playlist", false,
24-
true, "description 2").complete()
26+
cp.changePlaylistDescription(
27+
createdPlaylist.id, "test playlist", false,
28+
true, "description 2"
29+
).complete()
2530

2631
cp.addTracksToPlaylist(createdPlaylist.id, "3WDIhWoRWVcaHdRwMEHkkS", "7FjZU7XFs7P9jHI9Z0yRhK").complete()
2732

28-
cp.uploadPlaylistCover(createdPlaylist.id, imageUrl = "https://developer.spotify.com/assets/WebAPI_intro.png").complete()
33+
cp.uploadPlaylistCover(
34+
createdPlaylist.id,
35+
imageUrl = "https://developer.spotify.com/assets/WebAPI_intro.png"
36+
).complete()
2937

3038
var updatedPlaylist = cp.getClientPlaylist(createdPlaylist.id).complete()!!
3139
val fullPlaylist = updatedPlaylist.toFullPlaylist().complete()!!
3240

33-
assertTrue(updatedPlaylist.collaborative && updatedPlaylist.public == false &&
34-
updatedPlaylist.name == "test playlist" && fullPlaylist.description == "description 2")
41+
assertTrue(
42+
updatedPlaylist.collaborative && updatedPlaylist.public == false &&
43+
updatedPlaylist.name == "test playlist" && fullPlaylist.description == "description 2"
44+
)
3545

3646
assertTrue(updatedPlaylist.tracks.total == 2 && updatedPlaylist.images.isNotEmpty())
3747

38-
cp.reorderTracks(updatedPlaylist.id, 1, insertionPoint = 0).complete()
48+
cp.reorderPlaylistTracks(updatedPlaylist.id, 1, insertionPoint = 0).complete()
3949

4050
updatedPlaylist = cp.getClientPlaylist(createdPlaylist.id).complete()!!
4151

@@ -55,9 +65,41 @@ class ClientPlaylistAPITest : Spek({
5565

5666
assertTrue(cp.getPlaylistTracks(createdPlaylist.id).complete().items.size == 4)
5767

58-
cp.removePlaylistTrack(createdPlaylist.id, trackIdOne).complete()
68+
cp.removeTrackFromPlaylist(createdPlaylist.id, trackIdOne).complete()
5969

60-
assertEquals(listOf(trackIdTwo, trackIdTwo), cp.getPlaylistTracks(createdPlaylist.id).complete().items.map { it.track.id })
70+
assertEquals(
71+
listOf(trackIdTwo, trackIdTwo),
72+
cp.getPlaylistTracks(createdPlaylist.id).complete().items.map { it.track.id })
73+
74+
cp.addTrackToPlaylist(createdPlaylist.id, trackIdOne).complete()
75+
76+
cp.removeTrackFromPlaylist(createdPlaylist.id, trackIdTwo, SpotifyTrackPositions(1)).complete()
77+
78+
assertEquals(
79+
listOf(trackIdTwo, trackIdOne),
80+
cp.getPlaylistTracks(createdPlaylist.id).complete().items.map { it.track.id })
81+
82+
cp.setPlaylistTracks(createdPlaylist.id, trackIdOne, trackIdOne, trackIdTwo, trackIdTwo).complete()
83+
84+
cp.removeTracksFromPlaylist(createdPlaylist.id, trackIdOne, trackIdTwo).complete()
85+
86+
assertTrue(cp.getPlaylistTracks(createdPlaylist.id).complete().items.isEmpty())
87+
88+
cp.setPlaylistTracks(createdPlaylist.id, trackIdTwo, trackIdOne, trackIdTwo, trackIdTwo, trackIdOne)
89+
.complete()
90+
91+
cp.removeTracksFromPlaylist(
92+
createdPlaylist.id, Pair(trackIdOne, SpotifyTrackPositions(4)),
93+
Pair(trackIdTwo, SpotifyTrackPositions(0))
94+
).complete()
95+
96+
assertEquals(
97+
listOf(trackIdOne, trackIdTwo, trackIdTwo),
98+
cp.getPlaylistTracks(createdPlaylist.id).complete().items.map { it.track.id })
99+
100+
assertThrows<BadRequestException> {
101+
cp.removeTracksFromPlaylist(createdPlaylist.id, Pair(trackIdOne, SpotifyTrackPositions(3))).complete()
102+
}
61103
}
62104

63105
it("destroy (unfollow) playlist") {

0 commit comments

Comments
 (0)