Добавьте файлы проекта.

This commit is contained in:
FrigaT
2026-04-10 12:12:33 +03:00
parent 9615cf42ee
commit 11d0b0d72f
383 changed files with 9661 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
using YandexMusic.API.Common;
using YandexMusic.API.Models.Album;
using YandexMusic.API.Models.Common;
using YandexMusic.API.Requests.Album;
namespace YandexMusic.API;
/// <summary>
/// API для взаимодействия с альбомами
/// </summary>
public class YAlbumAPI : YCommonAPI
{
public YAlbumAPI(YandexMusicApi yandex) : base(yandex)
{
}
/// <summary>Получает альбом по идентификатору.</summary>
/// <param name="storage">Хранилище авторизации.</param>
/// <param name="albumId">Идентификатор альбома.</param>
/// <returns></returns>
public Task<YResponse<YAlbum>> GetAsync(AuthStorage storage, string albumId)
{
return new YGetAlbumBuilder(api, storage)
.Build(albumId)
.GetResponseAsync();
}
/// <summary>Получение альбомов по списку идентификаторов.</summary>
/// <param name="storage">Хранилище авторизации.</param>
/// <param name="albumIds">Идентификаторы альбомов.</param>
/// <returns></returns>
public Task<YResponse<List<YAlbum>>> GetAsync(AuthStorage storage, IEnumerable<string> albumIds)
{
return new YGetAlbumsBuilder(api, storage)
.Build(albumIds)
.GetResponseAsync();
}
}

View File

@@ -0,0 +1,71 @@
using YandexMusic.API.Common;
using YandexMusic.API.Models.Artist;
using YandexMusic.API.Models.Common;
using YandexMusic.API.Requests.Artist;
namespace YandexMusic.API;
/// <summary>
/// API для взаимодействия с исполнителями
/// </summary>
public class YArtistAPI : YCommonAPI
{
public YArtistAPI(YandexMusicApi yandex) : base(yandex)
{
}
/// <summary>
/// Получение исполнителя
/// </summary>
/// <param name="storage">Хранилище</param>
/// <param name="artistId">Идентификатор</param>
public Task<YResponse<YArtistBriefInfo>> GetAsync(AuthStorage storage, string artistId)
{
return new YGetArtistBuilder(api, storage)
.Build(artistId)
.GetResponseAsync();
}
/// <summary>
/// Получение исполнителей
/// </summary>
/// <param name="storage">Хранилище</param>
/// <param name="artistIds">Идентификаторы</param>
public Task<YResponse<List<YArtist>>> GetAsync(AuthStorage storage, IEnumerable<string> artistIds)
{
return new YGetArtistsBuilder(api, storage)
.Build(artistIds)
.GetResponseAsync();
}
/// <summary>
/// Получение треков исполнителя с пагинацией
/// <remarks>
/// Треки поставляются по <paramref name="pageSize"/> штук на страницу,
/// для получения всех треков необходимо использовать метод <see cref="GetAllTracksAsync"/>
/// </remarks>
/// </summary>
/// <param name="storage">Хранилище</param>
/// <param name="artistId">Идентификатор исполнителя</param>
/// <param name="page">Страница ответов</param>
/// <param name="pageSize">Количество треков на странице ответов</param>
public Task<YResponse<YTracksPage>> GetTracksAsync(AuthStorage storage, string artistId, int page = 0, int pageSize = 20)
{
return new YGetArtistTrackBuilder(api, storage)
.Build((artistId, page, pageSize))
.GetResponseAsync();
}
/// <summary>
/// Получение всех треков исполнителя
/// </summary>
/// <param name="storage">Хранилище</param>
/// <param name="artistId">Идентификатор исполнителя</param>
public async Task<YResponse<YTracksPage>> GetAllTracksAsync(AuthStorage storage, string artistId)
{
YResponse<YArtistBriefInfo> response = await GetAsync(storage, artistId);
return await GetTracksAsync(storage, artistId, pageSize: response.Result.Artist.Counts.Tracks);
}
}

View File

@@ -0,0 +1,14 @@
namespace YandexMusic.API;
/// <summary>
/// Родительский класс для ветки API
/// </summary>
public class YCommonAPI
{
protected YandexMusicApi api;
public YCommonAPI(YandexMusicApi yandex)
{
api = yandex;
}
}

View File

@@ -0,0 +1,40 @@
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();
}
}
}

View File

@@ -0,0 +1,63 @@
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();
}
}
}

View File

@@ -0,0 +1,258 @@
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 Получение списка "Вы недавно слушали"
}
}

View File

@@ -0,0 +1,33 @@
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();
}
}
}

View File

@@ -0,0 +1,335 @@
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 Операции над плейлистами
}
}

View File

@@ -0,0 +1,73 @@
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();
}
}
}

View File

@@ -0,0 +1,116 @@
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();
}
}
}

View File

@@ -0,0 +1,141 @@
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();
}
}
}

View File

@@ -0,0 +1,308 @@
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 Получение данных трека
}
}

View File

@@ -0,0 +1,72 @@
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();
}
}
}

View File

@@ -0,0 +1,303 @@
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();
}
}
}

View File

@@ -0,0 +1,103 @@
using System.Net;
using YandexMusic.API.Common.Debug;
using YandexMusic.API.Common.Providers;
using YandexMusic.API.Models.Account;
using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Common
{
/// <summary>
/// Хранилище данных пользователя
/// </summary>
public class AuthStorage
{
#region Свойства
/// <summary>
/// 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;
}
}
}

View File

