using PlaylistShared.Data.Contexts; using PlaylistShared.Data.Entities; using System.Net.Http.Headers; using System.Text; using System.Text.Json; namespace PlaylistShared.Services; public class YandexMusicService : IYandexMusicService { private readonly AppDbContext _db; private readonly HttpClient _http; private readonly IConfiguration _config; private readonly ILogger _logger; public YandexMusicService(AppDbContext db, IHttpClientFactory factory, IConfiguration config, ILogger logger) { _db = db; _http = factory.CreateClient(); _config = config; _logger = logger; } private async Task GetUserWithValidTokenAsync(string userId) { var user = await _db.Users.FindAsync(userId); if (user == null) throw new Exception("User not found"); if (user.AccessTokenExpiresAt <= DateTime.UtcNow.AddMinutes(5)) { var newToken = await RefreshYandexTokenAsync(user.RefreshToken!); user.AccessToken = newToken.access_token; user.RefreshToken = newToken.refresh_token; user.AccessTokenExpiresAt = DateTime.UtcNow.AddSeconds(newToken.expires_in); await _db.SaveChangesAsync(); } return user; } private async Task<(string access_token, string refresh_token, int expires_in)> RefreshYandexTokenAsync(string refreshToken) { var content = new FormUrlEncodedContent(new[] { new KeyValuePair("grant_type", "refresh_token"), new KeyValuePair("refresh_token", refreshToken), new KeyValuePair("client_id", _config["YandexOAuth:ClientId"]), new KeyValuePair("client_secret", _config["YandexOAuth:ClientSecret"]) }); var response = await _http.PostAsync(_config["YandexOAuth:TokenEndpoint"], content); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(json); return ( doc.RootElement.GetProperty("access_token").GetString()!, doc.RootElement.GetProperty("refresh_token").GetString()!, doc.RootElement.GetProperty("expires_in").GetInt32() ); } public async Task CreatePlaylistAsync(string userId, string title) { var user = await GetUserWithValidTokenAsync(userId); _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("OAuth", user.AccessToken); var payload = new { title, visibility = "public" }; var json = JsonSerializer.Serialize(payload); var response = await _http.PostAsync("https://api.music.yandex.net/users/self/playlists/create", new StringContent(json, Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); var resultJson = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize>(resultJson); return result!.Result.Kind; } public async Task AddTrackToPlaylistAsync(string userId, string yandexPlaylistId, string trackId) { var user = await GetUserWithValidTokenAsync(userId); var uid = user.YandexId; // или user.Id? В API Яндекса используется uid из аккаунта, но можно использовать "self" // Получаем альбом трека (опционально, можно передать без albumId) // Для простоты используем трек без albumId var diff = new[] { new { op = "insert", at = 0, tracks = new[] { new { id = trackId } } } }; var json = JsonSerializer.Serialize(diff); var content = new StringContent(json, Encoding.UTF8, "application/json"); var url = $"https://api.music.yandex.net/users/{uid}/playlists/{yandexPlaylistId}/change"; var response = await _http.PostAsync(url, content); response.EnsureSuccessStatusCode(); } public async Task RemoveTrackFromPlaylistAsync(string userId, string yandexPlaylistId, string trackId) { var user = await GetUserWithValidTokenAsync(userId); var uid = user.YandexId; // Сначала нужно получить позицию трека в плейлисте – упростим: удаляем по индексу (не надёжно) // Лучше получить плейлист, найти индекс трека, потом удалить var playlist = await GetPlaylistInfoAsync(userId, yandexPlaylistId); var tracks = await GetPlaylistTracksAsync(userId, yandexPlaylistId); var index = tracks.FindIndex(t => t.Id == trackId); if (index == -1) throw new Exception("Track not found in playlist"); var diff = new[] { new { op = "delete", from = index, to = index + 1 } }; var json = JsonSerializer.Serialize(diff); var content = new StringContent(json, Encoding.UTF8, "application/json"); var url = $"https://api.music.yandex.net/users/{uid}/playlists/{yandexPlaylistId}/change"; var response = await _http.PostAsync(url, content); response.EnsureSuccessStatusCode(); } public async Task> GetPlaylistTracksAsync(string userId, string yandexPlaylistId) { var user = await GetUserWithValidTokenAsync(userId); _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("OAuth", user.AccessToken); var uid = user.YandexId; var url = $"https://api.music.yandex.net/users/{uid}/playlists/{yandexPlaylistId}"; var response = await _http.GetAsync(url); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var data = JsonSerializer.Deserialize>(json); return data!.Result.Tracks?.Select(t => new YandexTrackInfo( t.Id.ToString(), t.Title, t.Artists?.FirstOrDefault()?.Name, t.Albums?.FirstOrDefault()?.Title, t.DurationMs )).ToList() ?? new(); } public async Task GetPlaylistInfoAsync(string userId, string yandexPlaylistId) { var user = await GetUserWithValidTokenAsync(userId); _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("OAuth", user.AccessToken); var uid = user.YandexId; var url = $"https://api.music.yandex.net/users/{uid}/playlists/{yandexPlaylistId}"; var response = await _http.GetAsync(url); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var data = JsonSerializer.Deserialize>(json); return new YandexPlaylistInfo(data!.Result.Kind, data.Result.Title, data.Result.TrackCount); } public async Task RefreshUserTokenAsync(string userId) { var user = await _db.Users.FindAsync(userId); if (user?.RefreshToken == null) return null; var newToken = await RefreshYandexTokenAsync(user.RefreshToken); user.AccessToken = newToken.access_token; user.RefreshToken = newToken.refresh_token; user.AccessTokenExpiresAt = DateTime.UtcNow.AddSeconds(newToken.expires_in); await _db.SaveChangesAsync(); return newToken.access_token; } // Вспомогательные record private record YandexApiResponse(T Result); private record YandexPlaylistData(string Kind); private record YandexPlaylistFull(string Kind, string Title, int TrackCount, List Tracks); private record YandexTrack(string Id, string Title, int DurationMs, List Artists, List Albums); private record YandexArtist(string Name); private record YandexAlbum(string Title); }