From f103dac6c8f4b5d68fbe45ded894ec8a2a165d7c Mon Sep 17 00:00:00 2001 From: Josh Patra Date: Fri, 6 Feb 2026 01:48:12 -0500 Subject: [PATCH] Add comprehensive lyrics startup validation with '22' test - Revert album endpoint back to ?id= (correct parameter) - Update SquidWTF validator to test '22' by Taylor Swift - Create LyricsStartupValidator testing all lyrics services: * LRCLib API * Spotify Lyrics Sidecar (docker container) * Spotify API configuration - Test song: '22' by Taylor Swift (Spotify ID: 3yII7UwgLF6K5zW3xad3MP) - Register lyrics validator in startup orchestrator --- allstarr/Program.cs | 1 + .../Services/Lyrics/LyricsStartupValidator.cs | 191 ++++++++++++++++++ .../SquidWTF/SquidWTFMetadataService.cs | 2 +- .../SquidWTF/SquidWTFStartupValidator.cs | 35 +++- 4 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 allstarr/Services/Lyrics/LyricsStartupValidator.cs diff --git a/allstarr/Program.cs b/allstarr/Program.cs index ee5727c..60e17b6 100644 --- a/allstarr/Program.cs +++ b/allstarr/Program.cs @@ -482,6 +482,7 @@ builder.Services.AddSingleton(sp => sp.GetRequiredService>(), sp.GetRequiredService().CreateClient(), squidWtfApiUrls)); +builder.Services.AddSingleton(); // Register orchestrator as hosted service builder.Services.AddHostedService(); diff --git a/allstarr/Services/Lyrics/LyricsStartupValidator.cs b/allstarr/Services/Lyrics/LyricsStartupValidator.cs new file mode 100644 index 0000000..b20bb31 --- /dev/null +++ b/allstarr/Services/Lyrics/LyricsStartupValidator.cs @@ -0,0 +1,191 @@ +using System.Text.Json; +using Microsoft.Extensions.Options; +using allstarr.Models.Settings; +using allstarr.Services.Validation; + +namespace allstarr.Services.Lyrics; + +/// +/// Validates lyrics services (LRCLib, Spotify Lyrics Sidecar, Spotify API) at startup +/// Tests with "22" by Taylor Swift (Spotify ID: 3yII7UwgLF6K5zW3xad3MP) +/// +public class LyricsStartupValidator : BaseStartupValidator +{ + private readonly SpotifyApiSettings _spotifySettings; + private readonly HttpClient _httpClient; + + // Test song: "22" by Taylor Swift + private const string TestSongTitle = "22"; + private const string TestArtist = "Taylor Swift"; + private const string TestAlbum = "Red"; + private const int TestDuration = 232; // seconds + private const string TestSpotifyId = "3yII7UwgLF6K5zW3xad3MP"; + + public override string ServiceName => "Lyrics Services"; + + public LyricsStartupValidator( + IOptions spotifySettings, + IHttpClientFactory httpClientFactory) + : base(httpClientFactory.CreateClient()) + { + _spotifySettings = spotifySettings.Value; + _httpClient = httpClientFactory.CreateClient(); + _httpClient.Timeout = TimeSpan.FromSeconds(10); + } + + public override async Task ValidateAsync(CancellationToken cancellationToken) + { + Console.WriteLine(); + WriteStatus("Lyrics Test Song", $"{TestSongTitle} by {TestArtist}", ConsoleColor.Cyan); + WriteDetail($"Spotify ID: {TestSpotifyId}"); + + var allSuccess = true; + + // Test 1: LRCLib + allSuccess &= await TestLrclibAsync(cancellationToken); + + // Test 2: Spotify Lyrics Sidecar + allSuccess &= await TestSpotifyLyricsSidecarAsync(cancellationToken); + + // Test 3: Spotify API (if enabled) + if (_spotifySettings.Enabled) + { + allSuccess &= await TestSpotifyApiAsync(cancellationToken); + } + else + { + WriteStatus("Spotify API", "DISABLED", ConsoleColor.Yellow); + WriteDetail("Enable SpotifyApi__Enabled to test Spotify API lyrics"); + } + + return allSuccess + ? ValidationResult.Success("Lyrics services validation completed") + : ValidationResult.Warning("Some lyrics services had issues"); + } + + private async Task TestLrclibAsync(CancellationToken cancellationToken) + { + try + { + var url = $"https://lrclib.net/api/get?artist_name={Uri.EscapeDataString(TestArtist)}&track_name={Uri.EscapeDataString(TestSongTitle)}&album_name={Uri.EscapeDataString(TestAlbum)}&duration={TestDuration}"; + + var response = await _httpClient.GetAsync(url, cancellationToken); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(cancellationToken); + var doc = JsonDocument.Parse(json); + + var hasSyncedLyrics = doc.RootElement.TryGetProperty("syncedLyrics", out var synced) && + !string.IsNullOrEmpty(synced.GetString()); + var hasPlainLyrics = doc.RootElement.TryGetProperty("plainLyrics", out var plain) && + !string.IsNullOrEmpty(plain.GetString()); + + WriteStatus("LRCLib", "WORKING", ConsoleColor.Green); + WriteDetail($"✓ Synced: {hasSyncedLyrics}, Plain: {hasPlainLyrics}"); + return true; + } + else if (response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + WriteStatus("LRCLib", "NO LYRICS FOUND", ConsoleColor.Yellow); + WriteDetail("Service is working but no lyrics available for test song"); + return true; // Service is working, just no lyrics + } + else + { + WriteStatus("LRCLib", $"HTTP {(int)response.StatusCode}", ConsoleColor.Red); + return false; + } + } + catch (Exception ex) + { + WriteStatus("LRCLib", "ERROR", ConsoleColor.Red); + WriteDetail($"Failed to connect: {ex.Message}"); + return false; + } + } + + private async Task TestSpotifyLyricsSidecarAsync(CancellationToken cancellationToken) + { + try + { + if (string.IsNullOrEmpty(_spotifySettings.LyricsApiUrl)) + { + WriteStatus("Spotify Lyrics Sidecar", "NOT CONFIGURED", ConsoleColor.Yellow); + WriteDetail("Set SpotifyApi__LyricsApiUrl to enable"); + return true; // Not an error, just not configured + } + + var url = $"{_spotifySettings.LyricsApiUrl}/?trackid={TestSpotifyId}&format=id3"; + + var response = await _httpClient.GetAsync(url, cancellationToken); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(cancellationToken); + var doc = JsonDocument.Parse(json); + + var hasError = doc.RootElement.TryGetProperty("error", out var error) && error.GetBoolean(); + + if (hasError) + { + var message = doc.RootElement.TryGetProperty("message", out var msg) + ? msg.GetString() + : "Unknown error"; + WriteStatus("Spotify Lyrics Sidecar", "API ERROR", ConsoleColor.Yellow); + WriteDetail($"⚠ {message}"); + WriteDetail("Check if sp_dc cookie is valid"); + return false; + } + + var syncType = doc.RootElement.TryGetProperty("syncType", out var st) + ? st.GetString() + : "UNKNOWN"; + var lineCount = doc.RootElement.TryGetProperty("lines", out var lines) + ? lines.GetArrayLength() + : 0; + + WriteStatus("Spotify Lyrics Sidecar", "WORKING", ConsoleColor.Green); + WriteDetail($"✓ Type: {syncType}, Lines: {lineCount}"); + return true; + } + else + { + WriteStatus("Spotify Lyrics Sidecar", $"HTTP {(int)response.StatusCode}", ConsoleColor.Red); + WriteDetail("Check if spotify-lyrics container is running"); + return false; + } + } + catch (Exception ex) + { + WriteStatus("Spotify Lyrics Sidecar", "ERROR", ConsoleColor.Red); + WriteDetail($"Failed to connect: {ex.Message}"); + WriteDetail("Ensure spotify-lyrics container is running in docker-compose"); + return false; + } + } + + private async Task TestSpotifyApiAsync(CancellationToken cancellationToken) + { + try + { + if (string.IsNullOrEmpty(_spotifySettings.ClientId)) + { + WriteStatus("Spotify API", "NOT CONFIGURED", ConsoleColor.Yellow); + WriteDetail("Set SpotifyApi__ClientId to enable"); + return true; + } + + WriteStatus("Spotify API", "CONFIGURED", ConsoleColor.Green); + WriteDetail($"Client ID: {_spotifySettings.ClientId.Substring(0, Math.Min(8, _spotifySettings.ClientId.Length))}..."); + WriteDetail("Note: Spotify API is used for track matching, not lyrics"); + return true; + } + catch (Exception ex) + { + WriteStatus("Spotify API", "ERROR", ConsoleColor.Red); + WriteDetail($"Validation failed: {ex.Message}"); + return false; + } + } +} diff --git a/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs b/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs index 3e93fbc..5b206e2 100644 --- a/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs +++ b/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs @@ -289,7 +289,7 @@ public class SquidWTFMetadataService : IMusicMetadataService return await TryWithFallbackAsync(async (baseUrl) => { - var url = $"{baseUrl}/album/?f={externalId}"; + var url = $"{baseUrl}/album/?id={externalId}"; var response = await _httpClient.GetAsync(url); if (!response.IsSuccessStatusCode) return null; diff --git a/allstarr/Services/SquidWTF/SquidWTFStartupValidator.cs b/allstarr/Services/SquidWTF/SquidWTFStartupValidator.cs index 8104f02..24d0d48 100644 --- a/allstarr/Services/SquidWTF/SquidWTFStartupValidator.cs +++ b/allstarr/Services/SquidWTF/SquidWTFStartupValidator.cs @@ -107,8 +107,8 @@ public class SquidWTFStartupValidator : BaseStartupValidator { try { - // Test search with a simple query - var searchUrl = $"{baseUrl}/search/?s=Taylor%20Swift"; + // Test search with "22" by Taylor Swift + var searchUrl = $"{baseUrl}/search/?s=22%20Taylor%20Swift"; var searchResponse = await _httpClient.GetAsync(searchUrl, cancellationToken); if (searchResponse.IsSuccessStatusCode) @@ -121,7 +121,36 @@ public class SquidWTFStartupValidator : BaseStartupValidator { var itemCount = items.GetArrayLength(); WriteStatus("Search Functionality", "WORKING", ConsoleColor.Green); - WriteDetail($"Test search returned {itemCount} results"); + WriteDetail($"Test search for '22' by Taylor Swift returned {itemCount} results"); + + // Check if we found the actual song + bool foundTaylorSwift22 = false; + foreach (var item in items.EnumerateArray()) + { + if (item.TryGetProperty("title", out var title) && + item.TryGetProperty("artists", out var artists) && + artists.GetArrayLength() > 0) + { + var titleStr = title.GetString() ?? ""; + var artistName = artists[0].TryGetProperty("name", out var name) + ? name.GetString() ?? "" + : ""; + + if (titleStr.Contains("22", StringComparison.OrdinalIgnoreCase) && + artistName.Contains("Taylor Swift", StringComparison.OrdinalIgnoreCase)) + { + foundTaylorSwift22 = true; + var trackId = item.TryGetProperty("id", out var id) ? id.GetInt64() : 0; + WriteDetail($"✓ Found: '{titleStr}' by {artistName} (ID: {trackId})"); + break; + } + } + } + + if (!foundTaylorSwift22) + { + WriteDetail("⚠ Could not find exact match for '22' by Taylor Swift in results"); + } } else {