@@ -0,0 +1,44 @@
using System.Net;
namespace YandexMusic.API.Common
{
/// <summary>
/// Загрузчик файлов по ссылке
/// </summary>
public class DataDownloader
{
private AuthStorage authStorage;
private async Task<HttpContent> GetResponseContent(string url, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
{
HttpRequestMessage message = new(new HttpMethod(WebRequestMethods.Http.Get), url);
HttpResponseMessage response = await authStorage.Provider.GetWebResponseAsync(message, httpCompletionOption);
return response.Content;
}
public async Task<Stream> AsStream(string url, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
{
HttpContent content = await GetResponseContent(url, httpCompletionOption);
return await content.ReadAsStreamAsync();
}
public async Task<byte[]> AsBytes(string url)
{
HttpContent content = await GetResponseContent(url);
return await content.ReadAsByteArrayAsync();
}
public async Task ToFile(string url, string fileName)
{
using Stream stream = await AsStream(url);
using FileStream fs = File.Create(fileName);
await stream.CopyToAsync(fs);
}
public DataDownloader(AuthStorage storage)
{
authStorage = storage;
}
}
}

View File

@@ -0,0 +1,75 @@
using System.Security.Cryptography;
using System.Text;
namespace YandexMusic.API.Common
{
/// <summary>
/// Класс для шифровки
/// </summary>
public class Encryptor
{
#region Поля
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();
}
}
}

View File

@@ -0,0 +1,60 @@
using YandexMusic.API.Models.Common;
namespace YandexMusic.API.Common.Providers
{
public class CommonRequestProvider : IRequestProvider
{
#region Поля
protected AuthStorage storage;
#endregion Поля
public CommonRequestProvider(AuthStorage authStorage)
{
storage = authStorage;
}
#region IRequestProvider
public virtual Task<HttpResponseMessage> GetWebResponseAsync(HttpRequestMessage message, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
{
throw new NotImplementedException();
}
public virtual async Task<T> GetDataFromResponseAsync<T>(YandexMusicApi api, HttpResponseMessage response)
{
string result = await response.Content.ReadAsStringAsync();
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
}
}

View File

@@ -0,0 +1,69 @@
using System.Net;
using YandexMusic.API.Models.Common;
namespace YandexMusic.API.Common.Providers
{
/// <summary>
/// Стандартный провайдер запросов
/// </summary>
public class DefaultRequestProvider : CommonRequestProvider
{
#region Вспомогательные функции
private Exception ProcessException(Exception ex)
{
if (ex is not WebException webException)
return ex;
if (webException.Response is null)
return ex;
Stream s = webException.Response.GetResponseStream();
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)
{
}
#region IRequestProvider
public override Task<HttpResponseMessage> GetWebResponseAsync(HttpRequestMessage message,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
{
try
{
HttpClient client = new(new SocketsHttpHandler
{
Proxy = storage.Context.WebProxy,
AutomaticDecompression = DecompressionMethods.GZip,
UseCookies = true,
CookieContainer = storage.Context.Cookies,
});
return client.SendAsync(message, completionOption);
}
catch (Exception ex)
{
throw ProcessException(ex);
}
}
#endregion IRequestProvider
}
}

View File

@@ -0,0 +1,25 @@
namespace YandexMusic.API.Common.Providers
{
/// <summary>
/// Интерфейс для провайдеров обработки запросов
/// </summary>
public interface IRequestProvider
{
/// <summary>
/// Функция получения ответа
/// </summary>
/// <param name="message">Запрос</param>
/// <param name="completionOption">Опция завершения запроса</param>
/// <returns></returns>
Task<HttpResponseMessage> GetWebResponseAsync(HttpRequestMessage message, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead);
/// <summary>
/// Функция формирования ответа
/// </summary>
/// <typeparam name="T">Тип объекта с ответом</typeparam>
/// <param name="api">API</param>
/// <param name="response">Ответ</param>
/// <returns></returns>
Task<T> GetDataFromResponseAsync<T>(YandexMusicApi api, HttpResponseMessage response);
}
}

View File

@@ -0,0 +1,27 @@
namespace YandexMusic.API.Common.Providers
{
/// <summary>
/// Провайдер запросов данными из файла
/// </summary>
public class MockRequestProvider : CommonRequestProvider
{
public MockRequestProvider(AuthStorage authStorage) : base(authStorage)
{
storage = authStorage;
}
#region IRequestProvider
public override Task<HttpResponseMessage> GetWebResponseAsync(HttpRequestMessage message,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
{
throw new NotImplementedException();
}
#endregion IRequestProvider
}
}

View File

@@ -0,0 +1,7 @@
namespace YandexMusic.API.Common.Ynison
{
public class UpperSnakeCaseNamingStrategy : SnakeCaseNamingStrategy
{
protected override string ResolvePropertyName(string name) => base.ResolvePropertyName(name).ToUpper();
}
}

View File

@@ -0,0 +1,315 @@
using System.Net.WebSockets;
using YandexMusic.API.Models.Track;
using YandexMusic.API.Models.Ynison;
using YandexMusic.API.Models.Ynison.Messages;
namespace YandexMusic.API.Common.Ynison
{
public class YnisonPlayer : IDisposable
{
#region Поля
private readonly JsonSerializerSettings jsonSettings = new()
{
Converters = new List<JsonConverter> {
new StringEnumConverter(new UpperSnakeCaseNamingStrategy())
},
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new DefaultContractResolver
{
// Важно! Унисон отдаёт данные в SnakeCase
NamingStrategy = new SnakeCaseNamingStrategy()
}
};
private AuthStorage storage;
private YnisonWebSocket redirector;
private YnisonWebSocket state;
#endregion Поля
#region Свойства
/// <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; }
}
public delegate void OnReceiveEventHandler(YnisonPlayer player, ReceiveEventArgs args);
/// <summary>
/// Получение данных
/// </summary>
public event OnReceiveEventHandler OnReceive;
public class CloseEventArgs
{
public WebSocketCloseStatus? Status { get; set; }
public string Description { get; set; }
}
public delegate void OnCloseEventHandler(YnisonPlayer player, CloseEventArgs args);
/// <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);
throw exception ?? new Exception("Ошибка десериализации ответа с ошибкой.");
}
return Deserialize<T>(messageType, data);
}
private string DefaultState()
{
YYnisonVersion version = new()
{
DeviceId = storage.DeviceId,
Version = "0"
};
YYnisonUpdateFullStateMessage fullState = new()
{
UpdateFullState = new()
{
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
}
}
}
};
return SerializeJson(fullState);
}
private YTrack GetCurrent()
{
if (State == null)
return null;
int index = State.PlayerState.PlayerQueue.CurrentPlayableIndex;
if (index < 0 || index > State.PlayerState.PlayerQueue.PlayableList.Count)
return null;
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
}
}

View File

@@ -0,0 +1,179 @@
using System.Net.WebSockets;
using System.Text;
namespace YandexMusic.API.Common.Ynison
{
public class YnisonWebSocket : IDisposable
{
#region Поля
private readonly JsonSerializerSettings jsonSettings = new()
{
Converters = new List<JsonConverter> {
new StringEnumConverter {
NamingStrategy = new CamelCaseNamingStrategy()
}
},
NullValueHandling = NullValueHandling.Ignore
};
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; }
}
public delegate void OnReceiveEventHandler(YnisonWebSocket socket, ReceiveEventArgs args);
/// <summary>
/// Получение данных
/// </summary>
public event OnReceiveEventHandler OnReceive;
public class CloseEventArgs
{
public WebSocketCloseStatus? Status { get; set; }
public string Description { get; set; }
}
public delegate void OnCloseEventHandler(YnisonWebSocket socket, CloseEventArgs args);
/// <summary>
/// Закрытие соединения
/// </summary>
public event OnCloseEventHandler OnClose;
#endregion События
#region Вспомогательные функции
private string SerializeJson(object obj)
{
return JsonConvert.SerializeObject(obj, jsonSettings);
}
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);
data.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
} while (!result.EndOfMessage);
return data.ToString();
}
#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;
}
public async Task BeginReceive()
{
if (socketClient.State != WebSocketState.Open)
return;
do
{
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);
}
public ValueTask Send(string json)
{
ReadOnlyMemory<byte> message = new(Encoding.UTF8.GetBytes(json));
return socketClient.SendAsync(message, WebSocketMessageType.Text, false, CancellationToken.None);
}
public Task StopReceive()
{
if (socketClient.State != WebSocketState.Open)
return Task.CompletedTask;
cancellationTokenSource.Cancel(false);
return Task.CompletedTask;
}
#region IDisposable
public void Dispose()
{
socketClient?.Dispose();
cancellationTokenSource?.Dispose();
}
#endregion IDisposable
}
}

