diff --git a/allstarr/Services/Spotify/SpotifyApiClient.cs b/allstarr/Services/Spotify/SpotifyApiClient.cs index 5372c3b..c66e6b8 100644 --- a/allstarr/Services/Spotify/SpotifyApiClient.cs +++ b/allstarr/Services/Spotify/SpotifyApiClient.cs @@ -775,53 +775,18 @@ public class SpotifyApiClient : IDisposable while (true) { - // GraphQL query to fetch user playlists - var graphqlQuery = new + // GraphQL query to fetch user playlists - using libraryV3 operation + var queryParams = new Dictionary { - operationName = "fetchLibraryPlaylists", - variables = new - { - offset, - limit - }, - query = @" - query fetchLibraryPlaylists($offset: Int!, $limit: Int!) { - me { - library { - playlists(offset: $offset, limit: $limit) { - totalCount - items { - playlist { - uri - name - description - images { - url - } - ownerV2 { - data { - __typename - ... on User { - id - name - } - } - } - } - } - } - } - } - }" + { "operationName", "libraryV3" }, + { "variables", $"{{\"filters\":[\"Playlists\",\"By Spotify\"],\"order\":null,\"textFilter\":\"\",\"features\":[\"LIKED_SONGS\",\"YOUR_EPISODES\"],\"offset\":{offset},\"limit\":{limit}}}" }, + { "extensions", "{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"50650f72ea32a99b5b46240bee22fea83024eec302478a9a75cfd05a0814ba99\"}}" } }; - var request = new HttpRequestMessage(HttpMethod.Post, $"{WebApiBase}/query") - { - Content = new StringContent( - JsonSerializer.Serialize(graphqlQuery), - System.Text.Encoding.UTF8, - "application/json") - }; + var queryString = string.Join("&", queryParams.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}")); + var url = $"{WebApiBase}/query?{queryString}"; + + var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await _webApiClient.SendAsync(request, cancellationToken); @@ -849,20 +814,44 @@ public class SpotifyApiClient : IDisposable if (!root.TryGetProperty("data", out var data) || !data.TryGetProperty("me", out var me) || - !me.TryGetProperty("library", out var library) || - !library.TryGetProperty("playlists", out var playlistsData) || - !playlistsData.TryGetProperty("items", out var items)) + !me.TryGetProperty("libraryV3", out var library) || + !library.TryGetProperty("items", out var items)) { break; } + // Get total count + if (library.TryGetProperty("totalCount", out var totalCount)) + { + var total = totalCount.GetInt32(); + if (total == 0) break; + } + var itemCount = 0; foreach (var item in items.EnumerateArray()) { itemCount++; - if (!item.TryGetProperty("playlist", out var playlist)) + if (!item.TryGetProperty("item", out var playlistItem) || + !playlistItem.TryGetProperty("data", out var playlist)) + { continue; + } + + // Get playlist URI/ID + string? uri = null; + if (playlistItem.TryGetProperty("uri", out var uriProp)) + { + uri = uriProp.GetString(); + } + else if (playlistItem.TryGetProperty("_uri", out var uriProp2)) + { + uri = uriProp2.GetString(); + } + + if (string.IsNullOrEmpty(uri)) continue; + + var spotifyId = uri.Replace("spotify:playlist:", "", StringComparison.OrdinalIgnoreCase); var itemName = playlist.TryGetProperty("name", out var n) ? n.GetString() ?? "" : ""; @@ -873,22 +862,19 @@ public class SpotifyApiClient : IDisposable continue; } - var uri = playlist.TryGetProperty("uri", out var u) ? u.GetString() ?? "" : ""; - var spotifyId = uri.Replace("spotify:playlist:", "", StringComparison.OrdinalIgnoreCase); - // Get track count if available var trackCount = 0; if (playlist.TryGetProperty("content", out var content) && - content.TryGetProperty("totalCount", out var totalCount)) + content.TryGetProperty("totalCount", out var totalTrackCount)) { - trackCount = totalCount.GetInt32(); + trackCount = totalTrackCount.GetInt32(); } // Get owner name string? ownerName = null; if (playlist.TryGetProperty("ownerV2", out var ownerV2) && ownerV2.TryGetProperty("data", out var ownerData) && - ownerData.TryGetProperty("name", out var ownerNameProp)) + ownerData.TryGetProperty("username", out var ownerNameProp)) { ownerName = ownerNameProp.GetString(); }