chore: init commit from GitHub
This commit is contained in:
35
SWAD.API/Services/MusicAPI/Api/ApiService.cs
Normal file
35
SWAD.API/Services/MusicAPI/Api/ApiService.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using SWAD.API.Consts.Enums;
|
||||
using SWAD.API.Controllers.DTOs;
|
||||
using SWAD.API.Models.Config.ApiServices.Structure;
|
||||
|
||||
namespace SWAD.API.Services.MusicAPI.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract ApiService of music services
|
||||
/// </summary>
|
||||
public abstract class ApiService
|
||||
{
|
||||
public ApiServiceData Config { get; protected init; } = null!;
|
||||
public MusicService ServiceType { get; protected init; }
|
||||
public abstract Task<string?> GetLinkByQuery(TrackDto query);
|
||||
public abstract Task<TrackDto> GetQueryObject(string link, string CountryCode = "US");
|
||||
protected string GetQuery(TrackDto dto) => $"{dto.Name} - {dto.Artist}";
|
||||
|
||||
/// <summary>
|
||||
/// Get all implemented API Services
|
||||
/// </summary>
|
||||
/// <returns>API Services types</returns>
|
||||
public static IEnumerable<Type> GetAllImplementations()
|
||||
{
|
||||
var type = typeof(ApiService);
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => type.IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract);
|
||||
return types;
|
||||
}
|
||||
|
||||
protected struct Messages
|
||||
{
|
||||
public const string AuthFailMessage = "Update token request for service {0} failed";
|
||||
}
|
||||
}
|
||||
109
SWAD.API/Services/MusicAPI/Api/SpotifyService.cs
Normal file
109
SWAD.API/Services/MusicAPI/Api/SpotifyService.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SWAD.API.Consts.Enums;
|
||||
using SWAD.API.Controllers.DTOs;
|
||||
using SWAD.API.Models.Config.ApiServices;
|
||||
using SWAD.API.Models.JsonStructures.MusicAPI.Spotify;
|
||||
using SWAD.API.Services.MusicAPI.Auth;
|
||||
|
||||
namespace SWAD.API.Services.MusicAPI.Api;
|
||||
|
||||
public class SpotifyService : ApiService
|
||||
{
|
||||
private readonly SpotifyAuthService _authService;
|
||||
private readonly Uri _searchUri;
|
||||
private SpotifyAuthResponse? _token;
|
||||
|
||||
public SpotifyService(IOptions<ApiServicesConfig> config, IEnumerable<AbstractAuthService> authServices)
|
||||
{
|
||||
ServiceType = MusicService.Spotify;
|
||||
var configServices = config.Value.ServicesData;
|
||||
Config = configServices.First(x => x.Name == ServiceType.ToString());
|
||||
_authService = (authServices.First(x => x.ServiceType == ServiceType) as SpotifyAuthService) ?? throw new ApplicationException("Auth service not found");
|
||||
_searchUri = new Uri(new Uri(Config.Endpoints.Api), Config.ApiPaths.Search);
|
||||
}
|
||||
|
||||
//Auto revoke on Expire
|
||||
private SpotifyAuthResponse? Token
|
||||
{
|
||||
get => _token?.ExpireAt < DateTime.UtcNow ? null : _token;
|
||||
set => _token = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get link to spotify by search query
|
||||
/// </summary>
|
||||
/// <param Name="query">DTO with search query</param>
|
||||
/// <returns>link from spotify</returns>
|
||||
/// <exception cref="AuthenticationFailureException">If token problems</exception>
|
||||
/// <exception cref="HttpRequestException">If response is bad</exception>
|
||||
public override async Task<string?> GetLinkByQuery(TrackDto query)
|
||||
{
|
||||
if (Token == null)
|
||||
{
|
||||
var newToken = await _authService.GetToken() as SpotifyAuthResponse;
|
||||
Token = newToken ??
|
||||
throw new AuthenticationFailureException(string.Format(Messages.AuthFailMessage, ServiceType));
|
||||
}
|
||||
|
||||
using var client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
|
||||
|
||||
var url = new UriBuilder(_searchUri)
|
||||
{
|
||||
Port = -1
|
||||
};
|
||||
var urlQuery = HttpUtility.ParseQueryString(url.Query);
|
||||
urlQuery["q"] = GetQuery(query);
|
||||
urlQuery["type"] = "track";
|
||||
urlQuery["limit"] = "1";
|
||||
urlQuery["offset"] = "0";
|
||||
url.Query = urlQuery.ToString();
|
||||
|
||||
var response = await client.GetAsync(url.Uri);
|
||||
if (!response.IsSuccessStatusCode) throw new HttpRequestException("Request unsuccessful");
|
||||
var json = await JsonSerializer.DeserializeAsync<SpotifySearchResponse>(
|
||||
await response.Content.ReadAsStreamAsync());
|
||||
|
||||
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||
return json!.Tracks.Items[0].ExternalUrls.Spotify;
|
||||
}
|
||||
|
||||
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 SpotifyAuthResponse;
|
||||
Token = newToken ??
|
||||
throw new AuthenticationFailureException(string.Format(Messages.AuthFailMessage, ServiceType));
|
||||
}
|
||||
using var client = new HttpClient();
|
||||
//Prepare request
|
||||
var clientHeaders = client.DefaultRequestHeaders;
|
||||
clientHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
|
||||
|
||||
var builder = new UriBuilder(Config.Endpoints.Api + Path.Combine(Config.ApiPaths.GetTrack, id))
|
||||
{
|
||||
Port = -1,
|
||||
Query = $"market={countryCode}"
|
||||
};
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get, builder.Uri);
|
||||
//Get response
|
||||
var response = await client.SendAsync(httpRequest);
|
||||
if (!response.IsSuccessStatusCode) throw new HttpRequestException("Request unsuccessful");
|
||||
|
||||
var json =
|
||||
await JsonSerializer.DeserializeAsync<SpotifyTrackResponse>(await response.Content.ReadAsStreamAsync());
|
||||
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||
var artists = string.Join(", ", json!.Artists.ToList().ConvertAll(x => x.Name));
|
||||
|
||||
return new TrackDto(json.Name, artists, json.Albums.Name, ServiceType);
|
||||
}
|
||||
}
|
||||
189
SWAD.API/Services/MusicAPI/Api/TidalService.cs
Normal file
189
SWAD.API/Services/MusicAPI/Api/TidalService.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
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, 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<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)
|
||||
{
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
88
SWAD.API/Services/MusicAPI/Api/YandexService.cs
Normal file
88
SWAD.API/Services/MusicAPI/Api/YandexService.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Net;
|
||||
using System.Web;
|
||||
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.Yandex;
|
||||
using SWAD.API.Services.MusicAPI.Auth;
|
||||
|
||||
namespace SWAD.API.Services.MusicAPI.Api;
|
||||
|
||||
public class YandexService : ApiService
|
||||
{
|
||||
public YandexService(IOptions<ApiServicesConfig> config, IEnumerable<AbstractAuthService> authServices)
|
||||
{
|
||||
ServiceType = MusicService.Yandex;
|
||||
var configServices = config.Value.ServicesData;
|
||||
Config = configServices.First(x => x.Name == ServiceType.ToString());
|
||||
}
|
||||
|
||||
|
||||
public override async Task<string?> GetLinkByQuery(TrackDto query)
|
||||
{
|
||||
var searchUri = new Uri(new Uri(Config.Endpoints.Api), Config.ApiPaths.Search);
|
||||
|
||||
using var client = new HttpClient();
|
||||
var clientHeaders = client.DefaultRequestHeaders;
|
||||
|
||||
var response = await client.PostAsync(searchUri, new FormUrlEncodedContent([
|
||||
new("text", GetQuery(query))
|
||||
]));
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var requestDebugData = await response.Content.ReadAsStringAsync();
|
||||
throw new HttpRequestException($"{ErrorResources.Unsuccessful}\n Service returned: {requestDebugData}");
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadFromJsonAsync<YandexSearchResponse>();
|
||||
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||
if (json.Entities.Count < 1)
|
||||
{
|
||||
throw new TrackNotFoundException($"Track is not found in {ServiceType}");
|
||||
}
|
||||
|
||||
var results = json.Entities.FirstOrDefault(x => x.Type == "track").Results;
|
||||
/*
|
||||
* ВНИМАНИЕ
|
||||
* Yandex API писали шизы поэтому когда будешь добавлять поля в YandexSearchResponse и здесь их юзать
|
||||
* ПОМНИ: ID у артистов и альбомов в разных местах JSON в одних и тех же объектах то string то number
|
||||
* ЭТО ВЫЗОВЕТ ИСКЛЮЧЕНИЕ
|
||||
* Думой
|
||||
*/
|
||||
var resultTrack = results.FirstOrDefault(x => x.Track.Title == query.Name) ??
|
||||
results[0];
|
||||
return $"{Config.Endpoints.MusicLink[0]}{resultTrack.Track.Id}";
|
||||
}
|
||||
|
||||
public override async Task<TrackDto> GetQueryObject(string link, string countryCode = "RU")
|
||||
{
|
||||
var url = new Uri(link);
|
||||
var id = url.Segments[^1];
|
||||
//Prepare request
|
||||
using var client = new HttpClient();
|
||||
var getTrackUrl = new UriBuilder(new Uri(new Uri(Config.Endpoints.Api), Config.ApiPaths.GetTrack))
|
||||
{
|
||||
Port = -1
|
||||
};
|
||||
var urlQuery = HttpUtility.ParseQueryString(url.Query);
|
||||
urlQuery["track"] = id;
|
||||
getTrackUrl.Query = urlQuery.ToString();
|
||||
//Get response
|
||||
var response = await client.GetAsync(getTrackUrl.Uri);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
throw new TrackNotFoundException("Ну, сори, яндекс кал");
|
||||
}
|
||||
else
|
||||
throw new HttpRequestException(ErrorResources.Unsuccessful);
|
||||
|
||||
var json = await response.Content.ReadFromJsonAsync<YandexTrackResponse>();
|
||||
// ReSharper disable once NullableWarningSuppressionIsUsed
|
||||
var artists = string.Join(", ", json!.Artists.ToList().ConvertAll(x => x.Name));
|
||||
return new TrackDto(json.Track.Title, artists, json.Track.Albums[0].Title, ServiceType);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user