Полностью переписанное api
All checks were successful
Release / pack-and-publish (release) Successful in 36s
All checks were successful
Release / pack-and-publish (release) Successful in 36s
This commit is contained in:
@@ -1,307 +1,110 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
using YandexMusic.API.Common;
|
||||
using YandexMusic.API.Models.Common;
|
||||
using YandexMusic.API.Models.Track;
|
||||
using YandexMusic.API.Requests.Track;
|
||||
|
||||
namespace YandexMusic.API;
|
||||
|
||||
/// <summary>
|
||||
/// API для взаимодействия с треками
|
||||
/// </summary>
|
||||
public partial class YTrackAPI : YCommonAPI
|
||||
/// <summary>API для работы с треками (получение, загрузка, метаданные).</summary>
|
||||
public class YTrackAPI : YCommonAPI
|
||||
{
|
||||
#region Вспомогательные функции
|
||||
public YTrackAPI(YandexMusicApi api) : base(api) { }
|
||||
|
||||
private string BuildLinkForDownload(YTrackDownloadInfo mainDownloadResponse, YStorageDownloadFile storageDownload)
|
||||
private static string BuildDownloadLink(YTrackDownloadInfo info, YStorageDownloadFile storageDownload)
|
||||
{
|
||||
string path = storageDownload.Path;
|
||||
string host = storageDownload.Host;
|
||||
string ts = storageDownload.Ts;
|
||||
string s = storageDownload.S;
|
||||
string codec = mainDownloadResponse.Codec;
|
||||
var path = storageDownload.Path;
|
||||
var host = storageDownload.Host;
|
||||
var ts = storageDownload.Ts;
|
||||
var s = storageDownload.S;
|
||||
var codec = info.Codec;
|
||||
|
||||
string secret = $"XGRlBW9FXlekgbPrRHuSiA{path.Substring(1, path.Length - 1)}{s}";
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] md5Hash = md5.ComputeHash(Encoding.UTF8.GetBytes(secret));
|
||||
HMACSHA1 hmacsha1 = new();
|
||||
byte[] hmasha1Hash = hmacsha1.ComputeHash(md5Hash);
|
||||
string sign = BitConverter.ToString(hmasha1Hash).Replace("-", "").ToLower();
|
||||
|
||||
string link = $"https://{host}/get-{codec}/{sign}/{ts}{path}";
|
||||
|
||||
return link;
|
||||
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}";
|
||||
}
|
||||
|
||||
#endregion Вспомогательные функции
|
||||
public Task<YTrack?> GetAsync(string trackId)
|
||||
=> GetAsync(trackId);
|
||||
|
||||
public Task<List<YTrack>?> GetAsync(IEnumerable<string> trackIds)
|
||||
=> new YGetTracksBuilder(Api).ExecuteAsync(trackIds);
|
||||
public Task<List<YTrackDownloadInfo>?> GetMetadataForDownloadAsync(string trackKey, bool direct = false)
|
||||
=> new YTrackDownloadInfoBuilder(Api).ExecuteAsync((trackKey, direct));
|
||||
|
||||
public Task<List<YTrackDownloadInfo>?> GetMetadataForDownloadAsync(YTrack track, bool direct = false)
|
||||
=> GetMetadataForDownloadAsync(track.GetKey().ToString(), direct);
|
||||
|
||||
public YTrackAPI(YandexMusicApi yandex) : base(yandex)
|
||||
public Task<YStorageDownloadFile?> GetDownloadFileInfoAsync(YTrackDownloadInfo metadataInfo)
|
||||
=> new YStorageDownloadFileBuilder(Api).ExecuteAsync(metadataInfo.DownloadInfoUrl);
|
||||
|
||||
public async Task<string?> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение треков
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackId">Идентификатор трека</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<List<YTrack>>> GetAsync(AuthStorage storage, string trackId)
|
||||
public Task<string?> GetFileLinkAsync(YTrack track)
|
||||
=> GetFileLinkAsync(track.GetKey().ToString());
|
||||
|
||||
public async Task ExtractToFileAsync(string trackKey, string filePath)
|
||||
{
|
||||
return new YGetTracksBuilder(api, storage)
|
||||
.Build(new[] { trackId })
|
||||
.GetResponseAsync();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение треков
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackIds">Идентификаторы треков</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<List<YTrack>>> GetAsync(AuthStorage storage, IEnumerable<string> trackIds)
|
||||
public Task ExtractToFileAsync(YTrack track, string filePath)
|
||||
=> ExtractToFileAsync(track.GetKey().ToString(), filePath);
|
||||
|
||||
public async Task<byte[]> ExtractDataAsync(string trackKey)
|
||||
{
|
||||
return new YGetTracksBuilder(api, storage)
|
||||
.Build(trackIds)
|
||||
.GetResponseAsync();
|
||||
var url = await GetFileLinkAsync(trackKey);
|
||||
if (string.IsNullOrEmpty(url)) throw new Exception("Не удалось получить ссылку на трек");
|
||||
return await Api.HttpClient.GetByteArrayAsync(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение метаданных для загрузки
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackKey">Ключ трека в формате {идентифактор трека:идентификатор альбома}</param>
|
||||
/// <param name="direct">Должен ли ответ содержать прямую ссылку на загрузку</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<List<YTrackDownloadInfo>>> GetMetadataForDownloadAsync(AuthStorage storage, string trackKey, bool direct = false)
|
||||
public Task<byte[]> ExtractDataAsync(YTrack track)
|
||||
=> ExtractDataAsync(track.GetKey().ToString());
|
||||
|
||||
public async Task<Stream> ExtractStreamAsync(string trackKey, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
|
||||
{
|
||||
return new YTrackDownloadInfoBuilder(api, storage)
|
||||
.Build((trackKey, direct))
|
||||
.GetResponseAsync();
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение метаданных для загрузки
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="track">Трек</param>
|
||||
/// <param name="direct">Должен ли ответ содержать прямую ссылку на загрузку</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<List<YTrackDownloadInfo>>> GetMetadataForDownloadAsync(AuthStorage storage, YTrack track, bool direct = false)
|
||||
{
|
||||
return GetMetadataForDownloadAsync(storage, track.GetKey().ToString(), direct);
|
||||
}
|
||||
public Task<Stream> ExtractStreamAsync(YTrack track, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
|
||||
=> ExtractStreamAsync(track.GetKey().ToString(), completionOption);
|
||||
|
||||
/// <summary>
|
||||
/// Получение информации для формирования ссылки для загрузки
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="metadataInfo">Метаданные для загрузки</param>
|
||||
/// <returns></returns>
|
||||
public Task<YStorageDownloadFile> GetDownloadFileInfoAsync(AuthStorage storage, YTrackDownloadInfo metadataInfo)
|
||||
{
|
||||
return new YStorageDownloadFileBuilder(api, storage)
|
||||
.Build(metadataInfo.DownloadInfoUrl)
|
||||
.GetResponseAsync();
|
||||
}
|
||||
public Task<string?> 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));
|
||||
|
||||
/// <summary>
|
||||
/// Получение ссылки для загрузки
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackKey">Ключ трека в формате {идентификатор трека:идентификатор альбома}</param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> GetFileLinkAsync(AuthStorage storage, string trackKey)
|
||||
{
|
||||
YResponse<List<YTrackDownloadInfo>> meta = await GetMetadataForDownloadAsync(storage, trackKey);
|
||||
YTrackDownloadInfo info = meta.Result
|
||||
.OrderByDescending(i => i.BitrateInKbps)
|
||||
.First(m => m.Codec == "mp3");
|
||||
YStorageDownloadFile storageDownload = await GetDownloadFileInfoAsync(storage, info);
|
||||
return BuildLinkForDownload(info, storageDownload);
|
||||
}
|
||||
public Task<YTrackSupplement?> GetSupplementAsync(string trackId)
|
||||
=> new YGetTrackSupplementBuilder(Api).ExecuteAsync(trackId);
|
||||
|
||||
/// <summary>
|
||||
/// Получение ссылки для загрузки
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="track">Трек</param>
|
||||
/// <returns></returns>
|
||||
public Task<string> GetFileLinkAsync(AuthStorage storage, YTrack track)
|
||||
{
|
||||
return GetFileLinkAsync(storage, track.GetKey().ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отправка текущего состояния прослушиваемого трека
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="track">Трек</param>
|
||||
/// <param name="from">Наименования клиента, с которого происходит прослушивание</param>
|
||||
/// <param name="fromCache">Проигрывается ли трек с кеша</param>
|
||||
/// <param name="playId">Уникальный идентификатор проигрывания</param>
|
||||
/// <param name="playlistId">Уникальный идентификатор плейлиста, если таковой прослушивается</param>
|
||||
/// <param name="totalPlayedSeconds">Сколько было всего воспроизведено трека в секундах</param>
|
||||
/// <param name="endPositionSeconds">Окончательное значение воспроизведенных секунд</param>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<string> SendPlayTrackInfoAsync(AuthStorage storage, YTrack track, string from, bool fromCache = false, string playId = "", string playlistId = "", double totalPlayedSeconds = 0, double endPositionSeconds = 0)
|
||||
{
|
||||
return new YSendTrackInfoBuilder(api, storage)
|
||||
.Build((track, from, fromCache, playId, playlistId, totalPlayedSeconds, endPositionSeconds))
|
||||
.GetResponseAsync();
|
||||
}
|
||||
|
||||
#region GetSupplement
|
||||
|
||||
/// <summary>
|
||||
/// Получение дополнительной информации для трека
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackId">Идентификатор трека</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<YTrackSupplement>> GetSupplementAsync(AuthStorage storage, string trackId)
|
||||
{
|
||||
return new YGetTrackSupplementBuilder(api, storage)
|
||||
.Build(trackId)
|
||||
.GetResponseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение дополнительной информации для трека
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="track">Трек</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<YTrackSupplement>> GetSupplementAsync(AuthStorage storage, YTrack track)
|
||||
{
|
||||
return new YGetTrackSupplementBuilder(api, storage)
|
||||
.Build(track.GetKey().ToString())
|
||||
.GetResponseAsync();
|
||||
}
|
||||
|
||||
#endregion GetSupplement
|
||||
|
||||
#region GetSimilar
|
||||
|
||||
/// <summary>
|
||||
/// Получение похожих треков
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackId">Идентификатор трека</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<YTrackSimilar>> GetSimilarAsync(AuthStorage storage, string trackId)
|
||||
{
|
||||
return new YGetTrackSimilarBuilder(api, storage)
|
||||
.Build(trackId)
|
||||
.GetResponseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение похожих треков
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="track">Трек</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<YTrackSimilar>> GetSimilarAsync(AuthStorage storage, YTrack track)
|
||||
{
|
||||
return new YGetTrackSimilarBuilder(api, storage)
|
||||
.Build(track.GetKey().ToString())
|
||||
.GetResponseAsync();
|
||||
}
|
||||
|
||||
#endregion GetSimilar
|
||||
|
||||
#region Получение данных трека
|
||||
|
||||
#region В файл
|
||||
|
||||
/// <summary>
|
||||
/// Выгрузка в файл
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackKey">Ключ трека в формате {идентификатор трека:идентификатор альбома}</param>
|
||||
/// <param name="filePath">Путь для файла</param>
|
||||
public async Task ExtractToFileAsync(AuthStorage storage, string trackKey, string filePath)
|
||||
{
|
||||
string url = await GetFileLinkAsync(storage, trackKey);
|
||||
await new DataDownloader(storage).ToFile(url, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выгрузка в файл
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="track">Трек</param>
|
||||
/// <param name="filePath">Путь для файла</param>
|
||||
public Task ExtractToFileAsync(AuthStorage storage, YTrack track, string filePath)
|
||||
{
|
||||
return ExtractToFileAsync(storage, track.GetKey().ToString(), filePath);
|
||||
}
|
||||
|
||||
#endregion В файл
|
||||
|
||||
#region В массив байт
|
||||
|
||||
/// <summary>
|
||||
/// Получение двоичного массива данных
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackKey">Ключ трека в формате {идентификатор трека:идентификатор альбома}</param>
|
||||
/// <returns></returns>
|
||||
public async Task<byte[]> ExtractDataAsync(AuthStorage storage, string trackKey)
|
||||
{
|
||||
string url = await GetFileLinkAsync(storage, trackKey);
|
||||
return await new DataDownloader(storage).AsBytes(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение двоичного массива данных
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="track">Трек</param>
|
||||
/// <returns></returns>
|
||||
public Task<byte[]> ExtractDataAsync(AuthStorage storage, YTrack track)
|
||||
{
|
||||
return ExtractDataAsync(storage, track.GetKey().ToString());
|
||||
}
|
||||
|
||||
#endregion В массив байт
|
||||
|
||||
#region В поток
|
||||
|
||||
/// <summary>
|
||||
/// Получение потока данных
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="trackKey">Ключ трека в формате {идентификатор трека:идентификатор альбома}</param>
|
||||
/// <param name="httpCompletionOption">Параметры передачи управления при http запросе</param>
|
||||
/// <returns></returns>
|
||||
public async Task<Stream> ExtractStreamAsync(AuthStorage storage, string trackKey,
|
||||
HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
|
||||
{
|
||||
string url = await GetFileLinkAsync(storage, trackKey);
|
||||
return await new DataDownloader(storage).AsStream(url, httpCompletionOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение потока данных
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="track">Трек</param>
|
||||
/// <param name="httpCompletionOption">Параметры передачи управления при http запросе</param>
|
||||
/// <returns></returns>
|
||||
public Task<Stream> ExtractStreamAsync(AuthStorage storage, YTrack track,
|
||||
HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
|
||||
{
|
||||
return ExtractStreamAsync(storage, track.GetKey().ToString(), httpCompletionOption);
|
||||
}
|
||||
|
||||
#endregion В поток
|
||||
|
||||
#endregion Получение данных трека
|
||||
public Task<YTrackSupplement?> GetSupplementAsync(YTrack track)
|
||||
=> GetSupplementAsync(track.GetKey().ToString());
|
||||
|
||||
public Task<YTrackSimilar?> GetSimilarAsync(string trackId)
|
||||
=> new YGetTrackSimilarBuilder(Api).ExecuteAsync(trackId);
|
||||
|
||||
public Task<YTrackSimilar?> GetSimilarAsync(YTrack track)
|
||||
=> GetSimilarAsync(track.GetKey().ToString());
|
||||
}
|
||||
Reference in New Issue
Block a user