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 config, IEnumerable 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 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, WebUtility.UrlEncode(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.tidal.v1+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(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 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(await nameResponse.Content.ReadAsStreamAsync()); StringBuilder artists = new(); foreach (var data in trackJson?.Data.RelationShips.Artist.Data) { 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( 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.tidal.v1+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.tidal.v1+json"); return httpRequest; } }