Lisoveliy 4a95ba0d86
Some checks failed
Deploy / update (push) Has been cancelled
Build Project .NET / build (push) Has been cancelled
fix: fixed search of tidal tracks using WebUtils
2025-05-12 21:37:29 +03:00

189 lines
7.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using SWAD.API.Consts;
using SWAD.API.Consts.Enums;
using SWAD.API.Controllers.DTOs;
using SWAD.API.Exceptions;
using SWAD.API.Models.Config.ApiServices;
using SWAD.API.Models.JsonStructures.MusicAPI.Tidal;
using SWAD.API.Services.MusicAPI.Auth;
namespace SWAD.API.Services.MusicAPI.Api;
public class TidalService : ApiService
{
private readonly TidalAuthService _authService;
private TidalAuthResponse? _token;
public TidalService(IOptions<ApiServicesConfig> config, IEnumerable<AbstractAuthService> authServices)
{
ServiceType = MusicService.Tidal;
var configServices = config.Value.ServicesData;
Config = configServices.First(x => x.Name == ServiceType.ToString());
_authService = authServices.First(x => x.ServiceType == ServiceType) as TidalAuthService
?? throw new ApplicationException("Auth service not found");
}
//Auto revoke on Expire
private TidalAuthResponse? Token
{
get => _token?.ExpireAt < DateTime.UtcNow ? null : _token;
set => _token = value;
}
public override async Task<string?> GetLinkByQuery(TrackDto query)
{
var searchUri = new UriBuilder(Config.Endpoints.Api);
if (Token == null)
{
var newToken = await _authService.GetToken() as TidalAuthResponse;
Token = newToken ??
throw new AuthenticationFailureException(string.Format(Messages.AuthFailMessage, ServiceType));
}
using var client = new HttpClient();
var clientHeaders = client.DefaultRequestHeaders;
clientHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
clientHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.api+json"));
searchUri.Scheme = "https";
searchUri.Path = Path.Combine(searchUri.Path, Config.ApiPaths.Search, Uri.EscapeDataString(GetQuery(query)
//TODO: Это нужно, разработчики TIDAL дауны
));
var url = new UriBuilder(searchUri.Uri)
{
Port = -1
};
var urlQuery = HttpUtility.ParseQueryString(url.Query);
//TODO: придумать что-то с этим
urlQuery["countryCode"] = "US";
urlQuery["include"] = "tracks";
url.Query = urlQuery.ToString();
var httpRequest = new HttpRequestMessage(HttpMethod.Get, url.Uri);
httpRequest.Content =
new StringContent(string.Empty, new MediaTypeHeaderValue("application/vnd.api+json"));
var response = await client.SendAsync(httpRequest);
if (!response.IsSuccessStatusCode)
{
var requestDebugData = await response.Content.ReadAsStringAsync();
throw new HttpRequestException(ErrorResources.Unsuccessful);
}
var json =
await JsonSerializer.DeserializeAsync<TidalSearchResponse>(await response.Content.ReadAsStreamAsync());
// ReSharper disable once NullableWarningSuppressionIsUsed
if (json.Data.RelationShips.Track.Data.Count < 1)
{
throw new TrackNotFoundException($"Track is not found in {ServiceType}");
}
//return null!; //TODO: Rewrite whole service
return Config.Endpoints.MusicLink[0] + json.Data.RelationShips.Track.Data[0].Id;
}
public override async Task<TrackDto> GetQueryObject(string link, string countryCode = "US")
{
var url = new Uri(link);
var id = url.Segments[^1];
if (Token == null)
{
var newToken = await _authService.GetToken() as TidalAuthResponse;
Token = newToken ??
throw new AuthenticationFailureException(string.Format(Messages.AuthFailMessage, ServiceType));
}
//Prepare request
using var client = new HttpClient();
//Get response
var nameResponse = await client.SendAsync(GetRequestMessage(client, id, countryCode));
if (!nameResponse.IsSuccessStatusCode)
if (nameResponse.StatusCode == HttpStatusCode.NotFound)
{
//TODO: переписать на норм обработку CountryCode
Thread.Sleep(1000);
nameResponse = await client.SendAsync(GetRequestMessage(client, id, "GB")); //Оверрайдим на европу
if (!nameResponse.IsSuccessStatusCode)
{
throw new HttpRequestException(ErrorResources.Unsuccessful);
}
}
else
throw new HttpRequestException(ErrorResources.Unsuccessful);
var trackJson =
await JsonSerializer.DeserializeAsync<TidalTrackResponse>(await nameResponse.Content.ReadAsStreamAsync());
StringBuilder artists = new();
foreach (var data in trackJson?.Data.RelationShips.Artist.Data ?? new())
{
var artistsResponse = await client.SendAsync(GetRequestMessage(client, data.Id, countryCode, true));
if (!artistsResponse.IsSuccessStatusCode)
if (artistsResponse.StatusCode == HttpStatusCode.NotFound)
{
//TODO: переписать на норм обработку CountryCode
Thread.Sleep(1000);
artistsResponse =
await client.SendAsync(GetRequestMessage(client, id, "GB", true)); //Оверрайдим на европу
if (!artistsResponse.IsSuccessStatusCode)
{
throw new HttpRequestException(ErrorResources.Unsuccessful);
}
}
else
throw new HttpRequestException(ErrorResources.Unsuccessful);
var artistJson =
await JsonSerializer.DeserializeAsync<TidalArtistResponse>(
await artistsResponse.Content.ReadAsStreamAsync());
artists.AppendJoin(",", artistJson?.Data.Attributes.Name);
}
// ReSharper disable once NullableWarningSuppressionIsUsed
//var artists = string.Join(", ", json!.Resource.Artists.ToList().ConvertAll(x => x.Name));
return new TrackDto(trackJson?.Data.Attributes.Title!, artists.ToString(), null!, ServiceType);
}
private HttpRequestMessage GetRequestMessage(HttpClient client, string id, string countryCode,
bool isArtist = false)
{
var clientHeaders = client.DefaultRequestHeaders;
clientHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
clientHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.api+json"));
UriBuilder builder;
if (isArtist)
{
builder = new UriBuilder(Config.Endpoints.Api + Path.Combine(Config.ApiPaths.GetArtist, id))
{
Port = -1
};
}
else
{
builder = new UriBuilder(Config.Endpoints.Api + Path.Combine(Config.ApiPaths.GetTrack, id))
{
Port = -1,
Query = "include=artists"
};
}
var urlQuery = HttpUtility.ParseQueryString(builder.Query);
//TODO: Придумать что-то с countryCode
urlQuery["countryCode"] = countryCode;
builder.Query = urlQuery.ToString();
var httpRequest = new HttpRequestMessage(HttpMethod.Get, builder.Uri);
httpRequest.Content = new StringContent(string.Empty, Encoding.UTF8, "application/vnd.api+json");
return httpRequest;
}
}