mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
fix: add base64 encoded SquidWTF endpoints with automatic fallback
- Decode 6 endpoints at startup (triton, wolf, hund, maus, vogel, katze) - Automatic fallback when endpoint fails - All services cycle through endpoints on failure - URLs stored as base64, decoded once in Program.cs - Fixes search/download issues
This commit is contained in:
@@ -11,9 +11,30 @@ using allstarr.Services.Common;
|
|||||||
using allstarr.Services.Lyrics;
|
using allstarr.Services.Lyrics;
|
||||||
using allstarr.Middleware;
|
using allstarr.Middleware;
|
||||||
using allstarr.Filters;
|
using allstarr.Filters;
|
||||||
|
using Microsoft.Extensions.Http;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Decode SquidWTF API base URLs once at startup
|
||||||
|
var squidWtfApiUrls = DecodeSquidWtfUrls();
|
||||||
|
static List<string> DecodeSquidWtfUrls()
|
||||||
|
{
|
||||||
|
var encodedUrls = new[]
|
||||||
|
{
|
||||||
|
"aHR0cHM6Ly90cml0b24uc3F1aWQud3Rm", // triton
|
||||||
|
"aHR0cHM6Ly93b2xmLnFxZGwuc2l0ZQ==", // wolf
|
||||||
|
"aHR0cDovL2h1bmQucXFkbC5zaXRl", // hund
|
||||||
|
"aHR0cHM6Ly9tYXVzLnFxZGwuc2l0ZQ==", // maus
|
||||||
|
"aHR0cHM6Ly92b2dlbC5xcWRsLnNpdGU=", // vogel
|
||||||
|
"aHR0cHM6Ly9rYXR6ZS5xcWRsLnNpdGU=" // katze
|
||||||
|
};
|
||||||
|
|
||||||
|
return encodedUrls
|
||||||
|
.Select(encoded => Encoding.UTF8.GetString(Convert.FromBase64String(encoded)))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
// Determine backend type FIRST
|
// Determine backend type FIRST
|
||||||
var backendType = builder.Configuration.GetValue<BackendType>("Backend:Type");
|
var backendType = builder.Configuration.GetValue<BackendType>("Backend:Type");
|
||||||
|
|
||||||
@@ -55,6 +76,17 @@ builder.Services.AddControllers()
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
|
builder.Services.ConfigureAll<HttpClientFactoryOptions>(options =>
|
||||||
|
{
|
||||||
|
options.HttpMessageHandlerBuilderActions.Add(builder =>
|
||||||
|
{
|
||||||
|
builder.PrimaryHandler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = true,
|
||||||
|
MaxAutomaticRedirections = 5
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
@@ -151,9 +183,26 @@ else if (musicService == MusicService.Deezer)
|
|||||||
}
|
}
|
||||||
else if (musicService == MusicService.SquidWTF)
|
else if (musicService == MusicService.SquidWTF)
|
||||||
{
|
{
|
||||||
// SquidWTF services
|
// SquidWTF services - pass decoded URLs with fallback support
|
||||||
builder.Services.AddSingleton<IMusicMetadataService, SquidWTFMetadataService>();
|
builder.Services.AddSingleton<IMusicMetadataService>(sp =>
|
||||||
builder.Services.AddSingleton<IDownloadService, SquidWTFDownloadService>();
|
new SquidWTFMetadataService(
|
||||||
|
sp.GetRequiredService<IHttpClientFactory>(),
|
||||||
|
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SubsonicSettings>>(),
|
||||||
|
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SquidWTFSettings>>(),
|
||||||
|
sp.GetRequiredService<ILogger<SquidWTFMetadataService>>(),
|
||||||
|
sp.GetRequiredService<RedisCacheService>(),
|
||||||
|
squidWtfApiUrls));
|
||||||
|
builder.Services.AddSingleton<IDownloadService>(sp =>
|
||||||
|
new SquidWTFDownloadService(
|
||||||
|
sp.GetRequiredService<IHttpClientFactory>(),
|
||||||
|
sp.GetRequiredService<IConfiguration>(),
|
||||||
|
sp.GetRequiredService<ILocalLibraryService>(),
|
||||||
|
sp.GetRequiredService<IMusicMetadataService>(),
|
||||||
|
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SubsonicSettings>>(),
|
||||||
|
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SquidWTFSettings>>(),
|
||||||
|
sp,
|
||||||
|
sp.GetRequiredService<ILogger<SquidWTFDownloadService>>(),
|
||||||
|
squidWtfApiUrls));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Startup validation - register validators based on backend
|
// Startup validation - register validators based on backend
|
||||||
@@ -168,7 +217,11 @@ else
|
|||||||
|
|
||||||
builder.Services.AddSingleton<IStartupValidator, DeezerStartupValidator>();
|
builder.Services.AddSingleton<IStartupValidator, DeezerStartupValidator>();
|
||||||
builder.Services.AddSingleton<IStartupValidator, QobuzStartupValidator>();
|
builder.Services.AddSingleton<IStartupValidator, QobuzStartupValidator>();
|
||||||
builder.Services.AddSingleton<IStartupValidator, SquidWTFStartupValidator>();
|
builder.Services.AddSingleton<IStartupValidator>(sp =>
|
||||||
|
new SquidWTFStartupValidator(
|
||||||
|
sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<SquidWTFSettings>>(),
|
||||||
|
sp.GetRequiredService<IHttpClientFactory>().CreateClient(),
|
||||||
|
squidWtfApiUrls));
|
||||||
|
|
||||||
// Register orchestrator as hosted service
|
// Register orchestrator as hosted service
|
||||||
builder.Services.AddHostedService<StartupValidationOrchestrator>();
|
builder.Services.AddHostedService<StartupValidationOrchestrator>();
|
||||||
|
|||||||
@@ -26,20 +26,8 @@ public class SquidWTFDownloadService : BaseDownloadService
|
|||||||
private DateTime _lastRequestTime = DateTime.MinValue;
|
private DateTime _lastRequestTime = DateTime.MinValue;
|
||||||
private readonly int _minRequestIntervalMs = 200;
|
private readonly int _minRequestIntervalMs = 200;
|
||||||
|
|
||||||
// Primary and backup endpoints (base64 encoded to avoid detection)
|
private readonly List<string> _apiUrls;
|
||||||
private const string PrimaryEndpoint = "aHR0cHM6Ly90cml0b24uc3F1aWQud3RmLw=="; // triton.squid.wtf
|
private int _currentUrlIndex = 0;
|
||||||
|
|
||||||
private static readonly string[] BackupEndpoints = new[]
|
|
||||||
{
|
|
||||||
"aHR0cHM6Ly93b2xmLnFxZGwuc2l0ZS8=", // wolf
|
|
||||||
"aHR0cHM6Ly9tYXVzLnFxZGwuc2l0ZS8=", // maus
|
|
||||||
"aHR0cHM6Ly92b2dlbC5xcWRsLnNpdGUv", // vogel
|
|
||||||
"aHR0cHM6Ly9rYXR6ZS5xcWRsLnNpdGUv", // katze
|
|
||||||
"aHR0cHM6Ly9odW5kLnFxZGwuc2l0ZS8=" // hund
|
|
||||||
};
|
|
||||||
|
|
||||||
private string _currentApiBase;
|
|
||||||
private int _currentEndpointIndex = -1;
|
|
||||||
|
|
||||||
protected override string ProviderName => "squidwtf";
|
protected override string ProviderName => "squidwtf";
|
||||||
|
|
||||||
@@ -51,77 +39,49 @@ public class SquidWTFDownloadService : BaseDownloadService
|
|||||||
IOptions<SubsonicSettings> subsonicSettings,
|
IOptions<SubsonicSettings> subsonicSettings,
|
||||||
IOptions<SquidWTFSettings> SquidWTFSettings,
|
IOptions<SquidWTFSettings> SquidWTFSettings,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
ILogger<SquidWTFDownloadService> logger)
|
ILogger<SquidWTFDownloadService> logger,
|
||||||
|
List<string> apiUrls)
|
||||||
: base(configuration, localLibraryService, metadataService, subsonicSettings.Value, serviceProvider, logger)
|
: base(configuration, localLibraryService, metadataService, subsonicSettings.Value, serviceProvider, logger)
|
||||||
{
|
{
|
||||||
_httpClient = httpClientFactory.CreateClient();
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
_squidwtfSettings = SquidWTFSettings.Value;
|
_squidwtfSettings = SquidWTFSettings.Value;
|
||||||
_currentApiBase = DecodeEndpoint(PrimaryEndpoint);
|
_apiUrls = apiUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<T> TryWithFallbackAsync<T>(Func<string, Task<T>> action)
|
||||||
|
{
|
||||||
|
for (int attempt = 0; attempt < _apiUrls.Count; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var baseUrl = _apiUrls[_currentUrlIndex];
|
||||||
|
return await action(baseUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "Request failed with endpoint {Endpoint}, trying next...", _apiUrls[_currentUrlIndex]);
|
||||||
|
_currentUrlIndex = (_currentUrlIndex + 1) % _apiUrls.Count;
|
||||||
|
|
||||||
|
if (attempt == _apiUrls.Count - 1)
|
||||||
|
{
|
||||||
|
Logger.LogError("All SquidWTF endpoints failed");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception("All SquidWTF endpoints failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DecodeEndpoint(string base64)
|
|
||||||
{
|
|
||||||
var bytes = Convert.FromBase64String(base64);
|
|
||||||
return Encoding.UTF8.GetString(bytes).TrimEnd('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> TryNextEndpointAsync()
|
|
||||||
{
|
|
||||||
_currentEndpointIndex++;
|
|
||||||
|
|
||||||
if (_currentEndpointIndex >= BackupEndpoints.Length)
|
|
||||||
{
|
|
||||||
Logger.LogError("All backup endpoints exhausted");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentApiBase = DecodeEndpoint(BackupEndpoints[_currentEndpointIndex]);
|
|
||||||
Logger.LogInformation("Switching to backup endpoint {Index}", _currentEndpointIndex + 1);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await _httpClient.GetAsync(_currentApiBase);
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
Logger.LogInformation("Backup endpoint {Index} is available", _currentEndpointIndex + 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogWarning(ex, "Backup endpoint {Index} failed", _currentEndpointIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await TryNextEndpointAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region BaseDownloadService Implementation
|
#region BaseDownloadService Implementation
|
||||||
|
|
||||||
public override async Task<bool> IsAvailableAsync()
|
public override async Task<bool> IsAvailableAsync()
|
||||||
{
|
{
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync(_currentApiBase);
|
var response = await _httpClient.GetAsync(baseUrl);
|
||||||
Console.WriteLine($"Response code from is available async: {response.IsSuccessStatusCode}");
|
Console.WriteLine($"Response code from is available async: {response.IsSuccessStatusCode}");
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
if (!response.IsSuccessStatusCode && await TryNextEndpointAsync())
|
});
|
||||||
{
|
|
||||||
response = await _httpClient.GetAsync(_currentApiBase);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.IsSuccessStatusCode;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogWarning(ex, "SquidWTF service not available, trying backup");
|
|
||||||
|
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await IsAvailableAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string? ExtractExternalIdFromAlbumId(string albumId)
|
protected override string? ExtractExternalIdFromAlbumId(string albumId)
|
||||||
@@ -197,80 +157,69 @@ public class SquidWTFDownloadService : BaseDownloadService
|
|||||||
{
|
{
|
||||||
return await QueueRequestAsync(async () =>
|
return await QueueRequestAsync(async () =>
|
||||||
{
|
{
|
||||||
// Map quality settings to Tidal's quality levels
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
var quality = _squidwtfSettings.Quality?.ToUpperInvariant() switch
|
|
||||||
{
|
{
|
||||||
"FLAC" => "LOSSLESS",
|
// Map quality settings to Tidal's quality levels
|
||||||
"HI_RES" => "HI_RES_LOSSLESS",
|
var quality = _squidwtfSettings.Quality?.ToUpperInvariant() switch
|
||||||
"LOSSLESS" => "LOSSLESS",
|
{
|
||||||
"HIGH" => "HIGH",
|
"FLAC" => "LOSSLESS",
|
||||||
"LOW" => "LOW",
|
"HI_RES" => "HI_RES_LOSSLESS",
|
||||||
_ => "LOSSLESS" // Default to lossless
|
"LOSSLESS" => "LOSSLESS",
|
||||||
};
|
"HIGH" => "HIGH",
|
||||||
|
"LOW" => "LOW",
|
||||||
var url = $"{_currentApiBase}/track?id={trackId}&quality={quality}";
|
_ => "LOSSLESS" // Default to lossless
|
||||||
|
};
|
||||||
|
|
||||||
|
var url = $"{baseUrl}/track/?id={trackId}&quality={quality}";
|
||||||
|
|
||||||
Console.WriteLine($"%%%%%%%%%%%%%%%%%%% URL For downloads??: {url}");
|
Console.WriteLine($"%%%%%%%%%%%%%%%%%%% URL For downloads??: {url}");
|
||||||
|
|
||||||
try
|
var response = await _httpClient.GetAsync(url, cancellationToken);
|
||||||
{
|
response.EnsureSuccessStatusCode();
|
||||||
var response = await _httpClient.GetAsync(url, cancellationToken);
|
|
||||||
response.EnsureSuccessStatusCode();
|
var json = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
var doc = JsonDocument.Parse(json);
|
||||||
var json = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
||||||
var doc = JsonDocument.Parse(json);
|
if (!doc.RootElement.TryGetProperty("data", out var data))
|
||||||
|
{
|
||||||
if (!doc.RootElement.TryGetProperty("data", out var data))
|
throw new Exception("Invalid response from API");
|
||||||
{
|
}
|
||||||
throw new Exception("Invalid response from API");
|
|
||||||
}
|
// Get the manifest (base64 encoded JSON containing the actual CDN URL)
|
||||||
|
var manifestBase64 = data.GetProperty("manifest").GetString()
|
||||||
// Get the manifest (base64 encoded JSON containing the actual CDN URL)
|
?? throw new Exception("No manifest in response");
|
||||||
var manifestBase64 = data.GetProperty("manifest").GetString()
|
|
||||||
?? throw new Exception("No manifest in response");
|
// Decode the manifest
|
||||||
|
var manifestJson = Encoding.UTF8.GetString(Convert.FromBase64String(manifestBase64));
|
||||||
// Decode the manifest
|
var manifest = JsonDocument.Parse(manifestJson);
|
||||||
var manifestJson = Encoding.UTF8.GetString(Convert.FromBase64String(manifestBase64));
|
|
||||||
var manifest = JsonDocument.Parse(manifestJson);
|
// Extract the download URL from the manifest
|
||||||
|
if (!manifest.RootElement.TryGetProperty("urls", out var urls) || urls.GetArrayLength() == 0)
|
||||||
// Extract the download URL from the manifest
|
{
|
||||||
if (!manifest.RootElement.TryGetProperty("urls", out var urls) || urls.GetArrayLength() == 0)
|
throw new Exception("No download URLs in manifest");
|
||||||
{
|
}
|
||||||
throw new Exception("No download URLs in manifest");
|
|
||||||
}
|
var downloadUrl = urls[0].GetString()
|
||||||
|
?? throw new Exception("Download URL is null");
|
||||||
var downloadUrl = urls[0].GetString()
|
|
||||||
?? throw new Exception("Download URL is null");
|
var mimeType = manifest.RootElement.TryGetProperty("mimeType", out var mimeTypeEl)
|
||||||
|
? mimeTypeEl.GetString()
|
||||||
var mimeType = manifest.RootElement.TryGetProperty("mimeType", out var mimeTypeEl)
|
: "audio/flac";
|
||||||
? mimeTypeEl.GetString()
|
|
||||||
: "audio/flac";
|
var audioQuality = data.TryGetProperty("audioQuality", out var audioQualityEl)
|
||||||
|
? audioQualityEl.GetString()
|
||||||
var audioQuality = data.TryGetProperty("audioQuality", out var audioQualityEl)
|
: "LOSSLESS";
|
||||||
? audioQualityEl.GetString()
|
|
||||||
: "LOSSLESS";
|
Logger.LogDebug("Decoded manifest - URL: {Url}, MIME: {MimeType}, Quality: {Quality}",
|
||||||
|
downloadUrl, mimeType, audioQuality);
|
||||||
Logger.LogDebug("Decoded manifest - URL: {Url}, MIME: {MimeType}, Quality: {Quality}",
|
|
||||||
downloadUrl, mimeType, audioQuality);
|
return new DownloadResult
|
||||||
|
{
|
||||||
return new DownloadResult
|
DownloadUrl = downloadUrl,
|
||||||
{
|
MimeType = mimeType ?? "audio/flac",
|
||||||
DownloadUrl = downloadUrl,
|
AudioQuality = audioQuality ?? "LOSSLESS"
|
||||||
MimeType = mimeType ?? "audio/flac",
|
};
|
||||||
AudioQuality = audioQuality ?? "LOSSLESS"
|
});
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogWarning(ex, "Failed to get track info, trying backup endpoint");
|
|
||||||
|
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await GetTrackDownloadInfoAsync(trackId, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,89 +21,63 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
private readonly SubsonicSettings _settings;
|
private readonly SubsonicSettings _settings;
|
||||||
private readonly ILogger<SquidWTFMetadataService> _logger;
|
private readonly ILogger<SquidWTFMetadataService> _logger;
|
||||||
private readonly RedisCacheService _cache;
|
private readonly RedisCacheService _cache;
|
||||||
|
private readonly List<string> _apiUrls;
|
||||||
// Primary and backup endpoints (base64 encoded to avoid detection)
|
private int _currentUrlIndex = 0;
|
||||||
private const string PrimaryEndpoint = "aHR0cHM6Ly90cml0b24uc3F1aWQud3RmLw=="; // triton.squid.wtf
|
|
||||||
|
|
||||||
private static readonly string[] BackupEndpoints = new[]
|
|
||||||
{
|
|
||||||
"aHR0cHM6Ly93b2xmLnFxZGwuc2l0ZS8=", // wolf
|
|
||||||
"aHR0cHM6Ly9tYXVzLnFxZGwuc2l0ZS8=", // maus
|
|
||||||
"aHR0cHM6Ly92b2dlbC5xcWRsLnNpdGUv", // vogel
|
|
||||||
"aHR0cHM6Ly9rYXR6ZS5xcWRsLnNpdGUv", // katze
|
|
||||||
"aHR0cHM6Ly9odW5kLnFxZGwuc2l0ZS8=" // hund
|
|
||||||
};
|
|
||||||
|
|
||||||
private string _currentApiBase;
|
|
||||||
private int _currentEndpointIndex = -1;
|
|
||||||
|
|
||||||
public SquidWTFMetadataService(
|
public SquidWTFMetadataService(
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
IOptions<SubsonicSettings> settings,
|
IOptions<SubsonicSettings> settings,
|
||||||
IOptions<SquidWTFSettings> squidwtfSettings,
|
IOptions<SquidWTFSettings> squidwtfSettings,
|
||||||
ILogger<SquidWTFMetadataService> logger,
|
ILogger<SquidWTFMetadataService> logger,
|
||||||
RedisCacheService cache)
|
RedisCacheService cache,
|
||||||
|
List<string> apiUrls)
|
||||||
{
|
{
|
||||||
_httpClient = httpClientFactory.CreateClient();
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
_settings = settings.Value;
|
_settings = settings.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_currentApiBase = DecodeEndpoint(PrimaryEndpoint);
|
_apiUrls = apiUrls;
|
||||||
|
|
||||||
// Set up default headers
|
// Set up default headers
|
||||||
_httpClient.DefaultRequestHeaders.Add("User-Agent",
|
_httpClient.DefaultRequestHeaders.Add("User-Agent",
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0");
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetCurrentBaseUrl() => _apiUrls[_currentUrlIndex];
|
||||||
|
|
||||||
|
private async Task<T> TryWithFallbackAsync<T>(Func<string, Task<T>> action, T defaultValue)
|
||||||
|
{
|
||||||
|
for (int attempt = 0; attempt < _apiUrls.Count; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var baseUrl = _apiUrls[_currentUrlIndex];
|
||||||
|
return await action(baseUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Request failed with endpoint {Endpoint}, trying next...", _apiUrls[_currentUrlIndex]);
|
||||||
|
_currentUrlIndex = (_currentUrlIndex + 1) % _apiUrls.Count;
|
||||||
|
|
||||||
|
if (attempt == _apiUrls.Count - 1)
|
||||||
|
{
|
||||||
|
_logger.LogError("All SquidWTF endpoints failed");
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
private string DecodeEndpoint(string base64)
|
|
||||||
{
|
|
||||||
var bytes = Convert.FromBase64String(base64);
|
|
||||||
return Encoding.UTF8.GetString(bytes).TrimEnd('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> TryNextEndpointAsync()
|
|
||||||
{
|
|
||||||
_currentEndpointIndex++;
|
|
||||||
|
|
||||||
if (_currentEndpointIndex >= BackupEndpoints.Length)
|
|
||||||
{
|
|
||||||
_logger.LogError("All backup endpoints exhausted");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentApiBase = DecodeEndpoint(BackupEndpoints[_currentEndpointIndex]);
|
|
||||||
_logger.LogInformation("Switching to backup endpoint {Index}", _currentEndpointIndex + 1);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await _httpClient.GetAsync(_currentApiBase);
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Backup endpoint {Index} is available", _currentEndpointIndex + 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Backup endpoint {Index} failed", _currentEndpointIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await TryNextEndpointAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<Song>> SearchSongsAsync(string query, int limit = 20)
|
public async Task<List<Song>> SearchSongsAsync(string query, int limit = 20)
|
||||||
{
|
{
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var url = $"{_currentApiBase}/search?s={Uri.EscapeDataString(query)}";
|
var url = $"{baseUrl}/search/?s={Uri.EscapeDataString(query)}";
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await SearchSongsAsync(query, limit);
|
|
||||||
}
|
|
||||||
return new List<Song>();
|
return new List<Song>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,31 +99,18 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return songs;
|
return songs;
|
||||||
}
|
}, new List<Song>());
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Failed to search songs for query: {Query}", query);
|
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await SearchSongsAsync(query, limit);
|
|
||||||
}
|
|
||||||
return new List<Song>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Album>> SearchAlbumsAsync(string query, int limit = 20)
|
public async Task<List<Album>> SearchAlbumsAsync(string query, int limit = 20)
|
||||||
{
|
{
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var url = $"{_currentApiBase}/search?al={Uri.EscapeDataString(query)}";
|
var url = $"{baseUrl}/search/?al={Uri.EscapeDataString(query)}";
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await SearchAlbumsAsync(query, limit);
|
|
||||||
}
|
|
||||||
return new List<Album>();
|
return new List<Album>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,31 +133,18 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return albums;
|
return albums;
|
||||||
}
|
}, new List<Album>());
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Failed to search albums for query: {Query}", query);
|
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await SearchAlbumsAsync(query, limit);
|
|
||||||
}
|
|
||||||
return new List<Album>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Artist>> SearchArtistsAsync(string query, int limit = 20)
|
public async Task<List<Artist>> SearchArtistsAsync(string query, int limit = 20)
|
||||||
{
|
{
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var url = $"{_currentApiBase}/search?a={Uri.EscapeDataString(query)}";
|
var url = $"{baseUrl}/search/?a={Uri.EscapeDataString(query)}";
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await SearchArtistsAsync(query, limit);
|
|
||||||
}
|
|
||||||
return new List<Artist>();
|
return new List<Artist>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,32 +167,16 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return artists;
|
return artists;
|
||||||
}
|
}, new List<Artist>());
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Failed to search artists for query: {Query}", query);
|
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await SearchArtistsAsync(query, limit);
|
|
||||||
}
|
|
||||||
return new List<Artist>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<ExternalPlaylist>> SearchPlaylistsAsync(string query, int limit = 20)
|
public async Task<List<ExternalPlaylist>> SearchPlaylistsAsync(string query, int limit = 20)
|
||||||
{
|
{
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var url = $"{_currentApiBase}/search?p={Uri.EscapeDataString(query)}";
|
var url = $"{baseUrl}/search/?p={Uri.EscapeDataString(query)}";
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode) return new List<ExternalPlaylist>();
|
||||||
{
|
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await SearchPlaylistsAsync(query, limit);
|
|
||||||
}
|
|
||||||
return new List<ExternalPlaylist>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var json = await response.Content.ReadAsStringAsync();
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
var result = JsonDocument.Parse(json);
|
var result = JsonDocument.Parse(json);
|
||||||
@@ -260,16 +192,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return playlists;
|
return playlists;
|
||||||
}
|
}, new List<ExternalPlaylist>());
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Failed to search playlists for query: {Query}", query);
|
|
||||||
if (await TryNextEndpointAsync())
|
|
||||||
{
|
|
||||||
return await SearchPlaylistsAsync(query, limit);
|
|
||||||
}
|
|
||||||
return new List<ExternalPlaylist>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SearchResult> SearchAllAsync(string query, int songLimit = 20, int albumLimit = 20, int artistLimit = 20)
|
public async Task<SearchResult> SearchAllAsync(string query, int songLimit = 20, int albumLimit = 20, int artistLimit = 20)
|
||||||
@@ -295,10 +218,9 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
{
|
{
|
||||||
if (externalProvider != "squidwtf") return null;
|
if (externalProvider != "squidwtf") return null;
|
||||||
|
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
// Use the /info endpoint for full track metadata
|
var url = $"{baseUrl}/info/?id={externalId}";
|
||||||
var url = $"{_currentApiBase}/info?id={externalId}";
|
|
||||||
|
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
if (!response.IsSuccessStatusCode) return null;
|
if (!response.IsSuccessStatusCode) return null;
|
||||||
@@ -310,12 +232,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
return ParseTidalTrackFull(track);
|
return ParseTidalTrackFull(track);
|
||||||
}
|
}, (Song?)null);
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "GetSongAsync Exception");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Album?> GetAlbumAsync(string externalProvider, string externalId)
|
public async Task<Album?> GetAlbumAsync(string externalProvider, string externalId)
|
||||||
@@ -327,10 +244,9 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
var cached = await _cache.GetAsync<Album>(cacheKey);
|
var cached = await _cache.GetAsync<Album>(cacheKey);
|
||||||
if (cached != null) return cached;
|
if (cached != null) return cached;
|
||||||
|
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
// Use the /info endpoint for full track metadata
|
var url = $"{baseUrl}/album/?id={externalId}";
|
||||||
var url = $"{_currentApiBase}/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;
|
||||||
@@ -364,12 +280,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
await _cache.SetAsync(cacheKey, album, TimeSpan.FromHours(24));
|
await _cache.SetAsync(cacheKey, album, TimeSpan.FromHours(24));
|
||||||
|
|
||||||
return album;
|
return album;
|
||||||
}
|
}, (Album?)null);
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "GetAlbumAsync Exception");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Artist?> GetArtistAsync(string externalProvider, string externalId)
|
public async Task<Artist?> GetArtistAsync(string externalProvider, string externalId)
|
||||||
@@ -387,10 +298,9 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
// Use the /info endpoint for full track metadata
|
var url = $"{baseUrl}/artist/?f={externalId}";
|
||||||
var url = $"{_currentApiBase}/artist?f={externalId}";
|
|
||||||
_logger.LogInformation("Fetching artist from {Url}", url);
|
_logger.LogInformation("Fetching artist from {Url}", url);
|
||||||
|
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
@@ -451,25 +361,18 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
await _cache.SetAsync(cacheKey, artist, TimeSpan.FromHours(24));
|
await _cache.SetAsync(cacheKey, artist, TimeSpan.FromHours(24));
|
||||||
|
|
||||||
return artist;
|
return artist;
|
||||||
|
}, (Artist?)null);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "GetArtistAsync Exception.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Album>> GetArtistAlbumsAsync(string externalProvider, string externalId)
|
public async Task<List<Album>> GetArtistAlbumsAsync(string externalProvider, string externalId)
|
||||||
{
|
{
|
||||||
|
if (externalProvider != "squidwtf") return new List<Album>();
|
||||||
try
|
|
||||||
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
if (externalProvider != "squidwtf") return new List<Album>();
|
|
||||||
|
|
||||||
_logger.LogInformation("GetArtistAlbumsAsync called for SquidWTF artist {ExternalId}", externalId);
|
_logger.LogInformation("GetArtistAlbumsAsync called for SquidWTF artist {ExternalId}", externalId);
|
||||||
|
|
||||||
var url = $"{_currentApiBase}/artist?f={externalId}";
|
var url = $"{baseUrl}/artist/?f={externalId}";
|
||||||
_logger.LogInformation("Fetching artist albums from URL: {Url}", url);
|
_logger.LogInformation("Fetching artist albums from URL: {Url}", url);
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
|
|
||||||
@@ -503,21 +406,16 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return albums;
|
return albums;
|
||||||
}
|
}, new List<Album>());
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to get SquidWTF artist albums for {ExternalId}", externalId);
|
|
||||||
return new List<Album>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ExternalPlaylist?> GetPlaylistAsync(string externalProvider, string externalId)
|
public async Task<ExternalPlaylist?> GetPlaylistAsync(string externalProvider, string externalId)
|
||||||
{
|
{
|
||||||
if (externalProvider != "squidwtf") return null;
|
if (externalProvider != "squidwtf") return null;
|
||||||
|
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var url = $"{_currentApiBase}/playlist?id={externalId}";
|
var url = $"{baseUrl}/playlist/?id={externalId}";
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
if (!response.IsSuccessStatusCode) return null;
|
if (!response.IsSuccessStatusCode) return null;
|
||||||
|
|
||||||
@@ -527,21 +425,16 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
if (playlistElement.TryGetProperty("error", out _)) return null;
|
if (playlistElement.TryGetProperty("error", out _)) return null;
|
||||||
|
|
||||||
return ParseTidalPlaylist(playlistElement);
|
return ParseTidalPlaylist(playlistElement);
|
||||||
}
|
}, (ExternalPlaylist?)null);
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Song>> GetPlaylistTracksAsync(string externalProvider, string externalId)
|
public async Task<List<Song>> GetPlaylistTracksAsync(string externalProvider, string externalId)
|
||||||
{
|
{
|
||||||
if (externalProvider != "squidwtf") return new List<Song>();
|
if (externalProvider != "squidwtf") return new List<Song>();
|
||||||
|
|
||||||
try
|
return await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var url = $"{_currentApiBase}/playlist?id={externalId}";
|
var url = $"{baseUrl}/playlist/?id={externalId}";
|
||||||
var response = await _httpClient.GetAsync(url);
|
var response = await _httpClient.GetAsync(url);
|
||||||
if (!response.IsSuccessStatusCode) return new List<Song>();
|
if (!response.IsSuccessStatusCode) return new List<Song>();
|
||||||
|
|
||||||
@@ -592,12 +485,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return songs;
|
return songs;
|
||||||
}
|
}, new List<Song>());
|
||||||
catch
|
|
||||||
{
|
|
||||||
return new List<Song>();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Parser functions start here ---
|
// --- Parser functions start here ---
|
||||||
|
|||||||
@@ -12,25 +12,40 @@ namespace allstarr.Services.SquidWTF;
|
|||||||
public class SquidWTFStartupValidator : BaseStartupValidator
|
public class SquidWTFStartupValidator : BaseStartupValidator
|
||||||
{
|
{
|
||||||
private readonly SquidWTFSettings _settings;
|
private readonly SquidWTFSettings _settings;
|
||||||
|
private readonly List<string> _apiUrls;
|
||||||
// Primary endpoint (base64 encoded to avoid detection)
|
private int _currentUrlIndex = 0;
|
||||||
private const string PrimaryEndpoint = "aHR0cHM6Ly90cml0b24uc3F1aWQud3RmLw=="; // triton.squid.wtf
|
|
||||||
private readonly string _apiBase;
|
|
||||||
|
|
||||||
public override string ServiceName => "SquidWTF";
|
public override string ServiceName => "SquidWTF";
|
||||||
|
|
||||||
public SquidWTFStartupValidator(IOptions<SquidWTFSettings> settings, HttpClient httpClient)
|
public SquidWTFStartupValidator(IOptions<SquidWTFSettings> settings, HttpClient httpClient, List<string> apiUrls)
|
||||||
: base(httpClient)
|
: base(httpClient)
|
||||||
{
|
{
|
||||||
_settings = settings.Value;
|
_settings = settings.Value;
|
||||||
_apiBase = DecodeEndpoint(PrimaryEndpoint);
|
_apiUrls = apiUrls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DecodeEndpoint(string base64)
|
private async Task<T> TryWithFallbackAsync<T>(Func<string, Task<T>> action, T defaultValue)
|
||||||
{
|
{
|
||||||
var bytes = Convert.FromBase64String(base64);
|
for (int attempt = 0; attempt < _apiUrls.Count; attempt++)
|
||||||
return Encoding.UTF8.GetString(bytes).TrimEnd('/');
|
{
|
||||||
}
|
try
|
||||||
|
{
|
||||||
|
var baseUrl = _apiUrls[_currentUrlIndex];
|
||||||
|
return await action(baseUrl);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
WriteDetail($"Endpoint {_apiUrls[_currentUrlIndex]} failed, trying next...");
|
||||||
|
_currentUrlIndex = (_currentUrlIndex + 1) % _apiUrls.Count;
|
||||||
|
|
||||||
|
if (attempt == _apiUrls.Count - 1)
|
||||||
|
{
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task<ValidationResult> ValidateAsync(CancellationToken cancellationToken)
|
public override async Task<ValidationResult> ValidateAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -48,54 +63,36 @@ public class SquidWTFStartupValidator : BaseStartupValidator
|
|||||||
|
|
||||||
WriteStatus("SquidWTF Quality", quality, ConsoleColor.Cyan);
|
WriteStatus("SquidWTF Quality", quality, ConsoleColor.Cyan);
|
||||||
|
|
||||||
// Test connectivity
|
// Test connectivity with fallback
|
||||||
try
|
var result = await TryWithFallbackAsync(async (baseUrl) =>
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync(_apiBase, cancellationToken);
|
var response = await _httpClient.GetAsync(baseUrl, cancellationToken);
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
WriteStatus("SquidWTF API", "REACHABLE", ConsoleColor.Green);
|
WriteStatus("SquidWTF API", $"REACHABLE ({baseUrl})", ConsoleColor.Green);
|
||||||
WriteDetail("No authentication required - powered by Tidal");
|
WriteDetail("No authentication required - powered by Tidal");
|
||||||
|
|
||||||
// Try a test search to verify functionality
|
// Try a test search to verify functionality
|
||||||
await ValidateSearchFunctionality(cancellationToken);
|
await ValidateSearchFunctionality(baseUrl, cancellationToken);
|
||||||
|
|
||||||
return ValidationResult.Success("SquidWTF validation completed");
|
return ValidationResult.Success("SquidWTF validation completed");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WriteStatus("SquidWTF API", $"HTTP {(int)response.StatusCode}", ConsoleColor.Yellow);
|
throw new HttpRequestException($"HTTP {(int)response.StatusCode}");
|
||||||
WriteDetail("Service may be temporarily unavailable");
|
|
||||||
return ValidationResult.Failure($"{response.StatusCode}", "SquidWTF returned code");
|
|
||||||
}
|
}
|
||||||
}
|
}, ValidationResult.Failure("-1", "All SquidWTF endpoints failed"));
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
return result;
|
||||||
WriteStatus("SquidWTF API", "TIMEOUT", ConsoleColor.Yellow);
|
|
||||||
WriteDetail("Could not reach service within timeout period");
|
|
||||||
return ValidationResult.Failure("-1", "SquidWTF connection timeout");
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
|
||||||
WriteStatus("SquidWTF API", "UNREACHABLE", ConsoleColor.Red);
|
|
||||||
WriteDetail(ex.Message);
|
|
||||||
return ValidationResult.Failure("-1", $"Cannot connect to SquidWTF: {ex.Message}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
WriteStatus("SquidWTF API", "ERROR", ConsoleColor.Red);
|
|
||||||
WriteDetail(ex.Message);
|
|
||||||
return ValidationResult.Failure("-1", $"Validation error: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ValidateSearchFunctionality(CancellationToken cancellationToken)
|
private async Task ValidateSearchFunctionality(string baseUrl, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Test search with a simple query
|
// Test search with a simple query
|
||||||
var searchUrl = $"{_apiBase}/search?s=Taylor%20Swift";
|
var searchUrl = $"{baseUrl}/search/?s=Taylor%20Swift";
|
||||||
var searchResponse = await _httpClient.GetAsync(searchUrl, cancellationToken);
|
var searchResponse = await _httpClient.GetAsync(searchUrl, cancellationToken);
|
||||||
|
|
||||||
if (searchResponse.IsSuccessStatusCode)
|
if (searchResponse.IsSuccessStatusCode)
|
||||||
|
|||||||
Reference in New Issue
Block a user