Обнновлено до .net10
This commit is contained in:
@@ -5,34 +5,24 @@ using YandexMusic.API.Requests.Album;
|
|||||||
|
|
||||||
namespace YandexMusic.API;
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>API для работы с альбомами.</summary>
|
||||||
/// API для взаимодействия с альбомами
|
|
||||||
/// </summary>
|
|
||||||
public class YAlbumAPI : YCommonAPI
|
public class YAlbumAPI : YCommonAPI
|
||||||
{
|
{
|
||||||
public YAlbumAPI(YandexMusicApi yandex) : base(yandex)
|
/// <summary>Инициализирует новый экземпляр API альбомов.</summary>
|
||||||
{
|
/// <param name="yandex">Экземпляр основного API.</param>
|
||||||
}
|
public YAlbumAPI(YandexMusicApi yandex) : base(yandex) { }
|
||||||
|
|
||||||
/// <summary>Получает альбом по идентификатору.</summary>
|
/// <summary>Получает альбом по идентификатору.</summary>
|
||||||
/// <param name="storage">Хранилище авторизации.</param>
|
/// <param name="storage">Хранилище данных авторизации.</param>
|
||||||
/// <param name="albumId">Идентификатор альбома.</param>
|
/// <param name="albumId">Идентификатор альбома.</param>
|
||||||
/// <returns></returns>
|
/// <returns>Ответ API с моделью альбома.</returns>
|
||||||
public Task<YResponse<YAlbum>> GetAsync(AuthStorage storage, string albumId)
|
public Task<YResponse<YAlbum>> GetAsync(AuthStorage storage, string albumId)
|
||||||
{
|
=> new YGetAlbumBuilder(api, storage).Build(albumId).GetResponseAsync();
|
||||||
return new YGetAlbumBuilder(api, storage)
|
|
||||||
.Build(albumId)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Получение альбомов по списку идентификаторов.</summary>
|
/// <summary>Получает несколько альбомов по списку идентификаторов.</summary>
|
||||||
/// <param name="storage">Хранилище авторизации.</param>
|
/// <param name="storage">Хранилище данных авторизации.</param>
|
||||||
/// <param name="albumIds">Идентификаторы альбомов.</param>
|
/// <param name="albumIds">Список идентификаторов альбомов.</param>
|
||||||
/// <returns></returns>
|
/// <returns>Ответ API со списком альбомов.</returns>
|
||||||
public Task<YResponse<List<YAlbum>>> GetAsync(AuthStorage storage, IEnumerable<string> albumIds)
|
public Task<YResponse<List<YAlbum>>> GetAsync(AuthStorage storage, IEnumerable<string> albumIds)
|
||||||
{
|
=> new YGetAlbumsBuilder(api, storage).Build(albumIds).GetResponseAsync();
|
||||||
return new YGetAlbumsBuilder(api, storage)
|
|
||||||
.Build(albumIds)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
namespace YandexMusic.API;
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Родительский класс для всех веток API.</summary>
|
||||||
/// Родительский класс для ветки API
|
public abstract class YCommonAPI
|
||||||
/// </summary>
|
|
||||||
public class YCommonAPI
|
|
||||||
{
|
{
|
||||||
protected YandexMusicApi api;
|
/// <summary>Основной экземпляр API.</summary>
|
||||||
|
protected readonly YandexMusicApi api;
|
||||||
|
|
||||||
public YCommonAPI(YandexMusicApi yandex)
|
/// <summary>Инициализирует новый экземпляр.</summary>
|
||||||
{
|
/// <param name="yandex">Экземпляр основного API.</param>
|
||||||
api = yandex;
|
protected YCommonAPI(YandexMusicApi yandex) => api = yandex;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
39
YandexMusic.API/API/YLabelAPI.cs
Normal file
39
YandexMusic.API/API/YLabelAPI.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Label;
|
||||||
|
using YandexMusic.API.Requests.Label;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
public partial class YLabelAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
public YLabelAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Постраничное получение альбомов лейбла
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="label">Лейбл</param>
|
||||||
|
/// <param name="page">Страница</param>
|
||||||
|
public Task<YResponse<YLabelAlbums>> GetAlbumsByLabelAsync(AuthStorage storage, YLabel label, int page)
|
||||||
|
{
|
||||||
|
return new YGetLabelAlbumsBuilder(api, storage)
|
||||||
|
.Build((label, page))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Постраничное получение артистов лейбла
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="label">Лейбл</param>
|
||||||
|
/// <param name="page">Страница</param>
|
||||||
|
public Task<YResponse<YLabelArtists>> GetArtistsByLabelAsync(AuthStorage storage, YLabel label, int page)
|
||||||
|
{
|
||||||
|
return new YGetLabelArtistsBuilder(api, storage)
|
||||||
|
.Build((label, page))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Label;
|
|
||||||
using YandexMusic.API.Requests.Label;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
public partial class YLabelAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
public YLabelAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Постраничное получение альбомов лейбла
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="label">Лейбл</param>
|
|
||||||
/// <param name="page">Страница</param>
|
|
||||||
public Task<YResponse<YLabelAlbums>> GetAlbumsByLabelAsync(AuthStorage storage, YLabel label, int page)
|
|
||||||
{
|
|
||||||
return new YGetLabelAlbumsBuilder(api, storage)
|
|
||||||
.Build((label, page))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Постраничное получение артистов лейбла
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="label">Лейбл</param>
|
|
||||||
/// <param name="page">Страница</param>
|
|
||||||
public Task<YResponse<YLabelArtists>> GetArtistsByLabelAsync(AuthStorage storage, YLabel label, int page)
|
|
||||||
{
|
|
||||||
return new YGetLabelArtistsBuilder(api, storage)
|
|
||||||
.Build((label, page))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
43
YandexMusic.API/API/YLandingAPI.cs
Normal file
43
YandexMusic.API/API/YLandingAPI.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Feed;
|
||||||
|
using YandexMusic.API.Models.Landing;
|
||||||
|
using YandexMusic.API.Requests.Feed;
|
||||||
|
using YandexMusic.API.Requests.Landing;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>API для взаимодействия с главной страницей (лендингом).</summary>
|
||||||
|
public class YLandingAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
/// <summary>Инициализирует новый экземпляр API лендинга.</summary>
|
||||||
|
/// <param name="yandex">Экземпляр основного API.</param>
|
||||||
|
public YLandingAPI(YandexMusicApi yandex) : base(yandex) { }
|
||||||
|
|
||||||
|
/// <summary>Получает персональные блоки лендинга.</summary>
|
||||||
|
/// <param name="storage">Хранилище авторизации.</param>
|
||||||
|
/// <param name="blocks">Типы запрашиваемых блоков.</param>
|
||||||
|
/// <returns>Ответ API с лендингом.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Если массив blocks равен null.</exception>
|
||||||
|
public Task<YResponse<YLanding>> GetAsync(AuthStorage storage, params YLandingBlockType[] blocks)
|
||||||
|
{
|
||||||
|
if (blocks == null)
|
||||||
|
throw new ArgumentNullException(nameof(blocks), "Массив блоков не может быть null");
|
||||||
|
|
||||||
|
return new YGetLandingBuilder(api, storage)
|
||||||
|
.Build(blocks)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Получает ленту событий (фид).</summary>
|
||||||
|
/// <param name="storage">Хранилище авторизации.</param>
|
||||||
|
/// <returns>Ответ API с лентой.</returns>
|
||||||
|
public Task<YResponse<YFeed>> GetFeedAsync(AuthStorage storage)
|
||||||
|
=> new YGetFeedBuilder(api, storage).Build(null!).GetResponseAsync();
|
||||||
|
|
||||||
|
/// <summary>Получает лендинг детского раздела.</summary>
|
||||||
|
/// <param name="storage">Хранилище авторизации.</param>
|
||||||
|
/// <returns>Ответ API с детским лендингом.</returns>
|
||||||
|
public Task<YResponse<YChildrenLanding>> GetChildrenLandingAsync(AuthStorage storage)
|
||||||
|
=> new YGetChildrenLandingBuilder(api, storage).Build(null!).GetResponseAsync();
|
||||||
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Feed;
|
|
||||||
using YandexMusic.API.Models.Landing;
|
|
||||||
using YandexMusic.API.Requests.Feed;
|
|
||||||
using YandexMusic.API.Requests.Landing;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// API для взаимодействия с главной страницей
|
|
||||||
/// </summary>
|
|
||||||
public partial class YLandingAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
public YLandingAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение персональных списков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="blocks">Типы запрашиваемых блоков</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YLanding>> GetAsync(AuthStorage storage, params YLandingBlockType[] blocks)
|
|
||||||
{
|
|
||||||
if (blocks == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new YGetLandingBuilder(api, storage)
|
|
||||||
.Build(blocks)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение ленты
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YFeed>> GetFeedAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetFeedBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение лендинга детского раздела
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YChildrenLanding>> GetChildrenLandingAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetChildrenLandingBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
253
YandexMusic.API/API/YLibraryAPI.cs
Normal file
253
YandexMusic.API/API/YLibraryAPI.cs
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Album;
|
||||||
|
using YandexMusic.API.Models.Artist;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Landing.Entity.Entities.Context;
|
||||||
|
using YandexMusic.API.Models.Library;
|
||||||
|
using YandexMusic.API.Models.Playlist;
|
||||||
|
using YandexMusic.API.Models.Track;
|
||||||
|
using YandexMusic.API.Requests.Library;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API для взаимодействия с библиотекой
|
||||||
|
/// </summary>
|
||||||
|
public partial class YLibraryAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получение секции библиотеки
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Тип объекта библиотеки</typeparam>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="section">Секция</param>
|
||||||
|
/// <param name="type">Тип</param>
|
||||||
|
/// <returns>Список объектов из секции</returns>
|
||||||
|
private Task<YResponse<T>> GetLibrarySection<T>(AuthStorage storage, YLibrarySection section, YLibrarySectionType type = YLibrarySectionType.Likes)
|
||||||
|
{
|
||||||
|
return new YGetLibrarySectionBuilder<T>(api, storage)
|
||||||
|
.Build((section, type))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public YLibraryAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Лайки
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение лайкнутых треков
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YLibraryTracks>> GetLikedTracksAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return GetLibrarySection<YLibraryTracks>(storage, YLibrarySection.Tracks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение лайкнутых альбомов
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YLibraryAlbum>>> GetLikedAlbumsAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return GetLibrarySection<List<YLibraryAlbum>>(storage, YLibrarySection.Albums);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение лайкнутых исполнителей
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YArtist>>> GetLikedArtistsAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return GetLibrarySection<List<YArtist>>(storage, YLibrarySection.Artists);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение лайкнутых плейлистов
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YLibraryPlaylists>>> GetLikedPlaylistsAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return GetLibrarySection<List<YLibraryPlaylists>>(storage, YLibrarySection.Playlists);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Лайки
|
||||||
|
|
||||||
|
#region Дизлайки
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение дизлайкнутых треков
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YLibraryTracks>> GetDislikedTracksAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return GetLibrarySection<YLibraryTracks>(storage, YLibrarySection.Tracks, YLibrarySectionType.Dislikes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение дизлайкнутых исполнителей
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YArtist>>> GetDislikedArtistsAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return GetLibrarySection<List<YArtist>>(storage, YLibrarySection.Artists, YLibrarySectionType.Dislikes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Дизлайки
|
||||||
|
|
||||||
|
#region Добавление в списки лайков/дизлайков
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавить трек в список лайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="track">Трек</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YPlaylist>> AddTrackLikeAsync(AuthStorage storage, YTrack track)
|
||||||
|
{
|
||||||
|
return new YLibraryAddBuilder<YPlaylist>(api, storage)
|
||||||
|
.Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Likes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удалить трек из списка лайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="track">Трек</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YRevision>> RemoveTrackLikeAsync(AuthStorage storage, YTrack track)
|
||||||
|
{
|
||||||
|
return new YLibraryRemoveBuilder<YRevision>(api, storage)
|
||||||
|
.Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Likes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавить трек в список дизлайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="track">Трек</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YRevision>> AddTrackDislikeAsync(AuthStorage storage, YTrack track)
|
||||||
|
{
|
||||||
|
return new YLibraryAddBuilder<YRevision>(api, storage)
|
||||||
|
.Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Dislikes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удалить трек из списка дизлайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="track">Трек</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YRevision>> RemoveTrackDislikeAsync(AuthStorage storage, YTrack track)
|
||||||
|
{
|
||||||
|
return new YLibraryRemoveBuilder<YRevision>(api, storage)
|
||||||
|
.Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Dislikes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавить альбом в список лайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="album">Альбом</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<string>> AddAlbumLikeAsync(AuthStorage storage, YAlbum album)
|
||||||
|
{
|
||||||
|
return new YLibraryAddBuilder<string>(api, storage)
|
||||||
|
.Build((album.Id, YLibrarySection.Albums, YLibrarySectionType.Likes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удалить альбом из списка лайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="album">Альбом</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<string>> RemoveAlbumLikeAsync(AuthStorage storage, YAlbum album)
|
||||||
|
{
|
||||||
|
return new YLibraryRemoveBuilder<string>(api, storage)
|
||||||
|
.Build((album.Id, YLibrarySection.Albums, YLibrarySectionType.Likes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавить исполнителя в список лайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="artist">Исполнитель</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<string>> AddArtistLikeAsync(AuthStorage storage, YArtist artist)
|
||||||
|
{
|
||||||
|
return new YLibraryAddBuilder<string>(api, storage)
|
||||||
|
.Build((artist.Id, YLibrarySection.Artists, YLibrarySectionType.Likes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удалить исполнителя из списка лайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="artist">Исполнитель</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<string>> RemoveArtistLikeAsync(AuthStorage storage, YArtist artist)
|
||||||
|
{
|
||||||
|
return new YLibraryRemoveBuilder<string>(api, storage)
|
||||||
|
.Build((artist.Id, YLibrarySection.Artists, YLibrarySectionType.Likes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавить плейлист в список лайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="playlist">Плейлист</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<string>> AddPlaylistLikeAsync(AuthStorage storage, YPlaylist playlist)
|
||||||
|
{
|
||||||
|
return new YLibraryAddBuilder<string>(api, storage)
|
||||||
|
.Build((playlist.GetKey().ToString(), YLibrarySection.Playlists, YLibrarySectionType.Likes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удалить плейлист из списка лайкнутых
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="playlist">Плейлист</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<string>> RemovePlaylistLikeAsync(AuthStorage storage, YPlaylist playlist)
|
||||||
|
{
|
||||||
|
return new YLibraryRemoveBuilder<string>(api, storage)
|
||||||
|
.Build((playlist.GetKey().ToString(), YLibrarySection.Playlists, YLibrarySectionType.Likes))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Добавление/удаление в списки лайков/дизлайков
|
||||||
|
|
||||||
|
#region Получение списка "Вы недавно слушали"
|
||||||
|
|
||||||
|
public Task<YResponse<YRecentlyListenedContext>> GetRecentlyListenedAsync(AuthStorage storage, IEnumerable<YPlayContextType> contextTypes, int trackCount, int contextCount)
|
||||||
|
{
|
||||||
|
return new YGetLibraryRecentlyListenedBuilder(api, storage)
|
||||||
|
.Build((contextTypes, trackCount, contextCount))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Получение списка "Вы недавно слушали"
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Album;
|
|
||||||
using YandexMusic.API.Models.Artist;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Landing.Entity.Entities.Context;
|
|
||||||
using YandexMusic.API.Models.Library;
|
|
||||||
using YandexMusic.API.Models.Playlist;
|
|
||||||
using YandexMusic.API.Models.Track;
|
|
||||||
using YandexMusic.API.Requests.Library;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// API для взаимодействия с библиотекой
|
|
||||||
/// </summary>
|
|
||||||
public partial class YLibraryAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
#region Вспомогательные функции
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение секции библиотеки
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Тип объекта библиотеки</typeparam>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="section">Секция</param>
|
|
||||||
/// <param name="type">Тип</param>
|
|
||||||
/// <returns>Список объектов из секции</returns>
|
|
||||||
private Task<YResponse<T>> GetLibrarySection<T>(AuthStorage storage, YLibrarySection section, YLibrarySectionType type = YLibrarySectionType.Likes)
|
|
||||||
{
|
|
||||||
return new YGetLibrarySectionBuilder<T>(api, storage)
|
|
||||||
.Build((section, type))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Вспомогательные функции
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public YLibraryAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Лайки
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение лайкнутых треков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YLibraryTracks>> GetLikedTracksAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetLibrarySection<YLibraryTracks>(storage, YLibrarySection.Tracks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение лайкнутых альбомов
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YLibraryAlbum>>> GetLikedAlbumsAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetLibrarySection<List<YLibraryAlbum>>(storage, YLibrarySection.Albums);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение лайкнутых исполнителей
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YArtist>>> GetLikedArtistsAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetLibrarySection<List<YArtist>>(storage, YLibrarySection.Artists);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение лайкнутых плейлистов
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YLibraryPlaylists>>> GetLikedPlaylistsAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetLibrarySection<List<YLibraryPlaylists>>(storage, YLibrarySection.Playlists);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Лайки
|
|
||||||
|
|
||||||
#region Дизлайки
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение дизлайкнутых треков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YLibraryTracks>> GetDislikedTracksAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetLibrarySection<YLibraryTracks>(storage, YLibrarySection.Tracks, YLibrarySectionType.Dislikes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение дизлайкнутых исполнителей
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YArtist>>> GetDislikedArtistsAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetLibrarySection<List<YArtist>>(storage, YLibrarySection.Artists, YLibrarySectionType.Dislikes);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Дизлайки
|
|
||||||
|
|
||||||
#region Добавление в списки лайков/дизлайков
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Добавить трек в список лайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="track">Трек</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> AddTrackLikeAsync(AuthStorage storage, YTrack track)
|
|
||||||
{
|
|
||||||
return new YLibraryAddBuilder<YPlaylist>(api, storage)
|
|
||||||
.Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Likes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удалить трек из списка лайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="track">Трек</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YRevision>> RemoveTrackLikeAsync(AuthStorage storage, YTrack track)
|
|
||||||
{
|
|
||||||
return new YLibraryRemoveBuilder<YRevision>(api, storage)
|
|
||||||
.Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Likes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Добавить трек в список дизлайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="track">Трек</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YRevision>> AddTrackDislikeAsync(AuthStorage storage, YTrack track)
|
|
||||||
{
|
|
||||||
return new YLibraryAddBuilder<YRevision>(api, storage)
|
|
||||||
.Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Dislikes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удалить трек из списка дизлайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="track">Трек</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YRevision>> RemoveTrackDislikeAsync(AuthStorage storage, YTrack track)
|
|
||||||
{
|
|
||||||
return new YLibraryRemoveBuilder<YRevision>(api, storage)
|
|
||||||
.Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Dislikes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Добавить альбом в список лайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="album">Альбом</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<string>> AddAlbumLikeAsync(AuthStorage storage, YAlbum album)
|
|
||||||
{
|
|
||||||
return new YLibraryAddBuilder<string>(api, storage)
|
|
||||||
.Build((album.Id, YLibrarySection.Albums, YLibrarySectionType.Likes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удалить альбом из списка лайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="album">Альбом</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<string>> RemoveAlbumLikeAsync(AuthStorage storage, YAlbum album)
|
|
||||||
{
|
|
||||||
return new YLibraryRemoveBuilder<string>(api, storage)
|
|
||||||
.Build((album.Id, YLibrarySection.Albums, YLibrarySectionType.Likes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Добавить исполнителя в список лайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="artist">Исполнитель</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<string>> AddArtistLikeAsync(AuthStorage storage, YArtist artist)
|
|
||||||
{
|
|
||||||
return new YLibraryAddBuilder<string>(api, storage)
|
|
||||||
.Build((artist.Id, YLibrarySection.Artists, YLibrarySectionType.Likes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удалить исполнителя из списка лайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="artist">Исполнитель</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<string>> RemoveArtistLikeAsync(AuthStorage storage, YArtist artist)
|
|
||||||
{
|
|
||||||
return new YLibraryRemoveBuilder<string>(api, storage)
|
|
||||||
.Build((artist.Id, YLibrarySection.Artists, YLibrarySectionType.Likes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Добавить плейлист в список лайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Плейлист</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<string>> AddPlaylistLikeAsync(AuthStorage storage, YPlaylist playlist)
|
|
||||||
{
|
|
||||||
return new YLibraryAddBuilder<string>(api, storage)
|
|
||||||
.Build((playlist.GetKey().ToString(), YLibrarySection.Playlists, YLibrarySectionType.Likes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удалить плейлист из списка лайкнутых
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Плейлист</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<string>> RemovePlaylistLikeAsync(AuthStorage storage, YPlaylist playlist)
|
|
||||||
{
|
|
||||||
return new YLibraryRemoveBuilder<string>(api, storage)
|
|
||||||
.Build((playlist.GetKey().ToString(), YLibrarySection.Playlists, YLibrarySectionType.Likes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Добавление/удаление в списки лайков/дизлайков
|
|
||||||
|
|
||||||
#region Получение списка "Вы недавно слушали"
|
|
||||||
|
|
||||||
public Task<YResponse<YRecentlyListenedContext>> GetRecentlyListenedAsync(AuthStorage storage, IEnumerable<YPlayContextType> contextTypes, int trackCount, int contextCount)
|
|
||||||
{
|
|
||||||
return new YGetLibraryRecentlyListenedBuilder(api, storage)
|
|
||||||
.Build((contextTypes, trackCount, contextCount))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Получение списка "Вы недавно слушали"
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
YandexMusic.API/API/YPinsAPI.cs
Normal file
20
YandexMusic.API/API/YPinsAPI.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Pins;
|
||||||
|
using YandexMusic.API.Requests.Pins;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>API для взаимодействия с закреплёнными объектами (пинами).</summary>
|
||||||
|
public class YPinsAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
/// <summary>Инициализирует новый экземпляр API пинов.</summary>
|
||||||
|
/// <param name="yandex">Экземпляр основного API.</param>
|
||||||
|
public YPinsAPI(YandexMusicApi yandex) : base(yandex) { }
|
||||||
|
|
||||||
|
/// <summary>Получает список закреплённых объектов.</summary>
|
||||||
|
/// <param name="storage">Хранилище авторизации.</param>
|
||||||
|
/// <returns>Ответ API со списком пинов.</returns>
|
||||||
|
public Task<YResponse<YPins>> GetAsync(AuthStorage storage)
|
||||||
|
=> new YGetPinsBuilder(api, storage).Build(null!).GetResponseAsync();
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Pins;
|
|
||||||
using YandexMusic.API.Requests.Pins;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// API для взаимодействия с прикреплёнными объектами
|
|
||||||
/// </summary>
|
|
||||||
public partial class YPinsAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
public YPinsAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение списка прикреплённых объектов
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPins>> GetAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetPinsBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
149
YandexMusic.API/API/YPlaylistAPI.cs
Normal file
149
YandexMusic.API/API/YPlaylistAPI.cs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Landing;
|
||||||
|
using YandexMusic.API.Models.Landing.Entity.Entities;
|
||||||
|
using YandexMusic.API.Models.Playlist;
|
||||||
|
using YandexMusic.API.Models.Track;
|
||||||
|
using YandexMusic.API.Requests.Playlist;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>API для взаимодействия с плейлистами.</summary>
|
||||||
|
public class YPlaylistAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
/// <summary>Инициализирует новый экземпляр API плейлистов.</summary>
|
||||||
|
/// <param name="yandex">Экземпляр основного API.</param>
|
||||||
|
public YPlaylistAPI(YandexMusicApi yandex) : base(yandex) { }
|
||||||
|
|
||||||
|
/// <summary>Получает список персональных плейлистов с главной страницы.</summary>
|
||||||
|
/// <param name="storage">Хранилище авторизации.</param>
|
||||||
|
/// <returns>Список ответов с плейлистами.</returns>
|
||||||
|
public async Task<List<YResponse<YPlaylist>>> GetPersonalPlaylistsAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
var landing = await api.Landing.GetAsync(storage, YLandingBlockType.PersonalPlaylists);
|
||||||
|
var block = landing.Result?.Blocks?.FirstOrDefault(b => b.Type == YLandingBlockType.PersonalPlaylists);
|
||||||
|
if (block?.Entities == null)
|
||||||
|
return new List<YResponse<YPlaylist>>();
|
||||||
|
|
||||||
|
var tasks = block.Entities
|
||||||
|
.OfType<YLandingEntityPersonalPlaylist>()
|
||||||
|
.Select(e => api.Playlist.GetAsync(storage, e.Data?.Data));
|
||||||
|
|
||||||
|
return new List<YResponse<YPlaylist>>(await Task.WhenAll(tasks));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Получает избранные плейлисты.</summary>
|
||||||
|
public Task<YResponse<List<YPlaylist>>> FavoritesAsync(AuthStorage storage)
|
||||||
|
=> new YGetPlaylistFavoritesBuilder(api, storage).Build(null!).GetResponseAsync();
|
||||||
|
|
||||||
|
/// <summary>Получает плейлист дня.</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> OfTheDayAsync(AuthStorage storage)
|
||||||
|
=> GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.PlaylistOfTheDay);
|
||||||
|
|
||||||
|
/// <summary>Получает плейлист «Дежавю».</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> DejaVuAsync(AuthStorage storage)
|
||||||
|
=> GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.NeverHeard);
|
||||||
|
|
||||||
|
/// <summary>Получает плейлист «Премьера».</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> PremiereAsync(AuthStorage storage)
|
||||||
|
=> GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.RecentTracks);
|
||||||
|
|
||||||
|
/// <summary>Получает плейлист «Тайник».</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> MissedAsync(AuthStorage storage)
|
||||||
|
=> GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.MissedLikes);
|
||||||
|
|
||||||
|
/// <summary>Получает плейлист «Кинопоиск».</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> KinopoiskAsync(AuthStorage storage)
|
||||||
|
=> GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.Kinopoisk);
|
||||||
|
|
||||||
|
private async Task<YResponse<YPlaylist>> GetPersonalPlaylistAsync(AuthStorage storage, YGeneratedPlaylistType type)
|
||||||
|
{
|
||||||
|
var list = await GetPersonalPlaylistsAsync(storage);
|
||||||
|
return list.FirstOrDefault(e => string.Equals(e.Result?.GeneratedPlaylistType, type.ToString(), StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
?? throw new Exception($"Плейлист типа {type} не найден.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Получает плейлист по идентификатору пользователя и типа.</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> GetAsync(AuthStorage storage, string user, string kind)
|
||||||
|
=> new YGetPlaylistBuilder(api, storage).Build((user, kind)).GetResponseAsync();
|
||||||
|
|
||||||
|
/// <summary>Получает плейлист по UUID.</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> GetAsync(AuthStorage storage, string uuid)
|
||||||
|
=> new YGetPlaylistByUuidBuilder(api, storage).Build(uuid).GetResponseAsync();
|
||||||
|
|
||||||
|
/// <summary>Получает несколько плейлистов по списку пар (пользователь, тип).</summary>
|
||||||
|
public Task<YResponse<List<YPlaylist>>> GetAsync(AuthStorage storage, IEnumerable<(string user, string kind)> ids)
|
||||||
|
=> new YGetPlaylistsBuilder(api, storage).Build(ids).GetResponseAsync();
|
||||||
|
|
||||||
|
/// <summary>Получает плейлист по объекту плейлиста (обновляет его треки).</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> GetAsync(AuthStorage storage, YPlaylist playlist)
|
||||||
|
=> new YGetPlaylistBuilder(api, storage).Build((playlist.Owner.Uid, playlist.Kind)).GetResponseAsync();
|
||||||
|
|
||||||
|
/// <summary>Создаёт новый плейлист с заданным именем.</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> CreateAsync(AuthStorage storage, string name)
|
||||||
|
=> new YPlaylistCreateBuilder(api, storage).Build(name).GetResponseAsync();
|
||||||
|
|
||||||
|
/// <summary>Переименовывает плейлист.</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> RenameAsync(AuthStorage storage, string kind, string name)
|
||||||
|
=> new YPlaylistRenameBuilder(api, storage).Build((kind, name)).GetResponseAsync();
|
||||||
|
|
||||||
|
/// <summary>Переименовывает плейлист.</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> RenameAsync(AuthStorage storage, YPlaylist playlist, string name)
|
||||||
|
=> RenameAsync(storage, playlist.Kind, name);
|
||||||
|
|
||||||
|
/// <summary>Удаляет плейлист.</summary>
|
||||||
|
public async Task<bool> DeleteAsync(AuthStorage storage, string kind)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await new YPlaylistRemoveBuilder(api, storage).Build(kind).GetResponseAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Логирование ошибки можно добавить через ILogger
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Удаляет плейлист.</summary>
|
||||||
|
public Task<bool> DeleteAsync(AuthStorage storage, YPlaylist playlist)
|
||||||
|
=> DeleteAsync(storage, playlist.Kind);
|
||||||
|
|
||||||
|
/// <summary>Добавляет треки в начало плейлиста.</summary>
|
||||||
|
public async Task<YResponse<YPlaylist>> InsertTracksAsync(AuthStorage storage, YPlaylist playlist, IEnumerable<YTrack> tracks)
|
||||||
|
{
|
||||||
|
var change = await ChangePlaylistAsync(storage, playlist, new List<YPlaylistChange>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Operation = YPlaylistChangeType.Insert,
|
||||||
|
At = 0,
|
||||||
|
Tracks = tracks.Select(t => t.GetKey())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return await GetAsync(storage, change.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Удаляет треки из плейлиста.</summary>
|
||||||
|
public Task<YResponse<YPlaylist>> DeleteTracksAsync(AuthStorage storage, YPlaylist playlist, IEnumerable<YTrack> tracks)
|
||||||
|
{
|
||||||
|
var distinctTracks = tracks.Distinct().ToList();
|
||||||
|
var changes = distinctTracks
|
||||||
|
.Select(t => playlist.Tracks?.FindIndex(ct => ct.Track?.GetKey() == t.GetKey()) ?? -1)
|
||||||
|
.Where(i => i != -1)
|
||||||
|
.Select(i => new YPlaylistChange
|
||||||
|
{
|
||||||
|
Operation = YPlaylistChangeType.Delete,
|
||||||
|
From = i,
|
||||||
|
To = i + 1,
|
||||||
|
Tracks = new List<YTrackAlbumPair> { playlist.Tracks![i].Track!.GetKey() }
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return ChangePlaylistAsync(storage, playlist, changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<YResponse<YPlaylist>> ChangePlaylistAsync(AuthStorage storage, YPlaylist playlist, IEnumerable<YPlaylistChange> changes)
|
||||||
|
=> new YPlaylistChangeBuilder(api, storage).Build((playlist, changes)).GetResponseAsync();
|
||||||
|
}
|
||||||
@@ -1,335 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Landing;
|
|
||||||
using YandexMusic.API.Models.Landing.Entity.Entities;
|
|
||||||
using YandexMusic.API.Models.Playlist;
|
|
||||||
using YandexMusic.API.Models.Track;
|
|
||||||
using YandexMusic.API.Requests.Playlist;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// API для взамодействия с плейлистами
|
|
||||||
/// </summary>
|
|
||||||
public partial class YPlaylistAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
#region Вспомогательные функции
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение персональных плейлистов
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="type">Тип</param>
|
|
||||||
/// <returns>Плейлист</returns>
|
|
||||||
private async Task<YResponse<YPlaylist>> GetPersonalPlaylist(AuthStorage storage, YGeneratedPlaylistType type)
|
|
||||||
{
|
|
||||||
List<YResponse<YPlaylist>> list = await GetPersonalPlaylistsAsync(storage);
|
|
||||||
return list.FirstOrDefault(e => string.Equals(e.Result.GeneratedPlaylistType, type.ToString(), StringComparison.CurrentCultureIgnoreCase));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Изменение плейлиста
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Плейлист</param>
|
|
||||||
/// <param name="changes">Список изменений</param>
|
|
||||||
/// <returns>Плейлист после изменений</returns>
|
|
||||||
private Task<YResponse<YPlaylist>> ChangePlaylist(AuthStorage storage, YPlaylist playlist, IEnumerable<YPlaylistChange> changes)
|
|
||||||
{
|
|
||||||
return new YPlaylistChangeBuilder(api, storage)
|
|
||||||
.Build((playlist, changes))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<YTrack> RemoveIdentical(IEnumerable<YTrack> tracks)
|
|
||||||
{
|
|
||||||
return tracks.Distinct();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Вспомогательные функции
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public YPlaylistAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Список с главной
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение списка персональных плейлистов
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<List<YResponse<YPlaylist>>> GetPersonalPlaylistsAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
YResponse<YLanding> landing = await api.Landing.GetAsync(storage, YLandingBlockType.PersonalPlaylists);
|
|
||||||
|
|
||||||
IEnumerable<Task<YResponse<YPlaylist>>> tasks = landing
|
|
||||||
.Result
|
|
||||||
.Blocks
|
|
||||||
.FirstOrDefault(b => b.Type == YLandingBlockType.PersonalPlaylists)
|
|
||||||
?.Entities
|
|
||||||
.Select(e => api.Playlist.GetAsync(storage, ((YLandingEntityPersonalPlaylist)e).Data?.Data));
|
|
||||||
|
|
||||||
return tasks == null
|
|
||||||
? new List<YResponse<YPlaylist>>()
|
|
||||||
: new List<YResponse<YPlaylist>>(await Task.WhenAll(tasks));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Список с главной
|
|
||||||
|
|
||||||
#region Стандартные плейлисты
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Избранное
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YPlaylist>>> FavoritesAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetPlaylistFavoritesBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Плейлист дня
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> OfTheDayAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetPersonalPlaylist(storage, YGeneratedPlaylistType.PlaylistOfTheDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Дежавю
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> DejaVuAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetPersonalPlaylist(storage, YGeneratedPlaylistType.NeverHeard);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Премьера
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> PremiereAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetPersonalPlaylist(storage, YGeneratedPlaylistType.RecentTracks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Тайник
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> MissedAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetPersonalPlaylist(storage, YGeneratedPlaylistType.MissedLikes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Кинопоиск
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> KinopoiskAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return GetPersonalPlaylist(storage, YGeneratedPlaylistType.Kinopoisk);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Стандартные плейлисты
|
|
||||||
|
|
||||||
#region Получение плейлиста
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение плейлиста
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="user">Uid пользователя-владельца плейлиста</param>
|
|
||||||
/// <param name="kind">Тип</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> GetAsync(AuthStorage storage, string user, string kind)
|
|
||||||
{
|
|
||||||
return new YGetPlaylistBuilder(api, storage)
|
|
||||||
.Build((user, kind))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение плейлиста по uuid
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="uuid">uuid</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> GetAsync(AuthStorage storage, string uuid)
|
|
||||||
{
|
|
||||||
return new YGetPlaylistByUuidBuilder(api, storage)
|
|
||||||
.Build(uuid)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение плейлистов
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="ids">Список пар пользователь:тип</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YPlaylist>>> GetAsync(AuthStorage storage, IEnumerable<(string user, string kind)> ids)
|
|
||||||
{
|
|
||||||
return new YGetPlaylistsBuilder(api, storage)
|
|
||||||
.Build(ids)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение плейлиста
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Описание плейлиста, для которого будут запрошены треки</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> GetAsync(AuthStorage storage, YPlaylist playlist)
|
|
||||||
{
|
|
||||||
return new YGetPlaylistBuilder(api, storage)
|
|
||||||
.Build((playlist.Owner.Uid, playlist.Kind))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Получение плейлиста
|
|
||||||
|
|
||||||
#region Операции над плейлистами
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Создание
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="name">Заголовок</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> CreateAsync(AuthStorage storage, string name)
|
|
||||||
{
|
|
||||||
return new YPlaylistCreateBuilder(api, storage)
|
|
||||||
.Build(name)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Переименование
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="kinds">Идентификатор плейлиста</param>
|
|
||||||
/// <param name="name">Заголовок</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> RenameAsync(AuthStorage storage, string kinds, string name)
|
|
||||||
{
|
|
||||||
return new YPlaylistRenameBuilder(api, storage)
|
|
||||||
.Build((kinds, name))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Переименование
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Плейлист</param>
|
|
||||||
/// <param name="name">Заголовок</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> RenameAsync(AuthStorage storage, YPlaylist playlist, string name)
|
|
||||||
{
|
|
||||||
return RenameAsync(storage, playlist.Kind, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удаление
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="kinds">Тип</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<bool> DeleteAsync(AuthStorage storage, string kinds)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await new YPlaylistRemoveBuilder(api, storage)
|
|
||||||
.Build(kinds)
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удаление
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Плейлист</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<bool> DeleteAsync(AuthStorage storage, YPlaylist playlist)
|
|
||||||
{
|
|
||||||
return DeleteAsync(storage, playlist.Kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Добавление трека
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Плейлист</param>
|
|
||||||
/// <param name="tracks">Треки для добавления</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<YResponse<YPlaylist>> InsertTracksAsync(AuthStorage storage, YPlaylist playlist, IEnumerable<YTrack> tracks)
|
|
||||||
{
|
|
||||||
YResponse<YPlaylist> change = await ChangePlaylist(storage, playlist, new List<YPlaylistChange> {
|
|
||||||
new() {
|
|
||||||
Operation = YPlaylistChangeType.Insert,
|
|
||||||
At = 0,
|
|
||||||
Tracks = tracks.Select(t => t.GetKey())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return await GetAsync(storage, change.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Удаление треков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Плейлист</param>
|
|
||||||
/// <param name="tracks">Треки для удаления</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YPlaylist>> DeleteTracksAsync(AuthStorage storage, YPlaylist playlist, IEnumerable<YTrack> tracks)
|
|
||||||
{
|
|
||||||
List<YPlaylistChange> changes = RemoveIdentical(tracks)
|
|
||||||
.Select(t => playlist.Tracks.Select(c => c.Track).ToList().IndexOf(t))
|
|
||||||
.Where(i => i != -1)
|
|
||||||
.Select(i =>
|
|
||||||
{
|
|
||||||
YTrackContainer t = playlist.Tracks[i];
|
|
||||||
return new YPlaylistChange
|
|
||||||
{
|
|
||||||
Operation = YPlaylistChangeType.Delete,
|
|
||||||
From = i,
|
|
||||||
To = i + 1,
|
|
||||||
Tracks = new List<YTrackAlbumPair> {
|
|
||||||
t.Track.GetKey()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return ChangePlaylist(storage, playlist, changes);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Операции над плейлистами
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
72
YandexMusic.API/API/YQueueAPI.cs
Normal file
72
YandexMusic.API/API/YQueueAPI.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Queue;
|
||||||
|
using YandexMusic.API.Requests.Queue;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API для взаимодействия с очередями
|
||||||
|
/// </summary>
|
||||||
|
public partial class YQueueAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
public YQueueAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение всех очередей треков с разных устройств для синхронизации между ними
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="device">Устройство</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YQueueItemsContainer>> ListAsync(AuthStorage storage, string? device = null)
|
||||||
|
{
|
||||||
|
return new YQueuesListBuilder(api, storage)
|
||||||
|
.Build(device)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение очереди
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="queueId">Идентификатор очереди</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YQueue>> GetAsync(AuthStorage storage, string queueId)
|
||||||
|
{
|
||||||
|
return new YGetQueueBuilder(api, storage)
|
||||||
|
.Build(queueId)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создание новой очереди треков
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="queue">Очередь треков</param>
|
||||||
|
/// <param name="device">Устройство</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YNewQueue>> CreateAsync(AuthStorage storage, YQueue queue, string? device = null)
|
||||||
|
{
|
||||||
|
return new YQueueCreateBuilder(api, storage, device)
|
||||||
|
.Build(queue)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Установка текущего индекса проигрываемого трека в очереди треков
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="queueId">Идентификатор очереди</param>
|
||||||
|
/// <param name="currentIndex">Текущий индекс</param>
|
||||||
|
/// <param name="isInteractive">Флаг интерактивности</param>
|
||||||
|
/// <param name="device">Устройство</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YUpdatedQueue>> UpdatePositionAsync(AuthStorage storage, string queueId, int currentIndex, bool isInteractive, string device = null)
|
||||||
|
{
|
||||||
|
return new YQueueUpdatePositionBuilder(api, storage, device)
|
||||||
|
.Build((queueId, currentIndex, isInteractive))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Queue;
|
|
||||||
using YandexMusic.API.Requests.Queue;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// API для взаимодействия с очередями
|
|
||||||
/// </summary>
|
|
||||||
public partial class YQueueAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
public YQueueAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение всех очередей треков с разных устройств для синхронизации между ними
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="device">Устройство</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YQueueItemsContainer>> ListAsync(AuthStorage storage, string device = null)
|
|
||||||
{
|
|
||||||
return new YQueuesListBuilder(api, storage)
|
|
||||||
.Build(device)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение очереди
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="queueId">Идентификатор очереди</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YQueue>> GetAsync(AuthStorage storage, string queueId)
|
|
||||||
{
|
|
||||||
return new YGetQueueBuilder(api, storage)
|
|
||||||
.Build(queueId)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Создание новой очереди треков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="queue">Очередь треков</param>
|
|
||||||
/// <param name="device">Устройство</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YNewQueue>> CreateAsync(AuthStorage storage, YQueue queue, string device = null)
|
|
||||||
{
|
|
||||||
return new YQueueCreateBuilder(api, storage, device)
|
|
||||||
.Build(queue)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Установка текущего индекса проигрываемого трека в очереди треков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="queueId">Идентификатор очереди</param>
|
|
||||||
/// <param name="currentIndex">Текущий индекс</param>
|
|
||||||
/// <param name="isInteractive">Флаг интерактивности</param>
|
|
||||||
/// <param name="device">Устройство</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YUpdatedQueue>> UpdatePositionAsync(AuthStorage storage, string queueId, int currentIndex, bool isInteractive, string device = null)
|
|
||||||
{
|
|
||||||
return new YQueueUpdatePositionBuilder(api, storage, device)
|
|
||||||
.Build((queueId, currentIndex, isInteractive))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
113
YandexMusic.API/API/YRadioAPI.cs
Normal file
113
YandexMusic.API/API/YRadioAPI.cs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Radio;
|
||||||
|
using YandexMusic.API.Models.Track;
|
||||||
|
using YandexMusic.API.Requests.Radio;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API для взаимодействия с радио
|
||||||
|
/// </summary>
|
||||||
|
public partial class YRadioAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
public YRadioAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение списка рекомендованных радиостанций
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YStationsDashboard>> GetStationsDashboardAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return new YGetStationsDashboardBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение списка радиостанций
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YStation>>> GetStationsAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return new YGetStationsBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение информации о радиостанции
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="type">Тип</param>
|
||||||
|
/// <param name="tag">Тэг</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YStation>>> GetStationAsync(AuthStorage storage, string type, string tag)
|
||||||
|
{
|
||||||
|
return new YGetStationBuilder(api, storage)
|
||||||
|
.Build((type, tag))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение информации о радиостанции
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="id">Идентификатор станции</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YStation>>> GetStationAsync(AuthStorage storage, YStationId id)
|
||||||
|
{
|
||||||
|
return GetStationAsync(storage, id.Type, id.Tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение последовательности треков радиостанции
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="station">Радиостанция</param>
|
||||||
|
/// <param name="prevTrackId">Идентификатор предыдущего трека</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YStationSequence>> GetStationTracksAsync(AuthStorage storage, YStation station, string prevTrackId = "")
|
||||||
|
{
|
||||||
|
return new YGetStationTracksBuilder(api, storage)
|
||||||
|
.Build((station.Station, prevTrackId))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Установка настроек подбора треков
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="station">Радиостанция</param>
|
||||||
|
/// <param name="settings">Настройки</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<string>> SetStationSettings2Async(AuthStorage storage, YStation station, YStationSettings2 settings)
|
||||||
|
{
|
||||||
|
return new YSetSettings2Builder(api, storage)
|
||||||
|
.Build((station.Station, settings))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отправка обратной связи на действия при прослушивании радио
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="station">Радиостанция</param>
|
||||||
|
/// <param name="type">Тип обратной связи</param>
|
||||||
|
/// <param name="track">Трек</param>
|
||||||
|
/// <param name="batchId">Уникальный идентификатор партии треков. Возвращается при получении треков</param>
|
||||||
|
/// <param name="totalPlayedSeconds">Сколько было проиграно секунд трека перед действием</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<string> SendStationFeedBackAsync(AuthStorage storage, YStation station, YStationFeedbackType type, YTrack track = null, string batchId = "", double totalPlayedSeconds = 0)
|
||||||
|
{
|
||||||
|
return new YSetStationFeedbackBuilder(api, storage)
|
||||||
|
.Build((type, station, track, batchId, totalPlayedSeconds))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Radio;
|
|
||||||
using YandexMusic.API.Models.Track;
|
|
||||||
using YandexMusic.API.Requests.Radio;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// API для взаимодействия с радио
|
|
||||||
/// </summary>
|
|
||||||
public partial class YRadioAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
public YRadioAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение списка рекомендованных радиостанций
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YStationsDashboard>> GetStationsDashboardAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetStationsDashboardBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение списка радиостанций
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YStation>>> GetStationsAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetStationsBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение информации о радиостанции
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="type">Тип</param>
|
|
||||||
/// <param name="tag">Тэг</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YStation>>> GetStationAsync(AuthStorage storage, string type, string tag)
|
|
||||||
{
|
|
||||||
return new YGetStationBuilder(api, storage)
|
|
||||||
.Build((type, tag))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение информации о радиостанции
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="id">Идентификатор станции</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YStation>>> GetStationAsync(AuthStorage storage, YStationId id)
|
|
||||||
{
|
|
||||||
return GetStationAsync(storage, id.Type, id.Tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение последовательности треков радиостанции
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="station">Радиостанция</param>
|
|
||||||
/// <param name="prevTrackId">Идентификатор предыдущего трека</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YStationSequence>> GetStationTracksAsync(AuthStorage storage, YStation station, string prevTrackId = "")
|
|
||||||
{
|
|
||||||
return new YGetStationTracksBuilder(api, storage)
|
|
||||||
.Build((station.Station, prevTrackId))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Установка настроек подбора треков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="station">Радиостанция</param>
|
|
||||||
/// <param name="settings">Настройки</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<string>> SetStationSettings2Async(AuthStorage storage, YStation station, YStationSettings2 settings)
|
|
||||||
{
|
|
||||||
return new YSetSettings2Builder(api, storage)
|
|
||||||
.Build((station.Station, settings))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Отправка обратной связи на действия при прослушивании радио
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="station">Радиостанция</param>
|
|
||||||
/// <param name="type">Тип обратной связи</param>
|
|
||||||
/// <param name="track">Трек</param>
|
|
||||||
/// <param name="batchId">Уникальный идентификатор партии треков. Возвращается при получении треков</param>
|
|
||||||
/// <param name="totalPlayedSeconds">Сколько было проиграно секунд трека перед действием</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<string> SendStationFeedBackAsync(AuthStorage storage, YStation station, YStationFeedbackType type, YTrack track = null, string batchId = "", double totalPlayedSeconds = 0)
|
|
||||||
{
|
|
||||||
return new YSetStationFeedbackBuilder(api, storage)
|
|
||||||
.Build((type, station, track, batchId, totalPlayedSeconds))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
139
YandexMusic.API/API/YSearchAPI.cs
Normal file
139
YandexMusic.API/API/YSearchAPI.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Search;
|
||||||
|
using YandexMusic.API.Requests.Search;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API для поиска
|
||||||
|
/// </summary>
|
||||||
|
public partial class YSearchAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
|
||||||
|
public YSearchAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск по трекам
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="trackName">Имя трека</param>
|
||||||
|
/// <param name="pageNumber">Номер страницы</param>
|
||||||
|
/// <param name="pageSize">Размер страницы</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearch>> TrackAsync(AuthStorage storage, string trackName, int pageNumber = 0, int pageSize = 20)
|
||||||
|
{
|
||||||
|
return SearchAsync(storage, trackName, YSearchType.Track, pageNumber, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск по альбомам
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="albumName">Имя альбома</param>
|
||||||
|
/// <param name="pageNumber">Номер страницы</param>
|
||||||
|
/// <param name="pageSize">Размер страницы</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearch>> AlbumsAsync(AuthStorage storage, string albumName, int pageNumber = 0, int pageSize = 20)
|
||||||
|
{
|
||||||
|
return SearchAsync(storage, albumName, YSearchType.Album, pageNumber, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск по артисту
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="artistName">Имя артиста</param>
|
||||||
|
/// <param name="pageNumber">Номер страницы</param>
|
||||||
|
/// <param name="pageSize">Размер страницы</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearch>> ArtistAsync(AuthStorage storage, string artistName, int pageNumber = 0, int pageSize = 20)
|
||||||
|
{
|
||||||
|
return SearchAsync(storage, artistName, YSearchType.Artist, pageNumber, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск по плейлистам
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="playlistName">Имя плейлиста</param>
|
||||||
|
/// <param name="pageNumber">Номер страницы</param>
|
||||||
|
/// <param name="pageSize">Размер страницы</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearch>> PlaylistAsync(AuthStorage storage, string playlistName, int pageNumber = 0, int pageSize = 20)
|
||||||
|
{
|
||||||
|
return SearchAsync(storage, playlistName, YSearchType.Playlist, pageNumber, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск по плейлистам
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="podcastName">Имя подкаста</param>
|
||||||
|
/// <param name="pageNumber">Номер страницы</param>
|
||||||
|
/// <param name="pageSize">Размер страницы</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearch>> PodcastEpisodeAsync(AuthStorage storage, string podcastName, int pageNumber = 0, int pageSize = 20)
|
||||||
|
{
|
||||||
|
return SearchAsync(storage, podcastName, YSearchType.PodcastEpisode, pageNumber, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск по видео
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="videoName">Имя видео</param>
|
||||||
|
/// <param name="pageNumber">Номер страницы</param>
|
||||||
|
/// <param name="pageSize">Размер страницы</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearch>> VideosAsync(AuthStorage storage, string videoName, int pageNumber = 0, int pageSize = 20)
|
||||||
|
{
|
||||||
|
return SearchAsync(storage, videoName, YSearchType.Video, pageNumber, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск по пользователям
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="userName">Имя пользователя</param>
|
||||||
|
/// <param name="pageNumber">Номер страницы</param>
|
||||||
|
/// <param name="pageSize">Размер страницы</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearch>> UsersAsync(AuthStorage storage, string userName, int pageNumber = 0, int pageSize = 20)
|
||||||
|
{
|
||||||
|
return SearchAsync(storage, userName, YSearchType.User, pageNumber, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="searchText">Поисковый запрос</param>
|
||||||
|
/// <param name="searchType">Тип поиска</param>
|
||||||
|
/// <param name="page">Страница</param>
|
||||||
|
/// <param name="pageSize">Размер страницы</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearch>> SearchAsync(AuthStorage storage, string searchText, YSearchType searchType, int page = 0, int pageSize = 20)
|
||||||
|
{
|
||||||
|
return new YSearchBuilder(api, storage)
|
||||||
|
.Build((searchText, searchType, page, pageSize))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подсказка
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="searchText">Поисковый запрос</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YSearchSuggest>> SuggestAsync(AuthStorage storage, string searchText)
|
||||||
|
{
|
||||||
|
return new YSearchSuggestBuilder(api, storage)
|
||||||
|
.Build(searchText)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Search;
|
|
||||||
using YandexMusic.API.Requests.Search;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// API для поиска
|
|
||||||
/// </summary>
|
|
||||||
public partial class YSearchAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
public YSearchAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Поиск по трекам
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="trackName">Имя трека</param>
|
|
||||||
/// <param name="pageNumber">Номер страницы</param>
|
|
||||||
/// <param name="pageSize">Размер страницы</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearch>> TrackAsync(AuthStorage storage, string trackName, int pageNumber = 0, int pageSize = 20)
|
|
||||||
{
|
|
||||||
return SearchAsync(storage, trackName, YSearchType.Track, pageNumber, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Поиск по альбомам
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="albumName">Имя альбома</param>
|
|
||||||
/// <param name="pageNumber">Номер страницы</param>
|
|
||||||
/// <param name="pageSize">Размер страницы</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearch>> AlbumsAsync(AuthStorage storage, string albumName, int pageNumber = 0, int pageSize = 20)
|
|
||||||
{
|
|
||||||
return SearchAsync(storage, albumName, YSearchType.Album, pageNumber, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Поиск по артисту
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="artistName">Имя артиста</param>
|
|
||||||
/// <param name="pageNumber">Номер страницы</param>
|
|
||||||
/// <param name="pageSize">Размер страницы</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearch>> ArtistAsync(AuthStorage storage, string artistName, int pageNumber = 0, int pageSize = 20)
|
|
||||||
{
|
|
||||||
return SearchAsync(storage, artistName, YSearchType.Artist, pageNumber, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Поиск по плейлистам
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlistName">Имя плейлиста</param>
|
|
||||||
/// <param name="pageNumber">Номер страницы</param>
|
|
||||||
/// <param name="pageSize">Размер страницы</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearch>> PlaylistAsync(AuthStorage storage, string playlistName, int pageNumber = 0, int pageSize = 20)
|
|
||||||
{
|
|
||||||
return SearchAsync(storage, playlistName, YSearchType.Playlist, pageNumber, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Поиск по плейлистам
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="podcastName">Имя подкаста</param>
|
|
||||||
/// <param name="pageNumber">Номер страницы</param>
|
|
||||||
/// <param name="pageSize">Размер страницы</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearch>> PodcastEpisodeAsync(AuthStorage storage, string podcastName, int pageNumber = 0, int pageSize = 20)
|
|
||||||
{
|
|
||||||
return SearchAsync(storage, podcastName, YSearchType.PodcastEpisode, pageNumber, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Поиск по видео
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="videoName">Имя видео</param>
|
|
||||||
/// <param name="pageNumber">Номер страницы</param>
|
|
||||||
/// <param name="pageSize">Размер страницы</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearch>> VideosAsync(AuthStorage storage, string videoName, int pageNumber = 0, int pageSize = 20)
|
|
||||||
{
|
|
||||||
return SearchAsync(storage, videoName, YSearchType.Video, pageNumber, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Поиск по пользователям
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="userName">Имя пользователя</param>
|
|
||||||
/// <param name="pageNumber">Номер страницы</param>
|
|
||||||
/// <param name="pageSize">Размер страницы</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearch>> UsersAsync(AuthStorage storage, string userName, int pageNumber = 0, int pageSize = 20)
|
|
||||||
{
|
|
||||||
return SearchAsync(storage, userName, YSearchType.User, pageNumber, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Поиск
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="searchText">Поисковый запрос</param>
|
|
||||||
/// <param name="searchType">Тип поиска</param>
|
|
||||||
/// <param name="page">Страница</param>
|
|
||||||
/// <param name="pageSize">Размер страницы</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearch>> SearchAsync(AuthStorage storage, string searchText, YSearchType searchType, int page = 0, int pageSize = 20)
|
|
||||||
{
|
|
||||||
return new YSearchBuilder(api, storage)
|
|
||||||
.Build((searchText, searchType, page, pageSize))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Подсказка
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="searchText">Поисковый запрос</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YSearchSuggest>> SuggestAsync(AuthStorage storage, string searchText)
|
|
||||||
{
|
|
||||||
return new YSearchSuggestBuilder(api, storage)
|
|
||||||
.Build(searchText)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
307
YandexMusic.API/API/YTrackAPI.cs
Normal file
307
YandexMusic.API/API/YTrackAPI.cs
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
#region Вспомогательные функции
|
||||||
|
|
||||||
|
private string BuildLinkForDownload(YTrackDownloadInfo mainDownloadResponse, YStorageDownloadFile storageDownload)
|
||||||
|
{
|
||||||
|
string path = storageDownload.Path;
|
||||||
|
string host = storageDownload.Host;
|
||||||
|
string ts = storageDownload.Ts;
|
||||||
|
string s = storageDownload.S;
|
||||||
|
string codec = mainDownloadResponse.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Вспомогательные функции
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public YTrackAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение треков
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="trackId">Идентификатор трека</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YTrack>>> GetAsync(AuthStorage storage, string trackId)
|
||||||
|
{
|
||||||
|
return new YGetTracksBuilder(api, storage)
|
||||||
|
.Build(new[] { trackId })
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение треков
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="trackIds">Идентификаторы треков</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<List<YTrack>>> GetAsync(AuthStorage storage, IEnumerable<string> trackIds)
|
||||||
|
{
|
||||||
|
return new YGetTracksBuilder(api, storage)
|
||||||
|
.Build(trackIds)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
return new YTrackDownloadInfoBuilder(api, storage)
|
||||||
|
.Build((trackKey, direct))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 Получение данных трека
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
#region Вспомогательные функции
|
|
||||||
|
|
||||||
private string BuildLinkForDownload(YTrackDownloadInfo mainDownloadResponse, YStorageDownloadFile storageDownload)
|
|
||||||
{
|
|
||||||
string path = storageDownload.Path;
|
|
||||||
string host = storageDownload.Host;
|
|
||||||
string ts = storageDownload.Ts;
|
|
||||||
string s = storageDownload.S;
|
|
||||||
string codec = mainDownloadResponse.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Вспомогательные функции
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public YTrackAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение треков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="trackId">Идентификатор трека</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YTrack>>> GetAsync(AuthStorage storage, string trackId)
|
|
||||||
{
|
|
||||||
return new YGetTracksBuilder(api, storage)
|
|
||||||
.Build(new[] { trackId })
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение треков
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="trackIds">Идентификаторы треков</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<List<YTrack>>> GetAsync(AuthStorage storage, IEnumerable<string> trackIds)
|
|
||||||
{
|
|
||||||
return new YGetTracksBuilder(api, storage)
|
|
||||||
.Build(trackIds)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
return new YTrackDownloadInfoBuilder(api, storage)
|
|
||||||
.Build((trackKey, direct))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 Получение данных трека
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
71
YandexMusic.API/API/YUgcAPI.cs
Normal file
71
YandexMusic.API/API/YUgcAPI.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Models.Playlist;
|
||||||
|
using YandexMusic.API.Models.Ugc;
|
||||||
|
using YandexMusic.API.Requests.Ugc;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
public partial class YUgcAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
public YUgcAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение ссылки на загрузчик трека
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="playlist">Плейлист, куда будет загружен трек</param>
|
||||||
|
/// <param name="fileName">Название файла для загрузки</param>
|
||||||
|
public Task<YUgcUpload> GetUgcUploadLinkAsync(AuthStorage storage, YPlaylist playlist, string fileName)
|
||||||
|
{
|
||||||
|
return new YUgcGetUploadLinkBuilder(api, storage)
|
||||||
|
.Build((playlist, fileName))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Загрузка трека из файла
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="uploadLink">Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync</param>
|
||||||
|
/// <param name="filePath">Загружаемый файл</param>
|
||||||
|
public Task<YResponse<string>> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, string filePath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
throw new FileNotFoundException("Файл для загрузки не существует.", filePath);
|
||||||
|
|
||||||
|
return UploadUgcTrackAsync(storage, uploadLink, File.Open(filePath, FileMode.Open));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Загрузка трека из потока
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="uploadLink">Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync</param>
|
||||||
|
/// <param name="stream">Поток с данными для загрузки</param>
|
||||||
|
public Task<YResponse<string>> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, Stream stream)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
throw new NullReferenceException("Пустая ссылка на поток загрузки.");
|
||||||
|
|
||||||
|
using MemoryStream ms = new();
|
||||||
|
stream.CopyTo(ms);
|
||||||
|
|
||||||
|
return UploadUgcTrackAsync(storage, uploadLink, ms.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Загрузка трека из массива
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="uploadLink">Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync</param>
|
||||||
|
/// <param name="file">Загружаемый трек в виде массив байтов</param>
|
||||||
|
public Task<YResponse<string>> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, byte[] file)
|
||||||
|
{
|
||||||
|
return new YUgcUploadBuilder(api, storage)
|
||||||
|
.Build((uploadLink, file))
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Playlist;
|
|
||||||
using YandexMusic.API.Models.Ugc;
|
|
||||||
using YandexMusic.API.Requests.Ugc;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
public partial class YUgcAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
public YUgcAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение ссылки на загрузчик трека
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="playlist">Плейлист, куда будет загружен трек</param>
|
|
||||||
/// <param name="fileName">Название файла для загрузки</param>
|
|
||||||
public Task<YUgcUpload> GetUgcUploadLinkAsync(AuthStorage storage, YPlaylist playlist, string fileName)
|
|
||||||
{
|
|
||||||
return new YUgcGetUploadLinkBuilder(api, storage)
|
|
||||||
.Build((playlist, fileName))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Загрузка трека из файла
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="uploadLink">Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync</param>
|
|
||||||
/// <param name="filePath">Загружаемый файл</param>
|
|
||||||
public Task<YResponse<string>> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, string filePath)
|
|
||||||
{
|
|
||||||
if (!File.Exists(filePath))
|
|
||||||
throw new FileNotFoundException("Файл для загрузки не существует.", filePath);
|
|
||||||
|
|
||||||
return UploadUgcTrackAsync(storage, uploadLink, File.Open(filePath, FileMode.Open));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Загрузка трека из потока
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="uploadLink">Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync</param>
|
|
||||||
/// <param name="stream">Поток с данными для загрузки</param>
|
|
||||||
public Task<YResponse<string>> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, Stream stream)
|
|
||||||
{
|
|
||||||
if (stream == null)
|
|
||||||
throw new NullReferenceException("Пустая ссылка на поток загрузки.");
|
|
||||||
|
|
||||||
using MemoryStream ms = new();
|
|
||||||
stream.CopyTo(ms);
|
|
||||||
|
|
||||||
return UploadUgcTrackAsync(storage, uploadLink, ms.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Загрузка трека из массива
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="uploadLink">Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync</param>
|
|
||||||
/// <param name="file">Загружаемый трек в виде массив байтов</param>
|
|
||||||
public Task<YResponse<string>> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, byte[] file)
|
|
||||||
{
|
|
||||||
return new YUgcUploadBuilder(api, storage)
|
|
||||||
.Build((uploadLink, file))
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
300
YandexMusic.API/API/YUserAPI.cs
Normal file
300
YandexMusic.API/API/YUserAPI.cs
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Models.Account;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
using YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API для пользователя
|
||||||
|
/// </summary>
|
||||||
|
public partial class YUserAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
#region Вспомогательные функции
|
||||||
|
|
||||||
|
private async Task<bool> GetCsrfTokenAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
using HttpResponseMessage authMethodsResponse = await new YGetAuthMethodsBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
|
||||||
|
if (!authMethodsResponse.IsSuccessStatusCode)
|
||||||
|
throw new HttpRequestException("Невозможно получить CFRF-токен.");
|
||||||
|
|
||||||
|
string responseString = await authMethodsResponse.Content
|
||||||
|
.ReadAsStringAsync();
|
||||||
|
Match match = Regex.Match(responseString, "\"csrf_token\" value=\"([^\"]+)\"");
|
||||||
|
|
||||||
|
if (!match.Success || match.Groups.Count < 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
storage.AuthToken = new YAuthToken
|
||||||
|
{
|
||||||
|
CsfrToken = match.Groups[1].Value
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> LoginByCookiesAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
if (storage.AuthToken == null)
|
||||||
|
throw new AuthenticationException("Невозможно инициализировать сессию входа.");
|
||||||
|
|
||||||
|
YAccessToken accessToken = await new YGetAuthCookiesBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
|
||||||
|
storage.IsAuthorized = !string.IsNullOrEmpty(accessToken.AccessToken);
|
||||||
|
|
||||||
|
storage.AccessToken = accessToken;
|
||||||
|
storage.Token = accessToken.AccessToken;
|
||||||
|
|
||||||
|
YShortAccountInfo validateTokenResponse = await new YGetShortAccountInifoBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
|
||||||
|
if (validateTokenResponse.Status != YAuthStatus.Ok)
|
||||||
|
throw new Exception("Вход в аккаунт не выполнен.");
|
||||||
|
|
||||||
|
storage.IsAuthorized = !string.IsNullOrWhiteSpace(validateTokenResponse.Uid);
|
||||||
|
|
||||||
|
return storage.IsAuthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Вспомогательные функции
|
||||||
|
|
||||||
|
public YUserAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Авторизация
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="token">Токен авторизации</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task AuthorizeAsync(AuthStorage storage, string token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
throw new Exception("Задан пустой токен авторизации.");
|
||||||
|
|
||||||
|
storage.Token = token;
|
||||||
|
|
||||||
|
// Пытаемся получить информацию о пользователе
|
||||||
|
YResponse<YAccountResult> authInfo = await GetUserAuthAsync(storage);
|
||||||
|
|
||||||
|
// Если не авторизован, то авторизуем
|
||||||
|
if (string.IsNullOrEmpty(authInfo.Result.Account.Uid))
|
||||||
|
throw new Exception("Пользователь незалогинен.");
|
||||||
|
|
||||||
|
// Флаг авторизации
|
||||||
|
storage.IsAuthorized = true;
|
||||||
|
storage.User = authInfo.Result.Account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение информации об авторизации
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YResponse<YAccountResult>> GetUserAuthAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return new YGetAuthInfoBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создание сеанса и получение доступных методов авторизации
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="userName">Имя пользователя</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<YAuthTypes> CreateAuthSessionAsync(AuthStorage storage, string userName)
|
||||||
|
{
|
||||||
|
if (!await GetCsrfTokenAsync(storage))
|
||||||
|
throw new Exception("Невозможно инициализировать сессию входа.");
|
||||||
|
|
||||||
|
YAuthTypes types = await new YGetAuthLoginUserBuilder(api, storage)
|
||||||
|
.Build((storage.AuthToken.CsfrToken, userName))
|
||||||
|
.GetResponseAsync();
|
||||||
|
|
||||||
|
storage.AuthToken.TrackId = types.TrackId;
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение ссылки на QR-код
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<string> GetAuthQRLinkAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
if (!await GetCsrfTokenAsync(storage))
|
||||||
|
throw new Exception("Невозможно инициализировать сессию входа.");
|
||||||
|
|
||||||
|
YAuthQR result = await new YGetAuthQRBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
|
||||||
|
if (result.Status != YAuthStatus.Ok)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
storage.AuthToken = new YAuthToken
|
||||||
|
{
|
||||||
|
TrackId = result.TrackId,
|
||||||
|
CsfrToken = result.CsrfToken
|
||||||
|
};
|
||||||
|
|
||||||
|
return $"https://passport.yandex.ru/auth/magic/code/?track_id={result.TrackId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Авторизация по QR-коду
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<YAuthQRStatus> AuthorizeByQRAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
if (storage.AuthToken == null)
|
||||||
|
throw new Exception("Не выполнен запрос на авторизацию по QR.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
YAuthQRStatus qrStatus = await new YGetAuthLoginQRBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
if (qrStatus.Status != YAuthStatus.Ok)
|
||||||
|
return qrStatus;
|
||||||
|
|
||||||
|
bool ok = await LoginByCookiesAsync(storage);
|
||||||
|
if (!ok)
|
||||||
|
throw new AuthenticationException("Ошибка авторизации по QR.");
|
||||||
|
|
||||||
|
return qrStatus;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new AuthenticationException("Ошибка авторизации по QR.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение <see cref="YAuthCaptcha"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YAuthCaptcha> GetCaptchaAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
||||||
|
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
||||||
|
|
||||||
|
return new YGetAuthCaptchaBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Авторизация по captcha
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="captchaValue">Значение captcha</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YAuthBase> AuthorizeByCaptchaAsync(AuthStorage storage, string captchaValue)
|
||||||
|
{
|
||||||
|
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
||||||
|
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
||||||
|
|
||||||
|
return new YGetAuthLoginCaptchaBuilder(api, storage)
|
||||||
|
.Build(captchaValue)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение письма авторизации на почту пользователя
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<YAuthLetter> GetAuthLetterAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return new YGetAuthLetterBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Авторизация после подтверждения входа через письмо
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> AuthorizeByLetterAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
YAuthLetterStatus status = await new YGetAuthLoginLetterBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
|
||||||
|
if (status.Status == YAuthStatus.Ok && !status.MagicLinkConfirmed)
|
||||||
|
throw new Exception("Не подтвержден вход посредством e-mail.");
|
||||||
|
|
||||||
|
return await LoginByCookiesAsync(storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Авторизация с помощью пароля из приложения Яндекс
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">Хранилище</param>
|
||||||
|
/// <param name="password">Пароль</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<YAuthBase> AuthorizeByAppPasswordAsync(AuthStorage storage, string password)
|
||||||
|
{
|
||||||
|
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
||||||
|
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
||||||
|
|
||||||
|
YAuthBase response = await new YGetAuthAppPasswordBuilder(api, storage)
|
||||||
|
.Build(password)
|
||||||
|
.GetResponseAsync();
|
||||||
|
|
||||||
|
if (response.Status == YAuthStatus.Ok)
|
||||||
|
{
|
||||||
|
bool ok = await LoginByCookiesAsync(storage);
|
||||||
|
if (!ok)
|
||||||
|
throw new AuthenticationException("Ошибка авторизации.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение <see cref="YAccessToken"/> после авторизации с помощью QR, e-mail, пароля из приложения
|
||||||
|
/// </summary>
|
||||||
|
public async Task<YAccessToken> GetAccessTokenAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
if (storage.AuthToken == null)
|
||||||
|
throw new Exception("Не найдена сессия входа.");
|
||||||
|
|
||||||
|
YAccessToken accessToken = await new YGetMusicTokenBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
|
||||||
|
storage.Token = accessToken.AccessToken;
|
||||||
|
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получение информации о пользователе через логин Яндекса
|
||||||
|
/// </summary>
|
||||||
|
public Task<YLoginInfo> GetLoginInfoAsync(AuthStorage storage)
|
||||||
|
{
|
||||||
|
return new YGetLoginInfoBuilder(api, storage)
|
||||||
|
.Build(null)
|
||||||
|
.GetResponseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
using System.Security.Authentication;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
using YandexMusic.API.Common;
|
|
||||||
using YandexMusic.API.Models.Account;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Requests.Account;
|
|
||||||
|
|
||||||
namespace YandexMusic.API
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// API для пользователя
|
|
||||||
/// </summary>
|
|
||||||
public partial class YUserAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
#region Вспомогательные функции
|
|
||||||
|
|
||||||
private async Task<bool> GetCsrfTokenAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
using HttpResponseMessage authMethodsResponse = await new YGetAuthMethodsBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
if (!authMethodsResponse.IsSuccessStatusCode)
|
|
||||||
throw new HttpRequestException("Невозможно получить CFRF-токен.");
|
|
||||||
|
|
||||||
string responseString = await authMethodsResponse.Content
|
|
||||||
.ReadAsStringAsync();
|
|
||||||
Match match = Regex.Match(responseString, "\"csrf_token\" value=\"([^\"]+)\"");
|
|
||||||
|
|
||||||
if (!match.Success || match.Groups.Count < 2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
storage.AuthToken = new YAuthToken
|
|
||||||
{
|
|
||||||
CsfrToken = match.Groups[1].Value
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> LoginByCookiesAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
if (storage.AuthToken == null)
|
|
||||||
throw new AuthenticationException("Невозможно инициализировать сессию входа.");
|
|
||||||
|
|
||||||
YAccessToken accessToken = await new YGetAuthCookiesBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
storage.IsAuthorized = !string.IsNullOrEmpty(accessToken.AccessToken);
|
|
||||||
|
|
||||||
storage.AccessToken = accessToken;
|
|
||||||
storage.Token = accessToken.AccessToken;
|
|
||||||
|
|
||||||
YShortAccountInfo validateTokenResponse = await new YGetShortAccountInifoBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
if (validateTokenResponse.Status != YAuthStatus.Ok)
|
|
||||||
throw new Exception("Вход в аккаунт не выполнен.");
|
|
||||||
|
|
||||||
storage.IsAuthorized = !string.IsNullOrWhiteSpace(validateTokenResponse.Uid);
|
|
||||||
|
|
||||||
return storage.IsAuthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Вспомогательные функции
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public YUserAPI(YandexMusicApi yandex) : base(yandex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Авторизация
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="token">Токен авторизации</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task AuthorizeAsync(AuthStorage storage, string token)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(token))
|
|
||||||
throw new Exception("Задан пустой токен авторизации.");
|
|
||||||
|
|
||||||
storage.Token = token;
|
|
||||||
|
|
||||||
// Пытаемся получить информацию о пользователе
|
|
||||||
YResponse<YAccountResult> authInfo = await GetUserAuthAsync(storage);
|
|
||||||
|
|
||||||
// Если не авторизован, то авторизуем
|
|
||||||
if (string.IsNullOrEmpty(authInfo.Result.Account.Uid))
|
|
||||||
throw new Exception("Пользователь незалогинен.");
|
|
||||||
|
|
||||||
// Флаг авторизации
|
|
||||||
storage.IsAuthorized = true;
|
|
||||||
storage.User = authInfo.Result.Account;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение информации об авторизации
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YResponse<YAccountResult>> GetUserAuthAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetAuthInfoBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Создание сеанса и получение доступных методов авторизации
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="userName">Имя пользователя</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<YAuthTypes> CreateAuthSessionAsync(AuthStorage storage, string userName)
|
|
||||||
{
|
|
||||||
if (!await GetCsrfTokenAsync(storage))
|
|
||||||
throw new Exception("Невозможно инициализировать сессию входа.");
|
|
||||||
|
|
||||||
YAuthTypes types = await new YGetAuthLoginUserBuilder(api, storage)
|
|
||||||
.Build((storage.AuthToken.CsfrToken, userName))
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
storage.AuthToken.TrackId = types.TrackId;
|
|
||||||
|
|
||||||
return types;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение ссылки на QR-код
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<string> GetAuthQRLinkAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
if (!await GetCsrfTokenAsync(storage))
|
|
||||||
throw new Exception("Невозможно инициализировать сессию входа.");
|
|
||||||
|
|
||||||
YAuthQR result = await new YGetAuthQRBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
if (result.Status != YAuthStatus.Ok)
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
storage.AuthToken = new YAuthToken
|
|
||||||
{
|
|
||||||
TrackId = result.TrackId,
|
|
||||||
CsfrToken = result.CsrfToken
|
|
||||||
};
|
|
||||||
|
|
||||||
return $"https://passport.yandex.ru/auth/magic/code/?track_id={result.TrackId}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Авторизация по QR-коду
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<YAuthQRStatus> AuthorizeByQRAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
if (storage.AuthToken == null)
|
|
||||||
throw new Exception("Не выполнен запрос на авторизацию по QR.");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
YAuthQRStatus qrStatus = await new YGetAuthLoginQRBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
if (qrStatus.Status != YAuthStatus.Ok)
|
|
||||||
return qrStatus;
|
|
||||||
|
|
||||||
bool ok = await LoginByCookiesAsync(storage);
|
|
||||||
if (!ok)
|
|
||||||
throw new AuthenticationException("Ошибка авторизации по QR.");
|
|
||||||
|
|
||||||
return qrStatus;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new AuthenticationException("Ошибка авторизации по QR.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение <see cref="YAuthCaptcha"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YAuthCaptcha> GetCaptchaAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
|
||||||
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
|
||||||
|
|
||||||
return new YGetAuthCaptchaBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Авторизация по captcha
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="captchaValue">Значение captcha</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YAuthBase> AuthorizeByCaptchaAsync(AuthStorage storage, string captchaValue)
|
|
||||||
{
|
|
||||||
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
|
||||||
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
|
||||||
|
|
||||||
return new YGetAuthLoginCaptchaBuilder(api, storage)
|
|
||||||
.Build(captchaValue)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение письма авторизации на почту пользователя
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<YAuthLetter> GetAuthLetterAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetAuthLetterBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Авторизация после подтверждения входа через письмо
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<bool> AuthorizeByLetterAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
YAuthLetterStatus status = await new YGetAuthLoginLetterBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
if (status.Status == YAuthStatus.Ok && !status.MagicLinkConfirmed)
|
|
||||||
throw new Exception("Не подтвержден вход посредством e-mail.");
|
|
||||||
|
|
||||||
return await LoginByCookiesAsync(storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Авторизация с помощью пароля из приложения Яндекс
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">Хранилище</param>
|
|
||||||
/// <param name="password">Пароль</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<YAuthBase> AuthorizeByAppPasswordAsync(AuthStorage storage, string password)
|
|
||||||
{
|
|
||||||
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
|
||||||
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
|
||||||
|
|
||||||
YAuthBase response = await new YGetAuthAppPasswordBuilder(api, storage)
|
|
||||||
.Build(password)
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
if (response.Status == YAuthStatus.Ok)
|
|
||||||
{
|
|
||||||
bool ok = await LoginByCookiesAsync(storage);
|
|
||||||
if (!ok)
|
|
||||||
throw new AuthenticationException("Ошибка авторизации.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение <see cref="YAccessToken"/> после авторизации с помощью QR, e-mail, пароля из приложения
|
|
||||||
/// </summary>
|
|
||||||
public async Task<YAccessToken> GetAccessTokenAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
if (storage.AuthToken == null)
|
|
||||||
throw new Exception("Не найдена сессия входа.");
|
|
||||||
|
|
||||||
YAccessToken accessToken = await new YGetMusicTokenBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
|
|
||||||
storage.Token = accessToken.AccessToken;
|
|
||||||
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение информации о пользователе через логин Яндекса
|
|
||||||
/// </summary>
|
|
||||||
public Task<YLoginInfo> GetLoginInfoAsync(AuthStorage storage)
|
|
||||||
{
|
|
||||||
return new YGetLoginInfoBuilder(api, storage)
|
|
||||||
.Build(null)
|
|
||||||
.GetResponseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
YandexMusic.API/API/YYnisonAPI.cs
Normal file
21
YandexMusic.API/API/YYnisonAPI.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using YandexMusic.API.Common;
|
||||||
|
using YandexMusic.API.Common.Ynison;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
/// <summary>
|
||||||
|
/// API Ynison
|
||||||
|
/// </summary>
|
||||||
|
public partial class YYnisonAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
public YYnisonAPI(YandexMusicApi yandex) : base(yandex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public YnisonPlayer GetPlayer(AuthStorage storage)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(storage.Token))
|
||||||
|
throw new Exception("Токен пользователя не задан.");
|
||||||
|
|
||||||
|
return new(api, storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,103 +1,70 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
using YandexMusic.API.Common.Debug;
|
|
||||||
using YandexMusic.API.Common.Providers;
|
using YandexMusic.API.Common.Providers;
|
||||||
using YandexMusic.API.Models.Account;
|
using YandexMusic.API.Models.Account;
|
||||||
using YandexMusic.API.Requests.Common;
|
using YandexMusic.API.Requests.Common;
|
||||||
|
|
||||||
namespace YandexMusic.API.Common
|
namespace YandexMusic.API.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Хранилище данных пользователя
|
||||||
|
/// </summary>
|
||||||
|
public class AuthStorage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Хранилище данных пользователя
|
/// Http-контекст
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AuthStorage
|
public HttpContext Context { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Флаг авторизации
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAuthorized { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Идентификатор устройства
|
||||||
|
/// </summary>
|
||||||
|
public string DeviceId { get; set; } = "csharp";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Токен авторизации
|
||||||
|
/// </summary>
|
||||||
|
public string Token { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Аккаунт
|
||||||
|
/// </summary>
|
||||||
|
public YAccount User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Провайдер запросов
|
||||||
|
/// </summary>
|
||||||
|
public IRequestProvider Provider { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Токен доступа
|
||||||
|
/// </summary>
|
||||||
|
public YAccessToken AccessToken { get; set; }
|
||||||
|
|
||||||
|
internal YAuthToken AuthToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Конструктор
|
||||||
|
/// </summary>
|
||||||
|
public AuthStorage(IRequestProvider provider)
|
||||||
{
|
{
|
||||||
#region Свойства
|
User = new YAccount();
|
||||||
|
Context = new HttpContext();
|
||||||
/// <summary>
|
Provider = provider;
|
||||||
/// Http-контекст
|
|
||||||
/// </summary>
|
|
||||||
public HttpContext Context { get; }
|
|
||||||
|
|
||||||
public DebugSettings Debug { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Флаг авторизации
|
|
||||||
/// </summary>
|
|
||||||
public bool IsAuthorized { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Идентификатор устройства
|
|
||||||
/// </summary>
|
|
||||||
public string DeviceId { get; set; } = "csharp";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Токен авторизации
|
|
||||||
/// </summary>
|
|
||||||
public string Token { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Аккаунт
|
|
||||||
/// </summary>
|
|
||||||
public YAccount User { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Провайдер запросов
|
|
||||||
/// </summary>
|
|
||||||
public IRequestProvider Provider { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Токен доступа
|
|
||||||
/// </summary>
|
|
||||||
public YAccessToken AccessToken { get; set; }
|
|
||||||
|
|
||||||
internal YAuthToken AuthToken { get; set; }
|
|
||||||
|
|
||||||
#endregion Свойства
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Конструктор
|
|
||||||
/// </summary>
|
|
||||||
public AuthStorage(DebugSettings settings = null)
|
|
||||||
{
|
|
||||||
User = new YAccount();
|
|
||||||
Context = new HttpContext();
|
|
||||||
Debug = settings;
|
|
||||||
Provider = new DefaultRequestProvider(this);
|
|
||||||
|
|
||||||
if (Debug is { ClearDirectory: true })
|
|
||||||
{
|
|
||||||
Debug.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Конструктор
|
|
||||||
/// </summary>
|
|
||||||
public AuthStorage(IRequestProvider provider, DebugSettings settings = null)
|
|
||||||
{
|
|
||||||
User = new YAccount();
|
|
||||||
Context = new HttpContext();
|
|
||||||
Debug = settings;
|
|
||||||
Provider = provider;
|
|
||||||
|
|
||||||
if (Debug is { ClearDirectory: true })
|
|
||||||
{
|
|
||||||
Debug.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Установка прокси для пользователия
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="proxy">Прокси</param>
|
|
||||||
public void SetProxy(IWebProxy proxy)
|
|
||||||
{
|
|
||||||
Context.WebProxy = proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Установка прокси для пользователия
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="proxy">Прокси</param>
|
||||||
|
public void SetProxy(IWebProxy proxy)
|
||||||
|
{
|
||||||
|
Context.WebProxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,44 +1,43 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace YandexMusic.API.Common
|
namespace YandexMusic.API.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Загрузчик файлов по ссылке
|
||||||
|
/// </summary>
|
||||||
|
public class DataDownloader
|
||||||
{
|
{
|
||||||
/// <summary>
|
private AuthStorage authStorage;
|
||||||
/// Загрузчик файлов по ссылке
|
|
||||||
/// </summary>
|
private async Task<HttpContent> GetResponseContent(string url, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
|
||||||
public class DataDownloader
|
|
||||||
{
|
{
|
||||||
private AuthStorage authStorage;
|
HttpRequestMessage message = new(new HttpMethod(WebRequestMethods.Http.Get), url);
|
||||||
|
|
||||||
private async Task<HttpContent> GetResponseContent(string url, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
|
HttpResponseMessage response = await authStorage.Provider.GetWebResponseAsync(message, httpCompletionOption);
|
||||||
{
|
return response.Content;
|
||||||
HttpRequestMessage message = new(new HttpMethod(WebRequestMethods.Http.Get), url);
|
}
|
||||||
|
|
||||||
HttpResponseMessage response = await authStorage.Provider.GetWebResponseAsync(message, httpCompletionOption);
|
public async Task<Stream> AsStream(string url, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
|
||||||
return response.Content;
|
{
|
||||||
}
|
HttpContent content = await GetResponseContent(url, httpCompletionOption);
|
||||||
|
return await content.ReadAsStreamAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Stream> AsStream(string url, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
|
public async Task<byte[]> AsBytes(string url)
|
||||||
{
|
{
|
||||||
HttpContent content = await GetResponseContent(url, httpCompletionOption);
|
HttpContent content = await GetResponseContent(url);
|
||||||
return await content.ReadAsStreamAsync();
|
return await content.ReadAsByteArrayAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> AsBytes(string url)
|
public async Task ToFile(string url, string fileName)
|
||||||
{
|
{
|
||||||
HttpContent content = await GetResponseContent(url);
|
using Stream stream = await AsStream(url);
|
||||||
return await content.ReadAsByteArrayAsync();
|
using FileStream fs = File.Create(fileName);
|
||||||
}
|
await stream.CopyToAsync(fs);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ToFile(string url, string fileName)
|
public DataDownloader(AuthStorage storage)
|
||||||
{
|
{
|
||||||
using Stream stream = await AsStream(url);
|
authStorage = storage;
|
||||||
using FileStream fs = File.Create(fileName);
|
|
||||||
await stream.CopyToAsync(fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataDownloader(AuthStorage storage)
|
|
||||||
{
|
|
||||||
authStorage = storage;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,75 +1,63 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace YandexMusic.API.Common
|
namespace YandexMusic.API.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Класс для шифровки
|
||||||
|
/// </summary>
|
||||||
|
public class Encryptor
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly string IV = "encryption";
|
||||||
/// Класс для шифровки
|
private readonly byte[] IVHash;
|
||||||
/// </summary>
|
|
||||||
public class Encryptor
|
private readonly byte[] keyHash;
|
||||||
|
|
||||||
|
private readonly MD5 md5;
|
||||||
|
private readonly Aes aesAlg;
|
||||||
|
|
||||||
|
private byte[] GetHash(string value)
|
||||||
{
|
{
|
||||||
#region Поля
|
return md5.ComputeHash(Encoding.UTF8.GetBytes(value));
|
||||||
|
|
||||||
private readonly string IV = "encryption";
|
|
||||||
private readonly byte[] IVHash;
|
|
||||||
|
|
||||||
private readonly byte[] keyHash;
|
|
||||||
|
|
||||||
private readonly MD5 md5;
|
|
||||||
private readonly Aes aesAlg;
|
|
||||||
|
|
||||||
|
|
||||||
#endregion Поля
|
|
||||||
|
|
||||||
#region Вспомогательные функции
|
|
||||||
|
|
||||||
private byte[] GetHash(string value)
|
|
||||||
{
|
|
||||||
return md5.ComputeHash(Encoding.UTF8.GetBytes(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Вспомогательные функции
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Encryptor(string key)
|
|
||||||
{
|
|
||||||
md5 = MD5.Create();
|
|
||||||
|
|
||||||
aesAlg = Aes.Create();
|
|
||||||
aesAlg.BlockSize = 128;
|
|
||||||
aesAlg.Padding = PaddingMode.PKCS7;
|
|
||||||
|
|
||||||
keyHash = GetHash(key);
|
|
||||||
IVHash = GetHash(IV);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Encrypt(byte[] data)
|
|
||||||
{
|
|
||||||
using MemoryStream ms = new();
|
|
||||||
using CryptoStream csEncrypt = new(ms, aesAlg.CreateEncryptor(keyHash, IVHash), CryptoStreamMode.Write);
|
|
||||||
|
|
||||||
csEncrypt.Write(data, 0, data.Length);
|
|
||||||
|
|
||||||
if (!csEncrypt.HasFlushedFinalBlock)
|
|
||||||
csEncrypt.FlushFinalBlock();
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Decrypt(byte[] data)
|
|
||||||
{
|
|
||||||
using MemoryStream ms = new();
|
|
||||||
using CryptoStream csDecrypt = new(ms, aesAlg.CreateDecryptor(keyHash, IVHash), CryptoStreamMode.Write);
|
|
||||||
|
|
||||||
csDecrypt.Write(data, 0, data.Length);
|
|
||||||
|
|
||||||
if (!csDecrypt.HasFlushedFinalBlock)
|
|
||||||
csDecrypt.FlushFinalBlock();
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Encryptor(string key)
|
||||||
|
{
|
||||||
|
md5 = MD5.Create();
|
||||||
|
|
||||||
|
aesAlg = Aes.Create();
|
||||||
|
aesAlg.BlockSize = 128;
|
||||||
|
aesAlg.Padding = PaddingMode.PKCS7;
|
||||||
|
|
||||||
|
keyHash = GetHash(key);
|
||||||
|
IVHash = GetHash(IV);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Encrypt(byte[] data)
|
||||||
|
{
|
||||||
|
using MemoryStream ms = new();
|
||||||
|
using CryptoStream csEncrypt = new(ms, aesAlg.CreateEncryptor(keyHash, IVHash), CryptoStreamMode.Write);
|
||||||
|
|
||||||
|
csEncrypt.Write(data, 0, data.Length);
|
||||||
|
|
||||||
|
if (!csEncrypt.HasFlushedFinalBlock)
|
||||||
|
csEncrypt.FlushFinalBlock();
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Decrypt(byte[] data)
|
||||||
|
{
|
||||||
|
using MemoryStream ms = new();
|
||||||
|
using CryptoStream csDecrypt = new(ms, aesAlg.CreateDecryptor(keyHash, IVHash), CryptoStreamMode.Write);
|
||||||
|
|
||||||
|
csDecrypt.Write(data, 0, data.Length);
|
||||||
|
|
||||||
|
if (!csDecrypt.HasFlushedFinalBlock)
|
||||||
|
csDecrypt.FlushFinalBlock();
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,60 +1,56 @@
|
|||||||
using YandexMusic.API.Models.Common;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
namespace YandexMusic.API.Common.Providers
|
namespace YandexMusic.API.Common.Providers;
|
||||||
|
|
||||||
|
/// <summary>Базовый провайдер HTTP-запросов с общей логикой десериализации.</summary>
|
||||||
|
public abstract class CommonRequestProvider : IRequestProvider
|
||||||
{
|
{
|
||||||
public class CommonRequestProvider : IRequestProvider
|
/// <summary>Хранилище данных авторизации.</summary>
|
||||||
|
protected readonly AuthStorage storage;
|
||||||
|
|
||||||
|
/// <summary>Настройки сериализации JSON (регистронезависимые, поддержка enum-строк).</summary>
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
#region Поля
|
PropertyNameCaseInsensitive = true,
|
||||||
|
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
|
||||||
|
};
|
||||||
|
|
||||||
protected AuthStorage storage;
|
/// <summary>Инициализирует новый экземпляр провайдера.</summary>
|
||||||
|
/// <param name="authStorage">Хранилище авторизации.</param>
|
||||||
|
protected CommonRequestProvider(AuthStorage authStorage)
|
||||||
|
{
|
||||||
|
storage = authStorage;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion Поля
|
/// <summary>Выполняет HTTP-запрос и возвращает ответ.</summary>
|
||||||
|
public abstract Task<HttpResponseMessage> GetWebResponseAsync(
|
||||||
|
HttpRequestMessage message,
|
||||||
|
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead);
|
||||||
|
|
||||||
|
/// <summary>Преобразует HTTP-ответ в объект типа T.</summary>
|
||||||
|
public virtual async Task<T> GetDataFromResponseAsync<T>(
|
||||||
|
YandexMusicApi api,
|
||||||
|
HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
public CommonRequestProvider(AuthStorage authStorage)
|
|
||||||
{
|
{
|
||||||
storage = authStorage;
|
var error = JsonSerializer.Deserialize<YErrorResponse>(json, JsonOptions);
|
||||||
|
throw error ?? new Exception("Ошибка десериализации ответа с ошибкой.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
|
||||||
#region IRequestProvider
|
|
||||||
|
|
||||||
public virtual Task<HttpResponseMessage> GetWebResponseAsync(HttpRequestMessage message, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
|
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
// Если нужен контекст выполнения, он добавляется через кастомный конвертер
|
||||||
|
return JsonSerializer.Deserialize<T>(json, JsonOptions)
|
||||||
|
?? throw new JsonException("Десериализация вернула null");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public virtual async Task<T> GetDataFromResponseAsync<T>(YandexMusicApi api, HttpResponseMessage response)
|
|
||||||
{
|
{
|
||||||
string result = await response.Content.ReadAsStringAsync();
|
throw new Exception($"Ошибка десериализации: {ex.Message}", ex);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
YErrorResponse exception = JsonConvert.DeserializeObject<YErrorResponse>(result);
|
|
||||||
throw exception ?? new Exception("Ошибка десериализации ответа с ошибкой.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JsonSerializerSettings settings = new()
|
|
||||||
{
|
|
||||||
Converters = new List<JsonConverter> {
|
|
||||||
new YExecutionContextConverter(api, storage)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return storage.Debug != null
|
|
||||||
? storage.Debug.Deserialize<T>(response.RequestMessage?.RequestUri?.AbsolutePath, result, settings)
|
|
||||||
: JsonConvert.DeserializeObject<T>(result, settings);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new Exception($"Ошибка десериализации {ex}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion IRequestProvider
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,69 +1,45 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
using YandexMusic.API.Models.Common;
|
namespace YandexMusic.API.Common.Providers;
|
||||||
|
|
||||||
namespace YandexMusic.API.Common.Providers
|
/// <summary>Стандартный провайдер HTTP-запросов с использованием HttpClient.</summary>
|
||||||
|
public class DefaultRequestProvider : CommonRequestProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>Инициализирует новый экземпляр провайдера.</summary>
|
||||||
/// Стандартный провайдер запросов
|
/// <param name="authStorage">Хранилище авторизации.</param>
|
||||||
/// </summary>
|
public DefaultRequestProvider(AuthStorage authStorage) : base(authStorage) { }
|
||||||
public class DefaultRequestProvider : CommonRequestProvider
|
|
||||||
|
/// <summary>Выполняет HTTP-запрос и возвращает ответ.</summary>
|
||||||
|
/// <param name="message">HTTP-запрос.</param>
|
||||||
|
/// <param name="completionOption">Опция завершения запроса.</param>
|
||||||
|
/// <returns>HTTP-ответ.</returns>
|
||||||
|
public override async Task<HttpResponseMessage> GetWebResponseAsync(
|
||||||
|
HttpRequestMessage message,
|
||||||
|
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
|
||||||
{
|
{
|
||||||
#region Вспомогательные функции
|
using var handler = new SocketsHttpHandler
|
||||||
|
|
||||||
private Exception ProcessException(Exception ex)
|
|
||||||
{
|
{
|
||||||
if (ex is not WebException webException)
|
Proxy = storage.Context.WebProxy,
|
||||||
return ex;
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
|
||||||
|
UseCookies = true,
|
||||||
|
CookieContainer = storage.Context.Cookies,
|
||||||
|
AllowAutoRedirect = true,
|
||||||
|
MaxAutomaticRedirections = 10
|
||||||
|
};
|
||||||
|
|
||||||
if (webException.Response is null)
|
using var client = new HttpClient(handler);
|
||||||
return ex;
|
|
||||||
|
|
||||||
Stream s = webException.Response.GetResponseStream();
|
try
|
||||||
if (s is null)
|
|
||||||
return ex;
|
|
||||||
|
|
||||||
using StreamReader sr = new(s);
|
|
||||||
string result = sr.ReadToEnd();
|
|
||||||
|
|
||||||
YErrorResponse exception = JsonConvert.DeserializeObject<YErrorResponse>(result);
|
|
||||||
|
|
||||||
return exception ?? ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Вспомогательные функции
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public DefaultRequestProvider(AuthStorage authStorage) : base(authStorage)
|
|
||||||
{
|
{
|
||||||
|
return await client.SendAsync(message, completionOption);
|
||||||
}
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
|
||||||
|
|
||||||
#region IRequestProvider
|
|
||||||
|
|
||||||
public override Task<HttpResponseMessage> GetWebResponseAsync(HttpRequestMessage message,
|
|
||||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
|
|
||||||
{
|
{
|
||||||
try
|
// Пытаемся извлечь тело ошибки, если оно доступно
|
||||||
{
|
if (ex.InnerException == null)
|
||||||
HttpClient client = new(new SocketsHttpHandler
|
throw;
|
||||||
{
|
|
||||||
Proxy = storage.Context.WebProxy,
|
|
||||||
AutomaticDecompression = DecompressionMethods.GZip,
|
|
||||||
UseCookies = true,
|
|
||||||
CookieContainer = storage.Context.Cookies,
|
|
||||||
});
|
|
||||||
|
|
||||||
return client.SendAsync(message, completionOption);
|
throw new Exception($"Ошибка HTTP-запроса: {ex.Message}", ex);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw ProcessException(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion IRequestProvider
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
public MockRequestProvider(AuthStorage authStorage) : base(authStorage)
|
public MockRequestProvider(AuthStorage authStorage) : base(authStorage)
|
||||||
{
|
{
|
||||||
storage = authStorage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
43
YandexMusic.API/Common/Ynison/UpperSnakeCaseNamingPolicy.cs
Normal file
43
YandexMusic.API/Common/Ynison/UpperSnakeCaseNamingPolicy.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Common.Ynison;
|
||||||
|
|
||||||
|
/// <summary>Политика именования в формате UPPER_SNAKE_CASE (все буквы верхнего регистра, слова через подчёркивание).</summary>
|
||||||
|
public class UpperSnakeCaseNamingPolicy : SnakeCaseNamingPolicy
|
||||||
|
{
|
||||||
|
/// <summary>Преобразует имя свойства в формат UPPER_SNAKE_CASE.</summary>
|
||||||
|
public override string ConvertName(string name)
|
||||||
|
{
|
||||||
|
var snakeCase = base.ConvertName(name);
|
||||||
|
return snakeCase.ToUpperInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Базовая политика именования в формате snake_case (все буквы нижнего регистра, слова через подчёркивание).</summary>
|
||||||
|
public class SnakeCaseNamingPolicy : JsonNamingPolicy
|
||||||
|
{
|
||||||
|
/// <summary>Преобразует имя свойства в формат snake_case.</summary>
|
||||||
|
public override string ConvertName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
return name;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < name.Length; i++)
|
||||||
|
{
|
||||||
|
char c = name[i];
|
||||||
|
if (char.IsUpper(c))
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
sb.Append('_');
|
||||||
|
sb.Append(char.ToLowerInvariant(c));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace YandexMusic.API.Common.Ynison
|
|
||||||
{
|
|
||||||
public class UpperSnakeCaseNamingStrategy : SnakeCaseNamingStrategy
|
|
||||||
{
|
|
||||||
protected override string ResolvePropertyName(string name) => base.ResolvePropertyName(name).ToUpper();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,315 +1,183 @@
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
using YandexMusic.API.Models.Ynison;
|
using YandexMusic.API.Models.Ynison;
|
||||||
using YandexMusic.API.Models.Ynison.Messages;
|
using YandexMusic.API.Models.Ynison.Messages;
|
||||||
|
|
||||||
namespace YandexMusic.API.Common.Ynison
|
namespace YandexMusic.API.Common.Ynison;
|
||||||
|
|
||||||
|
/// <summary>Плеер для управления воспроизведением через протокол Ynison (WebSocket).</summary>
|
||||||
|
public class YnisonPlayer : IDisposable
|
||||||
{
|
{
|
||||||
public class YnisonPlayer : IDisposable
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
private readonly AuthStorage _storage;
|
||||||
|
private YnisonWebSocket? _redirector;
|
||||||
|
private YnisonWebSocket? _state;
|
||||||
|
|
||||||
|
/// <summary>API Яндекс Музыки.</summary>
|
||||||
|
public YandexMusicApi API { get; }
|
||||||
|
|
||||||
|
/// <summary>Текущее состояние плеера.</summary>
|
||||||
|
public YYnisonState? State { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Текущий проигрываемый трек.</summary>
|
||||||
|
public YTrack? Current => GetCurrentAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
/// <summary>Событие получения нового состояния.</summary>
|
||||||
|
public event EventHandler<ReceiveEventArgs>? OnReceive;
|
||||||
|
|
||||||
|
/// <summary>Событие закрытия соединения.</summary>
|
||||||
|
public event EventHandler<CloseEventArgs>? OnClose;
|
||||||
|
|
||||||
|
/// <summary>Аргументы события получения состояния.</summary>
|
||||||
|
public class ReceiveEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
#region Поля
|
/// <summary>Состояние плеера.</summary>
|
||||||
|
public YYnisonState State { get; init; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly JsonSerializerSettings jsonSettings = new()
|
/// <summary>Аргументы события закрытия соединения.</summary>
|
||||||
|
public class CloseEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>Статус закрытия.</summary>
|
||||||
|
public WebSocketCloseStatus? Status { get; init; }
|
||||||
|
/// <summary>Описание причины закрытия.</summary>
|
||||||
|
public string? Description { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal YnisonPlayer(YandexMusicApi api, AuthStorage authStorage)
|
||||||
|
{
|
||||||
|
API = api;
|
||||||
|
_storage = authStorage;
|
||||||
|
_jsonOptions = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
Converters = new List<JsonConverter> {
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
new StringEnumConverter(new UpperSnakeCaseNamingStrategy())
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
},
|
Converters = { new JsonStringEnumConverter(new UpperSnakeCaseNamingPolicy(), false) }
|
||||||
|
};
|
||||||
|
_redirector = new YnisonWebSocket();
|
||||||
|
_state = new YnisonWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
NullValueHandling = NullValueHandling.Ignore,
|
private string SerializeJson(object data) => JsonSerializer.Serialize(data, _jsonOptions);
|
||||||
ContractResolver = new DefaultContractResolver
|
|
||||||
|
private T Deserialize<T>(YYnisonMessageType messageType, string data)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<T>(data, _jsonOptions)
|
||||||
|
?? throw new JsonException("Десериализация вернула null");
|
||||||
|
}
|
||||||
|
|
||||||
|
private T DeserializeMessage<T>(YYnisonMessageType messageType, string data)
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(data);
|
||||||
|
if (doc.RootElement.TryGetProperty("error", out _))
|
||||||
|
{
|
||||||
|
var error = Deserialize<YYnisonErrorMessage>(YYnisonMessageType.Error, data);
|
||||||
|
throw error ?? new Exception("Ошибка десериализации ответа с ошибкой.");
|
||||||
|
}
|
||||||
|
return Deserialize<T>(messageType, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DefaultState()
|
||||||
|
{
|
||||||
|
var version = new YYnisonVersion
|
||||||
|
{
|
||||||
|
DeviceId = _storage.DeviceId,
|
||||||
|
Version = "0"
|
||||||
|
};
|
||||||
|
var fullState = new YYnisonUpdateFullStateMessage
|
||||||
|
{
|
||||||
|
UpdateFullState = new YYnisonFullState
|
||||||
{
|
{
|
||||||
// Важно! Унисон отдаёт данные в SnakeCase
|
Device = new YYnisonDevice
|
||||||
NamingStrategy = new SnakeCaseNamingStrategy()
|
{
|
||||||
|
Capabilities = new YYnisonDeviceCapabilities { CanBePlayer = true },
|
||||||
|
Info = new YYnisonDeviceInfo
|
||||||
|
{
|
||||||
|
DeviceId = _storage.DeviceId,
|
||||||
|
AppName = "Yandex Music API",
|
||||||
|
AppVersion = "0.0.1",
|
||||||
|
Type = "WEB",
|
||||||
|
Title = "YandexMusicAPI"
|
||||||
|
},
|
||||||
|
IsShadow = true
|
||||||
|
},
|
||||||
|
PlayerState = new YYnisonPlayerState
|
||||||
|
{
|
||||||
|
PlayerQueue = new YYnisonPlayerQueue { Version = version },
|
||||||
|
Status = new YYnisonPlayerStateStatus { Version = version }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return SerializeJson(fullState);
|
||||||
|
}
|
||||||
|
|
||||||
private AuthStorage storage;
|
private async Task<YTrack?> GetCurrentAsync()
|
||||||
private YnisonWebSocket redirector;
|
{
|
||||||
private YnisonWebSocket state;
|
if (State == null) return null;
|
||||||
|
int index = State.PlayerState.PlayerQueue.CurrentPlayableIndex;
|
||||||
|
if (index < 0 || index >= State.PlayerState.PlayerQueue.PlayableList.Count)
|
||||||
|
return null;
|
||||||
|
var item = State.PlayerState.PlayerQueue.PlayableList[index];
|
||||||
|
var response = await API.Track.GetAsync(_storage, item.PlayableId);
|
||||||
|
return response?.Result?.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion Поля
|
private async Task UpdateStateAsync()
|
||||||
|
{
|
||||||
#region Свойства
|
if (State == null) return;
|
||||||
|
var update = new YYnisonUpdatePlayerStateMessage
|
||||||
/// <summary>
|
|
||||||
/// API
|
|
||||||
/// </summary>
|
|
||||||
public YandexMusicApi API { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Состояние
|
|
||||||
/// </summary>
|
|
||||||
public YYnisonState State { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Текущий проигрываемый трек
|
|
||||||
/// </summary>
|
|
||||||
public YTrack Current => GetCurrent();
|
|
||||||
|
|
||||||
#endregion Свойства
|
|
||||||
|
|
||||||
#region События
|
|
||||||
|
|
||||||
public class ReceiveEventArgs
|
|
||||||
{
|
{
|
||||||
public YYnisonState State { get; internal set; }
|
UpdatePlayerState = State.PlayerState
|
||||||
}
|
};
|
||||||
|
update.UpdatePlayerState.Status.Version = new YYnisonVersion { DeviceId = _storage.DeviceId };
|
||||||
|
update.UpdatePlayerState.PlayerQueue.Version = new YYnisonVersion { DeviceId = _storage.DeviceId };
|
||||||
|
if (_state != null)
|
||||||
|
await _state.SendAsync(SerializeJson(update));
|
||||||
|
}
|
||||||
|
|
||||||
public delegate void OnReceiveEventHandler(YnisonPlayer player, ReceiveEventArgs args);
|
/// <summary>Подключается к Ynison и начинает получение состояния.</summary>
|
||||||
|
public async Task ConnectAsync()
|
||||||
/// <summary>
|
{
|
||||||
/// Получение данных
|
if (_redirector == null) throw new ObjectDisposedException(nameof(YnisonPlayer));
|
||||||
/// </summary>
|
await _redirector.ConnectAsync(_storage, "wss://ynison.music.yandex.ru/redirector.YnisonRedirectService/GetRedirectToYnison");
|
||||||
public event OnReceiveEventHandler OnReceive;
|
_redirector.OnReceive += async (socket, data) =>
|
||||||
|
|
||||||
|
|
||||||
public class CloseEventArgs
|
|
||||||
{
|
{
|
||||||
public WebSocketCloseStatus? Status { get; set; }
|
var redirectInfo = Deserialize<YYnisonRedirect>(YYnisonMessageType.Redirect, data.Data);
|
||||||
public string Description { get; set; }
|
if (_state == null) return;
|
||||||
}
|
if (_state.IsConnected) return;
|
||||||
|
await _state.ConnectAsync(_storage, $"wss://{redirectInfo.Host}/ynison_state.YnisonStateService/PutYnisonState", redirectInfo.RedirectTicket);
|
||||||
public delegate void OnCloseEventHandler(YnisonPlayer player, CloseEventArgs args);
|
_state.OnReceive += (s, d) =>
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Получение данных
|
|
||||||
/// </summary>
|
|
||||||
public event OnCloseEventHandler OnClose;
|
|
||||||
|
|
||||||
#endregion События
|
|
||||||
|
|
||||||
#region Вспомогательные функции
|
|
||||||
|
|
||||||
private string SerializeJson(object data)
|
|
||||||
{
|
|
||||||
return JsonConvert.SerializeObject(data, jsonSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
private T Deserialize<T>(YYnisonMessageType messageType, string data)
|
|
||||||
{
|
|
||||||
return storage.Debug != null
|
|
||||||
? storage.Debug.Deserialize<T>($"Ynison{messageType}", data, jsonSettings)
|
|
||||||
: JsonConvert.DeserializeObject<T>(data, jsonSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
private T DeserializeMessage<T>(YYnisonMessageType messageType, string data)
|
|
||||||
{
|
|
||||||
JObject o = JObject.Parse(data);
|
|
||||||
// Сообщение с ошибкой
|
|
||||||
if (o.ContainsKey("error"))
|
|
||||||
{
|
{
|
||||||
YYnisonErrorMessage exception = Deserialize<YYnisonErrorMessage>(YYnisonMessageType.Error, data);
|
var message = DeserializeMessage<YYnisonState>(YYnisonMessageType.State, d.Data);
|
||||||
throw exception ?? new Exception("Ошибка десериализации ответа с ошибкой.");
|
State = message;
|
||||||
}
|
OnReceive?.Invoke(this, new ReceiveEventArgs { State = State });
|
||||||
|
|
||||||
return Deserialize<T>(messageType, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string DefaultState()
|
|
||||||
{
|
|
||||||
YYnisonVersion version = new()
|
|
||||||
{
|
|
||||||
DeviceId = storage.DeviceId,
|
|
||||||
Version = "0"
|
|
||||||
};
|
};
|
||||||
|
_state.OnClose += (s, args) =>
|
||||||
YYnisonUpdateFullStateMessage fullState = new()
|
|
||||||
{
|
{
|
||||||
UpdateFullState = new()
|
OnClose?.Invoke(this, new CloseEventArgs { Status = args.Status, Description = args.Description });
|
||||||
{
|
|
||||||
Device = new()
|
|
||||||
{
|
|
||||||
Capabilities = new()
|
|
||||||
{
|
|
||||||
CanBePlayer = true
|
|
||||||
},
|
|
||||||
Info = new()
|
|
||||||
{
|
|
||||||
DeviceId = storage.DeviceId,
|
|
||||||
AppName = "Yandex Music API",
|
|
||||||
AppVersion = "0.0.1",
|
|
||||||
Type = "WEB",
|
|
||||||
Title = "YandexMusicAPI"
|
|
||||||
},
|
|
||||||
IsShadow = true
|
|
||||||
},
|
|
||||||
PlayerState = new()
|
|
||||||
{
|
|
||||||
PlayerQueue = new()
|
|
||||||
{
|
|
||||||
Version = version
|
|
||||||
},
|
|
||||||
Status = new()
|
|
||||||
{
|
|
||||||
Version = version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
_ = _state.BeginReceiveAsync();
|
||||||
|
await _state.SendAsync(DefaultState());
|
||||||
|
};
|
||||||
|
await _redirector.BeginReceiveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
return SerializeJson(fullState);
|
/// <summary>Отключается от Ynison.</summary>
|
||||||
}
|
public async Task DisconnectAsync()
|
||||||
|
{
|
||||||
|
if (_state != null) await _state.StopReceiveAsync();
|
||||||
|
if (_redirector != null) await _redirector.StopReceiveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private YTrack GetCurrent()
|
/// <summary>Освобождает ресурсы.</summary>
|
||||||
{
|
public void Dispose()
|
||||||
if (State == null)
|
{
|
||||||
return null;
|
_redirector?.Dispose();
|
||||||
|
_state?.Dispose();
|
||||||
int index = State.PlayerState.PlayerQueue.CurrentPlayableIndex;
|
_redirector = null;
|
||||||
if (index < 0 || index > State.PlayerState.PlayerQueue.PlayableList.Count)
|
_state = null;
|
||||||
return null;
|
GC.SuppressFinalize(this);
|
||||||
|
|
||||||
YYnisonPlayableItem item = State.PlayerState.PlayerQueue.PlayableList[index];
|
|
||||||
|
|
||||||
return API.Track.Get(storage, item.PlayableId)
|
|
||||||
.Result
|
|
||||||
.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateState()
|
|
||||||
{
|
|
||||||
YYnisonUpdatePlayerStateMessage update = new()
|
|
||||||
{
|
|
||||||
UpdatePlayerState = State.PlayerState
|
|
||||||
};
|
|
||||||
|
|
||||||
update.UpdatePlayerState.Status.Version = new()
|
|
||||||
{
|
|
||||||
DeviceId = storage.DeviceId
|
|
||||||
};
|
|
||||||
|
|
||||||
update.UpdatePlayerState.PlayerQueue.Version = new()
|
|
||||||
{
|
|
||||||
DeviceId = storage.DeviceId
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
state.Send(SerializeJson(update));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Вспомогательные функции
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region Подключение
|
|
||||||
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
redirector.Connect(storage, "wss://ynison.music.yandex.ru/redirector.YnisonRedirectService/GetRedirectToYnison");
|
|
||||||
redirector.OnReceive += (socket, data) =>
|
|
||||||
{
|
|
||||||
YYnisonRedirect redirectInfo = Deserialize<YYnisonRedirect>(YYnisonMessageType.Redirect, data.Data);
|
|
||||||
|
|
||||||
if (state.IsConnected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
state.Connect(storage, $"wss://{redirectInfo.Host}/ynison_state.YnisonStateService/PutYnisonState", redirectInfo.RedirectTicket);
|
|
||||||
state.OnReceive += (s, d) =>
|
|
||||||
{
|
|
||||||
YYnisonState message = DeserializeMessage<YYnisonState>(YYnisonMessageType.State, d.Data);
|
|
||||||
|
|
||||||
State = message;
|
|
||||||
|
|
||||||
OnReceive?.Invoke(this, new ReceiveEventArgs
|
|
||||||
{
|
|
||||||
State = State
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
state.OnClose += (s, args) =>
|
|
||||||
{
|
|
||||||
OnClose?.Invoke(this, new CloseEventArgs
|
|
||||||
{
|
|
||||||
Status = args.Status,
|
|
||||||
Description = args.Description
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
state.BeginReceive();
|
|
||||||
// Отправка изначального состояния
|
|
||||||
state.Send(DefaultState());
|
|
||||||
};
|
|
||||||
|
|
||||||
redirector.BeginReceive();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Disconnect()
|
|
||||||
{
|
|
||||||
state?.StopReceive();
|
|
||||||
redirector?.StopReceive();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Подключение
|
|
||||||
|
|
||||||
#region Плеер
|
|
||||||
|
|
||||||
/*
|
|
||||||
public void Play()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Next()
|
|
||||||
{
|
|
||||||
List<YYnisonPlayableItem> list = State.PlayerState.PlayerQueue.PlayableList;
|
|
||||||
|
|
||||||
if (State.PlayerState.PlayerQueue.EntityType == YYnisonEntityType.Radio)
|
|
||||||
{
|
|
||||||
YYnisonPlayableItem next = State.PlayerState.PlayerQueue.Queue.WaveQueue.RecommendedPlayableList
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
list.RemoveAt(0);
|
|
||||||
list.Add(next);
|
|
||||||
|
|
||||||
UpdateState();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (State.PlayerState.PlayerQueue.CurrentPlayableIndex < list.Count - 1)
|
|
||||||
{
|
|
||||||
State.PlayerState.PlayerQueue.CurrentPlayableIndex++;
|
|
||||||
UpdateState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Previous()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#endregion Плеер
|
|
||||||
|
|
||||||
internal YnisonPlayer(YandexMusicApi api, AuthStorage authStorage)
|
|
||||||
{
|
|
||||||
API = api;
|
|
||||||
storage = authStorage;
|
|
||||||
|
|
||||||
redirector = new();
|
|
||||||
state = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
redirector?.StopReceive();
|
|
||||||
redirector?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion IDisposable
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,179 +1,142 @@
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace YandexMusic.API.Common.Ynison
|
namespace YandexMusic.API.Common.Ynison;
|
||||||
|
|
||||||
|
/// <summary>WebSocket-клиент для взаимодействия с протоколом Ynison.</summary>
|
||||||
|
public class YnisonWebSocket : IDisposable
|
||||||
{
|
{
|
||||||
public class YnisonWebSocket : IDisposable
|
private readonly ClientWebSocket _socketClient = new();
|
||||||
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
private CancellationToken _cancellationToken;
|
||||||
|
private readonly StringBuilder _data = new();
|
||||||
|
private const int BufferSize = 4096;
|
||||||
|
|
||||||
|
/// <summary>Флаг, указывает, открыто ли соединение.</summary>
|
||||||
|
public bool IsConnected => _socketClient.State == WebSocketState.Open;
|
||||||
|
|
||||||
|
/// <summary>Событие получения сообщения.</summary>
|
||||||
|
public event EventHandler<ReceiveEventArgs>? OnReceive;
|
||||||
|
|
||||||
|
/// <summary>Событие закрытия соединения.</summary>
|
||||||
|
public event EventHandler<CloseEventArgs>? OnClose;
|
||||||
|
|
||||||
|
/// <summary>Аргументы события получения данных.</summary>
|
||||||
|
public class ReceiveEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
#region Поля
|
/// <summary>Полученные данные (JSON-строка).</summary>
|
||||||
|
public string Data { get; init; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly JsonSerializerSettings jsonSettings = new()
|
/// <summary>Аргументы события закрытия соединения.</summary>
|
||||||
|
public class CloseEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>Статус закрытия.</summary>
|
||||||
|
public WebSocketCloseStatus? Status { get; init; }
|
||||||
|
/// <summary>Описание причины закрытия.</summary>
|
||||||
|
public string? Description { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetProtocolData(string deviceId, string? redirectTicket)
|
||||||
|
{
|
||||||
|
var deviceInfo = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
Converters = new List<JsonConverter> {
|
{ "app_name", "Chrome" },
|
||||||
new StringEnumConverter {
|
{ "type", 1 }
|
||||||
NamingStrategy = new CamelCaseNamingStrategy()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
NullValueHandling = NullValueHandling.Ignore
|
|
||||||
};
|
};
|
||||||
|
var protocol = new Dictionary<string, string>
|
||||||
private readonly ClientWebSocket socketClient = new();
|
|
||||||
|
|
||||||
private CancellationTokenSource cancellationTokenSource = new();
|
|
||||||
private CancellationToken cancellation;
|
|
||||||
|
|
||||||
private readonly StringBuilder data = new();
|
|
||||||
private readonly int size = 4096;
|
|
||||||
|
|
||||||
#endregion Поля
|
|
||||||
|
|
||||||
#region Свойства
|
|
||||||
|
|
||||||
public bool IsConnected => socketClient.State == WebSocketState.Open;
|
|
||||||
|
|
||||||
#endregion Свойства
|
|
||||||
|
|
||||||
#region События
|
|
||||||
|
|
||||||
public class ReceiveEventArgs
|
|
||||||
{
|
{
|
||||||
public string Data { get; internal set; }
|
{ "Ynison-Device-Id", deviceId },
|
||||||
}
|
{ "Ynison-Device-Info", JsonSerializer.Serialize(deviceInfo) }
|
||||||
|
};
|
||||||
|
if (!string.IsNullOrEmpty(redirectTicket))
|
||||||
|
protocol.Add("Ynison-Redirect-Ticket", redirectTicket);
|
||||||
|
return JsonSerializer.Serialize(protocol);
|
||||||
|
}
|
||||||
|
|
||||||
public delegate void OnReceiveEventHandler(YnisonWebSocket socket, ReceiveEventArgs args);
|
private async Task<string> ReadSocketContentAsync()
|
||||||
/// <summary>
|
{
|
||||||
/// Получение данных
|
var buffer = new byte[BufferSize];
|
||||||
/// </summary>
|
WebSocketReceiveResult result;
|
||||||
public event OnReceiveEventHandler OnReceive;
|
do
|
||||||
|
|
||||||
public class CloseEventArgs
|
|
||||||
{
|
{
|
||||||
public WebSocketCloseStatus? Status { get; set; }
|
result = await _socketClient.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationToken);
|
||||||
public string Description { get; set; }
|
_data.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
|
||||||
}
|
} while (!result.EndOfMessage);
|
||||||
|
return _data.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
public delegate void OnCloseEventHandler(YnisonWebSocket socket, CloseEventArgs args);
|
/// <summary>Подключается к WebSocket.</summary>
|
||||||
/// <summary>
|
/// <param name="storage">Хранилище авторизации.</param>
|
||||||
/// Закрытие соединения
|
/// <param name="url">URL WebSocket.</param>
|
||||||
/// </summary>
|
/// <param name="redirectTicket">Тикет перенаправления (опционально).</param>
|
||||||
public event OnCloseEventHandler OnClose;
|
public async Task ConnectAsync(AuthStorage storage, string url, string? redirectTicket = null)
|
||||||
|
{
|
||||||
|
_socketClient.Options.AddSubProtocol("Bearer");
|
||||||
|
var protocolData = GetProtocolData(storage.DeviceId, redirectTicket);
|
||||||
|
_socketClient.Options.SetRequestHeader("Sec-WebSocket-Protocol", $"Bearer, v2, {protocolData}");
|
||||||
|
_socketClient.Options.SetRequestHeader("Origin", "https://music.yandex.ru");
|
||||||
|
_socketClient.Options.SetRequestHeader("Authorization", $"OAuth {storage.Token}");
|
||||||
|
_socketClient.Options.Proxy = storage.Context.WebProxy;
|
||||||
|
|
||||||
#endregion События
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
_cancellationToken = _cancellationTokenSource.Token;
|
||||||
|
|
||||||
#region Вспомогательные функции
|
await _socketClient.ConnectAsync(new Uri(url), CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
private string SerializeJson(object obj)
|
/// <summary>Начинает асинхронный приём сообщений.</summary>
|
||||||
|
public async Task BeginReceiveAsync()
|
||||||
|
{
|
||||||
|
if (_socketClient.State != WebSocketState.Open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return JsonConvert.SerializeObject(obj, jsonSettings);
|
while (!_cancellationToken.IsCancellationRequested && _socketClient.State == WebSocketState.Open)
|
||||||
}
|
|
||||||
|
|
||||||
private string GetProtocolData(string deviceId, string redirectTicket)
|
|
||||||
{
|
|
||||||
Dictionary<string, object> deviceInfo = new() {
|
|
||||||
{ "app_name", "Chrome" },
|
|
||||||
{ "type", 1 }
|
|
||||||
};
|
|
||||||
|
|
||||||
Dictionary<string, string> protocol = new() {
|
|
||||||
{ "Ynison-Device-Id", deviceId },
|
|
||||||
{ "Ynison-Device-Info", SerializeJson(deviceInfo) }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(redirectTicket))
|
|
||||||
protocol.Add("Ynison-Redirect-Ticket", redirectTicket);
|
|
||||||
|
|
||||||
return SerializeJson(protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> ReadSocketContent()
|
|
||||||
{
|
|
||||||
byte[] buffer = new byte[size];
|
|
||||||
WebSocketReceiveResult result;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
result = await socketClient.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
var content = await ReadSocketContentAsync();
|
||||||
data.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
|
OnReceive?.Invoke(this, new ReceiveEventArgs { Data = content });
|
||||||
} while (!result.EndOfMessage);
|
_data.Clear();
|
||||||
|
}
|
||||||
return data.ToString();
|
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
#endregion Вспомогательные функции
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public bool Connect(AuthStorage storage, string url, string redirectTicket = null)
|
|
||||||
{
|
{
|
||||||
socketClient.Options.AddSubProtocol("Bearer");
|
// Ожидаемая отмена
|
||||||
|
|
||||||
socketClient.Options.SetRequestHeader("Sec-WebSocket-Protocol", $"Bearer, v2, {GetProtocolData(storage.DeviceId, redirectTicket)}");
|
|
||||||
socketClient.Options.SetRequestHeader("Origin", "https://music.yandex.ru");
|
|
||||||
socketClient.Options.SetRequestHeader("Authorization", $"OAuth {storage.Token}");
|
|
||||||
|
|
||||||
socketClient.Options.Proxy = storage.Context.WebProxy;
|
|
||||||
|
|
||||||
socketClient.ConnectAsync(new Uri(url), CancellationToken.None)
|
|
||||||
.GetAwaiter()
|
|
||||||
.GetResult();
|
|
||||||
|
|
||||||
cancellation = cancellationTokenSource.Token;
|
|
||||||
|
|
||||||
return socketClient.State == WebSocketState.Open;
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
public async Task BeginReceive()
|
|
||||||
{
|
{
|
||||||
if (socketClient.State != WebSocketState.Open)
|
var closeStatus = _socketClient.CloseStatus;
|
||||||
return;
|
var closeDesc = _socketClient.CloseStatusDescription;
|
||||||
|
OnClose?.Invoke(this, new CloseEventArgs { Status = closeStatus, Description = closeDesc });
|
||||||
do
|
if (_socketClient.State == WebSocketState.Open)
|
||||||
{
|
await _socketClient.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||||
string content = await ReadSocketContent();
|
|
||||||
OnReceive?.Invoke(this, new ReceiveEventArgs
|
|
||||||
{
|
|
||||||
Data = content
|
|
||||||
});
|
|
||||||
|
|
||||||
data.Clear();
|
|
||||||
} while (!cancellation.IsCancellationRequested && socketClient.State == WebSocketState.Open);
|
|
||||||
|
|
||||||
OnClose?.Invoke(this, new CloseEventArgs
|
|
||||||
{
|
|
||||||
Status = socketClient.CloseStatus,
|
|
||||||
Description = socketClient.CloseStatusDescription
|
|
||||||
});
|
|
||||||
|
|
||||||
await socketClient.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Отправляет JSON-сообщение.</summary>
|
||||||
|
/// <param name="json">JSON-строка.</param>
|
||||||
|
public async ValueTask SendAsync(string json)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
|
await _socketClient.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, _cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public ValueTask Send(string json)
|
/// <summary>Останавливает приём сообщений.</summary>
|
||||||
|
public async Task StopReceiveAsync()
|
||||||
|
{
|
||||||
|
if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
ReadOnlyMemory<byte> message = new(Encoding.UTF8.GetBytes(json));
|
await _cancellationTokenSource.CancelAsync();
|
||||||
return socketClient.SendAsync(message, WebSocketMessageType.Text, false, CancellationToken.None);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task StopReceive()
|
/// <summary>Освобождает ресурсы.</summary>
|
||||||
{
|
public void Dispose()
|
||||||
if (socketClient.State != WebSocketState.Open)
|
{
|
||||||
return Task.CompletedTask;
|
_cancellationTokenSource?.Dispose();
|
||||||
|
_socketClient.Dispose();
|
||||||
cancellationTokenSource.Cancel(false);
|
GC.SuppressFinalize(this);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
socketClient?.Dispose();
|
|
||||||
cancellationTokenSource?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion IDisposable
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,24 @@
|
|||||||
using YandexMusic.API.Models.Album;
|
using YandexMusic.API.Models.Album;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для альбома
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YAlbumExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static YAlbum WithTracks(this YAlbum album)
|
||||||
/// Методы-расширения для альбома
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YAlbumExtensions
|
|
||||||
{
|
{
|
||||||
public static YAlbum WithTracks(this YAlbum album)
|
return WithTracksAsync(album).GetAwaiter().GetResult();
|
||||||
{
|
}
|
||||||
return WithTracksAsync(album).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string AddLike(this YAlbum album)
|
public static string AddLike(this YAlbum album)
|
||||||
{
|
{
|
||||||
return AddLikeAsync(album).GetAwaiter().GetResult();
|
return AddLikeAsync(album).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string RemoveLike(this YAlbum album)
|
public static string RemoveLike(this YAlbum album)
|
||||||
{
|
{
|
||||||
return RemoveLikeAsync(album).GetAwaiter().GetResult();
|
return RemoveLikeAsync(album).GetAwaiter().GetResult();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
using YandexMusic.API.Models.Album;
|
using YandexMusic.API.Models.Album;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для альбома
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YAlbumExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static async Task<YAlbum> WithTracksAsync(this YAlbum album)
|
||||||
/// Методы-расширения для альбома
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YAlbumExtensions
|
|
||||||
{
|
{
|
||||||
public static async Task<YAlbum> WithTracksAsync(this YAlbum album)
|
return album.Volumes != null
|
||||||
{
|
? album
|
||||||
return album.Volumes != null
|
: (await album.Context.API.Album.GetAsync(album.Context.Storage, album.Id))
|
||||||
? album
|
|
||||||
: (await album.Context.API.Album.GetAsync(album.Context.Storage, album.Id))
|
|
||||||
.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<string> AddLikeAsync(this YAlbum album)
|
|
||||||
{
|
|
||||||
return (await album.Context.API.Library.AddAlbumLikeAsync(album.Context.Storage, album))
|
|
||||||
.Result;
|
.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> RemoveLikeAsync(this YAlbum album)
|
public static async Task<string> AddLikeAsync(this YAlbum album)
|
||||||
{
|
{
|
||||||
return (await album.Context.API.Library.RemoveAlbumLikeAsync(album.Context.Storage, album))
|
return (await album.Context.API.Library.AddAlbumLikeAsync(album.Context.Storage, album))
|
||||||
.Result;
|
.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<string> RemoveLikeAsync(this YAlbum album)
|
||||||
|
{
|
||||||
|
return (await album.Context.API.Library.RemoveAlbumLikeAsync(album.Context.Storage, album))
|
||||||
|
.Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
using YandexMusic.API.Models.Artist;
|
using YandexMusic.API.Models.Artist;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для исполнителя
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YArtistExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static YArtistBriefInfo BriefInfo(this YArtist artist)
|
||||||
/// Методы-расширения для исполнителя
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YArtistExtensions
|
|
||||||
{
|
{
|
||||||
public static YArtistBriefInfo BriefInfo(this YArtist artist)
|
return BriefInfoAsync(artist).GetAwaiter().GetResult();
|
||||||
{
|
}
|
||||||
return BriefInfoAsync(artist).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static YTracksPage GetTracks(this YArtist artist, int page = 0, int pageSize = 20)
|
public static YTracksPage GetTracks(this YArtist artist, int page = 0, int pageSize = 20)
|
||||||
{
|
{
|
||||||
return GetTracksAsync(artist, page, pageSize).GetAwaiter().GetResult();
|
return GetTracksAsync(artist, page, pageSize).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<YTrack> GetAllTracks(this YArtist artist)
|
public static List<YTrack> GetAllTracks(this YArtist artist)
|
||||||
{
|
{
|
||||||
return GetAllTracksAsync(artist).GetAwaiter().GetResult();
|
return GetAllTracksAsync(artist).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string AddLike(this YArtist artist)
|
public static string AddLike(this YArtist artist)
|
||||||
{
|
{
|
||||||
return AddLikeAsync(artist).GetAwaiter().GetResult();
|
return AddLikeAsync(artist).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string RemoveLike(this YArtist artist)
|
public static string RemoveLike(this YArtist artist)
|
||||||
{
|
{
|
||||||
return RemoveLikeAsync(artist).GetAwaiter().GetResult();
|
return RemoveLikeAsync(artist).GetAwaiter().GetResult();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,40 @@
|
|||||||
using YandexMusic.API.Models.Artist;
|
using YandexMusic.API.Models.Artist;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для исполнителя
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YArtistExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static async Task<YArtistBriefInfo> BriefInfoAsync(this YArtist artist)
|
||||||
/// Методы-расширения для исполнителя
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YArtistExtensions
|
|
||||||
{
|
{
|
||||||
public static async Task<YArtistBriefInfo> BriefInfoAsync(this YArtist artist)
|
return (await artist.Context.API.Artist.GetAsync(artist.Context.Storage, artist.Id))
|
||||||
{
|
.Result;
|
||||||
return (await artist.Context.API.Artist.GetAsync(artist.Context.Storage, artist.Id))
|
}
|
||||||
.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<YTracksPage> GetTracksAsync(this YArtist artist, int page = 0, int pageSize = 20)
|
public static async Task<YTracksPage> GetTracksAsync(this YArtist artist, int page = 0, int pageSize = 20)
|
||||||
{
|
{
|
||||||
return (await artist.Context.API.Artist.GetTracksAsync(artist.Context.Storage, artist.Id, page, pageSize))
|
return (await artist.Context.API.Artist.GetTracksAsync(artist.Context.Storage, artist.Id, page, pageSize))
|
||||||
.Result;
|
.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<List<YTrack>> GetAllTracksAsync(this YArtist artist)
|
public static async Task<List<YTrack>> GetAllTracksAsync(this YArtist artist)
|
||||||
{
|
{
|
||||||
return (await artist.Context.API.Artist.GetAllTracksAsync(artist.Context.Storage, artist.Id))
|
return (await artist.Context.API.Artist.GetAllTracksAsync(artist.Context.Storage, artist.Id))
|
||||||
.Result.Tracks;
|
.Result.Tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> AddLikeAsync(this YArtist artist)
|
public static async Task<string> AddLikeAsync(this YArtist artist)
|
||||||
{
|
{
|
||||||
return (await artist.Context.API.Library.AddArtistLikeAsync(artist.Context.Storage, artist))
|
return (await artist.Context.API.Library.AddArtistLikeAsync(artist.Context.Storage, artist))
|
||||||
.Result;
|
.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> RemoveLikeAsync(this YArtist artist)
|
public static async Task<string> RemoveLikeAsync(this YArtist artist)
|
||||||
{
|
{
|
||||||
return (await artist.Context.API.Library.RemoveArtistLikeAsync(artist.Context.Storage, artist))
|
return (await artist.Context.API.Library.RemoveArtistLikeAsync(artist.Context.Storage, artist))
|
||||||
.Result;
|
.Result;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,55 @@
|
|||||||
using YandexMusic.API.Models.Playlist;
|
using YandexMusic.API.Models.Playlist;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для плейлиста
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YPlaylistExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
private static bool CheckUser(YPlaylist playlist)
|
||||||
/// Методы-расширения для плейлиста
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YPlaylistExtensions
|
|
||||||
{
|
{
|
||||||
private static bool CheckUser(YPlaylist playlist)
|
return playlist.Owner.Uid == playlist.Context.Storage.User.Uid;
|
||||||
{
|
}
|
||||||
return playlist.Owner.Uid == playlist.Context.Storage.User.Uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static YPlaylist WithTracks(this YPlaylist playlist)
|
public static YPlaylist WithTracks(this YPlaylist playlist)
|
||||||
{
|
{
|
||||||
return WithTracksAsync(playlist).GetAwaiter().GetResult();
|
return WithTracksAsync(playlist).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string AddLike(this YPlaylist playlist)
|
public static string AddLike(this YPlaylist playlist)
|
||||||
{
|
{
|
||||||
return AddLikeAsync(playlist).GetAwaiter().GetResult();
|
return AddLikeAsync(playlist).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string RemoveLike(this YPlaylist playlist)
|
public static string RemoveLike(this YPlaylist playlist)
|
||||||
{
|
{
|
||||||
return RemoveLikeAsync(playlist).GetAwaiter().GetResult();
|
return RemoveLikeAsync(playlist).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YPlaylist Rename(this YPlaylist playlist, string newName)
|
public static YPlaylist Rename(this YPlaylist playlist, string newName)
|
||||||
{
|
{
|
||||||
return RenameAsync(playlist, newName).GetAwaiter().GetResult();
|
return RenameAsync(playlist, newName).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Delete(this YPlaylist playlist)
|
public static bool Delete(this YPlaylist playlist)
|
||||||
{
|
{
|
||||||
return DeleteAsync(playlist).GetAwaiter().GetResult();
|
return DeleteAsync(playlist).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YPlaylist InsertTracks(this YPlaylist playlist, params YTrack[] tracks)
|
public static YPlaylist InsertTracks(this YPlaylist playlist, params YTrack[] tracks)
|
||||||
{
|
{
|
||||||
return InsertTracksAsync(playlist, tracks).GetAwaiter().GetResult();
|
return InsertTracksAsync(playlist, tracks).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YPlaylist RemoveTracks(this YPlaylist playlist, params YTrack[] tracks)
|
public static YPlaylist RemoveTracks(this YPlaylist playlist, params YTrack[] tracks)
|
||||||
{
|
{
|
||||||
return RemoveTracksAsync(playlist, tracks).GetAwaiter().GetResult();
|
return RemoveTracksAsync(playlist, tracks).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool UploadTracks(this YPlaylist playlist, string filePath, string fileName)
|
public static bool UploadTracks(this YPlaylist playlist, string filePath, string fileName)
|
||||||
{
|
{
|
||||||
return UploadTracksAsync(playlist, filePath, fileName).GetAwaiter().GetResult();
|
return UploadTracksAsync(playlist, filePath, fileName).GetAwaiter().GetResult();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,71 @@
|
|||||||
using YandexMusic.API.Models.Playlist;
|
using YandexMusic.API.Models.Playlist;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для плейлиста
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YPlaylistExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static async Task<YPlaylist> WithTracksAsync(this YPlaylist playlist)
|
||||||
/// Методы-расширения для плейлиста
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YPlaylistExtensions
|
|
||||||
{
|
{
|
||||||
public static async Task<YPlaylist> WithTracksAsync(this YPlaylist playlist)
|
return playlist.Tracks != null
|
||||||
{
|
? playlist
|
||||||
return playlist.Tracks != null
|
: (await playlist.Context.API.Playlist.GetAsync(playlist.Context.Storage, playlist))
|
||||||
? playlist
|
|
||||||
: (await playlist.Context.API.Playlist.GetAsync(playlist.Context.Storage, playlist))
|
|
||||||
.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<string> AddLikeAsync(this YPlaylist playlist)
|
|
||||||
{
|
|
||||||
return (await playlist.Context.API.Library.AddPlaylistLikeAsync(playlist.Context.Storage, playlist))
|
|
||||||
.Result;
|
.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> RemoveLikeAsync(this YPlaylist playlist)
|
public static async Task<string> AddLikeAsync(this YPlaylist playlist)
|
||||||
{
|
{
|
||||||
return (await playlist.Context.API.Library.RemovePlaylistLikeAsync(playlist.Context.Storage, playlist))
|
return (await playlist.Context.API.Library.AddPlaylistLikeAsync(playlist.Context.Storage, playlist))
|
||||||
.Result;
|
.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<YPlaylist> RenameAsync(this YPlaylist playlist, string newName)
|
public static async Task<string> RemoveLikeAsync(this YPlaylist playlist)
|
||||||
{
|
{
|
||||||
return CheckUser(playlist)
|
return (await playlist.Context.API.Library.RemovePlaylistLikeAsync(playlist.Context.Storage, playlist))
|
||||||
? (await playlist.Context.API.Playlist.RenameAsync(playlist.Context.Storage, playlist, newName))
|
.Result;
|
||||||
.Result
|
}
|
||||||
: playlist;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<bool> DeleteAsync(this YPlaylist playlist)
|
public static async Task<YPlaylist> RenameAsync(this YPlaylist playlist, string newName)
|
||||||
{
|
{
|
||||||
return CheckUser(playlist) && await playlist.Context.API.Playlist.DeleteAsync(playlist.Context.Storage, playlist);
|
return CheckUser(playlist)
|
||||||
}
|
? (await playlist.Context.API.Playlist.RenameAsync(playlist.Context.Storage, playlist, newName))
|
||||||
|
.Result
|
||||||
|
: playlist;
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<YPlaylist> InsertTracksAsync(this YPlaylist playlist, params YTrack[] tracks)
|
public static async Task<bool> DeleteAsync(this YPlaylist playlist)
|
||||||
{
|
{
|
||||||
return CheckUser(playlist)
|
return CheckUser(playlist) && await playlist.Context.API.Playlist.DeleteAsync(playlist.Context.Storage, playlist);
|
||||||
? (await playlist.Context.API.Playlist.InsertTracksAsync(playlist.Context.Storage, playlist, tracks))
|
}
|
||||||
.Result
|
|
||||||
: playlist;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<YPlaylist> RemoveTracksAsync(this YPlaylist playlist, params YTrack[] tracks)
|
public static async Task<YPlaylist> InsertTracksAsync(this YPlaylist playlist, params YTrack[] tracks)
|
||||||
{
|
{
|
||||||
return CheckUser(playlist)
|
return CheckUser(playlist)
|
||||||
? (await playlist.Context.API.Playlist.DeleteTracksAsync(playlist.Context.Storage, playlist, tracks))
|
? (await playlist.Context.API.Playlist.InsertTracksAsync(playlist.Context.Storage, playlist, tracks))
|
||||||
.Result
|
.Result
|
||||||
: playlist;
|
: playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<bool> UploadTracksAsync(this YPlaylist playlist, string filePath, string fileName)
|
public static async Task<YPlaylist> RemoveTracksAsync(this YPlaylist playlist, params YTrack[] tracks)
|
||||||
{
|
{
|
||||||
if (!CheckUser(playlist))
|
return CheckUser(playlist)
|
||||||
return false;
|
? (await playlist.Context.API.Playlist.DeleteTracksAsync(playlist.Context.Storage, playlist, tracks))
|
||||||
|
.Result
|
||||||
|
: playlist;
|
||||||
|
}
|
||||||
|
|
||||||
string target = (await playlist.Context.API.UserGeneratedContent.GetUgcUploadLinkAsync(playlist.Context.Storage, playlist, fileName))
|
public static async Task<bool> UploadTracksAsync(this YPlaylist playlist, string filePath, string fileName)
|
||||||
.PostTarget;
|
{
|
||||||
|
if (!CheckUser(playlist))
|
||||||
|
return false;
|
||||||
|
|
||||||
return (await playlist.Context.API.UserGeneratedContent.UploadUgcTrackAsync(playlist.Context.Storage, target, filePath))
|
string target = (await playlist.Context.API.UserGeneratedContent.GetUgcUploadLinkAsync(playlist.Context.Storage, playlist, fileName))
|
||||||
.Result == "CREATED";
|
.PostTarget;
|
||||||
}
|
|
||||||
|
return (await playlist.Context.API.UserGeneratedContent.UploadUgcTrackAsync(playlist.Context.Storage, target, filePath))
|
||||||
|
.Result == "CREATED";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
using YandexMusic.API.Models.Radio;
|
using YandexMusic.API.Models.Radio;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для радиостанции
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YStationResultExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static List<YSequenceItem> GetTracks(this YStation station, string prevTrackId = "")
|
||||||
/// Методы-расширения для радиостанции
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YStationResultExtensions
|
|
||||||
{
|
{
|
||||||
public static List<YSequenceItem> GetTracks(this YStation station, string prevTrackId = "")
|
return GetTracksAsync(station, prevTrackId).GetAwaiter().GetResult();
|
||||||
{
|
}
|
||||||
return GetTracksAsync(station, prevTrackId).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string SetSettings2(this YStation station, YStationSettings2 settings)
|
public static string SetSettings2(this YStation station, YStationSettings2 settings)
|
||||||
{
|
{
|
||||||
return SetSettings2Async(station, settings).GetAwaiter().GetResult();
|
return SetSettings2Async(station, settings).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SendFeedBack(this YStation station, YStationFeedbackType type, YTrack track = null, string batchId = "", double totalPlayedSeconds = 0)
|
public static string SendFeedBack(this YStation station, YStationFeedbackType type, YTrack track = null, string batchId = "", double totalPlayedSeconds = 0)
|
||||||
{
|
{
|
||||||
return SendFeedBackAsync(station, type, track, batchId, totalPlayedSeconds).GetAwaiter().GetResult();
|
return SendFeedBackAsync(station, type, track, batchId, totalPlayedSeconds).GetAwaiter().GetResult();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
using YandexMusic.API.Models.Radio;
|
using YandexMusic.API.Models.Radio;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для радиостанции
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YStationResultExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static async Task<List<YSequenceItem>> GetTracksAsync(this YStation station, string prevTrackId = "")
|
||||||
/// Методы-расширения для радиостанции
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YStationResultExtensions
|
|
||||||
{
|
{
|
||||||
public static async Task<List<YSequenceItem>> GetTracksAsync(this YStation station, string prevTrackId = "")
|
return (await station.Context.API.Radio.GetStationTracksAsync(station.Context.Storage, station, prevTrackId))
|
||||||
{
|
.Result.Sequence;
|
||||||
return (await station.Context.API.Radio.GetStationTracksAsync(station.Context.Storage, station, prevTrackId))
|
}
|
||||||
.Result.Sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<string> SetSettings2Async(this YStation station, YStationSettings2 settings)
|
public static async Task<string> SetSettings2Async(this YStation station, YStationSettings2 settings)
|
||||||
{
|
{
|
||||||
return (await station.Context.API.Radio.SetStationSettings2Async(station.Context.Storage, station, settings))
|
return (await station.Context.API.Radio.SetStationSettings2Async(station.Context.Storage, station, settings))
|
||||||
.Result;
|
.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<string> SendFeedBackAsync(this YStation station, YStationFeedbackType type, YTrack track = null, string batchId = "", double totalPlayedSeconds = 0)
|
public static Task<string> SendFeedBackAsync(this YStation station, YStationFeedbackType type, YTrack track = null, string batchId = "", double totalPlayedSeconds = 0)
|
||||||
{
|
{
|
||||||
return station.Context.API.Radio.SendStationFeedBackAsync(station.Context.Storage, station, type, track, batchId, totalPlayedSeconds);
|
return station.Context.API.Radio.SendStationFeedBackAsync(station.Context.Storage, station, type, track, batchId, totalPlayedSeconds);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,54 @@
|
|||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для трека
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YTrackExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static string GetLink(this YTrack track)
|
||||||
/// Методы-расширения для трека
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YTrackExtensions
|
|
||||||
{
|
{
|
||||||
public static string GetLink(this YTrack track)
|
return GetLinkAsync(track).GetAwaiter().GetResult();
|
||||||
{
|
}
|
||||||
return GetLinkAsync(track).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Save(this YTrack track, string filePath)
|
public static void Save(this YTrack track, string filePath)
|
||||||
{
|
{
|
||||||
SaveAsync(track, filePath).GetAwaiter().GetResult();
|
SaveAsync(track, filePath).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int AddLike(this YTrack track)
|
public static int AddLike(this YTrack track)
|
||||||
{
|
{
|
||||||
return AddLikeAsync(track).GetAwaiter().GetResult();
|
return AddLikeAsync(track).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int RemoveLike(this YTrack track)
|
public static int RemoveLike(this YTrack track)
|
||||||
{
|
{
|
||||||
return RemoveLikeAsync(track).GetAwaiter().GetResult();
|
return RemoveLikeAsync(track).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int AddDislike(this YTrack track)
|
public static int AddDislike(this YTrack track)
|
||||||
{
|
{
|
||||||
return AddDislikeAsync(track).GetAwaiter().GetResult();
|
return AddDislikeAsync(track).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int RemoveDislike(this YTrack track)
|
public static int RemoveDislike(this YTrack track)
|
||||||
{
|
{
|
||||||
return RemoveDislikeAsync(track).GetAwaiter().GetResult();
|
return RemoveDislikeAsync(track).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SendPlayTrackInfo(this YTrack track, string from, bool fromCache = false, string playId = "", string playlistId = "", double totalPlayedSeconds = 0, double endPositionSeconds = 0)
|
public static string SendPlayTrackInfo(this YTrack track, string from, bool fromCache = false, string playId = "", string playlistId = "", double totalPlayedSeconds = 0, double endPositionSeconds = 0)
|
||||||
{
|
{
|
||||||
return SendPlayTrackInfoAsync(track, from, fromCache, playId, playlistId, totalPlayedSeconds).GetAwaiter().GetResult();
|
return SendPlayTrackInfoAsync(track, from, fromCache, playId, playlistId, totalPlayedSeconds).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YTrackSupplement Supplement(this YTrack track)
|
public static YTrackSupplement Supplement(this YTrack track)
|
||||||
{
|
{
|
||||||
return SupplementAsync(track).GetAwaiter().GetResult();
|
return SupplementAsync(track).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YTrackSimilar Similar(this YTrack track)
|
public static YTrackSimilar Similar(this YTrack track)
|
||||||
{
|
{
|
||||||
return SimilarAsync(track).GetAwaiter().GetResult();
|
return SimilarAsync(track).GetAwaiter().GetResult();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,60 @@
|
|||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API
|
namespace YandexMusic.API.Extensions.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Методы-расширения для трека
|
||||||
|
/// </summary>
|
||||||
|
public static partial class YTrackExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static Task<string> GetLinkAsync(this YTrack track)
|
||||||
/// Методы-расширения для трека
|
|
||||||
/// </summary>
|
|
||||||
public static partial class YTrackExtensions
|
|
||||||
{
|
{
|
||||||
public static Task<string> GetLinkAsync(this YTrack track)
|
return track.Context.API.Track.GetFileLinkAsync(track.Context.Storage, track);
|
||||||
{
|
}
|
||||||
return track.Context.API.Track.GetFileLinkAsync(track.Context.Storage, track);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task SaveAsync(this YTrack track, string filePath)
|
public static Task SaveAsync(this YTrack track, string filePath)
|
||||||
{
|
{
|
||||||
return track.Context.API.Track.ExtractToFileAsync(track.Context.Storage, track, filePath);
|
return track.Context.API.Track.ExtractToFileAsync(track.Context.Storage, track, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<int> AddLikeAsync(this YTrack track)
|
public static async Task<int> AddLikeAsync(this YTrack track)
|
||||||
{
|
{
|
||||||
return (await track.Context.API.Library.AddTrackLikeAsync(track.Context.Storage, track))
|
return (await track.Context.API.Library.AddTrackLikeAsync(track.Context.Storage, track))
|
||||||
.Result.Revision;
|
.Result.Revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<int> RemoveLikeAsync(this YTrack track)
|
public static async Task<int> RemoveLikeAsync(this YTrack track)
|
||||||
{
|
{
|
||||||
return (await track.Context.API.Library.RemoveTrackLikeAsync(track.Context.Storage, track))
|
return (await track.Context.API.Library.RemoveTrackLikeAsync(track.Context.Storage, track))
|
||||||
.Result.Revision;
|
.Result.Revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<int> AddDislikeAsync(this YTrack track)
|
public static async Task<int> AddDislikeAsync(this YTrack track)
|
||||||
{
|
{
|
||||||
return (await track.Context.API.Library.AddTrackDislikeAsync(track.Context.Storage, track))
|
return (await track.Context.API.Library.AddTrackDislikeAsync(track.Context.Storage, track))
|
||||||
.Result.Revision;
|
.Result.Revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<int> RemoveDislikeAsync(this YTrack track)
|
public static async Task<int> RemoveDislikeAsync(this YTrack track)
|
||||||
{
|
{
|
||||||
return (await track.Context.API.Library.RemoveTrackDislikeAsync(track.Context.Storage, track))
|
return (await track.Context.API.Library.RemoveTrackDislikeAsync(track.Context.Storage, track))
|
||||||
?.Result.Revision ?? -1;
|
?.Result.Revision ?? -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<string> SendPlayTrackInfoAsync(this YTrack track, string from, bool fromCache = false, string playId = "", string playlistId = "", double totalPlayedSeconds = 0, double endPositionSeconds = 0)
|
public static Task<string> SendPlayTrackInfoAsync(this YTrack track, string from, bool fromCache = false, string playId = "", string playlistId = "", double totalPlayedSeconds = 0, double endPositionSeconds = 0)
|
||||||
{
|
{
|
||||||
return track.Context.API.Track.SendPlayTrackInfoAsync(track.Context.Storage, track, from, fromCache, playId, playlistId, totalPlayedSeconds);
|
return track.Context.API.Track.SendPlayTrackInfoAsync(track.Context.Storage, track, from, fromCache, playId, playlistId, totalPlayedSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<YTrackSupplement> SupplementAsync(this YTrack track)
|
public static async Task<YTrackSupplement> SupplementAsync(this YTrack track)
|
||||||
{
|
{
|
||||||
return (await track.Context.API.Track.GetSupplementAsync(track.Context.Storage, track))
|
return (await track.Context.API.Track.GetSupplementAsync(track.Context.Storage, track))
|
||||||
.Result;
|
.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<YTrackSimilar> SimilarAsync(this YTrack track)
|
public static async Task<YTrackSimilar> SimilarAsync(this YTrack track)
|
||||||
{
|
{
|
||||||
return (await track.Context.API.Track.GetSimilarAsync(track.Context.Storage, track))
|
return (await track.Context.API.Track.GetSimilarAsync(track.Context.Storage, track))
|
||||||
.Result;
|
.Result;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions
|
namespace YandexMusic.API.Extensions;
|
||||||
|
|
||||||
|
public static class HttpRequestHeaderExtensions
|
||||||
{
|
{
|
||||||
public static class HttpRequestHeaderExtensions
|
public static string GetName(this HttpRequestHeader header)
|
||||||
{
|
{
|
||||||
public static string GetName(this HttpRequestHeader header)
|
return header.ToString().SplitByCapitalLetter("-");
|
||||||
{
|
|
||||||
return header.ToString().SplitByCapitalLetter("-");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,50 +1,49 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions
|
namespace YandexMusic.API.Extensions;
|
||||||
|
|
||||||
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
public static class StringExtensions
|
public static string ReplaceRegex(this string str, string regExpr, string replStr, RegexOptions options = RegexOptions.IgnoreCase)
|
||||||
{
|
{
|
||||||
public static string ReplaceRegex(this string str, string regExpr, string replStr, RegexOptions options = RegexOptions.IgnoreCase)
|
return str == null
|
||||||
{
|
? string.Empty
|
||||||
return str == null
|
: Regex.Replace(str, regExpr, replStr);
|
||||||
? string.Empty
|
}
|
||||||
: Regex.Replace(str, regExpr, replStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string SplitByCapitalLetter(this string str, string delimiter)
|
public static string SplitByCapitalLetter(this string str, string delimiter)
|
||||||
{
|
{
|
||||||
return string.Join(delimiter, Regex.Matches(str, @"([A-Z]+)(?=([A-Z][a-z]|$)) | [A-Z][a-z].+?(?=([A-Z]|$))", RegexOptions.IgnorePatternWhitespace)
|
return string.Join(delimiter, Regex.Matches(str, @"([A-Z]+)(?=([A-Z][a-z]|$)) | [A-Z][a-z].+?(?=([A-Z]|$))", RegexOptions.IgnorePatternWhitespace)
|
||||||
|
.Cast<Match>()
|
||||||
|
.Select(m => m.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет соответствие регулярному выражению
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsMatch(this string str, string pattern, RegexOptions options)
|
||||||
|
{
|
||||||
|
return Regex.IsMatch(str, pattern, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет соответствие регулярному выражению
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsMatch(this string str, string pattern)
|
||||||
|
{
|
||||||
|
return IsMatch(str, pattern, RegexOptions.IgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Возвращает совпадения для регулярного выражения
|
||||||
|
/// </summary>
|
||||||
|
public static string[] GetMatches(this string str, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
|
||||||
|
{
|
||||||
|
return str.IsMatch(pattern, options)
|
||||||
|
? Regex.Matches(str, pattern, options)
|
||||||
.Cast<Match>()
|
.Cast<Match>()
|
||||||
.Select(m => m.ToString()));
|
.Select(m => m.Value)
|
||||||
}
|
.ToArray()
|
||||||
|
: new string[] { };
|
||||||
/// <summary>
|
|
||||||
/// Проверяет соответствие регулярному выражению
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsMatch(this string str, string pattern, RegexOptions options)
|
|
||||||
{
|
|
||||||
return Regex.IsMatch(str, pattern, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Проверяет соответствие регулярному выражению
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsMatch(this string str, string pattern)
|
|
||||||
{
|
|
||||||
return IsMatch(str, pattern, RegexOptions.IgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Возвращает совпадения для регулярного выражения
|
|
||||||
/// </summary>
|
|
||||||
public static string[] GetMatches(this string str, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
|
|
||||||
{
|
|
||||||
return str.IsMatch(pattern, options)
|
|
||||||
? Regex.Matches(str, pattern, options)
|
|
||||||
.Cast<Match>()
|
|
||||||
.Select(m => m.Value)
|
|
||||||
.ToArray()
|
|
||||||
: new string[] { };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAccessToken
|
||||||
{
|
{
|
||||||
public class YAccessToken
|
[JsonPropertyName("status")]
|
||||||
{
|
public string Status { get; set; }
|
||||||
[JsonProperty("status")]
|
|
||||||
public string Status { get; set; }
|
[JsonPropertyName("access_token")]
|
||||||
[JsonProperty("access_token")]
|
public string AccessToken { get; set; }
|
||||||
public string AccessToken { get; set; }
|
|
||||||
[JsonProperty("expires_in")]
|
[JsonPropertyName("expires_in")]
|
||||||
public string Expires { get; set; }
|
public string Expires { get; set; }
|
||||||
[JsonProperty("token_type")]
|
|
||||||
public string TokenType { get; set; }
|
[JsonPropertyName("token_type")]
|
||||||
public string Uid { get; set; }
|
public string TokenType { get; set; }
|
||||||
}
|
|
||||||
|
public string Uid { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,40 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
using YandexMusic.API.Models.Common;
|
using YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAccount
|
||||||
{
|
{
|
||||||
public class YAccount
|
public bool Child { get; set; }
|
||||||
{
|
|
||||||
public bool Child { get; set; }
|
public string Birthday { get; set; }
|
||||||
public string Birthday { get; set; }
|
|
||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
public string FirstName { get; set; }
|
|
||||||
public string FullName { get; set; }
|
public string FirstName { get; set; }
|
||||||
public bool HostedUser { get; set; }
|
|
||||||
public string Login { get; set; }
|
public string FullName { get; set; }
|
||||||
public bool NonOwnerFamilyMember { get; set; }
|
|
||||||
public DateTime Now { get; set; }
|
public bool HostedUser { get; set; }
|
||||||
[JsonProperty("passport-phones")]
|
|
||||||
public List<YPhone> PassportPhones { get; set; }
|
public string Login { get; set; }
|
||||||
public int Region { get; set; }
|
|
||||||
public string RegionCode { get; set; }
|
public bool NonOwnerFamilyMember { get; set; }
|
||||||
public DateTime RegisteredAt { get; set; }
|
|
||||||
public string SecondName { get; set; }
|
public DateTime Now { get; set; }
|
||||||
public bool ServiceAvailable { get; set; }
|
|
||||||
public string Uid { get; set; }
|
[JsonPropertyName("passport-phones")]
|
||||||
}
|
public List<YPhone> PassportPhones { get; set; }
|
||||||
|
|
||||||
|
public int Region { get; set; }
|
||||||
|
|
||||||
|
public string RegionCode { get; set; }
|
||||||
|
|
||||||
|
public DateTime RegisteredAt { get; set; }
|
||||||
|
|
||||||
|
public string SecondName { get; set; }
|
||||||
|
|
||||||
|
public bool ServiceAvailable { get; set; }
|
||||||
|
|
||||||
|
public string Uid { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,37 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
using YandexMusic.API.Models.Common;
|
using YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAccountResult
|
||||||
{
|
{
|
||||||
public class YAccountResult
|
public YAccount Account { get; set; }
|
||||||
|
|
||||||
|
public string DefaultEmail { get; set; }
|
||||||
|
|
||||||
|
public List<string> HasOptions { get; set; }
|
||||||
|
|
||||||
|
public YMasterHub MasterHub { get; set; }
|
||||||
|
|
||||||
|
public YPermissions Permissions { get; set; }
|
||||||
|
|
||||||
|
public YPlus Plus { get; set; }
|
||||||
|
|
||||||
|
public bool PretrialActive { get; set; }
|
||||||
|
|
||||||
|
public bool SubEditor { get; set; }
|
||||||
|
|
||||||
|
public int SubEditorLevel { get; set; }
|
||||||
|
|
||||||
|
public YSubscription Subscription { get; set; }
|
||||||
|
|
||||||
|
public YBar BarBelow { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("bar-below")]
|
||||||
|
private YBar BarBelow2
|
||||||
{
|
{
|
||||||
public YAccount Account { get; set; }
|
set => BarBelow = value;
|
||||||
public string DefaultEmail { get; set; }
|
|
||||||
public List<string> HasOptions { get; set; }
|
|
||||||
public YMasterHub MasterHub { get; set; }
|
|
||||||
public YPermissions Permissions { get; set; }
|
|
||||||
public YPlus Plus { get; set; }
|
|
||||||
public bool PretrialActive { get; set; }
|
|
||||||
public bool SubEditor { get; set; }
|
|
||||||
public int SubEditorLevel { get; set; }
|
|
||||||
public YSubscription Subscription { get; set; }
|
|
||||||
public YBar BarBelow { get; set; }
|
|
||||||
// Повторяющееся свойство с другим названием
|
|
||||||
[JsonProperty("bar-below")]
|
|
||||||
private YBar BarBelow2
|
|
||||||
{
|
|
||||||
set => BarBelow = value;
|
|
||||||
}
|
|
||||||
public string Userhash { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Userhash { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAuthBase
|
||||||
{
|
{
|
||||||
public class YAuthBase
|
public YAuthStatus Status { get; set; }
|
||||||
{
|
|
||||||
public YAuthStatus Status { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("redirect_url")]
|
[JsonPropertyName("redirect_url")]
|
||||||
public string RedirectUrl { get; set; }
|
public string RedirectUrl { get; set; }
|
||||||
|
|
||||||
public List<YAuthError> Errors { get; set; }
|
public List<YAuthError> Errors { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAuthCaptcha : YAuthBase
|
||||||
{
|
{
|
||||||
public class YAuthCaptcha : YAuthBase
|
public string Id { get; set; }
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public string Label { get; set; }
|
public string Label { get; set; }
|
||||||
|
|
||||||
public string Mode { get; set; }
|
public string Mode { get; set; }
|
||||||
|
|
||||||
public List<YAuthCaptchaError> Error { get; set; }
|
public List<YAuthCaptchaError> Error { get; set; }
|
||||||
|
|
||||||
public bool CountryFromAudioWhiteList { get; set; }
|
public bool CountryFromAudioWhiteList { get; set; }
|
||||||
|
|
||||||
public YAuthCaptchaOptions Options { get; set; }
|
public YAuthCaptchaOptions Options { get; set; }
|
||||||
|
|
||||||
public YAuthCaptchaVoice Voice { get; set; }
|
public YAuthCaptchaVoice Voice { get; set; }
|
||||||
|
|
||||||
[JsonProperty("image_url")]
|
[JsonPropertyName("image_url")]
|
||||||
public string ImageUrl { get; set; }
|
public string ImageUrl { get; set; }
|
||||||
|
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
|
|
||||||
public string Static { get; set; }
|
public string Static { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAuthCaptchaError
|
||||||
{
|
{
|
||||||
public class YAuthCaptchaError
|
public string Message { get; set; }
|
||||||
{
|
public YAuthCaptchaErrorCode Code { get; set; }
|
||||||
public string Message { get; set; }
|
|
||||||
public YAuthCaptchaErrorCode Code { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public enum YAuthCaptchaErrorCode
|
||||||
{
|
{
|
||||||
public enum YAuthCaptchaErrorCode
|
MissingValue,
|
||||||
{
|
CaptchaLocate,
|
||||||
MissingValue,
|
Incorrect
|
||||||
CaptchaLocate,
|
|
||||||
Incorrect
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAuthCaptchaOptions
|
||||||
{
|
{
|
||||||
public class YAuthCaptchaOptions
|
public bool AsyncCheck { get; set; }
|
||||||
{
|
|
||||||
public bool AsyncCheck { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
{
|
|
||||||
public class YAuthCaptchaVoice
|
|
||||||
{
|
|
||||||
public string Url { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("intro_url")]
|
namespace YandexMusic.API.Models.Account;
|
||||||
public string IntroUrl { get; set; }
|
|
||||||
}
|
public class YAuthCaptchaVoice
|
||||||
|
{
|
||||||
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("intro_url")]
|
||||||
|
public string IntroUrl { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,27 @@
|
|||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public enum YAuthError
|
||||||
{
|
{
|
||||||
public enum YAuthError
|
[EnumMember(Value = "authorization.invalid")]
|
||||||
{
|
AuthorizationInvalid,
|
||||||
[EnumMember(Value = "authorization.invalid")]
|
|
||||||
AuthorizationInvalid,
|
[EnumMember(Value = "sessionid.invalid")]
|
||||||
[EnumMember(Value = "sessionid.invalid")]
|
SessionIdInvalid,
|
||||||
SessionIdInvalid,
|
|
||||||
[EnumMember(Value = "password.not_matched")]
|
[EnumMember(Value = "password.not_matched")]
|
||||||
PasswordNotMatched,
|
PasswordNotMatched,
|
||||||
[EnumMember(Value = "password.empty")]
|
|
||||||
PasswordEmpty,
|
[EnumMember(Value = "password.empty")]
|
||||||
[EnumMember(Value = "captcha.required")]
|
PasswordEmpty,
|
||||||
CaptchaRequired,
|
|
||||||
[EnumMember(Value = "captcha.not_matched")]
|
[EnumMember(Value = "captcha.required")]
|
||||||
CaptchaNotMatched,
|
CaptchaRequired,
|
||||||
[EnumMember(Value = "oauth_token.invalid")]
|
|
||||||
OAuthTokenInvalid
|
[EnumMember(Value = "captcha.not_matched")]
|
||||||
}
|
CaptchaNotMatched,
|
||||||
|
|
||||||
|
[EnumMember(Value = "oauth_token.invalid")]
|
||||||
|
OAuthTokenInvalid,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
{
|
|
||||||
public class YAuthLetter : YAuthBase
|
|
||||||
{
|
|
||||||
public List<string> Code { get; set; }
|
|
||||||
|
|
||||||
public string Id { get; set; }
|
public class YAuthLetter : YAuthBase
|
||||||
}
|
{
|
||||||
|
public List<string> Code { get; set; }
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
{
|
|
||||||
public class YAuthLetterStatus : YAuthBase
|
|
||||||
{
|
|
||||||
[JsonProperty("magic_link_confirmed")]
|
|
||||||
public bool MagicLinkConfirmed { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("track_id")]
|
namespace YandexMusic.API.Models.Account;
|
||||||
public string TrackId { get; set; }
|
|
||||||
}
|
public class YAuthLetterStatus : YAuthBase
|
||||||
|
{
|
||||||
|
[JsonPropertyName("magic_link_confirmed")]
|
||||||
|
public bool MagicLinkConfirmed { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("track_id")]
|
||||||
|
public string TrackId { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,29 @@
|
|||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public enum YAuthMethod
|
||||||
{
|
{
|
||||||
public enum YAuthMethod
|
Password,
|
||||||
{
|
|
||||||
Password,
|
[EnumMember(Value = "magic_x_token")]
|
||||||
[EnumMember(Value = "magic_x_token")]
|
MagicToken,
|
||||||
MagicToken,
|
|
||||||
[EnumMember(Value = "magic_x_token_with_pictures")]
|
[EnumMember(Value = "magic_x_token_with_pictures")]
|
||||||
MagicTokenWithPictures,
|
MagicTokenWithPictures,
|
||||||
[EnumMember(Value = "magic_link")]
|
|
||||||
MagicLink,
|
[EnumMember(Value = "magic_link")]
|
||||||
Magic,
|
MagicLink,
|
||||||
Otp,
|
|
||||||
[EnumMember(Value = "social_gg")]
|
Magic,
|
||||||
Social,
|
|
||||||
WebAuthN,
|
Otp,
|
||||||
[EnumMember(Value = "sms_code")]
|
|
||||||
SmsCode
|
[EnumMember(Value = "social_gg")]
|
||||||
}
|
Social,
|
||||||
|
|
||||||
|
WebAuthN,
|
||||||
|
|
||||||
|
[EnumMember(Value = "sms_code")]
|
||||||
|
SmsCode,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
{
|
|
||||||
public class YAuthQR : YAuthBase
|
|
||||||
{
|
|
||||||
[JsonProperty("track_id")]
|
|
||||||
public string TrackId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("csrf_token")]
|
namespace YandexMusic.API.Models.Account;
|
||||||
public string CsrfToken { get; set; }
|
|
||||||
}
|
public class YAuthQR : YAuthBase
|
||||||
|
{
|
||||||
|
[JsonPropertyName("track_id")]
|
||||||
|
public string TrackId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("csrf_token")]
|
||||||
|
public string CsrfToken { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAuthQRStatus : YAuthBase
|
||||||
{
|
{
|
||||||
public class YAuthQRStatus : YAuthBase
|
[JsonPropertyName("default_uid")]
|
||||||
{
|
public int DefaultUid { get; set; }
|
||||||
[JsonProperty("default_uid")]
|
|
||||||
public int DefaultUid { get; set; }
|
|
||||||
|
|
||||||
public string RetPath { get; set; }
|
public string RetPath { get; set; }
|
||||||
|
|
||||||
[JsonProperty("track_id")]
|
[JsonPropertyName("track_id")]
|
||||||
public string TrackId { get; set; }
|
public string TrackId { get; set; }
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
public string State { get; set; }
|
public string State { get; set; }
|
||||||
|
|
||||||
public YAuthCaptcha Captcha { get; set; }
|
public YAuthCaptcha Captcha { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public enum YAuthStatus
|
||||||
{
|
{
|
||||||
public enum YAuthStatus
|
Ok,
|
||||||
{
|
Error
|
||||||
Ok,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
{
|
|
||||||
public class YAuthToken
|
|
||||||
{
|
|
||||||
[JsonProperty("csfr_token")]
|
|
||||||
public string CsfrToken { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("track_id")]
|
namespace YandexMusic.API.Models.Account;
|
||||||
public string TrackId { get; set; }
|
|
||||||
}
|
public class YAuthToken
|
||||||
|
{
|
||||||
|
[JsonPropertyName("csfr_token")]
|
||||||
|
public string CsfrToken { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("track_id")]
|
||||||
|
public string TrackId { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,47 +1,48 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAuthTypes : YAuthBase
|
||||||
{
|
{
|
||||||
public class YAuthTypes : YAuthBase
|
[JsonPropertyName("primary_alias_type")]
|
||||||
{
|
public string PrimaryAliasType { get; set; }
|
||||||
[JsonProperty("primary_alias_type")]
|
|
||||||
public string PrimaryAliasType { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("csrf_token")]
|
[JsonPropertyName("csrf_token")]
|
||||||
public string CsrfToken { get; set; }
|
public string CsrfToken { get; set; }
|
||||||
|
|
||||||
public string LocCsrf { get; set; }
|
public string LocCsrf { get; set; }
|
||||||
|
|
||||||
[JsonProperty("use_new_suggest_by_phone")]
|
[JsonPropertyName("use_new_suggest_by_phone")]
|
||||||
public bool UseNewSuggestByPhone { get; set; }
|
public bool UseNewSuggestByPhone { get; set; }
|
||||||
|
|
||||||
[JsonProperty("is_rfc_2fa_enabled")]
|
[JsonPropertyName("is_rfc_2fa_enabled")]
|
||||||
public bool IsRfc2faEnabled { get; set; }
|
public bool IsRfc2faEnabled { get; set; }
|
||||||
|
|
||||||
[JsonProperty("track_id")]
|
[JsonPropertyName("track_id")]
|
||||||
public string TrackId { get; set; }
|
public string TrackId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("can_authorize")]
|
[JsonPropertyName("can_authorize")]
|
||||||
public string CanAuthorize { get; set; }
|
public string CanAuthorize { get; set; }
|
||||||
|
|
||||||
[JsonProperty("preferred_auth_method")]
|
[JsonPropertyName("preferred_auth_method")]
|
||||||
public YAuthMethod PreferredAuthMethod { get; set; }
|
public YAuthMethod PreferredAuthMethod { get; set; }
|
||||||
|
|
||||||
[JsonProperty("auth_methods")]
|
[JsonPropertyName("auth_methods")]
|
||||||
public List<YAuthMethod> AuthMethods { get; set; }
|
public List<YAuthMethod> AuthMethods { get; set; }
|
||||||
|
|
||||||
[JsonProperty("can_register")]
|
[JsonPropertyName("can_register")]
|
||||||
public bool CanRegister { get; set; }
|
public bool CanRegister { get; set; }
|
||||||
|
|
||||||
[JsonProperty("location_id")]
|
[JsonPropertyName("location_id")]
|
||||||
public string LocationId { get; set; }
|
public string LocationId { get; set; }
|
||||||
|
|
||||||
public string Country { get; set; }
|
public string Country { get; set; }
|
||||||
|
|
||||||
[JsonProperty("phone_number")]
|
[JsonPropertyName("phone_number")]
|
||||||
public YPhoneNumber PhoneNumberNumber { get; set; }
|
public YPhoneNumber PhoneNumberNumber { get; set; }
|
||||||
|
|
||||||
[JsonProperty("magic_link_email")]
|
[JsonPropertyName("magic_link_email")]
|
||||||
public string MagicLinkEmail { get; set; }
|
public string MagicLinkEmail { get; set; }
|
||||||
|
|
||||||
public string TractorTargetLocationHost { get; set; }
|
public string TractorTargetLocationHost { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
using YandexMusic.API.Models.Common;
|
using YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YBar : YStyle
|
||||||
{
|
{
|
||||||
public class YBar : YStyle
|
public string AlertId { get; set; }
|
||||||
{
|
public string Text { get; set; }
|
||||||
public string AlertId { get; set; }
|
public string AlertType { get; set; }
|
||||||
public string Text { get; set; }
|
public YButton Button { get; set; }
|
||||||
public string AlertType { get; set; }
|
public bool CloseButton { get; set; }
|
||||||
public YButton Button { get; set; }
|
public YCloseButtonStyles CloseButtonStyles { get; set; }
|
||||||
public bool CloseButton { get; set; }
|
|
||||||
public YCloseButtonStyles CloseButtonStyles { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YLoginInfo
|
||||||
{
|
{
|
||||||
public class YLoginInfo
|
public string Id { get; set; }
|
||||||
{
|
public string Login { get; set; }
|
||||||
public string Id { get; set; }
|
[JsonPropertyName("client_id")]
|
||||||
public string Login { get; set; }
|
public string ClientId { get; set; }
|
||||||
[JsonProperty("client_id")]
|
[JsonPropertyName("display_name")]
|
||||||
public string ClientId { get; set; }
|
public string DisplayName { get; set; }
|
||||||
[JsonProperty("display_name")]
|
[JsonPropertyName("real_name")]
|
||||||
public string DisplayName { get; set; }
|
public string RealName { get; set; }
|
||||||
[JsonProperty("real_name")]
|
[JsonPropertyName("first_name")]
|
||||||
public string RealName { get; set; }
|
public string FirstName { get; set; }
|
||||||
[JsonProperty("first_name")]
|
[JsonPropertyName("last_name")]
|
||||||
public string FirstName { get; set; }
|
public string LastName { get; set; }
|
||||||
[JsonProperty("last_name")]
|
public string Sex { get; set; }
|
||||||
public string LastName { get; set; }
|
[JsonPropertyName("default_email")]
|
||||||
public string Sex { get; set; }
|
public string DefaultEmail { get; set; }
|
||||||
[JsonProperty("default_email")]
|
public List<string> Emails { get; set; }
|
||||||
public string DefaultEmail { get; set; }
|
public string Birthday { get; set; }
|
||||||
public List<string> Emails { get; set; }
|
[JsonPropertyName("default_avatar_id")]
|
||||||
public string Birthday { get; set; }
|
public string DefaultAvatarId { get; set; }
|
||||||
[JsonProperty("default_avatar_id")]
|
|
||||||
public string DefaultAvatarId { get; set; }
|
|
||||||
|
|
||||||
public string AvatarUrl => $"https://avatars.mds.yandex.net/get-yapic/{DefaultAvatarId}/islands-200";
|
public string AvatarUrl => $"https://avatars.mds.yandex.net/get-yapic/{DefaultAvatarId}/islands-200";
|
||||||
[JsonProperty("is_avatar_empty")]
|
[JsonPropertyName("is_avatar_empty")]
|
||||||
public bool IsAvatarEmpty { get; set; }
|
public bool IsAvatarEmpty { get; set; }
|
||||||
public string PsuId { get; set; }
|
public string PsuId { get; set; }
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YPhoneNumber
|
||||||
{
|
{
|
||||||
public class YPhoneNumber
|
[JsonPropertyName("masked_e164")]
|
||||||
{
|
public string MaskedE164 { get; set; }
|
||||||
[JsonProperty("masked_e164")]
|
|
||||||
public string MaskedE164 { get; set; }
|
|
||||||
|
|
||||||
public string E164 { get; set; }
|
public string E164 { get; set; }
|
||||||
|
|
||||||
public string International { get; set; }
|
public string International { get; set; }
|
||||||
|
|
||||||
[JsonProperty("masked_original")]
|
[JsonPropertyName("masked_original")]
|
||||||
public string MaskedOriginal { get; set; }
|
public string MaskedOriginal { get; set; }
|
||||||
|
|
||||||
public string Original { get; set; }
|
public string Original { get; set; }
|
||||||
|
|
||||||
[JsonProperty("masked_international")]
|
[JsonPropertyName("masked_international")]
|
||||||
public string MaskedInternational { get; set; }
|
public string MaskedInternational { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
namespace YandexMusic.API.Models.Account
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account
|
||||||
{
|
{
|
||||||
public class YShortAccountInfo : YAuthBase
|
public class YShortAccountInfo : YAuthBase
|
||||||
{
|
{
|
||||||
[JsonProperty("public_id")]
|
[JsonPropertyName("public_id")]
|
||||||
public string PublicId { get; set; }
|
public string PublicId { get; set; }
|
||||||
|
|
||||||
public string Uid { get; set; }
|
public string Uid { get; set; }
|
||||||
@@ -13,69 +15,69 @@
|
|||||||
|
|
||||||
public string Birthday { get; set; }
|
public string Birthday { get; set; }
|
||||||
|
|
||||||
[JsonProperty("has_password")]
|
[JsonPropertyName("has_password")]
|
||||||
public bool HasPassword { get; set; }
|
public bool HasPassword { get; set; }
|
||||||
|
|
||||||
public List<string> Partitions { get; set; }
|
public List<string> Partitions { get; set; }
|
||||||
|
|
||||||
[JsonProperty("primary_alias_type")]
|
[JsonPropertyName("primary_alias_type")]
|
||||||
public int PrimaryAliasType { get; set; }
|
public int PrimaryAliasType { get; set; }
|
||||||
|
|
||||||
[JsonProperty("display_name")]
|
[JsonPropertyName("display_name")]
|
||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
[JsonProperty("normalized_display_login")]
|
[JsonPropertyName("normalized_display_login")]
|
||||||
public string NormalizedDisplayLogin { get; set; }
|
public string NormalizedDisplayLogin { get; set; }
|
||||||
|
|
||||||
[JsonProperty("x_token_issued_at")]
|
[JsonPropertyName("x_token_issued_at")]
|
||||||
public int XTokenIssuedAt { get; set; }
|
public int XTokenIssuedAt { get; set; }
|
||||||
|
|
||||||
[JsonProperty("display_login")]
|
[JsonPropertyName("display_login")]
|
||||||
public string DisplayLogin { get; set; }
|
public string DisplayLogin { get; set; }
|
||||||
|
|
||||||
[JsonProperty("public_name")]
|
[JsonPropertyName("public_name")]
|
||||||
public string PublicName { get; set; }
|
public string PublicName { get; set; }
|
||||||
|
|
||||||
[JsonProperty("avatar_url")]
|
[JsonPropertyName("avatar_url")]
|
||||||
public string AvatarUrl { get; set; }
|
public string AvatarUrl { get; set; }
|
||||||
|
|
||||||
[JsonProperty("native_default_email")]
|
[JsonPropertyName("native_default_email")]
|
||||||
public string NativeDefaultEmail { get; set; }
|
public string NativeDefaultEmail { get; set; }
|
||||||
|
|
||||||
[JsonProperty("has_plus")]
|
[JsonPropertyName("has_plus")]
|
||||||
public bool HasPlus { get; set; }
|
public bool HasPlus { get; set; }
|
||||||
|
|
||||||
[JsonProperty("location_id")]
|
[JsonPropertyName("location_id")]
|
||||||
public int LocationId { get; set; }
|
public int LocationId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("gender")]
|
[JsonPropertyName("gender")]
|
||||||
public string Gender { get; set; }
|
public string Gender { get; set; }
|
||||||
|
|
||||||
[JsonProperty("is_avatar_empty")]
|
[JsonPropertyName("is_avatar_empty")]
|
||||||
public bool IsAvatarEmpty { get; set; }
|
public bool IsAvatarEmpty { get; set; }
|
||||||
|
|
||||||
[JsonProperty("machine_readable_login")]
|
[JsonPropertyName("machine_readable_login")]
|
||||||
public string MachineReadableLogin { get; set; }
|
public string MachineReadableLogin { get; set; }
|
||||||
|
|
||||||
[JsonProperty("has_cards")]
|
[JsonPropertyName("has_cards")]
|
||||||
public bool HasCards { get; set; }
|
public bool HasCards { get; set; }
|
||||||
|
|
||||||
[JsonProperty("has_family")]
|
[JsonPropertyName("has_family")]
|
||||||
public bool HasFamily { get; set; }
|
public bool HasFamily { get; set; }
|
||||||
|
|
||||||
[JsonProperty("picture_login_forbidden")]
|
[JsonPropertyName("picture_login_forbidden")]
|
||||||
public bool PictureLoginForbidden { get; set; }
|
public bool PictureLoginForbidden { get; set; }
|
||||||
|
|
||||||
[JsonProperty("can_account_join_master")]
|
[JsonPropertyName("can_account_join_master")]
|
||||||
public bool CanAccountJoinMaster { get; set; }
|
public bool CanAccountJoinMaster { get; set; }
|
||||||
|
|
||||||
[JsonProperty("secure_phone_number")]
|
[JsonPropertyName("secure_phone_number")]
|
||||||
public string SecurePhoneNumber { get; set; }
|
public string SecurePhoneNumber { get; set; }
|
||||||
|
|
||||||
[JsonProperty("x_token_client_id")]
|
[JsonPropertyName("x_token_client_id")]
|
||||||
public string XTokenClientId { get; set; }
|
public string XTokenClientId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("x_token_need_reset")]
|
[JsonPropertyName("x_token_need_reset")]
|
||||||
public bool XTokenNeedReset { get; set; }
|
public bool XTokenNeedReset { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,98 +1,59 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
using YandexMusic.API.Models.Artist;
|
using YandexMusic.API.Models.Artist;
|
||||||
using YandexMusic.API.Models.Common;
|
using YandexMusic.API.Models.Common;
|
||||||
using YandexMusic.API.Models.Common.Cover;
|
using YandexMusic.API.Models.Common.Cover;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Album
|
namespace YandexMusic.API.Models.Album;
|
||||||
|
|
||||||
|
/// <summary>Модель альбома.</summary>
|
||||||
|
public class YAlbum : YBaseModel
|
||||||
{
|
{
|
||||||
public sealed class YLabelConverter : JsonConverter
|
public YButton ActionButton { get; set; }
|
||||||
{
|
public List<YArtist> Artists { get; set; }
|
||||||
public override bool CanConvert(Type objectType)
|
public bool Available { get; set; }
|
||||||
{
|
public bool AvailableForMobile { get; set; }
|
||||||
throw new NotImplementedException();
|
public List<string> AvailableForOptions { get; set; }
|
||||||
}
|
public bool AvailableForPremiumUsers { get; set; }
|
||||||
|
public bool AvailablePartially { get; set; }
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
public string BackgroundImageUrl { get; set; }
|
||||||
{
|
public string BackgroundVideoUrl { get; set; }
|
||||||
if (reader.TokenType == JsonToken.Null)
|
public List<string> Bests { get; set; }
|
||||||
return null;
|
public List<string> Buy { get; set; }
|
||||||
|
public bool ChildContent { get; set; }
|
||||||
JArray jArray = JArray.Load(reader);
|
public string ContentWarning { get; set; }
|
||||||
JTokenType tokenType = jArray.FirstOrDefault()?.Type ?? JTokenType.String;
|
public string CoverUri { get; set; }
|
||||||
object label;
|
[JsonConverter(typeof(YCoverConverter))]
|
||||||
|
public YCover Cover { get; set; }
|
||||||
try
|
public YCustomWave CustomWave { get; set; }
|
||||||
{
|
public YDerivedColors DerivedColors { get; set; }
|
||||||
label = tokenType switch
|
public string Description { get; set; }
|
||||||
{
|
public List<string> Disclaimers { get; set; }
|
||||||
JTokenType.Object => jArray.ToObject<List<YLabel>>(),
|
public List<YAlbum> Duplicates { get; set; }
|
||||||
_ => jArray.ToObject<List<string>>()
|
public bool HasTrailer { get; set; }
|
||||||
};
|
public string Genre { get; set; }
|
||||||
}
|
public string Id { get; set; }
|
||||||
catch (Exception ex)
|
[JsonConverter(typeof(YLabelConverter))]
|
||||||
{
|
public dynamic Labels { get; set; }
|
||||||
throw new Exception($"Ошибка десериализации типа \"{objectType.Name}\".", ex);
|
public int LikesCount { get; set; }
|
||||||
}
|
public bool ListeningFinished { get; set; }
|
||||||
|
public string MetaTagId { get; set; }
|
||||||
return label;
|
public YMetaType MetaType { get; set; }
|
||||||
}
|
public string OgImage { get; set; }
|
||||||
|
public YPager Pager { get; set; }
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
public List<YPrerolls> Prerolls { get; set; }
|
||||||
{
|
public bool Recent { get; set; }
|
||||||
JArray array = JArray.FromObject(value);
|
public DateTime ReleaseDate { get; set; }
|
||||||
|
public string ShortDescription { get; set; }
|
||||||
array.WriteTo(writer);
|
public YSortOrder SortOrder { get; set; }
|
||||||
}
|
public string StorageDir { get; set; }
|
||||||
}
|
public string Title { get; set; }
|
||||||
|
public int TrackCount { get; set; }
|
||||||
public class YAlbum : YBaseModel
|
public YTrackPosition TrackPosition { get; set; }
|
||||||
{
|
public YTrailer Trailer { get; set; }
|
||||||
public YButton ActionButton { get; set; }
|
public string Type { get; set; }
|
||||||
public List<YArtist> Artists { get; set; }
|
public string Version { get; set; }
|
||||||
public bool Available { get; set; }
|
public bool VeryImportant { get; set; }
|
||||||
public bool AvailableForMobile { get; set; }
|
public List<List<YTrack>> Volumes { get; set; }
|
||||||
public List<string> AvailableForOptions { get; set; }
|
public int Year { get; set; }
|
||||||
public bool AvailableForPremiumUsers { get; set; }
|
|
||||||
public bool AvailablePartially { get; set; }
|
|
||||||
public string BackgroundImageUrl { get; set; }
|
|
||||||
public string BackgroundVideoUrl { get; set; }
|
|
||||||
public List<string> Bests { get; set; }
|
|
||||||
public List<string> Buy { get; set; }
|
|
||||||
public bool ChildContent { get; set; }
|
|
||||||
public string ContentWarning { get; set; }
|
|
||||||
public string CoverUri { get; set; }
|
|
||||||
[JsonConverter(typeof(YCoverConverter))]
|
|
||||||
public YCover Cover { get; set; }
|
|
||||||
public YCustomWave CustomWave { get; set; }
|
|
||||||
public YDerivedColors DerivedColors { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
public List<string> Disclaimers { get; set; }
|
|
||||||
public List<YAlbum> Duplicates { get; set; }
|
|
||||||
public bool HasTrailer { get; set; }
|
|
||||||
public string Genre { get; set; }
|
|
||||||
public string Id { get; set; }
|
|
||||||
[JsonConverter(typeof(YLabelConverter))]
|
|
||||||
public dynamic Labels { get; set; }
|
|
||||||
public int LikesCount { get; set; }
|
|
||||||
public bool ListeningFinished { get; set; }
|
|
||||||
public string MetaTagId { get; set; }
|
|
||||||
public YMetaType MetaType { get; set; }
|
|
||||||
public string OgImage { get; set; }
|
|
||||||
public YPager Pager { get; set; }
|
|
||||||
public List<YPrerolls> Prerolls { get; set; }
|
|
||||||
public bool Recent { get; set; }
|
|
||||||
public DateTime ReleaseDate { get; set; }
|
|
||||||
public string ShortDescription { get; set; }
|
|
||||||
public YSortOrder SortOrder { get; set; }
|
|
||||||
public string StorageDir { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
public int TrackCount { get; set; }
|
|
||||||
public YTrackPosition TrackPosition { get; set; }
|
|
||||||
public YTrailer Trailer { get; set; }
|
|
||||||
public string Type { get; set; }
|
|
||||||
public string Version { get; set; }
|
|
||||||
public bool VeryImportant { get; set; }
|
|
||||||
public List<List<YTrack>> Volumes { get; set; }
|
|
||||||
public int Year { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
32
YandexMusic.API/Models/Album/YLabelConverter.cs
Normal file
32
YandexMusic.API/Models/Album/YLabelConverter.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Album;
|
||||||
|
|
||||||
|
/// <summary>Конвертер для поля Labels, которое может быть списком строк или объектов.</summary>
|
||||||
|
public sealed class YLabelConverter : JsonConverter<object>
|
||||||
|
{
|
||||||
|
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (reader.TokenType != JsonTokenType.StartArray)
|
||||||
|
throw new JsonException("Ожидается массив");
|
||||||
|
|
||||||
|
var list = new List<object>();
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
if (reader.TokenType == JsonTokenType.EndArray) break;
|
||||||
|
if (reader.TokenType == JsonTokenType.String)
|
||||||
|
list.Add(reader.GetString()!);
|
||||||
|
else if (reader.TokenType == JsonTokenType.StartObject)
|
||||||
|
{
|
||||||
|
var label = JsonSerializer.Deserialize<YLabel>(ref reader, options);
|
||||||
|
list.Add(label!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||||
|
=> JsonSerializer.Serialize(writer, value, options);
|
||||||
|
}
|
||||||
@@ -1,41 +1,41 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
using YandexMusic.API.Models.Common;
|
using YandexMusic.API.Models.Common;
|
||||||
using YandexMusic.API.Models.Common.Cover;
|
using YandexMusic.API.Models.Common.Cover;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Artist
|
namespace YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
|
public class YArtist : YBaseModel
|
||||||
{
|
{
|
||||||
public class YArtist : YBaseModel
|
public YButton ActionButton { get; set; }
|
||||||
{
|
public bool Available { get; set; }
|
||||||
public YButton ActionButton { get; set; }
|
public bool Composer { get; set; }
|
||||||
public bool Available { get; set; }
|
public List<string> Countries { get; set; }
|
||||||
public bool Composer { get; set; }
|
public YArtistCounts Counts { get; set; }
|
||||||
public List<string> Countries { get; set; }
|
[JsonConverter(typeof(YCoverConverter))]
|
||||||
public YArtistCounts Counts { get; set; }
|
public YCover Cover { get; set; }
|
||||||
[JsonConverter(typeof(YCoverConverter))]
|
public List<string> DbAliases { get; set; }
|
||||||
public YCover Cover { get; set; }
|
|
||||||
public List<string> DbAliases { get; set; }
|
|
||||||
#warning Непонятная коллекция с содержимым разных типов
|
#warning Непонятная коллекция с содержимым разных типов
|
||||||
public List<object> Decomposed { get; set; }
|
public List<object> Decomposed { get; set; }
|
||||||
public YDerivedColors DerivedColors { get; set; }
|
public YDerivedColors DerivedColors { get; set; }
|
||||||
public YDescription Description { get; set; }
|
public YDescription Description { get; set; }
|
||||||
public YDeprecation Deprecation { get; set; }
|
public YDeprecation Deprecation { get; set; }
|
||||||
public List<string> Disclaimers { get; set; }
|
public List<string> Disclaimers { get; set; }
|
||||||
public string EndDate { get; set; }
|
public string EndDate { get; set; }
|
||||||
public string EnWikipediaLink { get; set; }
|
public string EnWikipediaLink { get; set; }
|
||||||
public List<YExtraAction> ExtraActions { get; set; }
|
public List<YExtraAction> ExtraActions { get; set; }
|
||||||
public List<string> Genres { get; set; }
|
public List<string> Genres { get; set; }
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string InitDate { get; set; }
|
public string InitDate { get; set; }
|
||||||
public int LikesCount { get; set; }
|
public int LikesCount { get; set; }
|
||||||
public List<YLink> Links { get; set; }
|
public List<YLink> Links { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool NoPicturesFromSearch { get; set; }
|
public bool NoPicturesFromSearch { get; set; }
|
||||||
public string OgImage { get; set; }
|
public string OgImage { get; set; }
|
||||||
public YArtistRatings Ratings { get; set; }
|
public YArtistRatings Ratings { get; set; }
|
||||||
public bool TicketsAvailable { get; set; }
|
public bool TicketsAvailable { get; set; }
|
||||||
public DateTime Timestamp { get; set; }
|
public DateTime Timestamp { get; set; }
|
||||||
public bool Various { get; set; }
|
public bool Various { get; set; }
|
||||||
public string YaMoneyId { get; set; }
|
public string YaMoneyId { get; set; }
|
||||||
public bool HasTrailer { get; set; }
|
public bool HasTrailer { get; set; }
|
||||||
public YTrailer Trailer { get; set; }
|
public YTrailer Trailer { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
using YandexMusic.API.Models.Album;
|
using YandexMusic.API.Models.Album;
|
||||||
using YandexMusic.API.Models.Common;
|
using YandexMusic.API.Models.Common;
|
||||||
using YandexMusic.API.Models.Common.Cover;
|
using YandexMusic.API.Models.Common.Cover;
|
||||||
using YandexMusic.API.Models.Playlist;
|
using YandexMusic.API.Models.Playlist;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Artist
|
namespace YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
|
public class YArtistBriefInfo
|
||||||
{
|
{
|
||||||
public class YArtistBriefInfo
|
public YButton ActionButton { get; set; }
|
||||||
{
|
public List<YAlbum> Albums { get; set; }
|
||||||
public YButton ActionButton { get; set; }
|
[JsonConverter(typeof(YCoverConverter))]
|
||||||
public List<YAlbum> Albums { get; set; }
|
public List<YCover> AllCovers { get; set; }
|
||||||
[JsonProperty(ItemConverterType = typeof(YCoverConverter))]
|
public List<YAlbum> AlsoAlbums { get; set; }
|
||||||
public List<YCover> AllCovers { get; set; }
|
public YArtist Artist { get; set; }
|
||||||
public List<YAlbum> AlsoAlbums { get; set; }
|
public string BackgroundVideoUrl { get; set; }
|
||||||
public YArtist Artist { get; set; }
|
public YBandlinkScannerLink BandlinkScannerLink { get; set; }
|
||||||
public string BackgroundVideoUrl { get; set; }
|
public List<YClip> Clips { get; set; }
|
||||||
public YBandlinkScannerLink BandlinkScannerLink { get; set; }
|
public List<YConcert> Concerts { get; set; }
|
||||||
public List<YClip> Clips { get; set; }
|
public YCustomWave CustomWave { get; set; }
|
||||||
public List<YConcert> Concerts { get; set; }
|
public List<YExtraAction> ExtraActions { get; set; }
|
||||||
public YCustomWave CustomWave { get; set; }
|
public bool HasPromotions { get; set; }
|
||||||
public List<YExtraAction> ExtraActions { get; set; }
|
public bool HasTrailer { get; set; }
|
||||||
public bool HasPromotions { get; set; }
|
public List<string> LastReleaseIds { get; set; }
|
||||||
public bool HasTrailer { get; set; }
|
public List<YAlbum> LastReleases { get; set; }
|
||||||
public List<string> LastReleaseIds { get; set; }
|
public List<YPlaylistUidPair> PlaylistIds { get; set; }
|
||||||
public List<YAlbum> LastReleases { get; set; }
|
public List<YPlaylist> Playlists { get; set; }
|
||||||
public List<YPlaylistUidPair> PlaylistIds { get; set; }
|
public List<YTrack> PopularTracks { get; set; }
|
||||||
public List<YPlaylist> Playlists { get; set; }
|
public List<YArtist> SimilarArtists { get; set; }
|
||||||
public List<YTrack> PopularTracks { get; set; }
|
public YStats Stats { get; set; }
|
||||||
public List<YArtist> SimilarArtists { get; set; }
|
public List<YVideo> Videos { get; set; }
|
||||||
public YStats Stats { get; set; }
|
public List<YVinyl> Vinyls { get; set; }
|
||||||
public List<YVideo> Videos { get; set; }
|
public List<YLink> Links { get; set; }
|
||||||
public List<YVinyl> Vinyls { get; set; }
|
|
||||||
public List<YLink> Links { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
namespace YandexMusic.API.Models.Artist
|
namespace YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
|
public class YArtistCounts
|
||||||
{
|
{
|
||||||
public class YArtistCounts
|
public int AlsoAlbums { get; set; }
|
||||||
{
|
public int AlsoTracks { get; set; }
|
||||||
public int AlsoAlbums { get; set; }
|
public int DirectAlbums { get; set; }
|
||||||
public int AlsoTracks { get; set; }
|
public int Tracks { get; set; }
|
||||||
public int DirectAlbums { get; set; }
|
|
||||||
public int Tracks { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
namespace YandexMusic.API.Models.Artist
|
namespace YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
|
public class YArtistRatings
|
||||||
{
|
{
|
||||||
public class YArtistRatings
|
public int Day { get; set; }
|
||||||
{
|
public int Month { get; set; }
|
||||||
public int Day { get; set; }
|
public int Week { get; set; }
|
||||||
public int Month { get; set; }
|
|
||||||
public int Week { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
namespace YandexMusic.API.Models.Artist
|
namespace YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
|
public class YBandlinkScannerLink
|
||||||
{
|
{
|
||||||
public class YBandlinkScannerLink
|
public string Title { get; set; }
|
||||||
{
|
public string Subtitle { get; set; }
|
||||||
public string Title { get; set; }
|
public string Url { get; set; }
|
||||||
public string Subtitle { get; set; }
|
public string ImgUrl { get; set; }
|
||||||
public string Url { get; set; }
|
|
||||||
public string ImgUrl { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,29 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
using YandexMusic.API.Models.Common;
|
using YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Artist
|
namespace YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
|
public class YConcert
|
||||||
{
|
{
|
||||||
public class YConcert
|
public string Address { get; set; }
|
||||||
{
|
public string AfishaUrl { get; set; }
|
||||||
public string Address { get; set; }
|
public YArtist Artist { get; set; }
|
||||||
public string AfishaUrl { get; set; }
|
public string City { get; set; }
|
||||||
public YArtist Artist { get; set; }
|
public string ConcertTitle { get; set; }
|
||||||
public string City { get; set; }
|
public string ContentRating { get; set; }
|
||||||
public string ConcertTitle { get; set; }
|
public List<decimal> Coordinates { get; set; }
|
||||||
public string ContentRating { get; set; }
|
public YCashback Cashback { get; set; }
|
||||||
public List<decimal> Coordinates { get; set; }
|
[JsonPropertyName("data-session-id")]
|
||||||
public YCashback Cashback { get; set; }
|
public string DataSessionId { get; set; }
|
||||||
[JsonProperty("data-session-id")]
|
public DateTime DateTime { get; set; }
|
||||||
public string DataSessionId { get; set; }
|
public string Hash { get; set; }
|
||||||
public DateTime DateTime { get; set; }
|
public string Id { get; set; }
|
||||||
public string Hash { get; set; }
|
public List<string> Images { get; set; }
|
||||||
public string Id { get; set; }
|
public string ImageUrl { get; set; }
|
||||||
public List<string> Images { get; set; }
|
public string Map { get; set; }
|
||||||
public string ImageUrl { get; set; }
|
public string MapUrl { get; set; }
|
||||||
public string Map { get; set; }
|
[JsonPropertyName("metro-stations")]
|
||||||
public string MapUrl { get; set; }
|
public List<YMetroStation> MetroStations { get; set; }
|
||||||
[JsonProperty("metro-stations")]
|
public string Place { get; set; }
|
||||||
public List<YMetroStation> MetroStations { get; set; }
|
public YPrice MinPrice { get; set; }
|
||||||
public string Place { get; set; }
|
|
||||||
public YPrice MinPrice { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace YandexMusic.API.Models.Artist
|
namespace YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
|
public class YDeprecation
|
||||||
{
|
{
|
||||||
public class YDeprecation
|
public string TargetArtistId { get; set; }
|
||||||
{
|
|
||||||
public string TargetArtistId { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
namespace YandexMusic.API.Models.Artist
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
|
public class YMetroStation
|
||||||
{
|
{
|
||||||
public class YMetroStation
|
[JsonPropertyName("line-color")]
|
||||||
{
|
public string LineColor { get; set; }
|
||||||
[JsonProperty("line-color")]
|
public string Title { get; set; }
|
||||||
public string LineColor { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using YandexMusic.API.Models.Common;
|
using YandexMusic.API.Models.Common;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Artist
|
namespace YandexMusic.API.Models.Artist;
|
||||||
{
|
|
||||||
public class YTracksPage
|
|
||||||
{
|
|
||||||
public YPager Pager { get; set; }
|
|
||||||
|
|
||||||
public List<YTrack> Tracks { get; set; }
|
public class YTracksPage
|
||||||
}
|
{
|
||||||
|
public YPager Pager { get; set; }
|
||||||
|
|
||||||
|
public List<YTrack> Tracks { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,71 +1,35 @@
|
|||||||
namespace YandexMusic.API.Models.Common.Cover
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Common.Cover;
|
||||||
|
|
||||||
|
public class YCoverConverter : JsonConverter<YCover>
|
||||||
{
|
{
|
||||||
public sealed class YCoverConverter : JsonConverter
|
public override YCover? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
public override bool CanConvert(Type objectType)
|
if (reader.TokenType != JsonTokenType.StartObject) return null;
|
||||||
|
using var doc = JsonDocument.ParseValue(ref reader);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
var type = root.TryGetProperty("type", out var t) ? t.GetString() : null;
|
||||||
|
if (root.TryGetProperty("error", out _)) type = "error";
|
||||||
|
|
||||||
|
return type switch
|
||||||
{
|
{
|
||||||
return typeof(YCover).IsAssignableFrom(objectType);
|
"color" => JsonSerializer.Deserialize<YCoverColor>(root.GetRawText(), options),
|
||||||
}
|
"error" => JsonSerializer.Deserialize<YCoverError>(root.GetRawText(), options),
|
||||||
|
"from-artist-photos" or "from-album-cover" => JsonSerializer.Deserialize<YCoverImage>(root.GetRawText(), options),
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
"pic" => JsonSerializer.Deserialize<YCoverPic>(root.GetRawText(), options),
|
||||||
{
|
"mosaic" => JsonSerializer.Deserialize<YCoverMosaic>(root.GetRawText(), options),
|
||||||
if (reader.TokenType == JsonToken.Null)
|
_ => JsonSerializer.Deserialize<YCover>(root.GetRawText(), options)
|
||||||
return null;
|
};
|
||||||
|
|
||||||
JObject jObject = JObject.Load(reader);
|
|
||||||
YCover cover;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Фиктивный тип, т.к. у такой обложки нет поля с типом
|
|
||||||
if (jObject["type"] == null)
|
|
||||||
jObject.Add("type", "color");
|
|
||||||
|
|
||||||
YCoverType type = jObject["error"] != null
|
|
||||||
? YCoverType.Error
|
|
||||||
: jObject["type"].ToObject<YCoverType>();
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case YCoverType.Error:
|
|
||||||
cover = jObject.ToObject<YCoverError>();
|
|
||||||
break;
|
|
||||||
case YCoverType.Color:
|
|
||||||
cover = jObject.ToObject<YCoverColor>();
|
|
||||||
break;
|
|
||||||
case YCoverType.FromAlbumCover:
|
|
||||||
case YCoverType.FromArtistPhotos:
|
|
||||||
cover = jObject.ToObject<YCoverImage>();
|
|
||||||
break;
|
|
||||||
case YCoverType.Pic:
|
|
||||||
cover = jObject.ToObject<YCoverPic>();
|
|
||||||
break;
|
|
||||||
case YCoverType.Mosaic:
|
|
||||||
cover = jObject.ToObject<YCoverMosaic>();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
cover = jObject.ToObject<YCover>();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new Exception($"Ошибка десериализации типа \"{objectType.Name}\".", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
JObject cover = JObject.FromObject(value, serializer);
|
|
||||||
|
|
||||||
cover.WriteTo(writer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class YCover
|
public override void Write(Utf8JsonWriter writer, YCover value, JsonSerializerOptions options)
|
||||||
{
|
=> JsonSerializer.Serialize(writer, value, options);
|
||||||
public YCoverType Type { get; set; }
|
}
|
||||||
}
|
|
||||||
|
[JsonConverter(typeof(YCoverConverter))]
|
||||||
|
public class YCover
|
||||||
|
{
|
||||||
|
public YCoverType Type { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Common.Cover
|
namespace YandexMusic.API.Models.Common.Cover;
|
||||||
|
|
||||||
|
public class YCoverColor : YCover
|
||||||
{
|
{
|
||||||
public class YCoverColor : YCover
|
public string Uri { get; set; }
|
||||||
{
|
public string Color { get; set; }
|
||||||
public string Uri { get; set; }
|
|
||||||
public string Color { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace YandexMusic.API.Models.Common.Cover
|
namespace YandexMusic.API.Models.Common.Cover;
|
||||||
|
|
||||||
|
public class YCoverError : YCover
|
||||||
{
|
{
|
||||||
public class YCoverError : YCover
|
public string Error { get; set; }
|
||||||
{
|
|
||||||
public string Error { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Common.Cover
|
namespace YandexMusic.API.Models.Common.Cover;
|
||||||
|
|
||||||
|
public class YCoverImage : YCover
|
||||||
{
|
{
|
||||||
public class YCoverImage : YCover
|
public string Prefix { get; set; }
|
||||||
{
|
public string Uri { get; set; }
|
||||||
public string Prefix { get; set; }
|
|
||||||
public string Uri { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Common.Cover
|
namespace YandexMusic.API.Models.Common.Cover;
|
||||||
|
|
||||||
|
public class YCoverMosaic : YCover
|
||||||
{
|
{
|
||||||
public class YCoverMosaic : YCover
|
public bool Custom { get; set; }
|
||||||
{
|
public List<string> ItemsUri { get; set; }
|
||||||
public bool Custom { get; set; }
|
|
||||||
public List<string> ItemsUri { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
namespace YandexMusic.API.Models.Common.Cover
|
namespace YandexMusic.API.Models.Common.Cover;
|
||||||
|
|
||||||
|
public class YCoverPic : YCover
|
||||||
{
|
{
|
||||||
public class YCoverPic : YCover
|
public bool Custom { get; set; }
|
||||||
{
|
public string Dir { get; set; }
|
||||||
public bool Custom { get; set; }
|
public bool IsCustom { get; set; }
|
||||||
public string Dir { get; set; }
|
public string Uri { get; set; }
|
||||||
public bool IsCustom { get; set; }
|
public string Version { get; set; }
|
||||||
public string Uri { get; set; }
|
|
||||||
public string Version { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Common.Cover
|
namespace YandexMusic.API.Models.Common.Cover;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum YCoverType
|
||||||
{
|
{
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
Color,
|
||||||
public enum YCoverType
|
Error,
|
||||||
{
|
[EnumMember(Value = "from-artist-photos")]
|
||||||
Color,
|
FromArtistPhotos,
|
||||||
Error,
|
[EnumMember(Value = "from-album-cover")]
|
||||||
[EnumMember(Value = "from-artist-photos")]
|
FromAlbumCover,
|
||||||
FromArtistPhotos,
|
Mosaic,
|
||||||
[EnumMember(Value = "from-album-cover")]
|
Pic,
|
||||||
FromAlbumCover,
|
|
||||||
Mosaic,
|
|
||||||
Pic
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YBaseModel
|
||||||
{
|
{
|
||||||
public class YBaseModel
|
[JsonIgnore]
|
||||||
{
|
public YExecutionContext Context { get; set; }
|
||||||
[JsonIgnore]
|
|
||||||
public YExecutionContext Context { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YButton : YStyle
|
||||||
{
|
{
|
||||||
public class YButton : YStyle
|
public string Text { get; set; }
|
||||||
{
|
public string Url { get; set; }
|
||||||
public string Text { get; set; }
|
public string Uri { get; set; }
|
||||||
public string Url { get; set; }
|
public string Color { get; set; }
|
||||||
#warning Дублирование?
|
public bool ViewBrowser { get; set; }
|
||||||
public string Uri { get; set; }
|
|
||||||
public string Color { get; set; }
|
|
||||||
public bool ViewBrowser { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YCashback
|
||||||
{
|
{
|
||||||
public class YCashback
|
public string Title { get; set; }
|
||||||
{
|
|
||||||
public string Title { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using YandexMusic.API.Models.Landing.Entity.Entities;
|
using YandexMusic.API.Models.Landing.Entity.Entities;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YChart
|
||||||
{
|
{
|
||||||
public class YChart
|
public int Position { get; set; }
|
||||||
{
|
public int Listeners { get; set; }
|
||||||
public int Position { get; set; }
|
public int Shift { get; set; }
|
||||||
public int Listeners { get; set; }
|
public YChartProgress Progress { get; set; }
|
||||||
public int Shift { get; set; }
|
|
||||||
public YChartProgress Progress { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
using YandexMusic.API.Models.Artist;
|
using YandexMusic.API.Models.Artist;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YClip
|
||||||
{
|
{
|
||||||
public class YClip
|
public List<YArtist> Artists { get; set; }
|
||||||
{
|
public string ClipId { get; set; }
|
||||||
public List<YArtist> Artists { get; set; }
|
public List<string> Disclaimers { get; set; }
|
||||||
public string ClipId { get; set; }
|
public int Duration { get; set; }
|
||||||
public List<string> Disclaimers { get; set; }
|
public bool Explicit { get; set; }
|
||||||
public int Duration { get; set; }
|
public string PlayerId { get; set; }
|
||||||
public bool Explicit { get; set; }
|
public string PreviewUrl { get; set; }
|
||||||
public string PlayerId { get; set; }
|
public string Uuid { get; set; }
|
||||||
public string PreviewUrl { get; set; }
|
public string Thumbnail { get; set; }
|
||||||
public string Uuid { get; set; }
|
public string Title { get; set; }
|
||||||
public string Thumbnail { get; set; }
|
public List<string> TrackIds { get; set; }
|
||||||
public string Title { get; set; }
|
|
||||||
public List<string> TrackIds { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YCloseButtonStyles
|
||||||
{
|
{
|
||||||
public class YCloseButtonStyles
|
public YStyle White { get; set; }
|
||||||
{
|
public YStyle Black { get; set; }
|
||||||
public YStyle White { get; set; }
|
|
||||||
public YStyle Black { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YCountsTracks
|
||||||
{
|
{
|
||||||
public class YCountsTracks
|
public int All { get; set; }
|
||||||
{
|
public int Favorite { get; set; }
|
||||||
public int All { get; set; }
|
public int Ugc { get; set; }
|
||||||
public int Favorite { get; set; }
|
|
||||||
public int Ugc { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YCustomWave
|
||||||
{
|
{
|
||||||
public class YCustomWave
|
public string AnimationUrl { get; set; }
|
||||||
{
|
public string BackgroundImageUrl { get; set; }
|
||||||
public string AnimationUrl { get; set; }
|
public string Header { get; set; }
|
||||||
public string BackgroundImageUrl { get; set; }
|
public string Position { get; set; }
|
||||||
public string Header { get; set; }
|
public string Title { get; set; }
|
||||||
public string Position { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YDerivedColors
|
||||||
{
|
{
|
||||||
public class YDerivedColors
|
public string Average { get; set; }
|
||||||
{
|
public string WaveText { get; set; }
|
||||||
public string Average { get; set; }
|
public string MiniPlayer { get; set; }
|
||||||
public string WaveText { get; set; }
|
public string Accent { get; set; }
|
||||||
public string MiniPlayer { get; set; }
|
|
||||||
public string Accent { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YDescription
|
||||||
{
|
{
|
||||||
public class YDescription
|
public string Text { get; set; }
|
||||||
{
|
public string Uri { get; set; }
|
||||||
public string Text { get; set; }
|
|
||||||
public string Uri { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YError
|
||||||
{
|
{
|
||||||
public class YError
|
public string Name { get; set; }
|
||||||
{
|
public string Message { get; set; }
|
||||||
public string Name { get; set; }
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
public class YErrorResponse : Exception
|
||||||
{
|
{
|
||||||
public class YErrorResponse : Exception
|
public YInvocationInfo InvocationInfo { get; set; }
|
||||||
{
|
public YError Error { get; set; }
|
||||||
public YInvocationInfo InvocationInfo { get; set; }
|
|
||||||
public YError Error { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,57 +1,12 @@
|
|||||||
using YandexMusic.API.Common;
|
using YandexMusic.API.Common;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Common
|
namespace YandexMusic.API.Models.Common;
|
||||||
|
|
||||||
|
/// <summary>Контекст выполнения, содержащий ссылки на API и хранилище.</summary>
|
||||||
|
public class YExecutionContext
|
||||||
{
|
{
|
||||||
public sealed class YExecutionContextConverter : JsonConverter
|
/// <summary>Экземпляр основного API.</summary>
|
||||||
{
|
public YandexMusicApi API { get; internal set; } = null!;
|
||||||
#region Поля
|
/// <summary>Хранилище данных авторизации.</summary>
|
||||||
|
public AuthStorage Storage { get; internal set; } = null!;
|
||||||
private YandexMusicApi api;
|
|
||||||
private AuthStorage storage;
|
|
||||||
|
|
||||||
#endregion Поля
|
|
||||||
|
|
||||||
public override bool CanConvert(Type objectType)
|
|
||||||
{
|
|
||||||
return typeof(YBaseModel).IsAssignableFrom(objectType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
YBaseModel obj = (YBaseModel)Activator.CreateInstance(objectType);
|
|
||||||
serializer.Populate(reader, obj);
|
|
||||||
|
|
||||||
obj.Context = new YExecutionContext
|
|
||||||
{
|
|
||||||
API = api,
|
|
||||||
Storage = storage
|
|
||||||
};
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new Exception($"Ошибка десериализации типа \"{objectType.Name}\".", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public YExecutionContextConverter(YandexMusicApi yandex, AuthStorage auth)
|
|
||||||
{
|
|
||||||
api = yandex;
|
|
||||||
storage = auth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class YExecutionContext
|
|
||||||
{
|
|
||||||
public YandexMusicApi API { get; internal set; }
|
|
||||||
public AuthStorage Storage { get; internal set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user