View File

@@ -0,0 +1,25 @@
using YandexMusic.API.Models.Album;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для альбома
/// </summary>
public static partial class YAlbumExtensions
{
public static YAlbum WithTracks(this YAlbum album)
{
return WithTracksAsync(album).GetAwaiter().GetResult();
}
public static string AddLike(this YAlbum album)
{
return AddLikeAsync(album).GetAwaiter().GetResult();
}
public static string RemoveLike(this YAlbum album)
{
return RemoveLikeAsync(album).GetAwaiter().GetResult();
}
}
}

View File

@@ -0,0 +1,30 @@
using YandexMusic.API.Models.Album;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для альбома
/// </summary>
public static partial class YAlbumExtensions
{
public static async Task<YAlbum> WithTracksAsync(this YAlbum album)
{
return album.Volumes != null
? 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;
}
public static async Task<string> RemoveLikeAsync(this YAlbum album)
{
return (await album.Context.API.Library.RemoveAlbumLikeAsync(album.Context.Storage, album))
.Result;
}
}
}

View File

@@ -0,0 +1,36 @@
using YandexMusic.API.Models.Artist;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для исполнителя
/// </summary>
public static partial class YArtistExtensions
{
public static YArtistBriefInfo BriefInfo(this YArtist artist)
{
return BriefInfoAsync(artist).GetAwaiter().GetResult();
}
public static YTracksPage GetTracks(this YArtist artist, int page = 0, int pageSize = 20)
{
return GetTracksAsync(artist, page, pageSize).GetAwaiter().GetResult();
}
public static List<YTrack> GetAllTracks(this YArtist artist)
{
return GetAllTracksAsync(artist).GetAwaiter().GetResult();
}
public static string AddLike(this YArtist artist)
{
return AddLikeAsync(artist).GetAwaiter().GetResult();
}
public static string RemoveLike(this YArtist artist)
{
return RemoveLikeAsync(artist).GetAwaiter().GetResult();
}
}
}

View File

@@ -0,0 +1,41 @@
using YandexMusic.API.Models.Artist;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для исполнителя
/// </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;
}
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))
.Result;
}
public static async Task<List<YTrack>> GetAllTracksAsync(this YArtist artist)
{
return (await artist.Context.API.Artist.GetAllTracksAsync(artist.Context.Storage, artist.Id))
.Result.Tracks;
}
public static async Task<string> AddLikeAsync(this YArtist artist)
{
return (await artist.Context.API.Library.AddArtistLikeAsync(artist.Context.Storage, artist))
.Result;
}
public static async Task<string> RemoveLikeAsync(this YArtist artist)
{
return (await artist.Context.API.Library.RemoveArtistLikeAsync(artist.Context.Storage, artist))
.Result;
}
}
}

View File

