using System.Security.Cryptography; using System.Text; using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Track; using YandexMusic.API.Requests.Track; namespace YandexMusic.API; /// API для работы с треками (получение, загрузка, метаданные). public class YTrackAPI : YCommonAPI { public YTrackAPI(YandexMusicApi api) : base(api) { } private static string BuildDownloadLink(YTrackDownloadInfo info, YStorageDownloadFile storageDownload) { var path = storageDownload.Path; var host = storageDownload.Host; var ts = storageDownload.Ts; var s = storageDownload.S; var codec = info.Codec; var secret = $"XGRlBW9FXlekgbPrRHuSiA{path[1..]}{s}"; var md5Hash = MD5.HashData(Encoding.UTF8.GetBytes(secret)); var hmacsha1 = new HMACSHA1(md5Hash); var sign = BitConverter.ToString(hmacsha1.ComputeHash(md5Hash)).Replace("-", "").ToLower(); return $"https://{host}/get-{codec}/{sign}/{ts}{path}"; } public Task GetAsync(string trackId) => GetAsync(trackId); public Task?> GetAsync(IEnumerable trackIds) => new YGetTracksBuilder(Api).ExecuteAsync(trackIds); public Task?> GetMetadataForDownloadAsync(string trackKey, bool direct = false) => new YTrackDownloadInfoBuilder(Api).ExecuteAsync((trackKey, direct)); public Task?> GetMetadataForDownloadAsync(YTrack track, bool direct = false) => GetMetadataForDownloadAsync(track.GetKey().ToString(), direct); public Task GetDownloadFileInfoAsync(YTrackDownloadInfo metadataInfo) => new YStorageDownloadFileBuilder(Api).ExecuteAsync(metadataInfo.DownloadInfoUrl); public async Task GetFileLinkAsync(string trackKey) { var meta = await GetMetadataForDownloadAsync(trackKey); var info = meta?.OrderByDescending(i => i.BitrateInKbps).FirstOrDefault(m => m.Codec == "mp3"); if (info == null) return null; var storageDownload = await GetDownloadFileInfoAsync(info); if (storageDownload == null) return null; return BuildDownloadLink(info, storageDownload); } public Task GetFileLinkAsync(YTrack track) => GetFileLinkAsync(track.GetKey().ToString()); public async Task ExtractToFileAsync(string trackKey, string filePath) { var url = await GetFileLinkAsync(trackKey); if (string.IsNullOrEmpty(url)) throw new Exception("Не удалось получить ссылку на трек"); using var response = await Api.HttpClient.GetAsync(url); await using var fs = File.Create(filePath); await response.Content.CopyToAsync(fs); } public Task ExtractToFileAsync(YTrack track, string filePath) => ExtractToFileAsync(track.GetKey().ToString(), filePath); public async Task ExtractDataAsync(string trackKey) { var url = await GetFileLinkAsync(trackKey); if (string.IsNullOrEmpty(url)) throw new Exception("Не удалось получить ссылку на трек"); return await Api.HttpClient.GetByteArrayAsync(url); } public Task ExtractDataAsync(YTrack track) => ExtractDataAsync(track.GetKey().ToString()); public async Task ExtractStreamAsync(string trackKey, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { var url = await GetFileLinkAsync(trackKey); if (string.IsNullOrEmpty(url)) throw new Exception("Не удалось получить ссылку на трек"); var response = await Api.HttpClient.GetAsync(url, completionOption); return await response.Content.ReadAsStreamAsync(); } public Task ExtractStreamAsync(YTrack track, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) => ExtractStreamAsync(track.GetKey().ToString(), completionOption); public Task SendPlayTrackInfoAsync( YTrack track, string from, bool fromCache = false, string playId = "", string playlistId = "", double totalPlayedSeconds = 0, double endPositionSeconds = 0) => new YSendTrackInfoBuilder(Api).ExecuteAsync((track, from, fromCache, playId, playlistId, totalPlayedSeconds, endPositionSeconds)); public Task GetSupplementAsync(string trackId) => new YGetTrackSupplementBuilder(Api).ExecuteAsync(trackId); public Task GetSupplementAsync(YTrack track) => GetSupplementAsync(track.GetKey().ToString()); public Task GetSimilarAsync(string trackId) => new YGetTrackSimilarBuilder(Api).ExecuteAsync(trackId); public Task GetSimilarAsync(YTrack track) => GetSimilarAsync(track.GetKey().ToString()); }