Fix GraphQL query for fetching user playlists - use libraryV3

- Changed from fetchLibraryPlaylists to libraryV3 operation (correct Spotify GraphQL endpoint)
- Use GET request with query params instead of POST (matches Jellyfin plugin implementation)
- Updated response parsing to match libraryV3 structure (me.libraryV3.items[].item.data)
- Fixed owner field to use 'username' instead of 'name'
- This should resolve the BadRequest (400) errors when fetching user playlists
This commit is contained in:
2026-02-09 16:36:10 -05:00
parent 670544a9d6
commit 88bf083386

View File

@@ -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<string, string>
{
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();
}