@@ -0,0 +1,56 @@
using YandexMusic.API.Models.Playlist;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для плейлиста
/// </summary>
public static partial class YPlaylistExtensions
{
private static bool CheckUser(YPlaylist playlist)
{
return playlist.Owner.Uid == playlist.Context.Storage.User.Uid;
}
public static YPlaylist WithTracks(this YPlaylist playlist)
{
return WithTracksAsync(playlist).GetAwaiter().GetResult();
}
public static string AddLike(this YPlaylist playlist)
{
return AddLikeAsync(playlist).GetAwaiter().GetResult();
}
public static string RemoveLike(this YPlaylist playlist)
{
return RemoveLikeAsync(playlist).GetAwaiter().GetResult();
}
public static YPlaylist Rename(this YPlaylist playlist, string newName)
{
return RenameAsync(playlist, newName).GetAwaiter().GetResult();
}
public static bool Delete(this YPlaylist playlist)
{
return DeleteAsync(playlist).GetAwaiter().GetResult();
}
public static YPlaylist InsertTracks(this YPlaylist playlist, params YTrack[] tracks)
{
return InsertTracksAsync(playlist, tracks).GetAwaiter().GetResult();
}
public static YPlaylist RemoveTracks(this YPlaylist playlist, params YTrack[] tracks)
{
return RemoveTracksAsync(playlist, tracks).GetAwaiter().GetResult();
}
public static bool UploadTracks(this YPlaylist playlist, string filePath, string fileName)
{
return UploadTracksAsync(playlist, filePath, fileName).GetAwaiter().GetResult();
}
}
}

View File

@@ -0,0 +1,72 @@
using YandexMusic.API.Models.Playlist;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для плейлиста
/// </summary>
public static partial class YPlaylistExtensions
{
public static async Task<YPlaylist> WithTracksAsync(this YPlaylist playlist)
{
return playlist.Tracks != null
? 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;
}
public static async Task<string> RemoveLikeAsync(this YPlaylist playlist)
{
return (await playlist.Context.API.Library.RemovePlaylistLikeAsync(playlist.Context.Storage, playlist))
.Result;
}
public static async Task<YPlaylist> RenameAsync(this YPlaylist playlist, string newName)
{
return CheckUser(playlist)
? (await playlist.Context.API.Playlist.RenameAsync(playlist.Context.Storage, playlist, newName))
.Result
: playlist;
}
public static async Task<bool> DeleteAsync(this YPlaylist playlist)
{
return CheckUser(playlist) && await playlist.Context.API.Playlist.DeleteAsync(playlist.Context.Storage, playlist);
}
public static async Task<YPlaylist> InsertTracksAsync(this YPlaylist playlist, params YTrack[] tracks)
{
return CheckUser(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)
{
return CheckUser(playlist)
? (await playlist.Context.API.Playlist.DeleteTracksAsync(playlist.Context.Storage, playlist, tracks))
.Result
: playlist;
}
public static async Task<bool> UploadTracksAsync(this YPlaylist playlist, string filePath, string fileName)
{
if (!CheckUser(playlist))
return false;
string target = (await playlist.Context.API.UserGeneratedContent.GetUgcUploadLinkAsync(playlist.Context.Storage, playlist, fileName))
.PostTarget;
return (await playlist.Context.API.UserGeneratedContent.UploadUgcTrackAsync(playlist.Context.Storage, target, filePath))
.Result == "CREATED";
}
}
}

View File

@@ -0,0 +1,26 @@
using YandexMusic.API.Models.Radio;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для радиостанции
/// </summary>
public static partial class YStationResultExtensions
{
public static List<YSequenceItem> GetTracks(this YStation station, string prevTrackId = "")
{
return GetTracksAsync(station, prevTrackId).GetAwaiter().GetResult();
}
public static string SetSettings2(this YStation station, YStationSettings2 settings)
{
return SetSettings2Async(station, settings).GetAwaiter().GetResult();
}
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();
}
}
}

View File

@@ -0,0 +1,28 @@
using YandexMusic.API.Models.Radio;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для радиостанции
/// </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;
}
public static async Task<string> SetSettings2Async(this YStation station, YStationSettings2 settings)
{
return (await station.Context.API.Radio.SetStationSettings2Async(station.Context.Storage, station, settings))
.Result;
}
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);
}
}
}

View File

@@ -0,0 +1,55 @@
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для трека
/// </summary>
public static partial class YTrackExtensions
{
public static string GetLink(this YTrack track)
{
return GetLinkAsync(track).GetAwaiter().GetResult();
}
public static void Save(this YTrack track, string filePath)
{
SaveAsync(track, filePath).GetAwaiter().GetResult();
}
public static int AddLike(this YTrack track)
{
return AddLikeAsync(track).GetAwaiter().GetResult();
}
public static int RemoveLike(this YTrack track)
{
return RemoveLikeAsync(track).GetAwaiter().GetResult();
}
public static int AddDislike(this YTrack track)
{
return AddDislikeAsync(track).GetAwaiter().GetResult();
}
public static int RemoveDislike(this YTrack track)
{
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)
{
return SendPlayTrackInfoAsync(track, from, fromCache, playId, playlistId, totalPlayedSeconds).GetAwaiter().GetResult();
}
public static YTrackSupplement Supplement(this YTrack track)
{
return SupplementAsync(track).GetAwaiter().GetResult();
}
public static YTrackSimilar Similar(this YTrack track)
{
return SimilarAsync(track).GetAwaiter().GetResult();
}
}
}

View File

