mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
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
This commit is contained in:
@@ -482,6 +482,7 @@ builder.Services.AddSingleton<IStartupValidator>(sp =>
|
|||||||
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SquidWTFSettings>>(),
|
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SquidWTFSettings>>(),
|
||||||
sp.GetRequiredService<IHttpClientFactory>().CreateClient(),
|
sp.GetRequiredService<IHttpClientFactory>().CreateClient(),
|
||||||
squidWtfApiUrls));
|
squidWtfApiUrls));
|
||||||
|
builder.Services.AddSingleton<IStartupValidator, LyricsStartupValidator>();
|
||||||
|
|
||||||
// Register orchestrator as hosted service
|
// Register orchestrator as hosted service
|
||||||
builder.Services.AddHostedService<StartupValidationOrchestrator>();
|
builder.Services.AddHostedService<StartupValidationOrchestrator>();
|
||||||
|
|||||||
191
allstarr/Services/Lyrics/LyricsStartupValidator.cs
Normal file
191
allstarr/Services/Lyrics/LyricsStartupValidator.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using allstarr.Models.Settings;
|
||||||
|
using allstarr.Services.Validation;
|
||||||
|
|
||||||
|
namespace allstarr.Services.Lyrics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates lyrics services (LRCLib, Spotify Lyrics Sidecar, Spotify API) at startup
|
||||||
|
/// Tests with "22" by Taylor Swift (Spotify ID: 3yII7UwgLF6K5zW3xad3MP)
|
||||||
|
/// </summary>
|
||||||
|
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<SpotifyApiSettings> spotifySettings,
|
||||||
|
IHttpClientFactory httpClientFactory)
|
||||||
|
: base(httpClientFactory.CreateClient())
|
||||||
|
{
|
||||||
|
_spotifySettings = spotifySettings.Value;
|
||||||
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
|
_httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<ValidationResult> 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<bool> 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<bool> 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<bool> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -289,7 +289,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
|
|
||||||
return await TryWithFallbackAsync(async (baseUrl) =>
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var url = $"{baseUrl}/album/?f={externalId}";
|
var url = $"{baseUrl}/album/?id={externalId}";
|
||||||
|
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
if (!response.IsSuccessStatusCode) return null;
|
if (!response.IsSuccessStatusCode) return null;
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ public class SquidWTFStartupValidator : BaseStartupValidator
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Test search with a simple query
|
// Test search with "22" by Taylor Swift
|
||||||
var searchUrl = $"{baseUrl}/search/?s=Taylor%20Swift";
|
var searchUrl = $"{baseUrl}/search/?s=22%20Taylor%20Swift";
|
||||||
var searchResponse = await _httpClient.GetAsync(searchUrl, cancellationToken);
|
var searchResponse = await _httpClient.GetAsync(searchUrl, cancellationToken);
|
||||||
|
|
||||||
if (searchResponse.IsSuccessStatusCode)
|
if (searchResponse.IsSuccessStatusCode)
|
||||||
@@ -121,7 +121,36 @@ public class SquidWTFStartupValidator : BaseStartupValidator
|
|||||||
{
|
{
|
||||||
var itemCount = items.GetArrayLength();
|
var itemCount = items.GetArrayLength();
|
||||||
WriteStatus("Search Functionality", "WORKING", ConsoleColor.Green);
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user