mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
fix: use GraphQL for user playlists to avoid 429 rate limits
Some checks failed
CI / build-and-test (push) Has been cancelled
Some checks failed
CI / build-and-test (push) Has been cancelled
- Switched from REST API /me/playlists to GraphQL fetchLibraryPlaylists - GraphQL endpoint is less aggressively rate-limited by Spotify - Fixes 429 errors when using 'Select from My Playlists' dropdown - Background services already use GraphQL and work fine
This commit is contained in:
@@ -744,61 +744,126 @@ public class SpotifyApiClient : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
// Use GraphQL endpoint instead of REST API to avoid rate limiting
|
||||
// GraphQL is less aggressive with rate limits
|
||||
var playlists = new List<SpotifyPlaylist>();
|
||||
var offset = 0;
|
||||
const int limit = 50;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var url = $"{OfficialApiBase}/me/playlists?offset={offset}&limit={limit}";
|
||||
// GraphQL query to fetch user playlists
|
||||
var graphqlQuery = new
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}"
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"{WebApiBase}/query")
|
||||
{
|
||||
Content = new StringContent(
|
||||
JsonSerializer.Serialize(graphqlQuery),
|
||||
System.Text.Encoding.UTF8,
|
||||
"application/json")
|
||||
};
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode) break;
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning("GraphQL user playlists request failed: {StatusCode}", response.StatusCode);
|
||||
break;
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (!root.TryGetProperty("items", out var items) || items.GetArrayLength() == 0)
|
||||
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))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var itemCount = 0;
|
||||
foreach (var item in items.EnumerateArray())
|
||||
{
|
||||
var itemName = item.TryGetProperty("name", out var n) ? n.GetString() ?? "" : "";
|
||||
itemCount++;
|
||||
|
||||
if (!item.TryGetProperty("playlist", out var playlist))
|
||||
continue;
|
||||
|
||||
var itemName = playlist.TryGetProperty("name", out var n) ? n.GetString() ?? "" : "";
|
||||
|
||||
// Check if name matches (case-insensitive)
|
||||
if (itemName.Contains(searchName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var uri = playlist.TryGetProperty("uri", out var u) ? u.GetString() ?? "" : "";
|
||||
var spotifyId = uri.Replace("spotify:playlist:", "", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
playlists.Add(new SpotifyPlaylist
|
||||
{
|
||||
SpotifyId = item.TryGetProperty("id", out var itemId) ? itemId.GetString() ?? "" : "",
|
||||
SpotifyId = spotifyId,
|
||||
Name = itemName,
|
||||
Description = item.TryGetProperty("description", out var desc) ? desc.GetString() : null,
|
||||
TotalTracks = item.TryGetProperty("tracks", out var tracks) &&
|
||||
tracks.TryGetProperty("total", out var total)
|
||||
? total.GetInt32() : 0,
|
||||
SnapshotId = item.TryGetProperty("snapshot_id", out var snap) ? snap.GetString() : null
|
||||
Description = playlist.TryGetProperty("description", out var desc) ? desc.GetString() : null,
|
||||
TotalTracks = 0, // GraphQL doesn't return track count in this query
|
||||
SnapshotId = null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (items.GetArrayLength() < limit) break;
|
||||
if (itemCount < limit) break;
|
||||
offset += limit;
|
||||
|
||||
// GraphQL is less rate-limited, but still add a small delay
|
||||
if (_settings.RateLimitDelayMs > 0)
|
||||
{
|
||||
await Task.Delay(_settings.RateLimitDelayMs, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Found {Count} playlists matching '{SearchName}' via GraphQL", playlists.Count, searchName);
|
||||
return playlists;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching user playlists for '{SearchName}'", searchName);
|
||||
_logger.LogError(ex, "Error searching user playlists for '{SearchName}' via GraphQL", searchName);
|
||||
return new List<SpotifyPlaylist>();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user