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
|
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 playlists = new List<SpotifyPlaylist>();
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
const int limit = 50;
|
const int limit = 50;
|
||||||
|
|
||||||
while (true)
|
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);
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
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);
|
var json = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
using var doc = JsonDocument.Parse(json);
|
using var doc = JsonDocument.Parse(json);
|
||||||
var root = doc.RootElement;
|
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;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemCount = 0;
|
||||||
foreach (var item in items.EnumerateArray())
|
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)
|
// Check if name matches (case-insensitive)
|
||||||
if (itemName.Contains(searchName, StringComparison.OrdinalIgnoreCase))
|
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
|
playlists.Add(new SpotifyPlaylist
|
||||||
{
|
{
|
||||||
SpotifyId = item.TryGetProperty("id", out var itemId) ? itemId.GetString() ?? "" : "",
|
SpotifyId = spotifyId,
|
||||||
Name = itemName,
|
Name = itemName,
|
||||||
Description = item.TryGetProperty("description", out var desc) ? desc.GetString() : null,
|
Description = playlist.TryGetProperty("description", out var desc) ? desc.GetString() : null,
|
||||||
TotalTracks = item.TryGetProperty("tracks", out var tracks) &&
|
TotalTracks = 0, // GraphQL doesn't return track count in this query
|
||||||
tracks.TryGetProperty("total", out var total)
|
SnapshotId = null
|
||||||
? total.GetInt32() : 0,
|
|
||||||
SnapshotId = item.TryGetProperty("snapshot_id", out var snap) ? snap.GetString() : null
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.GetArrayLength() < limit) break;
|
if (itemCount < limit) break;
|
||||||
offset += limit;
|
offset += limit;
|
||||||
|
|
||||||
|
// GraphQL is less rate-limited, but still add a small delay
|
||||||
if (_settings.RateLimitDelayMs > 0)
|
if (_settings.RateLimitDelayMs > 0)
|
||||||
{
|
{
|
||||||
await Task.Delay(_settings.RateLimitDelayMs, cancellationToken);
|
await Task.Delay(_settings.RateLimitDelayMs, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Found {Count} playlists matching '{SearchName}' via GraphQL", playlists.Count, searchName);
|
||||||
return playlists;
|
return playlists;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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>();
|
return new List<SpotifyPlaylist>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user