@@ -0,0 +1,61 @@
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API
{
/// <summary>
/// Методы-расширения для трека
/// </summary>
public static partial class YTrackExtensions
{
public static Task<string> GetLinkAsync(this YTrack track)
{
return track.Context.API.Track.GetFileLinkAsync(track.Context.Storage, track);
}
public static Task SaveAsync(this YTrack track, string filePath)
{
return track.Context.API.Track.ExtractToFileAsync(track.Context.Storage, track, filePath);
}
public static async Task<int> AddLikeAsync(this YTrack track)
{
return (await track.Context.API.Library.AddTrackLikeAsync(track.Context.Storage, track))
.Result.Revision;
}
public static async Task<int> RemoveLikeAsync(this YTrack track)
{
return (await track.Context.API.Library.RemoveTrackLikeAsync(track.Context.Storage, track))
.Result.Revision;
}
public static async Task<int> AddDislikeAsync(this YTrack track)
{
return (await track.Context.API.Library.AddTrackDislikeAsync(track.Context.Storage, track))
.Result.Revision;
}
public static async Task<int> RemoveDislikeAsync(this YTrack track)
{
return (await track.Context.API.Library.RemoveTrackDislikeAsync(track.Context.Storage, track))
?.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)
{
return track.Context.API.Track.SendPlayTrackInfoAsync(track.Context.Storage, track, from, fromCache, playId, playlistId, totalPlayedSeconds);
}
public static async Task<YTrackSupplement> SupplementAsync(this YTrack track)
{
return (await track.Context.API.Track.GetSupplementAsync(track.Context.Storage, track))
.Result;
}
public static async Task<YTrackSimilar> SimilarAsync(this YTrack track)
{
return (await track.Context.API.Track.GetSimilarAsync(track.Context.Storage, track))
.Result;
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Net;
namespace YandexMusic.API.Extensions
{
public static class HttpRequestHeaderExtensions
{
public static string GetName(this HttpRequestHeader header)
{
return header.ToString().SplitByCapitalLetter("-");
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Text.RegularExpressions;
namespace YandexMusic.API.Extensions
{
public static class StringExtensions
{
public static string ReplaceRegex(this string str, string regExpr, string replStr, RegexOptions options = RegexOptions.IgnoreCase)
{
return str == null
? string.Empty
: Regex.Replace(str, regExpr, replStr);
}
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)
.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>()
.Select(m => m.Value)
.ToArray()
: new string[] { };
}
}
}

View File

@@ -0,0 +1,15 @@
namespace YandexMusic.API.Models.Account
{
public class YAccessToken
{
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public string Expires { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
public string Uid { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using YandexMusic.API.Models.Common;
namespace YandexMusic.API.Models.Account
{
public class YAccount
{
public bool Child { get; set; }
public string Birthday { get; set; }
public string DisplayName { get; set; }
public string FirstName { get; set; }
public string FullName { get; set; }
public bool HostedUser { get; set; }
public string Login { get; set; }
public bool NonOwnerFamilyMember { get; set; }
public DateTime Now { get; set; }
[JsonProperty("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; }
}
}

View File

@@ -0,0 +1,26 @@
using YandexMusic.API.Models.Common;
namespace YandexMusic.API.Models.Account
{
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; }
// Повторяющееся свойство с другим названием
[JsonProperty("bar-below")]
private YBar BarBelow2
{
set => BarBelow = value;
}
public string Userhash { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthBase
{
public YAuthStatus Status { get; set; }
[JsonProperty("redirect_url")]
public string RedirectUrl { get; set; }
public List<YAuthError> Errors { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthCaptcha : YAuthBase
{
public string Id { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public string Mode { get; set; }
public List<YAuthCaptchaError> Error { get; set; }
public bool CountryFromAudioWhiteList { get; set; }
public YAuthCaptchaOptions Options { get; set; }
public YAuthCaptchaVoice Voice { get; set; }
[JsonProperty("image_url")]
public string ImageUrl { get; set; }
public string Key { get; set; }
public string Static { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthCaptchaError
{
public string Message { get; set; }
public YAuthCaptchaErrorCode Code { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace YandexMusic.API.Models.Account
{
public enum YAuthCaptchaErrorCode
{
MissingValue,
CaptchaLocate,
Incorrect
}
}

View File

@@ -0,0 +1,7 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthCaptchaOptions
{
public bool AsyncCheck { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthCaptchaVoice
{
public string Url { get; set; }
[JsonProperty("intro_url")]
public string IntroUrl { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System.Runtime.Serialization;
namespace YandexMusic.API.Models.Account
{
public enum YAuthError
{
[EnumMember(Value = "authorization.invalid")]
AuthorizationInvalid,
[EnumMember(Value = "sessionid.invalid")]
SessionIdInvalid,
[EnumMember(Value = "password.not_matched")]
PasswordNotMatched,
[EnumMember(Value = "password.empty")]
PasswordEmpty,
[EnumMember(Value = "captcha.required")]
CaptchaRequired,
[EnumMember(Value = "captcha.not_matched")]
CaptchaNotMatched,
[EnumMember(Value = "oauth_token.invalid")]
OAuthTokenInvalid
}
}

View File

@@ -0,0 +1,9 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthLetter : YAuthBase
{
public List<string> Code { get; set; }
public string Id { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthLetterStatus : YAuthBase
{
[JsonProperty("magic_link_confirmed")]
public bool MagicLinkConfirmed { get; set; }
[JsonProperty("track_id")]
public string TrackId { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System.Runtime.Serialization;
namespace YandexMusic.API.Models.Account
{
public enum YAuthMethod
{
Password,
[EnumMember(Value = "magic_x_token")]
MagicToken,
[EnumMember(Value = "magic_x_token_with_pictures")]
MagicTokenWithPictures,
[EnumMember(Value = "magic_link")]
MagicLink,
Magic,
Otp,
[EnumMember(Value = "social_gg")]
Social,
WebAuthN,
[EnumMember(Value = "sms_code")]
SmsCode
}
}

View File

@@ -0,0 +1,11 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthQR : YAuthBase
{
[JsonProperty("track_id")]
public string TrackId { get; set; }
[JsonProperty("csrf_token")]
public string CsrfToken { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthQRStatus : YAuthBase
{
[JsonProperty("default_uid")]
public int DefaultUid { get; set; }
public string RetPath { get; set; }
[JsonProperty("track_id")]
public string TrackId { get; set; }
public string Id { get; set; }
public string State { get; set; }
public YAuthCaptcha Captcha { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Account
{
public enum YAuthStatus
{
Ok,
Error
}
}

View File

@@ -0,0 +1,11 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthToken
{
[JsonProperty("csfr_token")]
public string CsfrToken { get; set; }
[JsonProperty("track_id")]
public string TrackId { get; set; }
}
}

View File

@@ -0,0 +1,47 @@
namespace YandexMusic.API.Models.Account
{
public class YAuthTypes : YAuthBase
{
[JsonProperty("primary_alias_type")]
public string PrimaryAliasType { get; set; }
[JsonProperty("csrf_token")]
public string CsrfToken { get; set; }
public string LocCsrf { get; set; }
[JsonProperty("use_new_suggest_by_phone")]
public bool UseNewSuggestByPhone { get; set; }
[JsonProperty("is_rfc_2fa_enabled")]
public bool IsRfc2faEnabled { get; set; }
[JsonProperty("track_id")]
public string TrackId { get; set; }
[JsonProperty("can_authorize")]
public string CanAuthorize { get; set; }
[JsonProperty("preferred_auth_method")]
public YAuthMethod PreferredAuthMethod { get; set; }
[JsonProperty("auth_methods")]
public List<YAuthMethod> AuthMethods { get; set; }
[JsonProperty("can_register")]
public bool CanRegister { get; set; }
[JsonProperty("location_id")]
public string LocationId { get; set; }
public string Country { get; set; }
[JsonProperty("phone_number")]
public YPhoneNumber PhoneNumberNumber { get; set; }
[JsonProperty("magic_link_email")]
public string MagicLinkEmail { get; set; }
public string TractorTargetLocationHost { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
using YandexMusic.API.Models.Common;
namespace YandexMusic.API.Models.Account
{
public class YBar : YStyle
{
public string AlertId { get; set; }
public string Text { get; set; }
public string AlertType { get; set; }
public YButton Button { get; set; }
public bool CloseButton { get; set; }
public YCloseButtonStyles CloseButtonStyles { get; set; }
}
}

View File

@@ -0,0 +1,31 @@
namespace YandexMusic.API.Models.Account
{
public class YLoginInfo
{
public string Id { get; set; }
public string Login { get; set; }
[JsonProperty("client_id")]
public string ClientId { get; set; }
[JsonProperty("display_name")]
public string DisplayName { get; set; }
[JsonProperty("real_name")]
public string RealName { get; set; }
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
public string Sex { get; set; }
[JsonProperty("default_email")]
public string DefaultEmail { get; set; }
public List<string> Emails { get; set; }
public string Birthday { get; set; }
[JsonProperty("default_avatar_id")]
public string DefaultAvatarId { get; set; }
public string AvatarUrl => $"https://avatars.mds.yandex.net/get-yapic/{DefaultAvatarId}/islands-200";
[JsonProperty("is_avatar_empty")]
public bool IsAvatarEmpty { get; set; }
public string PsuId { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
namespace YandexMusic.API.Models.Account
{
public class YPhoneNumber
{
[JsonProperty("masked_e164")]
public string MaskedE164 { get; set; }
public string E164 { get; set; }
public string International { get; set; }
[JsonProperty("masked_original")]
public string MaskedOriginal { get; set; }
public string Original { get; set; }
[JsonProperty("masked_international")]
public string MaskedInternational { get; set; }
}
}

View File

@@ -0,0 +1,81 @@
namespace YandexMusic.API.Models.Account
{
public class YShortAccountInfo : YAuthBase
{
[JsonProperty("public_id")]
public string PublicId { get; set; }
public string Uid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Birthday { get; set; }
[JsonProperty("has_password")]
public bool HasPassword { get; set; }
public List<string> Partitions { get; set; }
[JsonProperty("primary_alias_type")]
public int PrimaryAliasType { get; set; }
[JsonProperty("display_name")]
public string DisplayName { get; set; }
[JsonProperty("normalized_display_login")]
public string NormalizedDisplayLogin { get; set; }
[JsonProperty("x_token_issued_at")]
public int XTokenIssuedAt { get; set; }
[JsonProperty("display_login")]
public string DisplayLogin { get; set; }
[JsonProperty("public_name")]
public string PublicName { get; set; }
[JsonProperty("avatar_url")]
public string AvatarUrl { get; set; }
[JsonProperty("native_default_email")]
public string NativeDefaultEmail { get; set; }
[JsonProperty("has_plus")]
public bool HasPlus { get; set; }
[JsonProperty("location_id")]
public int LocationId { get; set; }
[JsonProperty("gender")]
public string Gender { get; set; }
[JsonProperty("is_avatar_empty")]
public bool IsAvatarEmpty { get; set; }
[JsonProperty("machine_readable_login")]
public string MachineReadableLogin { get; set; }
[JsonProperty("has_cards")]
public bool HasCards { get; set; }
[JsonProperty("has_family")]
public bool HasFamily { get; set; }
[JsonProperty("picture_login_forbidden")]
public bool PictureLoginForbidden { get; set; }
[JsonProperty("can_account_join_master")]
public bool CanAccountJoinMaster { get; set; }
[JsonProperty("secure_phone_number")]
public string SecurePhoneNumber { get; set; }
[JsonProperty("x_token_client_id")]
public string XTokenClientId { get; set; }
[JsonProperty("x_token_need_reset")]
public bool XTokenNeedReset { get; set; }
}
}

View File

@@ -0,0 +1,98 @@
using YandexMusic.API.Models.Artist;
using YandexMusic.API.Models.Common;
using YandexMusic.API.Models.Common.Cover;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Models.Album
{
public sealed class YLabelConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
JArray jArray = JArray.Load(reader);
JTokenType tokenType = jArray.FirstOrDefault()?.Type ?? JTokenType.String;
object label;
try
{
label = tokenType switch
{
JTokenType.Object => jArray.ToObject<List<YLabel>>(),
_ => jArray.ToObject<List<string>>()
};
}
catch (Exception ex)
{
throw new Exception($"Ошибка десериализации типа \"{objectType.Name}\".", ex);
}
return label;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JArray array = JArray.FromObject(value);
array.WriteTo(writer);
}
}
public class YAlbum : YBaseModel
{
public YButton ActionButton { get; set; }
public List<YArtist> Artists { get; set; }
public bool Available { get; set; }
public bool AvailableForMobile { get; set; }
public List<string> AvailableForOptions { 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; }
}
}

View File

@@ -0,0 +1,41 @@
using YandexMusic.API.Models.Common;
using YandexMusic.API.Models.Common.Cover;
namespace YandexMusic.API.Models.Artist
{
public class YArtist : YBaseModel
{
public YButton ActionButton { get; set; }
public bool Available { get; set; }
public bool Composer { get; set; }
public List<string> Countries { get; set; }
public YArtistCounts Counts { get; set; }
[JsonConverter(typeof(YCoverConverter))]
public YCover Cover { get; set; }
public List<string> DbAliases { get; set; }
#warning Непонятная коллекция с содержимым разных типов
public List<object> Decomposed { get; set; }
public YDerivedColors DerivedColors { get; set; }
public YDescription Description { get; set; }
public YDeprecation Deprecation { get; set; }
public List<string> Disclaimers { get; set; }
public string EndDate { get; set; }
public string EnWikipediaLink { get; set; }
public List<YExtraAction> ExtraActions { get; set; }
public List<string> Genres { get; set; }
public string Id { get; set; }
public string InitDate { get; set; }
public int LikesCount { get; set; }
public List<YLink> Links { get; set; }
public string Name { get; set; }
public bool NoPicturesFromSearch { get; set; }
public string OgImage { get; set; }
public YArtistRatings Ratings { get; set; }
public bool TicketsAvailable { get; set; }
public DateTime Timestamp { get; set; }
public bool Various { get; set; }
public string YaMoneyId { get; set; }
public bool HasTrailer { get; set; }
public YTrailer Trailer { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
using YandexMusic.API.Models.Album;
using YandexMusic.API.Models.Common;
using YandexMusic.API.Models.Common.Cover;
using YandexMusic.API.Models.Playlist;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Models.Artist
{
public class YArtistBriefInfo
{
public YButton ActionButton { get; set; }
public List<YAlbum> Albums { get; set; }
[JsonProperty(ItemConverterType = typeof(YCoverConverter))]
public List<YCover> AllCovers { get; set; }
public List<YAlbum> AlsoAlbums { get; set; }
public YArtist Artist { get; set; }
public string BackgroundVideoUrl { get; set; }
public YBandlinkScannerLink BandlinkScannerLink { get; set; }
public List<YClip> Clips { get; set; }
public List<YConcert> Concerts { get; set; }
public YCustomWave CustomWave { get; set; }
public List<YExtraAction> ExtraActions { get; set; }
public bool HasPromotions { get; set; }
public bool HasTrailer { get; set; }
public List<string> LastReleaseIds { get; set; }
public List<YAlbum> LastReleases { get; set; }
public List<YPlaylistUidPair> PlaylistIds { get; set; }
public List<YPlaylist> Playlists { get; set; }
public List<YTrack> PopularTracks { get; set; }
public List<YArtist> SimilarArtists { get; set; }
public YStats Stats { get; set; }
public List<YVideo> Videos { get; set; }
public List<YVinyl> Vinyls { get; set; }
public List<YLink> Links { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace YandexMusic.API.Models.Artist
{
public class YArtistCounts
{
public int AlsoAlbums { get; set; }
public int AlsoTracks { get; set; }
public int DirectAlbums { get; set; }
public int Tracks { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace YandexMusic.API.Models.Artist
{
public class YArtistRatings
{
public int Day { get; set; }
public int Month { get; set; }
public int Week { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace YandexMusic.API.Models.Artist
{
public class YBandlinkScannerLink
{
public string Title { get; set; }
public string Subtitle { get; set; }
public string Url { get; set; }
public string ImgUrl { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
using YandexMusic.API.Models.Common;
namespace YandexMusic.API.Models.Artist
{
public class YConcert
{
public string Address { get; set; }
public string AfishaUrl { get; set; }
public YArtist Artist { get; set; }
public string City { get; set; }
public string ConcertTitle { get; set; }
public string ContentRating { get; set; }
public List<decimal> Coordinates { get; set; }
public YCashback Cashback { get; set; }
[JsonProperty("data-session-id")]
public string DataSessionId { get; set; }
public DateTime DateTime { get; set; }
public string Hash { get; set; }
public string Id { get; set; }
public List<string> Images { get; set; }
public string ImageUrl { get; set; }
public string Map { get; set; }
public string MapUrl { get; set; }
[JsonProperty("metro-stations")]
public List<YMetroStation> MetroStations { get; set; }
public string Place { get; set; }
public YPrice MinPrice { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace YandexMusic.API.Models.Artist
{
public class YDeprecation
{
public string TargetArtistId { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace YandexMusic.API.Models.Artist
{
public class YMetroStation
{
[JsonProperty("line-color")]
public string LineColor { get; set; }
public string Title { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using YandexMusic.API.Models.Common;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Models.Artist
{
public class YTracksPage
{
public YPager Pager { get; set; }
public List<YTrack> Tracks { get; set; }
}
}

View File

@@ -0,0 +1,71 @@
namespace YandexMusic.API.Models.Common.Cover
{
public sealed class YCoverConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(YCover).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
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 YCoverType Type { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common.Cover
{
public class YCoverColor : YCover
{
public string Uri { get; set; }
public string Color { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace YandexMusic.API.Models.Common.Cover
{
public class YCoverError : YCover
{
public string Error { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common.Cover
{
public class YCoverImage : YCover
{
public string Prefix { get; set; }
public string Uri { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common.Cover
{
public class YCoverMosaic : YCover
{
public bool Custom { get; set; }
public List<string> ItemsUri { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace YandexMusic.API.Models.Common.Cover
{
public class YCoverPic : YCover
{
public bool Custom { get; set; }
public string Dir { get; set; }
public bool IsCustom { get; set; }
public string Uri { get; set; }
public string Version { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System.Runtime.Serialization;
namespace YandexMusic.API.Models.Common.Cover
{
[JsonConverter(typeof(StringEnumConverter))]
public enum YCoverType
{
Color,
Error,
[EnumMember(Value = "from-artist-photos")]
FromArtistPhotos,
[EnumMember(Value = "from-album-cover")]
FromAlbumCover,
Mosaic,
Pic
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YBaseModel
{
[JsonIgnore]
public YExecutionContext Context { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace YandexMusic.API.Models.Common
{
public class YButton : YStyle
{
public string Text { get; set; }
public string Url { get; set; }
#warning Дублирование?
public string Uri { get; set; }
public string Color { get; set; }
public bool ViewBrowser { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace YandexMusic.API.Models.Common
{
public class YCashback
{
public string Title { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using YandexMusic.API.Models.Landing.Entity.Entities;
namespace YandexMusic.API.Models.Common
{
public class YChart
{
public int Position { get; set; }
public int Listeners { get; set; }
public int Shift { get; set; }
public YChartProgress Progress { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
using YandexMusic.API.Models.Artist;
namespace YandexMusic.API.Models.Common
{
public class YClip
{
public List<YArtist> Artists { get; set; }
public string ClipId { get; set; }
public List<string> Disclaimers { get; set; }
public int Duration { get; set; }
public bool Explicit { get; set; }
public string PlayerId { get; set; }
public string PreviewUrl { get; set; }
public string Uuid { get; set; }
public string Thumbnail { get; set; }
public string Title { get; set; }
public List<string> TrackIds { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YCloseButtonStyles
{
public YStyle White { get; set; }
public YStyle Black { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace YandexMusic.API.Models.Common
{
public class YCountsTracks
{
public int All { get; set; }
public int Favorite { get; set; }
public int Ugc { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace YandexMusic.API.Models.Common
{
public class YCustomWave
{
public string AnimationUrl { get; set; }
public string BackgroundImageUrl { get; set; }
public string Header { get; set; }
public string Position { get; set; }
public string Title { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace YandexMusic.API.Models.Common
{
public class YDerivedColors
{
public string Average { get; set; }
public string WaveText { get; set; }
public string MiniPlayer { get; set; }
public string Accent { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YDescription
{
public string Text { get; set; }
public string Uri { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YError
{
public string Name { get; set; }
public string Message { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YErrorResponse : Exception
{
public YInvocationInfo InvocationInfo { get; set; }
public YError Error { get; set; }
}
}

View File

@@ -0,0 +1,57 @@
using YandexMusic.API.Common;
namespace YandexMusic.API.Models.Common
{
public sealed class YExecutionContextConverter : JsonConverter
{
#region Поля
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; }
}
}

View File

@@ -0,0 +1,11 @@
namespace YandexMusic.API.Models.Common
{
public class YExtraAction
{
public string Type { get; set; }
public string Title { get; set; }
public string Color { get; set; }
public string Url { get; set; }
public bool ViewBrowser { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace YandexMusic.API.Models.Common
{
public class YId
{
public string Id { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
namespace YandexMusic.API.Models.Common
{
public class YInvocationInfo
{
[JsonProperty("app-name")]
public string AppName { get; set; }
[JsonProperty("exec-duration-millis")]
public int ExecDurationMillis { get; set; }
public string HostName { get; set; }
[JsonProperty("req-id")]
public string ReqId { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YLabel
{
public string Id { get; set; }
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YLikedCounts
{
public long LikedAlbums { get; set; }
public long LikedArtists { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
namespace YandexMusic.API.Models.Common
{
public class YLink
{
public string Href { get; set; }
public string Url { get; set; }
public string ImgUrl { get; set; }
public string SocialNetwork { get; set; }
public string Subtitle { get; set; }
public string Title { get; set; }
public YLinkType Type { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
namespace YandexMusic.API.Models.Common
{
public enum YLinkType
{
/// <summary>
/// Официальный сайт
/// </summary>
Official,
/// <summary>
/// Социальная сеть
/// </summary>
Social,
/// <summary>
/// Twitter
/// </summary>
Twitter,
/// <summary>
/// YouTube
/// </summary>
YouTube
}
}

View File

@@ -0,0 +1,12 @@
namespace YandexMusic.API.Models.Common
{
public class YLyrics
{
public string Id { get; set; }
public string Lyrics { get; set; }
public string FullLyrics { get; set; }
public bool HasRights { get; set; }
public bool ShowTranslation { get; set; }
public string TextLanguage { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YLyricsInfo
{
public bool HasAvailableSyncLyrics { get; set; }
public bool HasAvailableTextLyrics { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YMajor
{
public string Id { get; set; }
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public class YMasterHub
{
public YSubscription[] ActiveSubscriptions { get; set; }
public YSubscription[] AvailableSubscriptions { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace YandexMusic.API.Models.Common
{
public enum YMetaType
{
Music,
Podcast
}
}

Some files were not shown because too many files have changed in this diff Show More