diff --git a/YandexMusic.API/API/YAlbumAPI.cs b/YandexMusic.API/API/YAlbumAPI.cs index 8c52247..aa59ec6 100644 --- a/YandexMusic.API/API/YAlbumAPI.cs +++ b/YandexMusic.API/API/YAlbumAPI.cs @@ -1,6 +1,4 @@ -using YandexMusic.API.Common; using YandexMusic.API.Models.Album; -using YandexMusic.API.Models.Common; using YandexMusic.API.Requests.Album; namespace YandexMusic.API; @@ -8,21 +6,13 @@ namespace YandexMusic.API; /// API для работы с альбомами. public class YAlbumAPI : YCommonAPI { - /// Инициализирует новый экземпляр API альбомов. - /// Экземпляр основного API. - public YAlbumAPI(YandexMusicApi yandex) : base(yandex) { } + public YAlbumAPI(YandexMusicApi api) : base(api) { } /// Получает альбом по идентификатору. - /// Хранилище данных авторизации. - /// Идентификатор альбома. - /// Ответ API с моделью альбома. - public Task> GetAsync(AuthStorage storage, string albumId) - => new YGetAlbumBuilder(api, storage).Build(albumId).GetResponseAsync(); + public Task GetAsync(string albumId) + => new YGetAlbumBuilder(Api).ExecuteAsync(albumId); /// Получает несколько альбомов по списку идентификаторов. - /// Хранилище данных авторизации. - /// Список идентификаторов альбомов. - /// Ответ API со списком альбомов. - public Task>> GetAsync(AuthStorage storage, IEnumerable albumIds) - => new YGetAlbumsBuilder(api, storage).Build(albumIds).GetResponseAsync(); + public Task?> GetAsync(IEnumerable albumIds) + => new YGetAlbumsBuilder(Api).ExecuteAsync(albumIds); } \ No newline at end of file diff --git a/YandexMusic.API/API/YArtistAPI.cs b/YandexMusic.API/API/YArtistAPI.cs index f132417..8a1a2ea 100644 --- a/YandexMusic.API/API/YArtistAPI.cs +++ b/YandexMusic.API/API/YArtistAPI.cs @@ -1,71 +1,27 @@ -using YandexMusic.API.Common; using YandexMusic.API.Models.Artist; -using YandexMusic.API.Models.Common; using YandexMusic.API.Requests.Artist; namespace YandexMusic.API; -/// -/// API для взаимодействия с исполнителями -/// +/// API для работы с исполнителями. public class YArtistAPI : YCommonAPI { - public YArtistAPI(YandexMusicApi yandex) : base(yandex) + public YArtistAPI(YandexMusicApi api) : base(api) { } + + public Task GetAsync(string artistId) + => new YGetArtistBuilder(Api).ExecuteAsync(artistId); + + public Task?> GetAsync(IEnumerable artistIds) + => new YGetArtistsBuilder(Api).ExecuteAsync(artistIds); + + public Task GetTracksAsync(string artistId, int page = 0, int pageSize = 20) + => new YGetArtistTrackBuilder(Api).ExecuteAsync((artistId, page, pageSize)); + + public async Task GetAllTracksAsync(string artistId) { + var info = await GetAsync(artistId); + if (info?.Artist?.Counts?.Tracks == null) + return null; + return await GetTracksAsync(artistId, pageSize: info.Artist.Counts.Tracks); } - - /// - /// Получение исполнителя - /// - /// Хранилище - /// Идентификатор - public Task> GetAsync(AuthStorage storage, string artistId) - { - return new YGetArtistBuilder(api, storage) - .Build(artistId) - .GetResponseAsync(); - } - - /// - /// Получение исполнителей - /// - /// Хранилище - /// Идентификаторы - public Task>> GetAsync(AuthStorage storage, IEnumerable artistIds) - { - return new YGetArtistsBuilder(api, storage) - .Build(artistIds) - .GetResponseAsync(); - } - - /// - /// Получение треков исполнителя с пагинацией - /// - /// Треки поставляются по штук на страницу, - /// для получения всех треков необходимо использовать метод - /// - /// - /// Хранилище - /// Идентификатор исполнителя - /// Страница ответов - /// Количество треков на странице ответов - public Task> GetTracksAsync(AuthStorage storage, string artistId, int page = 0, int pageSize = 20) - { - return new YGetArtistTrackBuilder(api, storage) - .Build((artistId, page, pageSize)) - .GetResponseAsync(); - } - - /// - /// Получение всех треков исполнителя - /// - /// Хранилище - /// Идентификатор исполнителя - public async Task> GetAllTracksAsync(AuthStorage storage, string artistId) - { - YResponse response = await GetAsync(storage, artistId); - return await GetTracksAsync(storage, artistId, pageSize: response.Result.Artist.Counts.Tracks); - } - - } \ No newline at end of file diff --git a/YandexMusic.API/API/YCommonAPI.cs b/YandexMusic.API/API/YCommonAPI.cs index b0f63dc..dab25a4 100644 --- a/YandexMusic.API/API/YCommonAPI.cs +++ b/YandexMusic.API/API/YCommonAPI.cs @@ -1,12 +1,10 @@ namespace YandexMusic.API; -/// Родительский класс для всех веток API. +/// Базовый класс для всех веток API. public abstract class YCommonAPI { /// Основной экземпляр API. - protected readonly YandexMusicApi api; + protected YandexMusicApi Api { get; } - /// Инициализирует новый экземпляр. - /// Экземпляр основного API. - protected YCommonAPI(YandexMusicApi yandex) => api = yandex; + protected YCommonAPI(YandexMusicApi api) => Api = api; } \ No newline at end of file diff --git a/YandexMusic.API/API/YLabelAPI.cs b/YandexMusic.API/API/YLabelAPI.cs index 42b53c8..245e5a7 100644 --- a/YandexMusic.API/API/YLabelAPI.cs +++ b/YandexMusic.API/API/YLabelAPI.cs @@ -1,39 +1,19 @@ -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 +/// API для работы с лейблами. +public class YLabelAPI : YCommonAPI { - public YLabelAPI(YandexMusicApi yandex) : base(yandex) - { - } + public YLabelAPI(YandexMusicApi api) : base(api) { } - /// - /// Постраничное получение альбомов лейбла - /// - /// Хранилище - /// Лейбл - /// Страница - public Task> GetAlbumsByLabelAsync(AuthStorage storage, YLabel label, int page) - { - return new YGetLabelAlbumsBuilder(api, storage) - .Build((label, page)) - .GetResponseAsync(); - } + /// Получает альбомы лейбла с пагинацией. + public Task GetAlbumsByLabelAsync(YLabel label, int page = 0) + => new YGetLabelAlbumsBuilder(Api).ExecuteAsync((label, page)); - /// - /// Постраничное получение артистов лейбла - /// - /// Хранилище - /// Лейбл - /// Страница - public Task> GetArtistsByLabelAsync(AuthStorage storage, YLabel label, int page) - { - return new YGetLabelArtistsBuilder(api, storage) - .Build((label, page)) - .GetResponseAsync(); - } + /// Получает артистов лейбла с пагинацией. + public Task GetArtistsByLabelAsync(YLabel label, int page = 0) + => new YGetLabelArtistsBuilder(Api).ExecuteAsync((label, page)); } \ No newline at end of file diff --git a/YandexMusic.API/API/YLandingAPI.cs b/YandexMusic.API/API/YLandingAPI.cs index b91cecc..847b912 100644 --- a/YandexMusic.API/API/YLandingAPI.cs +++ b/YandexMusic.API/API/YLandingAPI.cs @@ -1,43 +1,21 @@ -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Models.Feed; +using YandexMusic.API.Models.Feed; using YandexMusic.API.Models.Landing; using YandexMusic.API.Requests.Feed; using YandexMusic.API.Requests.Landing; namespace YandexMusic.API; -/// API для взаимодействия с главной страницей (лендингом). +/// API для работы с главной страницей (лендингом). public class YLandingAPI : YCommonAPI { - /// Инициализирует новый экземпляр API лендинга. - /// Экземпляр основного API. - public YLandingAPI(YandexMusicApi yandex) : base(yandex) { } + public YLandingAPI(YandexMusicApi api) : base(api) { } - /// Получает персональные блоки лендинга. - /// Хранилище авторизации. - /// Типы запрашиваемых блоков. - /// Ответ API с лендингом. - /// Если массив blocks равен null. - public Task> GetAsync(AuthStorage storage, params YLandingBlockType[] blocks) - { - if (blocks == null) - throw new ArgumentNullException(nameof(blocks), "Массив блоков не может быть null"); + public Task GetAsync(params YLandingBlockType[] blocks) + => new YGetLandingBuilder(Api).ExecuteAsync(blocks); - return new YGetLandingBuilder(api, storage) - .Build(blocks) - .GetResponseAsync(); - } + public Task GetFeedAsync() + => new YGetFeedBuilder(Api).ExecuteAsync(null!); - /// Получает ленту событий (фид). - /// Хранилище авторизации. - /// Ответ API с лентой. - public Task> GetFeedAsync(AuthStorage storage) - => new YGetFeedBuilder(api, storage).Build(null!).GetResponseAsync(); - - /// Получает лендинг детского раздела. - /// Хранилище авторизации. - /// Ответ API с детским лендингом. - public Task> GetChildrenLandingAsync(AuthStorage storage) - => new YGetChildrenLandingBuilder(api, storage).Build(null!).GetResponseAsync(); + public Task GetChildrenLandingAsync() + => new YGetChildrenLandingBuilder(Api).ExecuteAsync(null!); } \ No newline at end of file diff --git a/YandexMusic.API/API/YLibraryAPI.cs b/YandexMusic.API/API/YLibraryAPI.cs index df1b86c..95da341 100644 --- a/YandexMusic.API/API/YLibraryAPI.cs +++ b/YandexMusic.API/API/YLibraryAPI.cs @@ -1,4 +1,3 @@ -using YandexMusic.API.Common; using YandexMusic.API.Models.Album; using YandexMusic.API.Models.Artist; using YandexMusic.API.Models.Common; @@ -10,244 +9,82 @@ using YandexMusic.API.Requests.Library; namespace YandexMusic.API; -/// -/// API для взаимодействия с библиотекой -/// -public partial class YLibraryAPI : YCommonAPI +/// API для работы с библиотекой (лайки, дизлайки, недавно прослушанное). +public class YLibraryAPI : YCommonAPI { - /// - /// Получение секции библиотеки - /// - /// Тип объекта библиотеки - /// Хранилище - /// Секция - /// Тип - /// Список объектов из секции - private Task> GetLibrarySection(AuthStorage storage, YLibrarySection section, YLibrarySectionType type = YLibrarySectionType.Likes) - { - return new YGetLibrarySectionBuilder(api, storage) - .Build((section, type)) - .GetResponseAsync(); - } - - - - public YLibraryAPI(YandexMusicApi yandex) : base(yandex) - { - } + public YLibraryAPI(YandexMusicApi api) : base(api) { } #region Лайки - /// - /// Получение лайкнутых треков - /// - /// Хранилище - /// - public Task> GetLikedTracksAsync(AuthStorage storage) - { - return GetLibrarySection(storage, YLibrarySection.Tracks); - } + public Task GetLikedTracksAsync() + => new YGetLibrarySectionBuilder(Api).ExecuteAsync((YLibrarySection.Tracks, YLibrarySectionType.Likes)); - /// - /// Получение лайкнутых альбомов - /// - /// Хранилище - /// - public Task>> GetLikedAlbumsAsync(AuthStorage storage) - { - return GetLibrarySection>(storage, YLibrarySection.Albums); - } + public Task?> GetLikedAlbumsAsync() + => new YGetLibrarySectionBuilder>(Api).ExecuteAsync((YLibrarySection.Albums, YLibrarySectionType.Likes)); - /// - /// Получение лайкнутых исполнителей - /// - /// Хранилище - /// - public Task>> GetLikedArtistsAsync(AuthStorage storage) - { - return GetLibrarySection>(storage, YLibrarySection.Artists); - } + public Task?> GetLikedArtistsAsync() + => new YGetLibrarySectionBuilder>(Api).ExecuteAsync((YLibrarySection.Artists, YLibrarySectionType.Likes)); - /// - /// Получение лайкнутых плейлистов - /// - /// Хранилище - /// - public Task>> GetLikedPlaylistsAsync(AuthStorage storage) - { - return GetLibrarySection>(storage, YLibrarySection.Playlists); - } + public Task?> GetLikedPlaylistsAsync() + => new YGetLibrarySectionBuilder>(Api).ExecuteAsync((YLibrarySection.Playlists, YLibrarySectionType.Likes)); - #endregion Лайки + #endregion #region Дизлайки - /// - /// Получение дизлайкнутых треков - /// - /// Хранилище - /// - public Task> GetDislikedTracksAsync(AuthStorage storage) - { - return GetLibrarySection(storage, YLibrarySection.Tracks, YLibrarySectionType.Dislikes); - } + public Task GetDislikedTracksAsync() + => new YGetLibrarySectionBuilder(Api).ExecuteAsync((YLibrarySection.Tracks, YLibrarySectionType.Dislikes)); - /// - /// Получение дизлайкнутых исполнителей - /// - /// Хранилище - /// - public Task>> GetDislikedArtistsAsync(AuthStorage storage) - { - return GetLibrarySection>(storage, YLibrarySection.Artists, YLibrarySectionType.Dislikes); - } + public Task?> GetDislikedArtistsAsync() + => new YGetLibrarySectionBuilder>(Api).ExecuteAsync((YLibrarySection.Artists, YLibrarySectionType.Dislikes)); - #endregion Дизлайки + #endregion - #region Добавление в списки лайков/дизлайков + #region Добавление/удаление - /// - /// Добавить трек в список лайкнутых - /// - /// Хранилище - /// Трек - /// - public Task> AddTrackLikeAsync(AuthStorage storage, YTrack track) - { - return new YLibraryAddBuilder(api, storage) - .Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Likes)) - .GetResponseAsync(); - } + public Task AddTrackLikeAsync(YTrack track) + => new YLibraryAddBuilder(Api).ExecuteAsync((track.Id, YLibrarySection.Tracks, YLibrarySectionType.Likes)) + .ContinueWith(t => t.Result?.Revision); - /// - /// Удалить трек из списка лайкнутых - /// - /// Хранилище - /// Трек - /// - public Task> RemoveTrackLikeAsync(AuthStorage storage, YTrack track) - { - return new YLibraryRemoveBuilder(api, storage) - .Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Likes)) - .GetResponseAsync(); - } + public Task RemoveTrackLikeAsync(YTrack track) + => new YLibraryRemoveBuilder(Api).ExecuteAsync((track.Id, YLibrarySection.Tracks, YLibrarySectionType.Likes)) + .ContinueWith(t => t.Result?.Revision); - /// - /// Добавить трек в список дизлайкнутых - /// - /// Хранилище - /// Трек - /// - public Task> AddTrackDislikeAsync(AuthStorage storage, YTrack track) - { - return new YLibraryAddBuilder(api, storage) - .Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Dislikes)) - .GetResponseAsync(); - } + public Task AddTrackDislikeAsync(YTrack track) + => new YLibraryAddBuilder(Api).ExecuteAsync((track.Id, YLibrarySection.Tracks, YLibrarySectionType.Dislikes)) + .ContinueWith(t => t.Result?.Revision); - /// - /// Удалить трек из списка дизлайкнутых - /// - /// Хранилище - /// Трек - /// - public Task> RemoveTrackDislikeAsync(AuthStorage storage, YTrack track) - { - return new YLibraryRemoveBuilder(api, storage) - .Build((track.GetKey().ToString(), YLibrarySection.Tracks, YLibrarySectionType.Dislikes)) - .GetResponseAsync(); - } + public Task RemoveTrackDislikeAsync(YTrack track) + => new YLibraryRemoveBuilder(Api).ExecuteAsync((track.Id, YLibrarySection.Tracks, YLibrarySectionType.Dislikes)) + .ContinueWith(t => t.Result?.Revision); - /// - /// Добавить альбом в список лайкнутых - /// - /// Хранилище - /// Альбом - /// - public Task> AddAlbumLikeAsync(AuthStorage storage, YAlbum album) - { - return new YLibraryAddBuilder(api, storage) - .Build((album.Id, YLibrarySection.Albums, YLibrarySectionType.Likes)) - .GetResponseAsync(); - } + public Task AddAlbumLikeAsync(YAlbum album) + => new YLibraryAddBuilder(Api).ExecuteAsync((album.Id, YLibrarySection.Albums, YLibrarySectionType.Likes)); - /// - /// Удалить альбом из списка лайкнутых - /// - /// Хранилище - /// Альбом - /// - public Task> RemoveAlbumLikeAsync(AuthStorage storage, YAlbum album) - { - return new YLibraryRemoveBuilder(api, storage) - .Build((album.Id, YLibrarySection.Albums, YLibrarySectionType.Likes)) - .GetResponseAsync(); - } + public Task RemoveAlbumLikeAsync(YAlbum album) + => new YLibraryRemoveBuilder(Api).ExecuteAsync((album.Id, YLibrarySection.Albums, YLibrarySectionType.Likes)); - /// - /// Добавить исполнителя в список лайкнутых - /// - /// Хранилище - /// Исполнитель - /// - public Task> AddArtistLikeAsync(AuthStorage storage, YArtist artist) - { - return new YLibraryAddBuilder(api, storage) - .Build((artist.Id, YLibrarySection.Artists, YLibrarySectionType.Likes)) - .GetResponseAsync(); - } + public Task AddArtistLikeAsync(YArtist artist) + => new YLibraryAddBuilder(Api).ExecuteAsync((artist.Id, YLibrarySection.Artists, YLibrarySectionType.Likes)); - /// - /// Удалить исполнителя из списка лайкнутых - /// - /// Хранилище - /// Исполнитель - /// - public Task> RemoveArtistLikeAsync(AuthStorage storage, YArtist artist) - { - return new YLibraryRemoveBuilder(api, storage) - .Build((artist.Id, YLibrarySection.Artists, YLibrarySectionType.Likes)) - .GetResponseAsync(); - } + public Task RemoveArtistLikeAsync(YArtist artist) + => new YLibraryRemoveBuilder(Api).ExecuteAsync((artist.Id, YLibrarySection.Artists, YLibrarySectionType.Likes)); - /// - /// Добавить плейлист в список лайкнутых - /// - /// Хранилище - /// Плейлист - /// - public Task> AddPlaylistLikeAsync(AuthStorage storage, YPlaylist playlist) - { - return new YLibraryAddBuilder(api, storage) - .Build((playlist.GetKey().ToString(), YLibrarySection.Playlists, YLibrarySectionType.Likes)) - .GetResponseAsync(); - } + public Task AddPlaylistLikeAsync(YPlaylist playlist) + => new YLibraryAddBuilder(Api).ExecuteAsync((playlist.GetKey().ToString(), YLibrarySection.Playlists, YLibrarySectionType.Likes)); - /// - /// Удалить плейлист из списка лайкнутых - /// - /// Хранилище - /// Плейлист - /// - public Task> RemovePlaylistLikeAsync(AuthStorage storage, YPlaylist playlist) - { - return new YLibraryRemoveBuilder(api, storage) - .Build((playlist.GetKey().ToString(), YLibrarySection.Playlists, YLibrarySectionType.Likes)) - .GetResponseAsync(); - } + public Task RemovePlaylistLikeAsync(YPlaylist playlist) + => new YLibraryRemoveBuilder(Api).ExecuteAsync((playlist.GetKey().ToString(), YLibrarySection.Playlists, YLibrarySectionType.Likes)); - #endregion Добавление/удаление в списки лайков/дизлайков + #endregion - #region Получение списка "Вы недавно слушали" - - public Task> GetRecentlyListenedAsync(AuthStorage storage, IEnumerable contextTypes, int trackCount, int contextCount) - { - return new YGetLibraryRecentlyListenedBuilder(api, storage) - .Build((contextTypes, trackCount, contextCount)) - .GetResponseAsync(); - } - - #endregion Получение списка "Вы недавно слушали" + #region Недавно прослушанное + public Task GetRecentlyListenedAsync( + IEnumerable contextTypes, + int trackCount = 50, + int contextCount = 10) + => new YGetLibraryRecentlyListenedBuilder(Api).ExecuteAsync((contextTypes, trackCount, contextCount)); + #endregion } \ No newline at end of file diff --git a/YandexMusic.API/API/YPinsAPI.cs b/YandexMusic.API/API/YPinsAPI.cs index bc1dc4f..1de69b8 100644 --- a/YandexMusic.API/API/YPinsAPI.cs +++ b/YandexMusic.API/API/YPinsAPI.cs @@ -1,20 +1,13 @@ -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Pins; using YandexMusic.API.Requests.Pins; namespace YandexMusic.API; -/// API для взаимодействия с закреплёнными объектами (пинами). +/// API для работы с закреплёнными объектами (пинами). public class YPinsAPI : YCommonAPI { - /// Инициализирует новый экземпляр API пинов. - /// Экземпляр основного API. - public YPinsAPI(YandexMusicApi yandex) : base(yandex) { } + public YPinsAPI(YandexMusicApi api) : base(api) { } - /// Получает список закреплённых объектов. - /// Хранилище авторизации. - /// Ответ API со списком пинов. - public Task> GetAsync(AuthStorage storage) - => new YGetPinsBuilder(api, storage).Build(null!).GetResponseAsync(); + public Task GetAsync() + => new YGetPinsBuilder(Api).ExecuteAsync(null!); } \ No newline at end of file diff --git a/YandexMusic.API/API/YPlaylistAPI.cs b/YandexMusic.API/API/YPlaylistAPI.cs index 2005312..2387f77 100644 --- a/YandexMusic.API/API/YPlaylistAPI.cs +++ b/YandexMusic.API/API/YPlaylistAPI.cs @@ -1,5 +1,3 @@ -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; @@ -8,143 +6,114 @@ using YandexMusic.API.Requests.Playlist; namespace YandexMusic.API; -/// API для взаимодействия с плейлистами. +/// API для работы с плейлистами. public class YPlaylistAPI : YCommonAPI { - /// Инициализирует новый экземпляр API плейлистов. - /// Экземпляр основного API. - public YPlaylistAPI(YandexMusicApi yandex) : base(yandex) { } + public YPlaylistAPI(YandexMusicApi api) : base(api) { } - /// Получает список персональных плейлистов с главной страницы. - /// Хранилище авторизации. - /// Список ответов с плейлистами. - public async Task>> GetPersonalPlaylistsAsync(AuthStorage storage) + public async Task> GetPersonalPlaylistsAsync() { - var landing = await api.Landing.GetAsync(storage, YLandingBlockType.PersonalPlaylists); - var block = landing.Result?.Blocks?.FirstOrDefault(b => b.Type == YLandingBlockType.PersonalPlaylists); + var landing = await Api.Landing.GetAsync(YLandingBlockType.PersonalPlaylists); + var block = landing?.Blocks?.FirstOrDefault(b => b.Type == YLandingBlockType.PersonalPlaylists); if (block?.Entities == null) - return new List>(); + return new List(); var tasks = block.Entities .OfType() - .Select(e => api.Playlist.GetAsync(storage, e.Data?.Data)); + .Select(e => GetAsync(e.Data?.Data?.Owner?.Uid ?? Api.Storage.User.Uid, e.Data?.Data?.Kind ?? "")) + .Where(t => t != null) + .ToList(); - return new List>(await Task.WhenAll(tasks)); + var results = await Task.WhenAll(tasks); + return results.Where(p => p != null).ToList()!; } - /// Получает избранные плейлисты. - public Task>> FavoritesAsync(AuthStorage storage) - => new YGetPlaylistFavoritesBuilder(api, storage).Build(null!).GetResponseAsync(); + public Task GetAsync(string user, string kind) + => new YGetPlaylistBuilder(Api).ExecuteAsync((user, kind)); - /// Получает плейлист дня. - public Task> OfTheDayAsync(AuthStorage storage) - => GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.PlaylistOfTheDay); + public Task GetAsync(string uuid) + => new YGetPlaylistByUuidBuilder(Api).ExecuteAsync(uuid); - /// Получает плейлист «Дежавю». - public Task> DejaVuAsync(AuthStorage storage) - => GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.NeverHeard); + public Task GetAsync(YPlaylist playlist) + => GetAsync(playlist.Owner.Uid, playlist.Kind); - /// Получает плейлист «Премьера». - public Task> PremiereAsync(AuthStorage storage) - => GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.RecentTracks); + public Task?> GetAsync(IEnumerable<(string user, string kind)> ids) + => new YGetPlaylistsBuilder(Api).ExecuteAsync(ids); - /// Получает плейлист «Тайник». - public Task> MissedAsync(AuthStorage storage) - => GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.MissedLikes); + public Task?> FavoritesAsync() + => new YGetPlaylistFavoritesBuilder(Api).ExecuteAsync(null!); - /// Получает плейлист «Кинопоиск». - public Task> KinopoiskAsync(AuthStorage storage) - => GetPersonalPlaylistAsync(storage, YGeneratedPlaylistType.Kinopoisk); + public async Task OfTheDayAsync() + => (await GetPersonalPlaylistsAsync()).FirstOrDefault(p => p?.GeneratedPlaylistType == YGeneratedPlaylistType.PlaylistOfTheDay.ToString()); - private async Task> GetPersonalPlaylistAsync(AuthStorage storage, YGeneratedPlaylistType type) - { - var list = await GetPersonalPlaylistsAsync(storage); - return list.FirstOrDefault(e => string.Equals(e.Result?.GeneratedPlaylistType, type.ToString(), StringComparison.CurrentCultureIgnoreCase)) - ?? throw new Exception($"Плейлист типа {type} не найден."); - } + public async Task DejaVuAsync() + => (await GetPersonalPlaylistsAsync()).FirstOrDefault(p => p?.GeneratedPlaylistType == YGeneratedPlaylistType.NeverHeard.ToString()); - /// Получает плейлист по идентификатору пользователя и типа. - public Task> GetAsync(AuthStorage storage, string user, string kind) - => new YGetPlaylistBuilder(api, storage).Build((user, kind)).GetResponseAsync(); + public async Task PremiereAsync() + => (await GetPersonalPlaylistsAsync()).FirstOrDefault(p => p?.GeneratedPlaylistType == YGeneratedPlaylistType.RecentTracks.ToString()); - /// Получает плейлист по UUID. - public Task> GetAsync(AuthStorage storage, string uuid) - => new YGetPlaylistByUuidBuilder(api, storage).Build(uuid).GetResponseAsync(); + public async Task MissedAsync() + => (await GetPersonalPlaylistsAsync()).FirstOrDefault(p => p?.GeneratedPlaylistType == YGeneratedPlaylistType.MissedLikes.ToString()); - /// Получает несколько плейлистов по списку пар (пользователь, тип). - public Task>> GetAsync(AuthStorage storage, IEnumerable<(string user, string kind)> ids) - => new YGetPlaylistsBuilder(api, storage).Build(ids).GetResponseAsync(); + public async Task KinopoiskAsync() + => (await GetPersonalPlaylistsAsync()).FirstOrDefault(p => p?.GeneratedPlaylistType == YGeneratedPlaylistType.Kinopoisk.ToString()); - /// Получает плейлист по объекту плейлиста (обновляет его треки). - public Task> GetAsync(AuthStorage storage, YPlaylist playlist) - => new YGetPlaylistBuilder(api, storage).Build((playlist.Owner.Uid, playlist.Kind)).GetResponseAsync(); + public Task CreateAsync(string name) + => new YPlaylistCreateBuilder(Api).ExecuteAsync(name); - /// Создаёт новый плейлист с заданным именем. - public Task> CreateAsync(AuthStorage storage, string name) - => new YPlaylistCreateBuilder(api, storage).Build(name).GetResponseAsync(); + public Task RenameAsync(string kind, string name) + => new YPlaylistRenameBuilder(Api).ExecuteAsync((kind, name)); - /// Переименовывает плейлист. - public Task> RenameAsync(AuthStorage storage, string kind, string name) - => new YPlaylistRenameBuilder(api, storage).Build((kind, name)).GetResponseAsync(); + public Task RenameAsync(YPlaylist playlist, string name) + => RenameAsync(playlist.Kind, name); - /// Переименовывает плейлист. - public Task> RenameAsync(AuthStorage storage, YPlaylist playlist, string name) - => RenameAsync(storage, playlist.Kind, name); - - /// Удаляет плейлист. - public async Task DeleteAsync(AuthStorage storage, string kind) + public async Task DeleteAsync(string kind) { try { - await new YPlaylistRemoveBuilder(api, storage).Build(kind).GetResponseAsync(); + await new YPlaylistRemoveBuilder(Api).ExecuteAsync(kind); return true; } - catch (Exception ex) + catch { - // Логирование ошибки можно добавить через ILogger return false; } } - /// Удаляет плейлист. - public Task DeleteAsync(AuthStorage storage, YPlaylist playlist) - => DeleteAsync(storage, playlist.Kind); + public Task DeleteAsync(YPlaylist playlist) + => DeleteAsync(playlist.Kind); - /// Добавляет треки в начало плейлиста. - public async Task> InsertTracksAsync(AuthStorage storage, YPlaylist playlist, IEnumerable tracks) + public async Task InsertTracksAsync(YPlaylist playlist, IEnumerable tracks) { - var change = await ChangePlaylistAsync(storage, playlist, new List + var change = await new YPlaylistChangeBuilder(Api).ExecuteAsync((playlist, new[] { - new() + new YPlaylistChange { Operation = YPlaylistChangeType.Insert, At = 0, Tracks = tracks.Select(t => t.GetKey()) } - }); - return await GetAsync(storage, change.Result); + })); + return change != null ? await GetAsync(change) : null; } - /// Удаляет треки из плейлиста. - public Task> DeleteTracksAsync(AuthStorage storage, YPlaylist playlist, IEnumerable tracks) + public async Task DeleteTracksAsync(YPlaylist playlist, IEnumerable tracks) { var distinctTracks = tracks.Distinct().ToList(); - - var changes = distinctTracks + var indices = distinctTracks .Select(t => playlist.Tracks?.FindIndex(ct => ct.Track?.GetKey() == t.GetKey()) ?? -1) .Where(i => i != -1) - .Select(i => new YPlaylistChange - { - Operation = YPlaylistChangeType.Delete, - From = i, - To = i + 1, - Tracks = new List { playlist.Tracks![i].Track!.GetKey() } - }) .ToList(); - return ChangePlaylistAsync(storage, playlist, changes); - } + var changes = indices.Select(i => new YPlaylistChange + { + Operation = YPlaylistChangeType.Delete, + From = i, + To = i + 1, + Tracks = new[] { playlist.Tracks![i].Track!.GetKey() } + }); - private Task> ChangePlaylistAsync(AuthStorage storage, YPlaylist playlist, IEnumerable changes) - => new YPlaylistChangeBuilder(api, storage).Build((playlist, changes)).GetResponseAsync(); + var change = await new YPlaylistChangeBuilder(Api).ExecuteAsync((playlist, changes)); + return change != null ? await GetAsync(change) : null; + } } \ No newline at end of file diff --git a/YandexMusic.API/API/YQueueAPI.cs b/YandexMusic.API/API/YQueueAPI.cs index f7881cf..8a016e6 100644 --- a/YandexMusic.API/API/YQueueAPI.cs +++ b/YandexMusic.API/API/YQueueAPI.cs @@ -1,72 +1,22 @@ -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Queue; using YandexMusic.API.Requests.Queue; namespace YandexMusic.API; -/// -/// API для взаимодействия с очередями -/// -public partial class YQueueAPI : YCommonAPI +/// API для работы с очередями воспроизведения. +public class YQueueAPI : YCommonAPI { - public YQueueAPI(YandexMusicApi yandex) : base(yandex) - { - } + public YQueueAPI(YandexMusicApi api) : base(api) { } - /// - /// Получение всех очередей треков с разных устройств для синхронизации между ними - /// - /// Хранилище - /// Устройство - /// - public Task> ListAsync(AuthStorage storage, string? device = null) - { - return new YQueuesListBuilder(api, storage) - .Build(device) - .GetResponseAsync(); - } + public Task ListAsync(string? device = null) + => new YQueuesListBuilder(Api, device).ExecuteAsync(null!); - /// - /// Получение очереди - /// - /// Хранилище - /// Идентификатор очереди - /// - public Task> GetAsync(AuthStorage storage, string queueId) - { - return new YGetQueueBuilder(api, storage) - .Build(queueId) - .GetResponseAsync(); - } + public Task GetAsync(string queueId) + => new YGetQueueBuilder(Api).ExecuteAsync(queueId); - /// - /// Создание новой очереди треков - /// - /// Хранилище - /// Очередь треков - /// Устройство - /// - public Task> CreateAsync(AuthStorage storage, YQueue queue, string? device = null) - { - return new YQueueCreateBuilder(api, storage, device) - .Build(queue) - .GetResponseAsync(); - } + public Task CreateAsync(YQueue queue, string? device = null) + => new YQueueCreateBuilder(Api, device).ExecuteAsync(queue); - /// - /// Установка текущего индекса проигрываемого трека в очереди треков - /// - /// Хранилище - /// Идентификатор очереди - /// Текущий индекс - /// Флаг интерактивности - /// Устройство - /// - public Task> UpdatePositionAsync(AuthStorage storage, string queueId, int currentIndex, bool isInteractive, string device = null) - { - return new YQueueUpdatePositionBuilder(api, storage, device) - .Build((queueId, currentIndex, isInteractive)) - .GetResponseAsync(); - } -} + public Task UpdatePositionAsync(string queueId, int currentIndex, bool isInteractive, string? device = null) + => new YQueueUpdatePositionBuilder(Api, device).ExecuteAsync((queueId, currentIndex, isInteractive)); +} \ No newline at end of file diff --git a/YandexMusic.API/API/YRadioAPI.cs b/YandexMusic.API/API/YRadioAPI.cs index 02172bc..e13611d 100644 --- a/YandexMusic.API/API/YRadioAPI.cs +++ b/YandexMusic.API/API/YRadioAPI.cs @@ -1,113 +1,37 @@ -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; -/// -/// API для взаимодействия с радио -/// -public partial class YRadioAPI : YCommonAPI +/// API для работы с радио. +public class YRadioAPI : YCommonAPI { - public YRadioAPI(YandexMusicApi yandex) : base(yandex) - { - } + public YRadioAPI(YandexMusicApi api) : base(api) { } - /// - /// Получение списка рекомендованных радиостанций - /// - /// Хранилище - /// - public Task> GetStationsDashboardAsync(AuthStorage storage) - { - return new YGetStationsDashboardBuilder(api, storage) - .Build(null) - .GetResponseAsync(); - } + public Task GetStationsDashboardAsync() + => new YGetStationsDashboardBuilder(Api).ExecuteAsync(null!); - /// - /// Получение списка радиостанций - /// - /// Хранилище - /// - public Task>> GetStationsAsync(AuthStorage storage) - { - return new YGetStationsBuilder(api, storage) - .Build(null) - .GetResponseAsync(); - } + public Task?> GetStationsAsync() + => new YGetStationsBuilder(Api).ExecuteAsync(null!); - /// - /// Получение информации о радиостанции - /// - /// Хранилище - /// Тип - /// Тэг - /// - public Task>> GetStationAsync(AuthStorage storage, string type, string tag) - { - return new YGetStationBuilder(api, storage) - .Build((type, tag)) - .GetResponseAsync(); - } + public Task?> GetStationAsync(string type, string tag) + => new YGetStationBuilder(Api).ExecuteAsync((type, tag)); - /// - /// Получение информации о радиостанции - /// - /// Хранилище - /// Идентификатор станции - /// - public Task>> GetStationAsync(AuthStorage storage, YStationId id) - { - return GetStationAsync(storage, id.Type, id.Tag); - } + public Task?> GetStationAsync(YStationId id) + => GetStationAsync(id.Type, id.Tag); - /// - /// Получение последовательности треков радиостанции - /// - /// Хранилище - /// Радиостанция - /// Идентификатор предыдущего трека - /// - public Task> GetStationTracksAsync(AuthStorage storage, YStation station, string prevTrackId = "") - { - return new YGetStationTracksBuilder(api, storage) - .Build((station.Station, prevTrackId)) - .GetResponseAsync(); - } - - /// - /// Установка настроек подбора треков - /// - /// Хранилище - /// Радиостанция - /// Настройки - /// - public Task> SetStationSettings2Async(AuthStorage storage, YStation station, YStationSettings2 settings) - { - return new YSetSettings2Builder(api, storage) - .Build((station.Station, settings)) - .GetResponseAsync(); - } - - /// - /// Отправка обратной связи на действия при прослушивании радио - /// - /// Хранилище - /// Радиостанция - /// Тип обратной связи - /// Трек - /// Уникальный идентификатор партии треков. Возвращается при получении треков - /// Сколько было проиграно секунд трека перед действием - /// - public Task 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(); - } + public Task GetStationTracksAsync(YStation station, string prevTrackId = "") + => new YGetStationTracksBuilder(Api).ExecuteAsync((station.Station, prevTrackId)); + public Task SetStationSettings2Async(YStation station, YStationSettings2 settings) + => new YSetSettings2Builder(Api).ExecuteAsync((station.Station, settings)); + public Task SendStationFeedbackAsync( + YStation station, + YStationFeedbackType type, + YTrack? track = null, + string batchId = "", + double totalPlayedSeconds = 0) + => new YSetStationFeedbackBuilder(Api).ExecuteAsync((type, station, track, batchId, totalPlayedSeconds)); } \ No newline at end of file diff --git a/YandexMusic.API/API/YSearchAPI.cs b/YandexMusic.API/API/YSearchAPI.cs index 1a9a690..c4d5803 100644 --- a/YandexMusic.API/API/YSearchAPI.cs +++ b/YandexMusic.API/API/YSearchAPI.cs @@ -1,139 +1,38 @@ -using YandexMusic.API.Common; using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Search; using YandexMusic.API.Requests.Search; namespace YandexMusic.API; -/// -/// API для поиска -/// -public partial class YSearchAPI : YCommonAPI +/// API для поиска. +public class YSearchAPI : YCommonAPI { + public YSearchAPI(YandexMusicApi api) : base(api) { } - public YSearchAPI(YandexMusicApi yandex) : base(yandex) - { - } + public Task TrackAsync(string trackName, int page = 0, int pageSize = 20) + => SearchAsync(trackName, YSearchType.Track, page, pageSize); - /// - /// Поиск по трекам - /// - /// Хранилище - /// Имя трека - /// Номер страницы - /// Размер страницы - /// - public Task> TrackAsync(AuthStorage storage, string trackName, int pageNumber = 0, int pageSize = 20) - { - return SearchAsync(storage, trackName, YSearchType.Track, pageNumber, pageSize); - } + public Task AlbumsAsync(string albumName, int page = 0, int pageSize = 20) + => SearchAsync(albumName, YSearchType.Album, page, pageSize); - /// - /// Поиск по альбомам - /// - /// Хранилище - /// Имя альбома - /// Номер страницы - /// Размер страницы - /// - public Task> AlbumsAsync(AuthStorage storage, string albumName, int pageNumber = 0, int pageSize = 20) - { - return SearchAsync(storage, albumName, YSearchType.Album, pageNumber, pageSize); - } + public Task ArtistAsync(string artistName, int page = 0, int pageSize = 20) + => SearchAsync(artistName, YSearchType.Artist, page, pageSize); - /// - /// Поиск по артисту - /// - /// Хранилище - /// Имя артиста - /// Номер страницы - /// Размер страницы - /// - public Task> ArtistAsync(AuthStorage storage, string artistName, int pageNumber = 0, int pageSize = 20) - { - return SearchAsync(storage, artistName, YSearchType.Artist, pageNumber, pageSize); - } + public Task PlaylistAsync(string playlistName, int page = 0, int pageSize = 20) + => SearchAsync(playlistName, YSearchType.Playlist, page, pageSize); - /// - /// Поиск по плейлистам - /// - /// Хранилище - /// Имя плейлиста - /// Номер страницы - /// Размер страницы - /// - public Task> PlaylistAsync(AuthStorage storage, string playlistName, int pageNumber = 0, int pageSize = 20) - { - return SearchAsync(storage, playlistName, YSearchType.Playlist, pageNumber, pageSize); - } + public Task PodcastEpisodeAsync(string podcastName, int page = 0, int pageSize = 20) + => SearchAsync(podcastName, YSearchType.PodcastEpisode, page, pageSize); - /// - /// Поиск по плейлистам - /// - /// Хранилище - /// Имя подкаста - /// Номер страницы - /// Размер страницы - /// - public Task> PodcastEpisodeAsync(AuthStorage storage, string podcastName, int pageNumber = 0, int pageSize = 20) - { - return SearchAsync(storage, podcastName, YSearchType.PodcastEpisode, pageNumber, pageSize); - } + public Task VideosAsync(string videoName, int page = 0, int pageSize = 20) + => SearchAsync(videoName, YSearchType.Video, page, pageSize); - /// - /// Поиск по видео - /// - /// Хранилище - /// Имя видео - /// Номер страницы - /// Размер страницы - /// - public Task> VideosAsync(AuthStorage storage, string videoName, int pageNumber = 0, int pageSize = 20) - { - return SearchAsync(storage, videoName, YSearchType.Video, pageNumber, pageSize); - } - - /// - /// Поиск по пользователям - /// - /// Хранилище - /// Имя пользователя - /// Номер страницы - /// Размер страницы - /// - public Task> UsersAsync(AuthStorage storage, string userName, int pageNumber = 0, int pageSize = 20) - { - return SearchAsync(storage, userName, YSearchType.User, pageNumber, pageSize); - } - - /// - /// Поиск - /// - /// Хранилище - /// Поисковый запрос - /// Тип поиска - /// Страница - /// Размер страницы - /// - public Task> SearchAsync(AuthStorage storage, string searchText, YSearchType searchType, int page = 0, int pageSize = 20) - { - return new YSearchBuilder(api, storage) - .Build((searchText, searchType, page, pageSize)) - .GetResponseAsync(); - } - - /// - /// Подсказка - /// - /// Хранилище - /// Поисковый запрос - /// - public Task> SuggestAsync(AuthStorage storage, string searchText) - { - return new YSearchSuggestBuilder(api, storage) - .Build(searchText) - .GetResponseAsync(); - } + public Task UsersAsync(string userName, int page = 0, int pageSize = 20) + => SearchAsync(userName, YSearchType.User, page, pageSize); + public Task SearchAsync(string searchText, YSearchType searchType, int page = 0, int pageSize = 20) + => new YSearchBuilder(Api).ExecuteAsync((searchText, searchType, page, pageSize)); + public Task GetSearchSuggestionsAsync(string searchText) + => new YSearchSuggestBuilder(Api).ExecuteAsync(searchText); } \ No newline at end of file diff --git a/YandexMusic.API/API/YTrackAPI.cs b/YandexMusic.API/API/YTrackAPI.cs index f495dd6..290cdd4 100644 --- a/YandexMusic.API/API/YTrackAPI.cs +++ b/YandexMusic.API/API/YTrackAPI.cs @@ -1,307 +1,110 @@ using System.Security.Cryptography; using System.Text; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Track; using YandexMusic.API.Requests.Track; namespace YandexMusic.API; -/// -/// API для взаимодействия с треками -/// -public partial class YTrackAPI : YCommonAPI +/// API для работы с треками (получение, загрузка, метаданные). +public class YTrackAPI : YCommonAPI { - #region Вспомогательные функции + public YTrackAPI(YandexMusicApi api) : base(api) { } - private string BuildLinkForDownload(YTrackDownloadInfo mainDownloadResponse, YStorageDownloadFile storageDownload) + private static string BuildDownloadLink(YTrackDownloadInfo info, YStorageDownloadFile storageDownload) { - string path = storageDownload.Path; - string host = storageDownload.Host; - string ts = storageDownload.Ts; - string s = storageDownload.S; - string codec = mainDownloadResponse.Codec; + var path = storageDownload.Path; + var host = storageDownload.Host; + var ts = storageDownload.Ts; + var s = storageDownload.S; + var codec = info.Codec; - string secret = $"XGRlBW9FXlekgbPrRHuSiA{path.Substring(1, path.Length - 1)}{s}"; - MD5 md5 = MD5.Create(); - byte[] md5Hash = md5.ComputeHash(Encoding.UTF8.GetBytes(secret)); - HMACSHA1 hmacsha1 = new(); - byte[] hmasha1Hash = hmacsha1.ComputeHash(md5Hash); - string sign = BitConverter.ToString(hmasha1Hash).Replace("-", "").ToLower(); - - string link = $"https://{host}/get-{codec}/{sign}/{ts}{path}"; - - return link; + var secret = $"XGRlBW9FXlekgbPrRHuSiA{path[1..]}{s}"; + var md5Hash = MD5.HashData(Encoding.UTF8.GetBytes(secret)); + var hmacsha1 = new HMACSHA1(md5Hash); + var sign = BitConverter.ToString(hmacsha1.ComputeHash(md5Hash)).Replace("-", "").ToLower(); + return $"https://{host}/get-{codec}/{sign}/{ts}{path}"; } - #endregion Вспомогательные функции + public Task GetAsync(string trackId) + => GetAsync(trackId); + public Task?> GetAsync(IEnumerable trackIds) + => new YGetTracksBuilder(Api).ExecuteAsync(trackIds); + public Task?> GetMetadataForDownloadAsync(string trackKey, bool direct = false) + => new YTrackDownloadInfoBuilder(Api).ExecuteAsync((trackKey, direct)); + public Task?> GetMetadataForDownloadAsync(YTrack track, bool direct = false) + => GetMetadataForDownloadAsync(track.GetKey().ToString(), direct); - public YTrackAPI(YandexMusicApi yandex) : base(yandex) + public Task GetDownloadFileInfoAsync(YTrackDownloadInfo metadataInfo) + => new YStorageDownloadFileBuilder(Api).ExecuteAsync(metadataInfo.DownloadInfoUrl); + + public async Task GetFileLinkAsync(string trackKey) { + var meta = await GetMetadataForDownloadAsync(trackKey); + var info = meta?.OrderByDescending(i => i.BitrateInKbps).FirstOrDefault(m => m.Codec == "mp3"); + if (info == null) return null; + var storageDownload = await GetDownloadFileInfoAsync(info); + if (storageDownload == null) return null; + return BuildDownloadLink(info, storageDownload); } - /// - /// Получение треков - /// - /// Хранилище - /// Идентификатор трека - /// - public Task>> GetAsync(AuthStorage storage, string trackId) + public Task GetFileLinkAsync(YTrack track) + => GetFileLinkAsync(track.GetKey().ToString()); + + public async Task ExtractToFileAsync(string trackKey, string filePath) { - return new YGetTracksBuilder(api, storage) - .Build(new[] { trackId }) - .GetResponseAsync(); + var url = await GetFileLinkAsync(trackKey); + if (string.IsNullOrEmpty(url)) throw new Exception("Не удалось получить ссылку на трек"); + using var response = await Api.HttpClient.GetAsync(url); + await using var fs = File.Create(filePath); + await response.Content.CopyToAsync(fs); } - /// - /// Получение треков - /// - /// Хранилище - /// Идентификаторы треков - /// - public Task>> GetAsync(AuthStorage storage, IEnumerable trackIds) + public Task ExtractToFileAsync(YTrack track, string filePath) + => ExtractToFileAsync(track.GetKey().ToString(), filePath); + + public async Task ExtractDataAsync(string trackKey) { - return new YGetTracksBuilder(api, storage) - .Build(trackIds) - .GetResponseAsync(); + var url = await GetFileLinkAsync(trackKey); + if (string.IsNullOrEmpty(url)) throw new Exception("Не удалось получить ссылку на трек"); + return await Api.HttpClient.GetByteArrayAsync(url); } - /// - /// Получение метаданных для загрузки - /// - /// Хранилище - /// Ключ трека в формате {идентифактор трека:идентификатор альбома} - /// Должен ли ответ содержать прямую ссылку на загрузку - /// - public Task>> GetMetadataForDownloadAsync(AuthStorage storage, string trackKey, bool direct = false) + public Task ExtractDataAsync(YTrack track) + => ExtractDataAsync(track.GetKey().ToString()); + + public async Task ExtractStreamAsync(string trackKey, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { - return new YTrackDownloadInfoBuilder(api, storage) - .Build((trackKey, direct)) - .GetResponseAsync(); + var url = await GetFileLinkAsync(trackKey); + if (string.IsNullOrEmpty(url)) throw new Exception("Не удалось получить ссылку на трек"); + var response = await Api.HttpClient.GetAsync(url, completionOption); + return await response.Content.ReadAsStreamAsync(); } - /// - /// Получение метаданных для загрузки - /// - /// Хранилище - /// Трек - /// Должен ли ответ содержать прямую ссылку на загрузку - /// - public Task>> GetMetadataForDownloadAsync(AuthStorage storage, YTrack track, bool direct = false) - { - return GetMetadataForDownloadAsync(storage, track.GetKey().ToString(), direct); - } + public Task ExtractStreamAsync(YTrack track, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) + => ExtractStreamAsync(track.GetKey().ToString(), completionOption); - /// - /// Получение информации для формирования ссылки для загрузки - /// - /// Хранилище - /// Метаданные для загрузки - /// - public Task GetDownloadFileInfoAsync(AuthStorage storage, YTrackDownloadInfo metadataInfo) - { - return new YStorageDownloadFileBuilder(api, storage) - .Build(metadataInfo.DownloadInfoUrl) - .GetResponseAsync(); - } + public Task SendPlayTrackInfoAsync( + YTrack track, + string from, + bool fromCache = false, + string playId = "", + string playlistId = "", + double totalPlayedSeconds = 0, + double endPositionSeconds = 0) + => new YSendTrackInfoBuilder(Api).ExecuteAsync((track, from, fromCache, playId, playlistId, totalPlayedSeconds, endPositionSeconds)); - /// - /// Получение ссылки для загрузки - /// - /// Хранилище - /// Ключ трека в формате {идентификатор трека:идентификатор альбома} - /// - public async Task GetFileLinkAsync(AuthStorage storage, string trackKey) - { - YResponse> meta = await GetMetadataForDownloadAsync(storage, trackKey); - YTrackDownloadInfo info = meta.Result - .OrderByDescending(i => i.BitrateInKbps) - .First(m => m.Codec == "mp3"); - YStorageDownloadFile storageDownload = await GetDownloadFileInfoAsync(storage, info); - return BuildLinkForDownload(info, storageDownload); - } + public Task GetSupplementAsync(string trackId) + => new YGetTrackSupplementBuilder(Api).ExecuteAsync(trackId); - /// - /// Получение ссылки для загрузки - /// - /// Хранилище - /// Трек - /// - public Task GetFileLinkAsync(AuthStorage storage, YTrack track) - { - return GetFileLinkAsync(storage, track.GetKey().ToString()); - } - - /// - /// Отправка текущего состояния прослушиваемого трека - /// Хранилище - /// Трек - /// Наименования клиента, с которого происходит прослушивание - /// Проигрывается ли трек с кеша - /// Уникальный идентификатор проигрывания - /// Уникальный идентификатор плейлиста, если таковой прослушивается - /// Сколько было всего воспроизведено трека в секундах - /// Окончательное значение воспроизведенных секунд - /// - /// - public Task 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 - - /// - /// Получение дополнительной информации для трека - /// - /// Хранилище - /// Идентификатор трека - /// - public Task> GetSupplementAsync(AuthStorage storage, string trackId) - { - return new YGetTrackSupplementBuilder(api, storage) - .Build(trackId) - .GetResponseAsync(); - } - - /// - /// Получение дополнительной информации для трека - /// - /// Хранилище - /// Трек - /// - public Task> GetSupplementAsync(AuthStorage storage, YTrack track) - { - return new YGetTrackSupplementBuilder(api, storage) - .Build(track.GetKey().ToString()) - .GetResponseAsync(); - } - - #endregion GetSupplement - - #region GetSimilar - - /// - /// Получение похожих треков - /// - /// Хранилище - /// Идентификатор трека - /// - public Task> GetSimilarAsync(AuthStorage storage, string trackId) - { - return new YGetTrackSimilarBuilder(api, storage) - .Build(trackId) - .GetResponseAsync(); - } - - /// - /// Получение похожих треков - /// - /// Хранилище - /// Трек - /// - public Task> GetSimilarAsync(AuthStorage storage, YTrack track) - { - return new YGetTrackSimilarBuilder(api, storage) - .Build(track.GetKey().ToString()) - .GetResponseAsync(); - } - - #endregion GetSimilar - - #region Получение данных трека - - #region В файл - - /// - /// Выгрузка в файл - /// - /// Хранилище - /// Ключ трека в формате {идентификатор трека:идентификатор альбома} - /// Путь для файла - public async Task ExtractToFileAsync(AuthStorage storage, string trackKey, string filePath) - { - string url = await GetFileLinkAsync(storage, trackKey); - await new DataDownloader(storage).ToFile(url, filePath); - } - - /// - /// Выгрузка в файл - /// - /// Хранилище - /// Трек - /// Путь для файла - public Task ExtractToFileAsync(AuthStorage storage, YTrack track, string filePath) - { - return ExtractToFileAsync(storage, track.GetKey().ToString(), filePath); - } - - #endregion В файл - - #region В массив байт - - /// - /// Получение двоичного массива данных - /// - /// Хранилище - /// Ключ трека в формате {идентификатор трека:идентификатор альбома} - /// - public async Task ExtractDataAsync(AuthStorage storage, string trackKey) - { - string url = await GetFileLinkAsync(storage, trackKey); - return await new DataDownloader(storage).AsBytes(url); - } - - /// - /// Получение двоичного массива данных - /// - /// Хранилище - /// Трек - /// - public Task ExtractDataAsync(AuthStorage storage, YTrack track) - { - return ExtractDataAsync(storage, track.GetKey().ToString()); - } - - #endregion В массив байт - - #region В поток - - /// - /// Получение потока данных - /// - /// Хранилище - /// Ключ трека в формате {идентификатор трека:идентификатор альбома} - /// Параметры передачи управления при http запросе - /// - public async Task ExtractStreamAsync(AuthStorage storage, string trackKey, - HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) - { - string url = await GetFileLinkAsync(storage, trackKey); - return await new DataDownloader(storage).AsStream(url, httpCompletionOption); - } - - /// - /// Получение потока данных - /// - /// Хранилище - /// Трек - /// Параметры передачи управления при http запросе - /// - public Task ExtractStreamAsync(AuthStorage storage, YTrack track, - HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) - { - return ExtractStreamAsync(storage, track.GetKey().ToString(), httpCompletionOption); - } - - #endregion В поток - - #endregion Получение данных трека + public Task GetSupplementAsync(YTrack track) + => GetSupplementAsync(track.GetKey().ToString()); + public Task GetSimilarAsync(string trackId) + => new YGetTrackSimilarBuilder(Api).ExecuteAsync(trackId); + public Task GetSimilarAsync(YTrack track) + => GetSimilarAsync(track.GetKey().ToString()); } \ No newline at end of file diff --git a/YandexMusic.API/API/YUgcAPI.cs b/YandexMusic.API/API/YUgcAPI.cs index bc6536c..5f4ae6a 100644 --- a/YandexMusic.API/API/YUgcAPI.cs +++ b/YandexMusic.API/API/YUgcAPI.cs @@ -1,71 +1,36 @@ -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Models.Playlist; +using YandexMusic.API.Models.Playlist; using YandexMusic.API.Models.Ugc; using YandexMusic.API.Requests.Ugc; namespace YandexMusic.API; -public partial class YUgcAPI : YCommonAPI +/// API для загрузки пользовательского контента (UGC). +public class YUgcAPI : YCommonAPI { - public YUgcAPI(YandexMusicApi yandex) : base(yandex) - { - } + public YUgcAPI(YandexMusicApi api) : base(api) { } - /// - /// Получение ссылки на загрузчик трека - /// - /// Хранилище - /// Плейлист, куда будет загружен трек - /// Название файла для загрузки - public Task GetUgcUploadLinkAsync(AuthStorage storage, YPlaylist playlist, string fileName) - { - return new YUgcGetUploadLinkBuilder(api, storage) - .Build((playlist, fileName)) - .GetResponseAsync(); - } + public Task GetUgcUploadLinkAsync(YPlaylist playlist, string fileName) + => new YUgcGetUploadLinkBuilder(Api).ExecuteAsync((playlist, fileName)); - /// - /// Загрузка трека из файла - /// - /// Хранилище - /// Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync - /// Загружаемый файл - public Task> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, string filePath) + public async Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, string filePath) { if (!File.Exists(filePath)) - throw new FileNotFoundException("Файл для загрузки не существует.", filePath); - - return UploadUgcTrackAsync(storage, uploadLink, File.Open(filePath, FileMode.Open)); + throw new FileNotFoundException("Файл не найден", filePath); + return await UploadTrackToPlaylistAsync(playlist, fileName, await File.ReadAllBytesAsync(filePath)); } - /// - /// Загрузка трека из потока - /// - /// Хранилище - /// Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync - /// Поток с данными для загрузки - public Task> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, Stream stream) + public async Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, Stream stream) { - if (stream == null) - throw new NullReferenceException("Пустая ссылка на поток загрузки."); - - using MemoryStream ms = new(); - stream.CopyTo(ms); - - return UploadUgcTrackAsync(storage, uploadLink, ms.ToArray()); + using var ms = new MemoryStream(); + await stream.CopyToAsync(ms); + return await UploadTrackToPlaylistAsync(playlist, fileName, ms.ToArray()); } - /// - /// Загрузка трека из массива - /// - /// Хранилище - /// Ссылка на балансировщик для загрузки, можно получить из GetUgcUploadLinkAsync - /// Загружаемый трек в виде массив байтов - public Task> UploadUgcTrackAsync(AuthStorage storage, string uploadLink, byte[] file) + public async Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, byte[] file) { - return new YUgcUploadBuilder(api, storage) - .Build((uploadLink, file)) - .GetResponseAsync(); + var uploadLink = await GetUgcUploadLinkAsync(playlist, fileName); + if (uploadLink?.PostTarget == null) return null; + var result = await new YUgcUploadBuilder(Api).ExecuteAsync((uploadLink.PostTarget, file)); + return result?.Result; } } \ No newline at end of file diff --git a/YandexMusic.API/API/YUserAPI.cs b/YandexMusic.API/API/YUserAPI.cs index d66d638..140e60f 100644 --- a/YandexMusic.API/API/YUserAPI.cs +++ b/YandexMusic.API/API/YUserAPI.cs @@ -1,300 +1,159 @@ 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; -/// -/// API для пользователя -/// -public partial class YUserAPI : YCommonAPI +/// API для работы с пользователем и авторизации. +public class YUserAPI : YCommonAPI { - #region Вспомогательные функции + public YUserAPI(YandexMusicApi api) : base(api) { } - private async Task GetCsrfTokenAsync(AuthStorage storage) + private async Task GetCsrfTokenAsync() { - using HttpResponseMessage authMethodsResponse = await new YGetAuthMethodsBuilder(api, storage) - .Build(null) - .GetResponseAsync(); + using var response = await new YGetAuthMethodsBuilder(Api).ExecuteRawAsync(null!); + if (response == null || !response.IsSuccessStatusCode) + throw new HttpRequestException("Не удалось получить CSRF-токен"); - if (!authMethodsResponse.IsSuccessStatusCode) - throw new HttpRequestException("Невозможно получить CFRF-токен."); + var content = await response.Content.ReadAsStringAsync(); + var csrfMatch = Regex.Match(content, @"window\.__CSRF__\s*=\s*""([^""]+)"""); + var processMatch = Regex.Match(content, @"'process_uuid'\s*:\s*'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'"); - string responseString = await authMethodsResponse.Content - .ReadAsStringAsync(); - Match match = Regex.Match(responseString, "\"csrf_token\" value=\"([^\"]+)\""); - - if (!match.Success || match.Groups.Count < 2) + if (!csrfMatch.Success || !processMatch.Success) return false; - storage.AuthToken = new YAuthToken + Api.Storage.AuthToken = new YAuthToken { - CsfrToken = match.Groups[1].Value + CsfrToken = csrfMatch.Groups[1].Value, + ProcessUuid = processMatch.Groups[1].Value }; + await new YPostAuthStats(Api).ExecuteAsync(null!); + return true; + } + + private async Task LoginByCookiesAsync() + { + if (Api.Storage.AuthToken == null) + throw new AuthenticationException("Сессия входа не инициализирована"); + + var accessToken = await new YGetAuthCookiesBuilder(Api).ExecuteAsync(null!); + if (accessToken == null || string.IsNullOrEmpty(accessToken.AccessToken)) + return false; + + Api.Storage.AccessToken = accessToken; + Api.Storage.Token = accessToken.AccessToken; + + var shortInfo = await new YGetShortAccountInfoBuilder(Api).ExecuteAsync(null!); + if (shortInfo?.Status != YAuthStatus.Ok || string.IsNullOrWhiteSpace(shortInfo.Uid)) + throw new Exception("Не удалось подтвердить авторизацию"); + return true; } - private async Task 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) - { - } - - /// - /// Авторизация - /// - /// Хранилище - /// Токен авторизации - /// - public async Task AuthorizeAsync(AuthStorage storage, string token) + public async Task AuthorizeAsync(string token) { if (string.IsNullOrEmpty(token)) - throw new Exception("Задан пустой токен авторизации."); + throw new Exception("Токен не может быть пустым"); - storage.Token = token; + Api.Storage.Token = token; + var authInfo = await new YGetAuthInfoBuilder(Api).ExecuteAsync(null!); + if (authInfo?.Account?.Uid == null) + throw new Exception("Пользователь не авторизован"); - // Пытаемся получить информацию о пользователе - YResponse authInfo = await GetUserAuthAsync(storage); - - // Если не авторизован, то авторизуем - if (string.IsNullOrEmpty(authInfo.Result.Account.Uid)) - throw new Exception("Пользователь незалогинен."); - - // Флаг авторизации - storage.IsAuthorized = true; - storage.User = authInfo.Result.Account; + Api.Storage.SetAuthorized(authInfo.Account, token); } - /// - /// Получение информации об авторизации - /// - /// Хранилище - /// - public Task> GetUserAuthAsync(AuthStorage storage) + public Task GetUserAuthAsync() + => new YGetAuthInfoBuilder(Api).ExecuteAsync(null!); + + public async Task CreateAuthSessionAsync(string userName) { - return new YGetAuthInfoBuilder(api, storage) - .Build(null) - .GetResponseAsync(); + if (!await GetCsrfTokenAsync()) + throw new Exception("Не удалось инициализировать сессию"); + + var result = await new YGetAuthLoginUserBuilder(Api).ExecuteAsync((Api.Storage.AuthToken.CsfrToken, userName)); + if (result?.TrackId != null) + Api.Storage.AuthToken.TrackId = result.TrackId; + return result; } - /// - /// Создание сеанса и получение доступных методов авторизации - /// - /// Хранилище - /// Имя пользователя - /// - public async Task CreateAuthSessionAsync(AuthStorage storage, string userName) + public async Task GetAuthQRLinkAsync() { - if (!await GetCsrfTokenAsync(storage)) - throw new Exception("Невозможно инициализировать сессию входа."); + if (!await GetCsrfTokenAsync()) + throw new Exception("Не удалось инициализировать сессию"); - YAuthTypes types = await new YGetAuthLoginUserBuilder(api, storage) - .Build((storage.AuthToken.CsfrToken, userName)) - .GetResponseAsync(); + var qr = await new YGetAuthQRBuilder(Api).ExecuteAsync(null!); + if (qr?.Status != YAuthStatus.Ok || string.IsNullOrEmpty(qr.TrackId)) + return null; - storage.AuthToken.TrackId = types.TrackId; - - return types; - } - - /// - /// Получение ссылки на QR-код - /// - /// Хранилище - /// - public async Task 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 + Api.Storage.AuthToken = new YAuthToken { - TrackId = result.TrackId, - CsfrToken = result.CsrfToken + TrackId = qr.TrackId, + CsfrToken = qr.CsrfToken }; - - return $"https://passport.yandex.ru/auth/magic/code/?track_id={result.TrackId}"; + return $"https://passport.yandex.ru/auth/magic/code/?track_id={qr.TrackId}"; } - /// - /// Авторизация по QR-коду - /// - /// Хранилище - /// - public async Task AuthorizeByQRAsync(AuthStorage storage) + public async Task AuthorizeByQRAsync() { - if (storage.AuthToken == null) - throw new Exception("Не выполнен запрос на авторизацию по QR."); + if (Api.Storage.AuthToken == null) + throw new Exception("Сессия не инициализирована"); - 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); - } + var status = await new YGetAuthLoginQRBuilder(Api).ExecuteAsync(null!); + if (status?.Status == YAuthStatus.Ok && await LoginByCookiesAsync()) + return status; + throw new AuthenticationException("Ошибка авторизации по QR"); } - /// - /// Получение - /// - /// Хранилище - /// - public Task GetCaptchaAsync(AuthStorage storage) + public Task GetCaptchaAsync() { - if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken)) - throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием."); - - return new YGetAuthCaptchaBuilder(api, storage) - .Build(null) - .GetResponseAsync(); + if (Api.Storage.AuthToken == null) + throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием"); + return new YGetAuthCaptchaBuilder(Api).ExecuteAsync(null!); } - /// - /// Авторизация по captcha - /// - /// Хранилище - /// Значение captcha - /// - public Task AuthorizeByCaptchaAsync(AuthStorage storage, string captchaValue) + public Task AuthorizeByCaptchaAsync(string captchaValue) { - if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken)) - throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием."); - - return new YGetAuthLoginCaptchaBuilder(api, storage) - .Build(captchaValue) - .GetResponseAsync(); + if (Api.Storage.AuthToken == null) + throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием"); + return new YGetAuthLoginCaptchaBuilder(Api).ExecuteAsync(captchaValue); } - /// - /// Получение письма авторизации на почту пользователя - /// - /// Хранилище - /// - public Task GetAuthLetterAsync(AuthStorage storage) + public Task GetAuthLetterAsync() + => new YGetAuthLetterBuilder(Api).ExecuteAsync(null!); + + public async Task AuthorizeByLetterAsync() { - return new YGetAuthLetterBuilder(api, storage) - .Build(null) - .GetResponseAsync(); + var status = await new YGetAuthLoginLetterBuilder(Api).ExecuteAsync(null!); + if (status?.Status != YAuthStatus.Ok || !status.MagicLinkConfirmed) + throw new Exception("Письмо не подтверждено"); + return await LoginByCookiesAsync(); } - /// - /// Авторизация после подтверждения входа через письмо - /// - /// Хранилище - /// - public async Task AuthorizeByLetterAsync(AuthStorage storage) + public async Task AuthorizeByAppPasswordAsync(string password) { - YAuthLetterStatus status = await new YGetAuthLoginLetterBuilder(api, storage) - .Build(null) - .GetResponseAsync(); + if (Api.Storage.AuthToken == null) + throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием"); - if (status.Status == YAuthStatus.Ok && !status.MagicLinkConfirmed) - throw new Exception("Не подтвержден вход посредством e-mail."); - - return await LoginByCookiesAsync(storage); + var result = await new YGetAuthAppPasswordBuilder(Api).ExecuteAsync(password); + if (result?.Status == YAuthStatus.Ok && await LoginByCookiesAsync()) + return result; + throw new AuthenticationException("Ошибка авторизации по паролю"); } - /// - /// Авторизация с помощью пароля из приложения Яндекс - /// - /// Хранилище - /// Пароль - /// - public async Task AuthorizeByAppPasswordAsync(AuthStorage storage, string password) + public async Task GetAccessTokenAsync() { - if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken)) - throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием."); + if (Api.Storage.AuthToken == null) + throw new Exception("Сессия не инициализована"); - 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; + var token = await new YGetMusicTokenBuilder(Api).ExecuteAsync(null!); + if (token?.AccessToken != null) + Api.Storage.Token = token.AccessToken; + return token; } - /// - /// Получение после авторизации с помощью QR, e-mail, пароля из приложения - /// - public async Task 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; - } - - /// - /// Получение информации о пользователе через логин Яндекса - /// - public Task GetLoginInfoAsync(AuthStorage storage) - { - return new YGetLoginInfoBuilder(api, storage) - .Build(null) - .GetResponseAsync(); - } - - -} + public Task GetLoginInfoAsync() + => new YGetLoginInfoBuilder(Api).ExecuteAsync(null!); +} \ No newline at end of file diff --git a/YandexMusic.API/API/YYnisonAPI.cs b/YandexMusic.API/API/YYnisonAPI.cs index dc9b307..8ce018d 100644 --- a/YandexMusic.API/API/YYnisonAPI.cs +++ b/YandexMusic.API/API/YYnisonAPI.cs @@ -1,21 +1,16 @@ -using YandexMusic.API.Common; using YandexMusic.API.Common.Ynison; namespace YandexMusic.API; -/// -/// API Ynison -/// -public partial class YYnisonAPI : YCommonAPI + +/// API для работы с Ynison (WebSocket-плеер). +public class YYnisonAPI : YCommonAPI { - public YYnisonAPI(YandexMusicApi yandex) : base(yandex) - { - } + public YYnisonAPI(YandexMusicApi api) : base(api) { } - public YnisonPlayer GetPlayer(AuthStorage storage) + public YnisonPlayer GetPlayer() { - if (string.IsNullOrEmpty(storage.Token)) - throw new Exception("Токен пользователя не задан."); - - return new(api, storage); + if (string.IsNullOrEmpty(Api.Storage.Token)) + throw new Exception("Токен пользователя не задан"); + return new YnisonPlayer(Api, Api.Storage); } -} +} \ No newline at end of file diff --git a/YandexMusic.API/Common/AuthStorage.cs b/YandexMusic.API/Common/AuthStorage.cs index a255894..ad0c301 100644 --- a/YandexMusic.API/Common/AuthStorage.cs +++ b/YandexMusic.API/Common/AuthStorage.cs @@ -1,80 +1,61 @@ -using System.Net; -using YandexMusic.API.Common.Providers; using YandexMusic.API.Models.Account; -using YandexMusic.API.Requests.Common; namespace YandexMusic.API.Common; /// -/// Хранилище данных пользователя +/// Хранилище данных авторизации. Не содержит HTTP-зависимостей. /// public class AuthStorage { /// - /// Http-контекст - /// - public HttpContext Context { get; } - - /// - /// Флаг авторизации + /// Флаг, указывающий, авторизован ли пользователь. /// public bool IsAuthorized { get; internal set; } /// - /// Идентификатор устройства + /// Идентификатор устройства (используется в заголовках). /// public string DeviceId { get; set; } = "csharp"; /// - /// Токен авторизации + /// OAuth-токен для доступа к API. /// - public string Token { get; internal set; } + public string Token { get; internal set; } = string.Empty; /// - /// Аккаунт + /// Информация об аккаунте пользователя. /// - public YAccount User { get; set; } + public YAccount User { get; internal set; } = new(); /// - /// Провайдер запросов + /// Временный токен доступа (используется в некоторых сценариях авторизации). /// - public IRequestProvider Provider { get; } + public YAccessToken AccessToken { get; internal set; } = new(); /// - /// Токен доступа + /// Внутренние данные авторизации (CSRF, track_id и т.д.). /// - public YAccessToken AccessToken { get; set; } - - internal YAuthToken AuthToken { get; set; } + internal YAuthToken AuthToken { get; set; } = new(); /// - /// Конструктор + /// Устанавливает флаг авторизации и сохраняет информацию об аккаунте. /// - public AuthStorage(IRequestProvider provider) + internal void SetAuthorized(YAccount user, string token) + { + User = user ?? throw new ArgumentNullException(nameof(user)); + Token = token ?? throw new ArgumentNullException(nameof(token)); + IsAuthorized = true; + } + + /// + /// Сбрасывает состояние авторизации. + /// + internal void ResetAuthorization() { User = new YAccount(); - Context = new HttpContext(); - Provider = provider; + Token = string.Empty; + AccessToken = new YAccessToken(); + AuthToken = new YAuthToken(); + IsAuthorized = false; } - - /// - /// Конструктор - /// - public AuthStorage() - { - User = new YAccount(); - Context = new HttpContext(); - Provider = new DefaultRequestProvider(this); - } - - /// - /// Установка прокси для пользователия - /// - /// Прокси - public void SetProxy(IWebProxy proxy) - { - Context.WebProxy = proxy; - } - - } \ No newline at end of file diff --git a/YandexMusic.API/Common/DataDownloader.cs b/YandexMusic.API/Common/DataDownloader.cs deleted file mode 100644 index 9b73c6d..0000000 --- a/YandexMusic.API/Common/DataDownloader.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Net; - -namespace YandexMusic.API.Common; - -/// -/// Загрузчик файлов по ссылке -/// -public class DataDownloader -{ - private AuthStorage authStorage; - - private async Task 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 AsStream(string url, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) - { - HttpContent content = await GetResponseContent(url, httpCompletionOption); - return await content.ReadAsStreamAsync(); - } - - public async Task 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; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Common/Providers/CommonRequestProvider.cs b/YandexMusic.API/Common/Providers/CommonRequestProvider.cs deleted file mode 100644 index 9519d32..0000000 --- a/YandexMusic.API/Common/Providers/CommonRequestProvider.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using YandexMusic.API.Converters; -using YandexMusic.API.Models.Common; - -namespace YandexMusic.API.Common.Providers; - -/// Базовый провайдер HTTP-запросов с общей логикой десериализации. -public abstract class CommonRequestProvider : IRequestProvider -{ - /// Хранилище данных авторизации. - protected readonly AuthStorage storage; - - /// Инициализирует новый экземпляр провайдера. - /// Хранилище авторизации. - protected CommonRequestProvider(AuthStorage authStorage) - { - storage = authStorage; - } - - /// Выполняет HTTP-запрос и возвращает ответ. - public abstract Task GetWebResponseAsync( - HttpRequestMessage message, - HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead); - - /// Преобразует HTTP-ответ в объект типа T. - public virtual async Task GetDataFromResponseAsync( - YandexMusicApi api, - HttpResponseMessage response) - { - var json = await response.Content.ReadAsStringAsync(); - - JsonSerializerOptions JsonOptions = new() - { - PropertyNameCaseInsensitive = true, - Converters = { - new JsonStringEnumConverter(JsonNamingPolicy.KebabCaseLower), - new IntToStringConverter(), - new StringToIntConverter(), - new YExecutionContextConverter(api, storage), - } - }; - - if (!response.IsSuccessStatusCode) - { - var error = JsonSerializer.Deserialize(json, JsonOptions); - throw error ?? new Exception("Ошибка десериализации ответа с ошибкой."); - } - - try - { - // Если нужен контекст выполнения, он добавляется через кастомный конвертер - return JsonSerializer.Deserialize(json, JsonOptions) - ?? throw new JsonException("Десериализация вернула null"); - } - catch (Exception ex) - { - throw new Exception($"Ошибка десериализации: {ex.Message}", ex); - } - } -} \ No newline at end of file diff --git a/YandexMusic.API/Common/Providers/DefaultRequestProvider.cs b/YandexMusic.API/Common/Providers/DefaultRequestProvider.cs deleted file mode 100644 index b000c00..0000000 --- a/YandexMusic.API/Common/Providers/DefaultRequestProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Net; - -namespace YandexMusic.API.Common.Providers; - -/// Стандартный провайдер HTTP-запросов с использованием HttpClient. -public class DefaultRequestProvider : CommonRequestProvider -{ - /// Инициализирует новый экземпляр провайдера. - /// Хранилище авторизации. - public DefaultRequestProvider(AuthStorage authStorage) : base(authStorage) { } - - /// Выполняет HTTP-запрос и возвращает ответ. - /// HTTP-запрос. - /// Опция завершения запроса. - /// HTTP-ответ. - public override async Task GetWebResponseAsync( - HttpRequestMessage message, - HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) - { - using var handler = new SocketsHttpHandler - { - Proxy = storage.Context.WebProxy, - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - UseCookies = true, - CookieContainer = storage.Context.Cookies, - AllowAutoRedirect = true, - MaxAutomaticRedirections = 10 - }; - - using var client = new HttpClient(handler); - - try - { - return await client.SendAsync(message, completionOption); - } - catch (HttpRequestException ex) - { - // Пытаемся извлечь тело ошибки, если оно доступно - if (ex.InnerException == null) - throw; - - throw new Exception($"Ошибка HTTP-запроса: {ex.Message}", ex); - } - } -} \ No newline at end of file diff --git a/YandexMusic.API/Common/Providers/IRequestProvider.cs b/YandexMusic.API/Common/Providers/IRequestProvider.cs deleted file mode 100644 index d48b18e..0000000 --- a/YandexMusic.API/Common/Providers/IRequestProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace YandexMusic.API.Common.Providers -{ - /// - /// Интерфейс для провайдеров обработки запросов - /// - public interface IRequestProvider - { - /// - /// Функция получения ответа - /// - /// Запрос - /// Опция завершения запроса - /// - Task GetWebResponseAsync(HttpRequestMessage message, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead); - - /// - /// Функция формирования ответа - /// - /// Тип объекта с ответом - /// API - /// Ответ - /// - Task GetDataFromResponseAsync(YandexMusicApi api, HttpResponseMessage response); - } -} \ No newline at end of file diff --git a/YandexMusic.API/Common/YandexMusicHttpClientFactory.cs b/YandexMusic.API/Common/YandexMusicHttpClientFactory.cs new file mode 100644 index 0000000..946436f --- /dev/null +++ b/YandexMusic.API/Common/YandexMusicHttpClientFactory.cs @@ -0,0 +1,47 @@ +using System.Net; + +namespace YandexMusic.API.Common; + +/// +/// Фабрика для создания стандартного HttpClient с поддержкой кук, прокси и автоматической декомпрессией. +/// +public static class YandexMusicHttpClientFactory +{ + /// + /// Создаёт стандартный HttpClient с автоматическим управлением куками. + /// + /// Прокси-сервер (опционально). + /// Таймаут запросов (по умолчанию 30 секунд). + /// User-Agent (по умолчанию как у браузера Chrome). + /// Настроенный HttpClient. + public static HttpClient CreateDefault( + CookieContainer? cookieContainer = null, + IWebProxy? proxy = null, + TimeSpan? timeout = null, + string? userAgent = null) + { + var handler = new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, + UseCookies = true, + CookieContainer = cookieContainer ?? new CookieContainer(), + AllowAutoRedirect = true, + MaxAutomaticRedirections = 10, + Proxy = proxy, + UseProxy = proxy != null + }; + + var client = new HttpClient(handler, disposeHandler: true) + { + Timeout = timeout ?? TimeSpan.FromSeconds(30) + }; + + // Стандартный User-Agent, похожий на браузерный + client.DefaultRequestHeaders.Add("User-Agent", + userAgent ?? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); + client.DefaultRequestHeaders.Add("Accept", "*/*"); + client.DefaultRequestHeaders.Add("Accept-Language", "ru-RU,ru;q=0.9,en;q=0.8"); + + return client; + } +} \ No newline at end of file diff --git a/YandexMusic.API/Common/Ynison/YnisonPlayer.cs b/YandexMusic.API/Common/Ynison/YnisonPlayer.cs index 45fcb16..7c95e5f 100644 --- a/YandexMusic.API/Common/Ynison/YnisonPlayer.cs +++ b/YandexMusic.API/Common/Ynison/YnisonPlayer.cs @@ -1,4 +1,5 @@ -using System.Net.WebSockets; +using System.Net; +using System.Net.WebSockets; using System.Text.Json; using System.Text.Json.Serialization; using YandexMusic.API.Models.Track; @@ -12,6 +13,7 @@ public class YnisonPlayer : IDisposable { private readonly JsonSerializerOptions _jsonOptions; private readonly AuthStorage _storage; + private readonly IWebProxy? _proxy; private YnisonWebSocket? _redirector; private YnisonWebSocket? _state; @@ -33,40 +35,36 @@ public class YnisonPlayer : IDisposable /// Аргументы события получения состояния. public class ReceiveEventArgs : EventArgs { - /// Состояние плеера. public YYnisonState State { get; init; } = null!; } /// Аргументы события закрытия соединения. public class CloseEventArgs : EventArgs { - /// Статус закрытия. public WebSocketCloseStatus? Status { get; init; } - /// Описание причины закрытия. public string? Description { get; init; } } - internal YnisonPlayer(YandexMusicApi api, AuthStorage authStorage) + internal YnisonPlayer(YandexMusicApi api, AuthStorage authStorage, IWebProxy? proxy = null) { API = api; _storage = authStorage; + _proxy = proxy; _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { new JsonStringEnumConverter(new UpperSnakeCaseNamingPolicy(), false) } }; - _redirector = new YnisonWebSocket(); - _state = new YnisonWebSocket(); + _redirector = new YnisonWebSocket(_proxy); + _state = new YnisonWebSocket(_proxy); } private string SerializeJson(object data) => JsonSerializer.Serialize(data, _jsonOptions); private T Deserialize(YYnisonMessageType messageType, string data) - { - return JsonSerializer.Deserialize(data, _jsonOptions) + => JsonSerializer.Deserialize(data, _jsonOptions) ?? throw new JsonException("Десериализация вернула null"); - } private T DeserializeMessage(YYnisonMessageType messageType, string data) { @@ -120,8 +118,8 @@ public class YnisonPlayer : IDisposable if (index < 0 || index >= State.PlayerState.PlayerQueue.PlayableList.Count) return null; var item = State.PlayerState.PlayerQueue.PlayableList[index]; - var response = await API.Track.GetAsync(_storage, item.PlayableId); - return response?.Result?.FirstOrDefault(); + var response = await API.Track.GetAsync(item.PlayableId); + return response; } private async Task UpdateStateAsync() diff --git a/YandexMusic.API/Common/Ynison/YnisonWebSocket.cs b/YandexMusic.API/Common/Ynison/YnisonWebSocket.cs index 8c0b7a4..9a8cc34 100644 --- a/YandexMusic.API/Common/Ynison/YnisonWebSocket.cs +++ b/YandexMusic.API/Common/Ynison/YnisonWebSocket.cs @@ -1,4 +1,5 @@ -using System.Net.WebSockets; +using System.Net; +using System.Net.WebSockets; using System.Text; using System.Text.Json; @@ -7,14 +8,15 @@ namespace YandexMusic.API.Common.Ynison; /// WebSocket-клиент для взаимодействия с протоколом Ynison. public class YnisonWebSocket : IDisposable { - private readonly ClientWebSocket _socketClient = new(); + private ClientWebSocket? _socketClient; private CancellationTokenSource? _cancellationTokenSource; private CancellationToken _cancellationToken; private readonly StringBuilder _data = new(); private const int BufferSize = 4096; + private readonly IWebProxy? _proxy; /// Флаг, указывает, открыто ли соединение. - public bool IsConnected => _socketClient.State == WebSocketState.Open; + public bool IsConnected => _socketClient?.State == WebSocketState.Open; /// Событие получения сообщения. public event EventHandler? OnReceive; @@ -25,19 +27,25 @@ public class YnisonWebSocket : IDisposable /// Аргументы события получения данных. public class ReceiveEventArgs : EventArgs { - /// Полученные данные (JSON-строка). public string Data { get; init; } = null!; } /// Аргументы события закрытия соединения. public class CloseEventArgs : EventArgs { - /// Статус закрытия. public WebSocketCloseStatus? Status { get; init; } - /// Описание причины закрытия. public string? Description { get; init; } } + /// + /// Инициализирует новый экземпляр WebSocket-клиента. + /// + /// Прокси-сервер (опционально). + public YnisonWebSocket(IWebProxy? proxy = null) + { + _proxy = proxy; + } + private static string GetProtocolData(string deviceId, string? redirectTicket) { var deviceInfo = new Dictionary @@ -57,6 +65,9 @@ public class YnisonWebSocket : IDisposable private async Task ReadSocketContentAsync() { + if (_socketClient == null) + throw new InvalidOperationException("WebSocket не инициализирован"); + var buffer = new byte[BufferSize]; WebSocketReceiveResult result; do @@ -68,17 +79,19 @@ public class YnisonWebSocket : IDisposable } /// Подключается к WebSocket. - /// Хранилище авторизации. + /// Хранилище авторизации (для токена и deviceId). /// URL WebSocket. /// Тикет перенаправления (опционально). public async Task ConnectAsync(AuthStorage storage, string url, string? redirectTicket = null) { + _socketClient = new ClientWebSocket(); _socketClient.Options.AddSubProtocol("Bearer"); var protocolData = GetProtocolData(storage.DeviceId, redirectTicket); _socketClient.Options.SetRequestHeader("Sec-WebSocket-Protocol", $"Bearer, v2, {protocolData}"); _socketClient.Options.SetRequestHeader("Origin", "https://music.yandex.ru"); _socketClient.Options.SetRequestHeader("Authorization", $"OAuth {storage.Token}"); - _socketClient.Options.Proxy = storage.Context.WebProxy; + if (_proxy != null) + _socketClient.Options.Proxy = _proxy; _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; @@ -89,7 +102,7 @@ public class YnisonWebSocket : IDisposable /// Начинает асинхронный приём сообщений. public async Task BeginReceiveAsync() { - if (_socketClient.State != WebSocketState.Open) + if (_socketClient == null || _socketClient.State != WebSocketState.Open) return; try @@ -116,9 +129,11 @@ public class YnisonWebSocket : IDisposable } /// Отправляет JSON-сообщение. - /// JSON-строка. public async ValueTask SendAsync(string json) { + if (_socketClient == null) + throw new InvalidOperationException("WebSocket не инициализирован"); + var bytes = Encoding.UTF8.GetBytes(json); await _socketClient.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, _cancellationToken); } @@ -127,16 +142,14 @@ public class YnisonWebSocket : IDisposable public async Task StopReceiveAsync() { if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested) - { await _cancellationTokenSource.CancelAsync(); - } } /// Освобождает ресурсы. public void Dispose() { _cancellationTokenSource?.Dispose(); - _socketClient.Dispose(); + _socketClient?.Dispose(); GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/YandexMusic.API/Extensions/API/YAlbumExtensionsAsync.cs b/YandexMusic.API/Extensions/API/YAlbumExtensionsAsync.cs index 0007837..ddcf19d 100644 --- a/YandexMusic.API/Extensions/API/YAlbumExtensionsAsync.cs +++ b/YandexMusic.API/Extensions/API/YAlbumExtensionsAsync.cs @@ -3,27 +3,31 @@ using YandexMusic.API.Models.Album; namespace YandexMusic.API.Extensions.API; /// -/// Методы-расширения для альбома +/// Методы-расширения для альбома. /// -public static partial class YAlbumExtensions +public static class YAlbumExtensions { + /// + /// Получает полную информацию об альбоме вместе с треками (если ещё не загружены). + /// public static async Task WithTracksAsync(this YAlbum album) { - return album.Volumes != null - ? album - : (await album.Context.API.Album.GetAsync(album.Context.Storage, album.Id)) - .Result; + if (album.Volumes != null) + return album; + + var result = await album.Context.API.Album.GetAsync(album.Id); + return result ?? album; } - public static async Task AddLikeAsync(this YAlbum album) - { - return (await album.Context.API.Library.AddAlbumLikeAsync(album.Context.Storage, album)) - .Result; - } + /// + /// Добавляет альбом в список лайкнутых. + /// + public static async Task AddLikeAsync(this YAlbum album) + => await album.Context.API.Library.AddAlbumLikeAsync(album); - public static async Task RemoveLikeAsync(this YAlbum album) - { - return (await album.Context.API.Library.RemoveAlbumLikeAsync(album.Context.Storage, album)) - .Result; - } -} + /// + /// Удаляет альбом из списка лайкнутых. + /// + public static async Task RemoveLikeAsync(this YAlbum album) + => await album.Context.API.Library.RemoveAlbumLikeAsync(album); +} \ No newline at end of file diff --git a/YandexMusic.API/Extensions/API/YArtistExtensionsAsync.cs b/YandexMusic.API/Extensions/API/YArtistExtensionsAsync.cs index 0d99bd2..537e32f 100644 --- a/YandexMusic.API/Extensions/API/YArtistExtensionsAsync.cs +++ b/YandexMusic.API/Extensions/API/YArtistExtensionsAsync.cs @@ -4,37 +4,37 @@ using YandexMusic.API.Models.Track; namespace YandexMusic.API.Extensions.API; /// -/// Методы-расширения для исполнителя +/// Методы-расширения для исполнителя. /// -public static partial class YArtistExtensions +public static class YArtistExtensions { - public static async Task BriefInfoAsync(this YArtist artist) - { - return (await artist.Context.API.Artist.GetAsync(artist.Context.Storage, artist.Id)) - .Result; - } + /// + /// Получает расширенную информацию об исполнителе. + /// + public static async Task BriefInfoAsync(this YArtist artist) + => await artist.Context.API.Artist.GetAsync(artist.Id); - public static async Task 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 GetTracksAsync(this YArtist artist, int page = 0, int pageSize = 20) + => await artist.Context.API.Artist.GetTracksAsync(artist.Id, page, pageSize); - public static async Task> GetAllTracksAsync(this YArtist artist) - { - return (await artist.Context.API.Artist.GetAllTracksAsync(artist.Context.Storage, artist.Id)) - .Result.Tracks; - } + /// + /// Получает все треки исполнителя. + /// + public static async Task?> GetAllTracksAsync(this YArtist artist) + => (await artist.Context.API.Artist.GetAllTracksAsync(artist.Id))?.Tracks; - public static async Task AddLikeAsync(this YArtist artist) - { - return (await artist.Context.API.Library.AddArtistLikeAsync(artist.Context.Storage, artist)) - .Result; - } + /// + /// Добавляет исполнителя в список лайкнутых. + /// + public static async Task AddLikeAsync(this YArtist artist) + => await artist.Context.API.Library.AddArtistLikeAsync(artist); - public static async Task RemoveLikeAsync(this YArtist artist) - { - return (await artist.Context.API.Library.RemoveArtistLikeAsync(artist.Context.Storage, artist)) - .Result; - } -} + /// + /// Удаляет исполнителя из списка лайкнутых. + /// + public static async Task RemoveLikeAsync(this YArtist artist) + => await artist.Context.API.Library.RemoveArtistLikeAsync(artist); +} \ No newline at end of file diff --git a/YandexMusic.API/Extensions/API/YPlaylistExtensionsAsync.cs b/YandexMusic.API/Extensions/API/YPlaylistExtensionsAsync.cs index c42566b..7821e1d 100644 --- a/YandexMusic.API/Extensions/API/YPlaylistExtensionsAsync.cs +++ b/YandexMusic.API/Extensions/API/YPlaylistExtensionsAsync.cs @@ -4,73 +4,66 @@ using YandexMusic.API.Models.Track; namespace YandexMusic.API.Extensions.API; /// -/// Методы-расширения для плейлиста +/// Методы-расширения для плейлиста. /// -public static partial class YPlaylistExtensions +public static class YPlaylistExtensions { - private static bool CheckUser(YPlaylist playlist) + private static bool IsOwner(YPlaylist playlist) + => playlist.Owner.Uid == playlist.Context.Storage.User.Uid; + + /// + /// Получает полную информацию о плейлисте вместе с треками. + /// + public static async Task WithTracksAsync(this YPlaylist playlist) { - return playlist.Owner.Uid == playlist.Context.Storage.User.Uid; + if (playlist.Tracks != null) + return playlist; + return await playlist.Context.API.Playlist.GetAsync(playlist); } - public static async Task WithTracksAsync(this YPlaylist playlist) - { - return playlist.Tracks != null - ? playlist - : (await playlist.Context.API.Playlist.GetAsync(playlist.Context.Storage, playlist)) - .Result; - } + /// + /// Добавляет плейлист в список лайкнутых. + /// + public static async Task AddLikeAsync(this YPlaylist playlist) + => await playlist.Context.API.Library.AddPlaylistLikeAsync(playlist); - public static async Task AddLikeAsync(this YPlaylist playlist) - { - return (await playlist.Context.API.Library.AddPlaylistLikeAsync(playlist.Context.Storage, playlist)) - .Result; - } + /// + /// Удаляет плейлист из списка лайкнутых. + /// + public static async Task RemoveLikeAsync(this YPlaylist playlist) + => await playlist.Context.API.Library.RemovePlaylistLikeAsync(playlist); - public static async Task RemoveLikeAsync(this YPlaylist playlist) - { - return (await playlist.Context.API.Library.RemovePlaylistLikeAsync(playlist.Context.Storage, playlist)) - .Result; - } - - public static async Task 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 RenameAsync(this YPlaylist playlist, string newName) + => IsOwner(playlist) ? await playlist.Context.API.Playlist.RenameAsync(playlist, newName) : playlist; + /// + /// Удаляет плейлист (только для владельца). + /// public static async Task DeleteAsync(this YPlaylist playlist) + => IsOwner(playlist) && await playlist.Context.API.Playlist.DeleteAsync(playlist); + + /// + /// Вставляет треки в начало плейлиста (только для владельца). + /// + public static async Task InsertTracksAsync(this YPlaylist playlist, params YTrack[] tracks) + => IsOwner(playlist) ? await playlist.Context.API.Playlist.InsertTracksAsync(playlist, tracks) : playlist; + + /// + /// Удаляет треки из плейлиста (только для владельца). + /// + public static async Task RemoveTracksAsync(this YPlaylist playlist, params YTrack[] tracks) + => IsOwner(playlist) ? await playlist.Context.API.Playlist.DeleteTracksAsync(playlist, tracks) : playlist; + + /// + /// Загружает трек в плейлист (только для владельца). + /// + public static async Task UploadTrackAsync(this YPlaylist playlist, string filePath, string fileName) { - return CheckUser(playlist) && await playlist.Context.API.Playlist.DeleteAsync(playlist.Context.Storage, playlist); + if (!IsOwner(playlist)) return false; + var result = await playlist.Context.API.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, filePath); + return result == "CREATED"; } - - public static async Task 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 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 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"; - } -} +} \ No newline at end of file diff --git a/YandexMusic.API/Extensions/API/YStationResultExtensionsAsync.cs b/YandexMusic.API/Extensions/API/YStationResultExtensionsAsync.cs index 9ac2a48..0c40ae3 100644 --- a/YandexMusic.API/Extensions/API/YStationResultExtensionsAsync.cs +++ b/YandexMusic.API/Extensions/API/YStationResultExtensionsAsync.cs @@ -4,24 +4,25 @@ using YandexMusic.API.Models.Track; namespace YandexMusic.API.Extensions.API; /// -/// Методы-расширения для радиостанции +/// Методы-расширения для радиостанции. /// -public static partial class YStationResultExtensions +public static class YStationResultExtensions { - public static async Task> GetTracksAsync(this YStation station, string prevTrackId = "") - { - return (await station.Context.API.Radio.GetStationTracksAsync(station.Context.Storage, station, prevTrackId)) - .Result.Sequence; - } + /// + /// Получает список треков для радиостанции. + /// + public static async Task?> GetTracksAsync(this YStation station, string prevTrackId = "") + => (await station.Context.API.Radio.GetStationTracksAsync(station, prevTrackId))?.Sequence; - public static async Task SetSettings2Async(this YStation station, YStationSettings2 settings) - { - return (await station.Context.API.Radio.SetStationSettings2Async(station.Context.Storage, station, settings)) - .Result; - } + /// + /// Устанавливает настройки станции. + /// + public static async Task SetSettings2Async(this YStation station, YStationSettings2 settings) + => await station.Context.API.Radio.SetStationSettings2Async(station, settings); - public static Task 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); - } -} + /// + /// Отправляет обратную связь о прослушивании. + /// + public static Task SendFeedbackAsync(this YStation station, YStationFeedbackType type, YTrack? track = null, string batchId = "", double totalPlayedSeconds = 0) + => station.Context.API.Radio.SendStationFeedbackAsync(station, type, track, batchId, totalPlayedSeconds); +} \ No newline at end of file diff --git a/YandexMusic.API/Extensions/API/YTrackExtensionsAsync.cs b/YandexMusic.API/Extensions/API/YTrackExtensionsAsync.cs index e1168c3..5c2dfd8 100644 --- a/YandexMusic.API/Extensions/API/YTrackExtensionsAsync.cs +++ b/YandexMusic.API/Extensions/API/YTrackExtensionsAsync.cs @@ -3,58 +3,61 @@ using YandexMusic.API.Models.Track; namespace YandexMusic.API.Extensions.API; /// -/// Методы-расширения для трека +/// Методы-расширения для трека. /// -public static partial class YTrackExtensions +public static class YTrackExtensions { - public static Task GetLinkAsync(this YTrack track) - { - return track.Context.API.Track.GetFileLinkAsync(track.Context.Storage, track); - } + /// + /// Получает прямую ссылку на скачивание трека. + /// + public static Task GetLinkAsync(this YTrack track) + => track.Context.API.Track.GetFileLinkAsync(track); + /// + /// Сохраняет трек в файл. + /// public static Task SaveAsync(this YTrack track, string filePath) - { - return track.Context.API.Track.ExtractToFileAsync(track.Context.Storage, track, filePath); - } + => track.Context.API.Track.ExtractToFileAsync(track, filePath); - public static async Task AddLikeAsync(this YTrack track) - { - return (await track.Context.API.Library.AddTrackLikeAsync(track.Context.Storage, track)) - .Result.Revision; - } + /// + /// Добавляет трек в список лайкнутых. + /// + public static async Task AddLikeAsync(this YTrack track) + => await track.Context.API.Library.AddTrackLikeAsync(track); - public static async Task RemoveLikeAsync(this YTrack track) - { - return (await track.Context.API.Library.RemoveTrackLikeAsync(track.Context.Storage, track)) - .Result.Revision; - } + /// + /// Удаляет трек из списка лайкнутых. + /// + public static async Task RemoveLikeAsync(this YTrack track) + => await track.Context.API.Library.RemoveTrackLikeAsync(track); - public static async Task AddDislikeAsync(this YTrack track) - { - return (await track.Context.API.Library.AddTrackDislikeAsync(track.Context.Storage, track)) - .Result.Revision; - } + /// + /// Добавляет трек в список дизлайкнутых. + /// + public static async Task AddDislikeAsync(this YTrack track) + => await track.Context.API.Library.AddTrackDislikeAsync(track); - public static async Task RemoveDislikeAsync(this YTrack track) - { - return (await track.Context.API.Library.RemoveTrackDislikeAsync(track.Context.Storage, track)) - ?.Result.Revision ?? -1; - } + /// + /// Удаляет трек из списка дизлайкнутых. + /// + public static async Task RemoveDislikeAsync(this YTrack track) + => await track.Context.API.Library.RemoveTrackDislikeAsync(track); - public static Task 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 Task SendPlayTrackInfoAsync(this YTrack track, string from, bool fromCache = false, string playId = "", string playlistId = "", double totalPlayedSeconds = 0, double endPositionSeconds = 0) + => track.Context.API.Track.SendPlayTrackInfoAsync(track, from, fromCache, playId, playlistId, totalPlayedSeconds, endPositionSeconds); - public static async Task SupplementAsync(this YTrack track) - { - return (await track.Context.API.Track.GetSupplementAsync(track.Context.Storage, track)) - .Result; - } + /// + /// Получает дополнительную информацию о треке. + /// + public static async Task SupplementAsync(this YTrack track) + => await track.Context.API.Track.GetSupplementAsync(track); - public static async Task SimilarAsync(this YTrack track) - { - return (await track.Context.API.Track.GetSimilarAsync(track.Context.Storage, track)) - .Result; - } -} + /// + /// Получает похожие треки. + /// + public static async Task SimilarAsync(this YTrack track) + => await track.Context.API.Track.GetSimilarAsync(track); +} \ No newline at end of file diff --git a/YandexMusic.API/Extensions/HttpRequestHeaderExtensions.cs b/YandexMusic.API/Extensions/HttpRequestHeaderExtensions.cs deleted file mode 100644 index 18b647a..0000000 --- a/YandexMusic.API/Extensions/HttpRequestHeaderExtensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Net; - -namespace YandexMusic.API.Extensions; - -public static class HttpRequestHeaderExtensions -{ - public static string GetName(this HttpRequestHeader header) - { - return header.ToString().SplitByCapitalLetter("-"); - } -} \ No newline at end of file diff --git a/YandexMusic.API/Extensions/StringExtensions.cs b/YandexMusic.API/Extensions/StringExtensions.cs deleted file mode 100644 index 7bb85dc..0000000 --- a/YandexMusic.API/Extensions/StringExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -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() - .Select(m => m.ToString())); - } - - /// - /// Проверяет соответствие регулярному выражению - /// - public static bool IsMatch(this string str, string pattern, RegexOptions options) - { - return Regex.IsMatch(str, pattern, options); - } - - /// - /// Проверяет соответствие регулярному выражению - /// - public static bool IsMatch(this string str, string pattern) - { - return IsMatch(str, pattern, RegexOptions.IgnoreCase); - } - - /// - /// Возвращает совпадения для регулярного выражения - /// - public static string[] GetMatches(this string str, string pattern, RegexOptions options = RegexOptions.IgnoreCase) - { - return str.IsMatch(pattern, options) - ? Regex.Matches(str, pattern, options) - .Cast() - .Select(m => m.Value) - .ToArray() - : new string[] { }; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Models/Account/YAuthEmpty.cs b/YandexMusic.API/Models/Account/YAuthEmpty.cs new file mode 100644 index 0000000..0df55ba --- /dev/null +++ b/YandexMusic.API/Models/Account/YAuthEmpty.cs @@ -0,0 +1,5 @@ +namespace YandexMusic.API.Models.Account; + +public class YAuthEmpty +{ +} \ No newline at end of file diff --git a/YandexMusic.API/Models/Account/YAuthQr.cs b/YandexMusic.API/Models/Account/YAuthQr.cs index ea6b533..a728bb7 100644 --- a/YandexMusic.API/Models/Account/YAuthQr.cs +++ b/YandexMusic.API/Models/Account/YAuthQr.cs @@ -9,4 +9,4 @@ public class YAuthQR : YAuthBase [JsonPropertyName("csrf_token")] public string CsrfToken { get; set; } -} \ No newline at end of file +} diff --git a/YandexMusic.API/Models/Account/YAuthToken.cs b/YandexMusic.API/Models/Account/YAuthToken.cs index 6ccee11..b59465f 100644 --- a/YandexMusic.API/Models/Account/YAuthToken.cs +++ b/YandexMusic.API/Models/Account/YAuthToken.cs @@ -9,4 +9,9 @@ public class YAuthToken [JsonPropertyName("track_id")] public string TrackId { get; set; } + + [JsonPropertyName("process_uuid")] + public string ProcessUuid { get; set; } + + public Dictionary Cookie { get; set; } = new(); } \ No newline at end of file diff --git a/YandexMusic.API/Models/Common/YExecutionContext.cs b/YandexMusic.API/Models/Common/YExecutionContext.cs index ef76ed6..32d32ed 100644 --- a/YandexMusic.API/Models/Common/YExecutionContext.cs +++ b/YandexMusic.API/Models/Common/YExecutionContext.cs @@ -2,11 +2,15 @@ using YandexMusic.API.Common; namespace YandexMusic.API.Models.Common; -/// Контекст выполнения, содержащий ссылки на API и хранилище. +/// +/// Контекст выполнения, содержащий ссылки на API и хранилище. +/// Используется в моделях для вызова методов расширения. +/// public class YExecutionContext { /// Экземпляр основного API. public YandexMusicApi API { get; internal set; } = null!; + /// Хранилище данных авторизации. public AuthStorage Storage { get; internal set; } = null!; } \ No newline at end of file diff --git a/YandexMusic.API/Models/Common/YExecutionContextConverter.cs b/YandexMusic.API/Models/Common/YExecutionContextConverter.cs index 8d5834c..eddf76a 100644 --- a/YandexMusic.API/Models/Common/YExecutionContextConverter.cs +++ b/YandexMusic.API/Models/Common/YExecutionContextConverter.cs @@ -1,24 +1,34 @@ using System.Text.Json; using System.Text.Json.Serialization; using YandexMusic.API.Common; +using YandexMusic.API.Models.Common; -namespace YandexMusic.API.Models.Common; +namespace YandexMusic.API.Converters; -/// Конвертер для внедрения контекста выполнения (API и хранилище) в модели. +/// +/// Конвертер для внедрения контекста выполнения (API и хранилище) в модели, наследуемые от YBaseModel. +/// public class YExecutionContextConverter : JsonConverter { private readonly YandexMusicApi _api; private readonly AuthStorage _storage; + /// + /// Инициализирует новый экземпляр конвертера. + /// + /// Экземпляр основного API. + /// Хранилище авторизации. public YExecutionContextConverter(YandexMusicApi api, AuthStorage storage) { _api = api; _storage = storage; } + /// public override bool CanConvert(Type typeToConvert) => typeof(YBaseModel).IsAssignableFrom(typeToConvert); + /// public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // Убираем этот конвертер из опций, чтобы избежать рекурсии @@ -33,6 +43,7 @@ public class YExecutionContextConverter : JsonConverter return obj; } + /// public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { var innerOptions = new JsonSerializerOptions(options); diff --git a/YandexMusic.API/Requests/Account/YGetAuthAppPasswordBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthAppPasswordBuilder.cs index db263c8..5e86b57 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthAppPasswordBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthAppPasswordBuilder.cs @@ -1,26 +1,19 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Post, "registration-validations/auth/multi_step/commit_password")] -internal class YGetAuthAppPasswordBuilder : YRequestBuilder +public class YGetAuthAppPasswordBuilder : YAuthRequestBuilder { - public YGetAuthAppPasswordBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(string tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { "csrf_token", storage.AuthToken.CsfrToken }, - { "track_id", storage.AuthToken.TrackId }, - { "password", tuple }, + public YGetAuthAppPasswordBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "registration-validations/auth/multi_step/commit_password"; + protected override HttpContent? GetContent(string password) + => new FormUrlEncodedContent(new Dictionary + { + { "csrf_token", Api.Storage.AuthToken.CsfrToken }, + { "track_id", Api.Storage.AuthToken.TrackId }, + { "password", password }, { "retpath", "https://passport.yandex.ru/am/finish?status=ok&from=Login" } }); - } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthCaptchaBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthCaptchaBuilder.cs index 2e6f111..393a2e5 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthCaptchaBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthCaptchaBuilder.cs @@ -1,29 +1,13 @@ using System.Net; -using System.Net.Http.Headers; - -using YandexMusic.API.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; +using YandexMusic.API.Models.Account; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Post, "registration-validations/textcaptcha")] -internal class YGetAuthCaptchaBuilder : YRequestBuilder +public class YGetAuthCaptchaBuilder : YAuthRequestBuilder { - public YGetAuthCaptchaBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(string tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { "csrf_token", storage.AuthToken.CsfrToken }, - { "track_id", storage.AuthToken.TrackId }, - }); - } - - protected override void SetCustomHeaders(HttpRequestHeaders headers) - { - headers.Add("X-Requested-With", "XMLHttpRequest"); - } + public YGetAuthCaptchaBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "registration-validations/textcaptcha"; + protected override HttpContent? GetContent(object _) + => new FormUrlEncodedContent(new Dictionary { { "csrf_token", Api.Storage.AuthToken.CsfrToken }, { "track_id", Api.Storage.AuthToken.TrackId } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthCookiesBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthCookiesBuilder.cs index 8b66c39..5c014d5 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthCookiesBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthCookiesBuilder.cs @@ -1,36 +1,14 @@ using System.Net; -using System.Net.Http.Headers; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YMobileProxyRequest(WebRequestMethods.Http.Post, "1/bundle/oauth/token_by_sessionid")] -internal class YGetAuthCookiesBuilder : YRequestBuilder +public class YGetAuthCookiesBuilder : YAuthRequestBuilder { - public YGetAuthCookiesBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override void SetCustomHeaders(HttpRequestHeaders headers) - { - CookieCollection cookieCollection = new() { - storage.Context.Cookies.GetCookies(new Uri("https://yandex.ru/")), - storage.Context.Cookies.GetCookies(new Uri("https://passport.yandex.ru/")) - }; - - headers.Add("Ya-Client-Cookie", string.Join(";", cookieCollection.Select(c => $"{c.Name}={c.Value}"))); - headers.Add("Ya-Client-Host", "passport.yandex.ru"); - } - - protected override HttpContent GetContent(string tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { "client_id", YConstants.XClientId }, - { "client_secret", YConstants.XClientSecret } - }); - } + public YGetAuthCookiesBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "1/bundle/oauth/token_by_sessionid"; + protected override HttpContent? GetContent(object _) + => new FormUrlEncodedContent(new Dictionary { { "client_id", YConstants.XClientId }, { "client_secret", YConstants.XClientSecret } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthInfoBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthInfoBuilder.cs index 69b5cf6..9aa2b01 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthInfoBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthInfoBuilder.cs @@ -1,17 +1,11 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YApiRequest(WebRequestMethods.Http.Get, "account/status")] -public class YGetAuthInfoBuilder : YRequestBuilder, object> +public class YGetAuthInfoBuilder : YMusicRequestBuilder { - public YGetAuthInfoBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YGetAuthInfoBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "account/status"; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthLetterBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthLetterBuilder.cs index 7e50ca0..88c9b1a 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthLetterBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthLetterBuilder.cs @@ -1,29 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; +using YandexMusic.API.Models.Account; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Post, "registration-validations/auth/send_magic_letter")] -internal class YGetAuthLetterBuilder : YRequestBuilder +public class YGetAuthLetterBuilder : YAuthRequestBuilder { - public YGetAuthLetterBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(string tuple) - { - if (storage.AuthToken == null) - { - throw new Exception("Не найдена сессия входа."); - } - - return new FormUrlEncodedContent(new Dictionary - { - { "csrf_token", storage.AuthToken.CsfrToken }, - { "track_id", storage.AuthToken.TrackId }, - }); - } + public YGetAuthLetterBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "registration-validations/auth/send_magic_letter"; + protected override HttpContent? GetContent(object _) + => new FormUrlEncodedContent(new Dictionary { { "csrf_token", Api.Storage.AuthToken.CsfrToken }, { "track_id", Api.Storage.AuthToken.TrackId } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthLoginCaptchaBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthLoginCaptchaBuilder.cs index 6536013..00a5605 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthLoginCaptchaBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthLoginCaptchaBuilder.cs @@ -1,31 +1,13 @@ using System.Net; -using System.Net.Http.Headers; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Post, "registration-validations/checkHuman")] -internal class YGetAuthLoginCaptchaBuilder : YRequestBuilder +public class YGetAuthLoginCaptchaBuilder : YAuthRequestBuilder { - public YGetAuthLoginCaptchaBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(string tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { "csrf_token", storage.AuthToken.CsfrToken }, - { "track_id", storage.AuthToken.TrackId }, - { "answer", tuple } - }); - } - - protected override void SetCustomHeaders(HttpRequestHeaders headers) - { - headers.Add("X-Requested-With", "XMLHttpRequest"); - } + public YGetAuthLoginCaptchaBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "registration-validations/checkHuman"; + protected override HttpContent? GetContent(string captchaAnswer) + => new FormUrlEncodedContent(new Dictionary { { "csrf_token", Api.Storage.AuthToken.CsfrToken }, { "track_id", Api.Storage.AuthToken.TrackId }, { "answer", captchaAnswer } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthLoginLetterBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthLoginLetterBuilder.cs index 17866b2..052d8a6 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthLoginLetterBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthLoginLetterBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Post, "auth/letter/status/")] -internal class YGetAuthLoginLetterBuilder : YRequestBuilder +public class YGetAuthLoginLetterBuilder : YAuthRequestBuilder { - public YGetAuthLoginLetterBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(string tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { "csrf_token", storage.AuthToken.CsfrToken }, - { "track_id", storage.AuthToken.TrackId }, - }); - } + public YGetAuthLoginLetterBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "auth/letter/status/"; + protected override HttpContent? GetContent(object _) + => new FormUrlEncodedContent(new Dictionary { { "csrf_token", Api.Storage.AuthToken.CsfrToken }, { "track_id", Api.Storage.AuthToken.TrackId } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthLoginQRBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthLoginQRBuilder.cs index 2d038f2..009ed22 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthLoginQRBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthLoginQRBuilder.cs @@ -1,24 +1,23 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Post, "auth/new/magic/status/")] -internal class YGetAuthLoginQRBuilder : YRequestBuilder +internal class YGetAuthLoginQRBuilder : YAuthRequestBuilder { - public YGetAuthLoginQRBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) + public YGetAuthLoginQRBuilder(YandexMusicApi yandex) : base(yandex) { } + protected override string Method => WebRequestMethods.Http.Post; + + protected override string PathTemplate => "auth/new/magic/status/"; + protected override HttpContent GetContent(string tuple) { return new FormUrlEncodedContent(new Dictionary { - { "csrf_token", storage.AuthToken.CsfrToken }, - { "track_id", storage.AuthToken.TrackId } + { "csrf_token", Api.Storage.AuthToken.CsfrToken }, + { "track_id", Api.Storage.AuthToken.TrackId } }); } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthLoginUserBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthLoginUserBuilder.cs index 658ce4a..ec19b8f 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthLoginUserBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthLoginUserBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Post, "registration-validations/auth/multi_step/start")] -internal class YGetAuthLoginUserBuilder : YRequestBuilder +public class YGetAuthLoginUserBuilder : YAuthRequestBuilder { - public YGetAuthLoginUserBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent((string token, string login) tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { "csrf_token", tuple.token }, - { "login", tuple.login } - }); - } + public YGetAuthLoginUserBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "registration-validations/auth/multi_step/start"; + protected override HttpContent? GetContent((string token, string login) tuple) + => new FormUrlEncodedContent(new Dictionary { { "csrf_token", tuple.token }, { "login", tuple.login } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthMethodsBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthMethodsBuilder.cs index 72dfd78..a4d83a1 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthMethodsBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthMethodsBuilder.cs @@ -1,23 +1,13 @@ -using System.Collections.Specialized; -using System.Net; - -using YandexMusic.API.Common; +using System.Net; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Get, "am")] -internal class YGetAuthMethodsBuilder : YRequestBuilder +public class YGetAuthMethodsBuilder : YRequestBuilder { - public YGetAuthMethodsBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YGetAuthMethodsBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "am"; - protected override NameValueCollection GetQueryParams(string tuple) - { - return new NameValueCollection { - { "app_platform", "android" } - }; - } + protected override string BaseUrl => YConstants.Endpoints.PassportUrl; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetAuthQRBuilder.cs b/YandexMusic.API/Requests/Account/YGetAuthQRBuilder.cs index d81cc28..ab5fa65 100644 --- a/YandexMusic.API/Requests/Account/YGetAuthQRBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetAuthQRBuilder.cs @@ -1,25 +1,19 @@ using System.Net; - -using YandexMusic.API.Common; +using System.Net.Http.Headers; using YandexMusic.API.Models.Account; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YPassportRequest(WebRequestMethods.Http.Post, "registration-validations/auth/password/submit")] -internal class YGetAuthQRBuilder : YRequestBuilder +public class YGetAuthQRBuilder : YAuthRequestBuilder { - public YGetAuthQRBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) + public YGetAuthQRBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "pwl-yandex/api/passport/auth/password/submit"; + protected override HttpContent? GetContent(object _) + => new FormUrlEncodedContent(new Dictionary { { "retpath", "" } }); + protected override void SetCustomHeaders(HttpRequestHeaders headers) { - } - - protected override HttpContent GetContent(string tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { "csrf_token", storage.AuthToken.CsfrToken }, - { "retpath", "https://passport.yandex.ru/profile" }, - { "with_code", "1" }, - }); + headers.Add("X-Csrf-Token", Api.Storage.AuthToken.CsfrToken); + headers.Add("Process-Uuid", Api.Storage.AuthToken.ProcessUuid); } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetLoginInfoBuilder.cs b/YandexMusic.API/Requests/Account/YGetLoginInfoBuilder.cs index e637c6a..a71cc28 100644 --- a/YandexMusic.API/Requests/Account/YGetLoginInfoBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetLoginInfoBuilder.cs @@ -1,16 +1,12 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YLoginRequest(WebRequestMethods.Http.Get, "info")] -public class YGetLoginInfoBuilder : YRequestBuilder +public class YGetLoginInfoBuilder : YAuthRequestBuilder { - public YGetLoginInfoBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YGetLoginInfoBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "info"; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetMusicTokenBuilder.cs b/YandexMusic.API/Requests/Account/YGetMusicTokenBuilder.cs index 1c52dd6..50c61e5 100644 --- a/YandexMusic.API/Requests/Account/YGetMusicTokenBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetMusicTokenBuilder.cs @@ -1,35 +1,25 @@ using System.Net; using System.Net.Http.Headers; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YOAuthMobile(WebRequestMethods.Http.Post, "/1/token")] -internal class YGetMusicTokenBuilder : YRequestBuilder +public class YGetMusicTokenBuilder : YAuthRequestBuilder { - public YGetMusicTokenBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(string tuple) - { - return new FormUrlEncodedContent(new Dictionary + public YGetMusicTokenBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "/1/token"; + protected override HttpContent? GetContent(object _) + => new FormUrlEncodedContent(new Dictionary { { "client_id", YConstants.ClientId }, { "client_secret", YConstants.ClientSecret }, { "grant_type", "x-token" }, - { "access_token", storage.AccessToken.AccessToken } + { "access_token", Api.Storage.AccessToken.AccessToken } }); - } - protected override void SetCustomHeaders(HttpRequestHeaders headers) { headers.Remove("Authorization"); - - base.SetCustomHeaders(headers); } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YGetShortAccountInifoBuilder.cs b/YandexMusic.API/Requests/Account/YGetShortAccountInifoBuilder.cs index 13ee364..95956ed 100644 --- a/YandexMusic.API/Requests/Account/YGetShortAccountInifoBuilder.cs +++ b/YandexMusic.API/Requests/Account/YGetShortAccountInifoBuilder.cs @@ -1,30 +1,19 @@ using System.Collections.Specialized; using System.Net; using System.Net.Http.Headers; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Account; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Account; -[YMobileProxyRequest(WebRequestMethods.Http.Get, "/1/bundle/account/short_info/")] -internal class YGetShortAccountInifoBuilder : YRequestBuilder +public class YGetShortAccountInfoBuilder : YAuthRequestBuilder { - public YGetShortAccountInifoBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override NameValueCollection GetQueryParams(object tuple) - { - return new NameValueCollection { - { "avatar_size", "islands-300" } - }; - } - + public YGetShortAccountInfoBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "1/bundle/account/short_info/"; + protected override NameValueCollection GetQueryParams(object _) + => new() { { "avatar_size", "islands-300" } }; protected override void SetCustomHeaders(HttpRequestHeaders headers) { - headers.Add("Ya-Consumer-Authorization", $"OAuth {storage.AccessToken.AccessToken}"); + headers.Add("Ya-Consumer-Authorization", $"OAuth {Api.Storage.AccessToken.AccessToken}"); } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Account/YPostAuthStats.cs b/YandexMusic.API/Requests/Account/YPostAuthStats.cs new file mode 100644 index 0000000..e2cf8de --- /dev/null +++ b/YandexMusic.API/Requests/Account/YPostAuthStats.cs @@ -0,0 +1,19 @@ +using System.Net; +using System.Net.Http.Headers; +using YandexMusic.API.Models.Account; + +namespace YandexMusic.API.Requests.Account; + +public class YPostAuthStats : YAuthRequestBuilder +{ + public YPostAuthStats(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "pwl-yandex/api/passport/stats"; + protected override HttpContent? GetContent(object _) + => new FormUrlEncodedContent(new Dictionary { { "messageType", "CLIENT_READY" } }); + protected override void SetCustomHeaders(HttpRequestHeaders headers) + { + headers.Add("X-Csrf-Token", Api.Storage.AuthToken.CsfrToken); + headers.Add("Process-Uuid", Api.Storage.AuthToken.ProcessUuid); + } +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Album/YGetAlbumBuilder.cs b/YandexMusic.API/Requests/Album/YGetAlbumBuilder.cs index 7fa6695..37c7def 100644 --- a/YandexMusic.API/Requests/Album/YGetAlbumBuilder.cs +++ b/YandexMusic.API/Requests/Album/YGetAlbumBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Album; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Album; -[YApiRequest(WebRequestMethods.Http.Get, "albums/{albumId}/with-tracks")] -public class YGetAlbumBuilder : YRequestBuilder, string> +public class YGetAlbumBuilder : YMusicRequestBuilder { - public YGetAlbumBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetAlbumBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "albums/{albumId}/with-tracks"; protected override Dictionary GetSubstitutions(string albumId) - { - return new Dictionary { - { "albumId", albumId } - }; - } + => new() { { "albumId", albumId } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Album/YGetAlbumsBuilder.cs b/YandexMusic.API/Requests/Album/YGetAlbumsBuilder.cs index 04fd695..df67dd9 100644 --- a/YandexMusic.API/Requests/Album/YGetAlbumsBuilder.cs +++ b/YandexMusic.API/Requests/Album/YGetAlbumsBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Album; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Album; -[YApiRequest(WebRequestMethods.Http.Post, "albums")] -public class YGetAlbumsBuilder : YRequestBuilder>, IEnumerable> +public class YGetAlbumsBuilder : YMusicRequestBuilder?, IEnumerable> { - public YGetAlbumsBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(IEnumerable albumIds) - { - return new FormUrlEncodedContent(new Dictionary { - { "album-ids", string.Join(",", albumIds) } - }); - } + public YGetAlbumsBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "albums"; + protected override HttpContent? GetContent(IEnumerable albumIds) + => new FormUrlEncodedContent(new Dictionary { { "album-ids", string.Join(",", albumIds) } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Artist/YGetArtistBuilder.cs b/YandexMusic.API/Requests/Artist/YGetArtistBuilder.cs index a71d978..76bf032 100644 --- a/YandexMusic.API/Requests/Artist/YGetArtistBuilder.cs +++ b/YandexMusic.API/Requests/Artist/YGetArtistBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Artist; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Artist; -[YApiRequest(WebRequestMethods.Http.Get, "artists/{artistId}/brief-info")] -public class YGetArtistBuilder : YRequestBuilder, string> +public class YGetArtistBuilder : YMusicRequestBuilder { - public YGetArtistBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetArtistBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "artists/{artistId}/brief-info"; protected override Dictionary GetSubstitutions(string artistId) - { - return new Dictionary { - { "artistId", artistId } - }; - } + => new() { { "artistId", artistId } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Artist/YGetArtistTrackBuilder.cs b/YandexMusic.API/Requests/Artist/YGetArtistTrackBuilder.cs index b8049f5..c2d8ade 100644 --- a/YandexMusic.API/Requests/Artist/YGetArtistTrackBuilder.cs +++ b/YandexMusic.API/Requests/Artist/YGetArtistTrackBuilder.cs @@ -1,31 +1,16 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Artist; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Artist; -[YApiRequest(WebRequestMethods.Http.Get, "artists/{artistId}/tracks")] -public class YGetArtistTrackBuilder : YRequestBuilder, (string id, int page, int pageSize)> +public class YGetArtistTrackBuilder : YMusicRequestBuilder { - public YGetArtistTrackBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) { } - + public YGetArtistTrackBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "artists/{artistId}/tracks"; protected override Dictionary GetSubstitutions((string id, int page, int pageSize) tuple) - { - return new Dictionary { - { "artistId", tuple.id }, - }; - } - + => new() { { "artistId", tuple.id } }; protected override NameValueCollection GetQueryParams((string id, int page, int pageSize) tuple) - { - return new NameValueCollection { - { "page", tuple.page.ToString() }, - { "pageSize", tuple.pageSize.ToString() }, - }; - } + => new() { { "page", tuple.page.ToString() }, { "pageSize", tuple.pageSize.ToString() } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Artist/YGetArtistsBuilder.cs b/YandexMusic.API/Requests/Artist/YGetArtistsBuilder.cs index 7bd6c2f..aca9b31 100644 --- a/YandexMusic.API/Requests/Artist/YGetArtistsBuilder.cs +++ b/YandexMusic.API/Requests/Artist/YGetArtistsBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Artist; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Artist; -[YApiRequest(WebRequestMethods.Http.Post, "artists")] -public class YGetArtistsBuilder : YRequestBuilder>, IEnumerable> +public class YGetArtistsBuilder : YMusicRequestBuilder?, IEnumerable> { - public YGetArtistsBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(IEnumerable artistIds) - { - return new FormUrlEncodedContent(new Dictionary { - { "artist-Ids", string.Join(",", artistIds) } - }); - } + public YGetArtistsBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "artists"; + protected override HttpContent? GetContent(IEnumerable artistIds) + => new FormUrlEncodedContent(new Dictionary { { "artist-Ids", string.Join(",", artistIds) } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YApiRequestAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YApiRequestAttribute.cs deleted file mode 100644 index f7a0fd6..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YApiRequestAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -public class YApiRequestAttribute : YBasePathRequestAttribute -{ - public YApiRequestAttribute(string method, string url) : base(method, url) - { - basePath = "https://api.music.yandex.net"; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YBasePathRequestAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YBasePathRequestAttribute.cs deleted file mode 100644 index 4ce0030..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YBasePathRequestAttribute.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -/// -/// Атрибут запроса относительно базового адреса -/// -public class YBasePathRequestAttribute : YRequestAttribute -{ - #region Поля - - protected string basePath; - - #endregion Поля - - #region Свойства - public override string Url => GetFullUrl(); - - #endregion Свойства - - #region Вспомогательные функции - - private string GetFullUrl() - { - return $"{basePath.TrimEnd('/')}/{path.TrimStart('/')}"; - } - - #endregion Вспомогательные функции - - public YBasePathRequestAttribute(string method, string url) : base(method, url) - { - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YLoginRequestAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YLoginRequestAttribute.cs deleted file mode 100644 index bbf0cfe..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YLoginRequestAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -public class YLoginRequestAttribute : YBasePathRequestAttribute -{ - public YLoginRequestAttribute(string method, string url) : base(method, url) - { - basePath = "https://login.yandex.ru"; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YMobileProxyRequestAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YMobileProxyRequestAttribute.cs deleted file mode 100644 index f36f22a..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YMobileProxyRequestAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -public class YMobileProxyRequestAttribute : YBasePathRequestAttribute -{ - public YMobileProxyRequestAttribute(string method, string url) : base(method, url) - { - basePath = "https://mobileproxy.passport.yandex.net"; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YOAuthMobileAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YOAuthMobileAttribute.cs deleted file mode 100644 index ef5b13a..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YOAuthMobileAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -public class YOAuthMobileAttribute : YBasePathRequestAttribute -{ - public YOAuthMobileAttribute(string method, string url) : base(method, url) - { - basePath = "https://oauth.mobile.yandex.net"; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YOAuthRequestAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YOAuthRequestAttribute.cs deleted file mode 100644 index 3ebd9bd..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YOAuthRequestAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -public class YOAuthRequestAttribute : YBasePathRequestAttribute -{ - public YOAuthRequestAttribute(string method, string url) : base(method, url) - { - basePath = "https://oauth.yandex.ru"; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YPassportRequestAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YPassportRequestAttribute.cs deleted file mode 100644 index 83a0e2d..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YPassportRequestAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -public class YPassportRequestAttribute : YBasePathRequestAttribute -{ - public YPassportRequestAttribute(string method, string url) : base(method, url) - { - basePath = "https://passport.yandex.ru"; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YRequestAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YRequestAttribute.cs deleted file mode 100644 index 478d12a..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YRequestAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -/// -/// Атрибут запроса без привязки к базовому адресу -/// -public class YRequestAttribute : Attribute -{ - #region Поля - - protected string path; - - #endregion Поля - - #region Свойства - - public string Method { get; } - public virtual string Url => path; - - #endregion Свойства - - public YRequestAttribute(string method, string url) - { - Method = method; - path = url; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/Attributes/YWebApiRequestAttribute.cs b/YandexMusic.API/Requests/Common/Attributes/YWebApiRequestAttribute.cs deleted file mode 100644 index cb52e3b..0000000 --- a/YandexMusic.API/Requests/Common/Attributes/YWebApiRequestAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YandexMusic.API.Requests.Common.Attributes; - -public class YWebApiRequestAttribute : YBasePathRequestAttribute -{ - public YWebApiRequestAttribute(string method, string url) : base(method, url) - { - basePath = "https://music.yandex.ru"; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/HttpContext.cs b/YandexMusic.API/Requests/Common/HttpContext.cs deleted file mode 100644 index 4eb0833..0000000 --- a/YandexMusic.API/Requests/Common/HttpContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Net; - -namespace YandexMusic.API.Requests.Common; - -public class HttpContext -{ - public CookieContainer Cookies; - - public HttpContext() - { - Cookies = new CookieContainer(); - } - - public IWebProxy WebProxy { get; set; } - - public long GetTimeInterval() - { - DateTime dt = TimeZoneInfo.ConvertTimeToUtc(DateTime.Now); - DateTime dt1970 = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - TimeSpan tsInterval = dt.Subtract(dt1970); - long iMilliseconds = Convert.ToInt64(tsInterval.TotalMilliseconds); - - return iMilliseconds; - } -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/YAuthRequestBuilder.cs b/YandexMusic.API/Requests/Common/YAuthRequestBuilder.cs new file mode 100644 index 0000000..734fd3e --- /dev/null +++ b/YandexMusic.API/Requests/Common/YAuthRequestBuilder.cs @@ -0,0 +1,18 @@ +using System.Net.Http.Headers; +using YandexMusic.API.Requests.Common; + +namespace YandexMusic.API.Requests; + +/// Базовый класс для запросов к Passport (passport.yandex.ru). +public abstract class YAuthRequestBuilder : YJsonRequestBuilder +{ + protected override string BaseUrl => YConstants.Endpoints.PassportUrl; + + protected YAuthRequestBuilder(YandexMusicApi api) : base(api) { } + + protected override void SetCustomHeaders(HttpRequestHeaders headers) + { + base.SetCustomHeaders(headers); + headers.Add("X-Requested-With", "XMLHttpRequest"); + } +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/YConstants.cs b/YandexMusic.API/Requests/Common/YConstants.cs index 03a88a8..ee7d2da 100644 --- a/YandexMusic.API/Requests/Common/YConstants.cs +++ b/YandexMusic.API/Requests/Common/YConstants.cs @@ -7,4 +7,10 @@ internal class YConstants public const string XClientId = "c0ebe342af7d48fbbbfcf2d2eedb8f9e"; public const string XClientSecret = "ad0a908f0aa341a182a37ecd75bc319e"; + + internal static class Endpoints + { + public const string MusicUrl = "https://api.music.yandex.net"; + public const string PassportUrl = "https://passport.yandex.ru/"; + } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/YJsonRequestBuilder.cs b/YandexMusic.API/Requests/Common/YJsonRequestBuilder.cs new file mode 100644 index 0000000..4ff6f28 --- /dev/null +++ b/YandexMusic.API/Requests/Common/YJsonRequestBuilder.cs @@ -0,0 +1,54 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using YandexMusic.API.Converters; +using YandexMusic.API.Models.Common; + +namespace YandexMusic.API.Requests.Common; + +/// +/// Строитель запросов с десериализацией JSON-ответа в TResponse. +/// +public abstract class YJsonRequestBuilder : YRequestBuilder +{ + protected YJsonRequestBuilder(YandexMusicApi api) : base(api) { } + + protected virtual async Task DeserializeAsync(HttpResponseMessage response) + { + var json = await response.Content.ReadAsStringAsync(); + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + Converters = { + new JsonStringEnumConverter(JsonNamingPolicy.KebabCaseLower), + new IntToStringConverter(), + new StringToIntConverter(), + new YExecutionContextConverter(Api, Storage), + } + }; + + if (!response.IsSuccessStatusCode) + { + var error = JsonSerializer.Deserialize(json, options); + throw error ?? new Exception($"Ошибка HTTP {response.StatusCode}: {json}"); + } + + try + { + return JsonSerializer.Deserialize(json, options); + } + catch (Exception ex) + { + throw new Exception($"Ошибка десериализации: {ex.Message}\nJSON: {json}", ex); + } + } + + /// + /// Выполняет запрос и возвращает десериализованный объект типа TResponse. + /// + public async Task ExecuteAsync(TParams parameters) + { + using var response = await ExecuteRawAsync(parameters); + return await DeserializeAsync(response); + } +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/YMusicRequestBuilder.cs b/YandexMusic.API/Requests/Common/YMusicRequestBuilder.cs new file mode 100644 index 0000000..a4c7783 --- /dev/null +++ b/YandexMusic.API/Requests/Common/YMusicRequestBuilder.cs @@ -0,0 +1,55 @@ +using System.Net.Http.Headers; +using System.Text.Json; +using System.Text.Json.Serialization; +using YandexMusic.API.Converters; +using YandexMusic.API.Models.Common; +using YandexMusic.API.Requests.Common; + +namespace YandexMusic.API.Requests; + +/// Базовый класс для запросов к API Яндекс Музыки (api.music.yandex.net). +public abstract class YMusicRequestBuilder : YJsonRequestBuilder +{ + protected override string BaseUrl => YConstants.Endpoints.MusicUrl; + + protected YMusicRequestBuilder(YandexMusicApi api) : base(api) { } + + protected override void SetCustomHeaders(HttpRequestHeaders headers) + { + base.SetCustomHeaders(headers); + headers.Add("X-Yandex-Music-Client", Storage.DeviceId); + } + + protected override async Task DeserializeAsync(HttpResponseMessage response) + { + var json = await response.Content.ReadAsStringAsync(); + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + Converters = { + new JsonStringEnumConverter(JsonNamingPolicy.KebabCaseLower), + new IntToStringConverter(), + new StringToIntConverter(), + new YExecutionContextConverter(Api, Storage), + } + }; + + if (!response.IsSuccessStatusCode) + { + var error = JsonSerializer.Deserialize(json, options); + throw error ?? new Exception($"Ошибка HTTP {response.StatusCode}: {json}"); + } + + try + { + var uResponse = JsonSerializer.Deserialize>(json, options); + if (uResponse == null) return default; + return uResponse.Result; + } + catch (Exception ex) + { + throw new Exception($"Ошибка десериализации: {ex.Message}\nJSON: {json}", ex); + } + } +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/YRequest.cs b/YandexMusic.API/Requests/Common/YRequest.cs deleted file mode 100644 index 6f79a8e..0000000 --- a/YandexMusic.API/Requests/Common/YRequest.cs +++ /dev/null @@ -1,41 +0,0 @@ -using YandexMusic.API.Common; -using YandexMusic.API.Common.Providers; - -namespace YandexMusic.API.Requests.Common; - -internal class YRequest -{ - private HttpRequestMessage msg; - private IRequestProvider provider; - - protected YandexMusicApi api; - - public YRequest(HttpRequestMessage message, YandexMusicApi yandex, AuthStorage auth) - { - msg = message; - api = yandex; - provider = auth.Provider; - } - - public async Task GetResponseAsync() - { - if (msg == null) - return default; - - HttpResponseMessage response = await provider.GetWebResponseAsync(msg); - - if (typeof(T) == typeof(HttpResponseMessage)) - return (T)(object)response; - - try - { - return await provider.GetDataFromResponseAsync(api, response); - } - finally - { - response.Dispose(); - } - } - - -} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Common/YRequestBuilder.cs b/YandexMusic.API/Requests/Common/YRequestBuilder.cs index 8e18103..b6bed61 100644 --- a/YandexMusic.API/Requests/Common/YRequestBuilder.cs +++ b/YandexMusic.API/Requests/Common/YRequestBuilder.cs @@ -1,38 +1,39 @@ using System.Collections.Specialized; using System.Net; using System.Net.Http.Headers; -using System.Reflection; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.RegularExpressions; using System.Web; using YandexMusic.API.Common; -using YandexMusic.API.Extensions; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Common; -/// Базовый строитель HTTP-запросов к API Яндекс.Музыки. -/// Тип ответа. +/// Базовый строитель HTTP-запросов. /// Тип параметров запроса. -public abstract class YRequestBuilder +public abstract class YRequestBuilder { - private readonly YRequestAttribute _requestInfo; - private Dictionary _substitutions = null!; + /// HTTP-метод (GET, POST и т.д.). + protected abstract string Method { get; } + + /// Базовый URL (например, "https://api.music.yandex.net"). + protected abstract string BaseUrl { get; } + + /// Шаблон пути (может содержать плейсхолдеры вида {id}). + protected abstract string PathTemplate { get; } + private readonly JsonSerializerOptions _jsonOptions; - protected readonly YandexMusicApi api; - protected readonly AuthStorage storage; - protected string device; + /// Основной экземпляр API. + protected YandexMusicApi Api { get; } - protected YRequestBuilder(YandexMusicApi yandex, AuthStorage auth) + /// Хранилище авторизации (сокращение для Api.Storage). + protected AuthStorage Storage => Api.Storage; + + protected YRequestBuilder(YandexMusicApi api) { - _requestInfo = GetType().GetCustomAttribute() - ?? throw new NotImplementedException($"Отсутствует атрибут {nameof(YRequestAttribute)}"); - api = yandex; - storage = auth; - device = $"os=CSharp; os_version=; manufacturer=FrigaT; model=Yandex Music API; clid=; device_id={storage.DeviceId}; uuid=random"; - + Api = api; _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -41,57 +42,91 @@ public abstract class YRequestBuilder }; } - private Uri BuildUri(TParams tuple) + private string FullUrl => $"{BaseUrl.TrimEnd('/')}/{PathTemplate.TrimStart('/')}"; + + private Uri BuildUri(TParams parameters, Dictionary substitutions) { - var queryParams = GetQueryParams(tuple); + var queryParams = GetQueryParams(parameters); var modifiedParams = HttpUtility.ParseQueryString(string.Empty); foreach (string? key in queryParams) if (key != null) - modifiedParams[key] = ReplaceSubs(queryParams[key]!); - var endpoint = ReplaceSubs(_requestInfo.Url); + modifiedParams[key] = ReplaceSubs(queryParams[key]!, substitutions); + var endpoint = ReplaceSubs(FullUrl, substitutions); var builder = new UriBuilder(endpoint) { Query = modifiedParams.ToString() ?? string.Empty }; return builder.Uri; } - private HttpRequestMessage CreateMessage(TParams tuple) + private HttpRequestMessage CreateMessage(TParams parameters, Dictionary substitutions) { var msg = new HttpRequestMessage { - RequestUri = BuildUri(tuple), - Method = new HttpMethod(_requestInfo.Method), - Content = GetContent(tuple) + RequestUri = BuildUri(parameters, substitutions), + Method = new HttpMethod(Method), + Content = GetContent(parameters) }; - msg.Headers.TryAddWithoutValidation(HttpRequestHeader.AcceptCharset.GetName(), Encoding.UTF8.WebName); - msg.Headers.TryAddWithoutValidation(HttpRequestHeader.AcceptEncoding.GetName(), "gzip"); - if (!string.IsNullOrEmpty(storage.Token)) - msg.Headers.TryAddWithoutValidation(HttpRequestHeader.Authorization.GetName(), $"OAuth {storage.Token}"); + msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.AcceptCharset), Encoding.UTF8.WebName); + msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.AcceptEncoding), "gzip"); + if (!string.IsNullOrEmpty(Storage.Token)) + msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.Authorization), $"OAuth {Storage.Token}"); SetCustomHeaders(msg.Headers); return msg; } - protected string ReplaceSubs(string str) + // Вспомогательный метод: преобразование HttpRequestHeader в строку (как было в HttpRequestHeaderExtensions) + private static string GetHeaderName(HttpRequestHeader header) { - var subs = str.GetMatches(@"\{.+?\}"); + return SplitByCapitalLetter(header.ToString(), "-"); + } + + // Вспомогательный метод: разбиение строки по заглавным буквам (из StringExtensions) + private static string SplitByCapitalLetter(string str, string delimiter) + { + var matches = Regex.Matches(str, @"([A-Z]+)(?=([A-Z][a-z]|$)) | [A-Z][a-z].+?(?=([A-Z]|$))", RegexOptions.IgnorePatternWhitespace); + return string.Join(delimiter, matches.Cast().Select(m => m.Value)); + } + + // Вспомогательный метод: замена всех вхождений регулярного выражения (из StringExtensions) + private static string ReplaceRegex(string input, string pattern, string replacement, RegexOptions options = RegexOptions.IgnoreCase) + { + return string.IsNullOrEmpty(input) ? string.Empty : Regex.Replace(input, pattern, replacement, options); + } + + // Вспомогательный метод: получение совпадений по регулярному выражению (из StringExtensions) + private static string[] GetMatches(string input, string pattern, RegexOptions options = RegexOptions.IgnoreCase) + { + if (string.IsNullOrEmpty(input) || !Regex.IsMatch(input, pattern, options)) + return Array.Empty(); + return Regex.Matches(input, pattern, options) + .Cast() + .Select(m => m.Value) + .ToArray(); + } + + private string ReplaceSubs(string str, Dictionary substitutions) + { + var subs = GetMatches(str, @"\{.+?\}"); foreach (var s in subs) { - var key = s.ReplaceRegex(@"[\{\}]", string.Empty); - if (!_substitutions.TryGetValue(key, out var value)) + var key = ReplaceRegex(s, @"[\{\}]", string.Empty); + if (!substitutions.TryGetValue(key, out var value)) throw new Exception($"Не найдена подстановка {s}"); str = str.Replace(s, value); } return str; } - protected virtual Dictionary GetSubstitutions(TParams tuple) => []; - protected virtual NameValueCollection GetQueryParams(TParams tuple) => []; - protected virtual HttpContent? GetContent(TParams tuple) => null; + protected virtual Dictionary GetSubstitutions(TParams parameters) => []; + protected virtual NameValueCollection GetQueryParams(TParams parameters) => []; + protected virtual HttpContent? GetContent(TParams parameters) => null; protected virtual void SetCustomHeaders(HttpRequestHeaders headers) { } + protected string SerializeJson(object data) => JsonSerializer.Serialize(data, _jsonOptions); - internal YRequest Build(TParams tuple) + /// Выполняет запрос и возвращает десериализованный ответ. + public async Task ExecuteRawAsync(TParams parameters) { - _substitutions = GetSubstitutions(tuple); - var msg = CreateMessage(tuple); - return new YRequest(msg, api, storage); + var substitutions = GetSubstitutions(parameters); + using var msg = CreateMessage(parameters, substitutions); + return await Api.HttpClient.SendAsync(msg); } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Feed/YGetFeedBuilder.cs b/YandexMusic.API/Requests/Feed/YGetFeedBuilder.cs index 961b61b..9021d91 100644 --- a/YandexMusic.API/Requests/Feed/YGetFeedBuilder.cs +++ b/YandexMusic.API/Requests/Feed/YGetFeedBuilder.cs @@ -1,17 +1,12 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Feed; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Feed; -[YApiRequest(WebRequestMethods.Http.Get, "feed")] -public class YGetFeedBuilder : YRequestBuilder, object> +public class YGetFeedBuilder : YMusicRequestBuilder { - public YGetFeedBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YGetFeedBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "feed"; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Label/YGetLabelAlbumsBuilder.cs b/YandexMusic.API/Requests/Label/YGetLabelAlbumsBuilder.cs index c081433..7742887 100644 --- a/YandexMusic.API/Requests/Label/YGetLabelAlbumsBuilder.cs +++ b/YandexMusic.API/Requests/Label/YGetLabelAlbumsBuilder.cs @@ -1,32 +1,17 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Label; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Label; -[YApiRequest(WebRequestMethods.Http.Get, "labels/{labelId}/albums")] -public class YGetLabelAlbumsBuilder : YRequestBuilder, (YLabel label, int pageNumber)> +public class YGetLabelAlbumsBuilder : YMusicRequestBuilder { - public YGetLabelAlbumsBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override NameValueCollection GetQueryParams((YLabel label, int pageNumber) tuple) - { - return new NameValueCollection { - { "page", tuple.pageNumber.ToString() } - }; - } - + public YGetLabelAlbumsBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "labels/{labelId}/albums"; protected override Dictionary GetSubstitutions((YLabel label, int pageNumber) tuple) - { - return new Dictionary { - { "labelId", tuple.label.Id } - }; - } -} + => new() { { "labelId", tuple.label.Id } }; + protected override NameValueCollection GetQueryParams((YLabel label, int pageNumber) tuple) + => new() { { "page", tuple.pageNumber.ToString() } }; +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Label/YGetLabelArtistsBuilder.cs b/YandexMusic.API/Requests/Label/YGetLabelArtistsBuilder.cs index db010be..5a6ed12 100644 --- a/YandexMusic.API/Requests/Label/YGetLabelArtistsBuilder.cs +++ b/YandexMusic.API/Requests/Label/YGetLabelArtistsBuilder.cs @@ -1,32 +1,17 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Label; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Label; -[YApiRequest(WebRequestMethods.Http.Get, "labels/{labelId}/artists")] -public class YGetLabelArtistsBuilder : YRequestBuilder, (YLabel label, int pageNumber)> +public class YGetLabelArtistsBuilder : YMusicRequestBuilder { - public YGetLabelArtistsBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override NameValueCollection GetQueryParams((YLabel label, int pageNumber) tuple) - { - return new NameValueCollection { - { "page", tuple.pageNumber.ToString() } - }; - } - + public YGetLabelArtistsBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "labels/{labelId}/artists"; protected override Dictionary GetSubstitutions((YLabel label, int pageNumber) tuple) - { - return new Dictionary { - { "labelId", tuple.label.Id } - }; - } -} + => new() { { "labelId", tuple.label.Id } }; + protected override NameValueCollection GetQueryParams((YLabel label, int pageNumber) tuple) + => new() { { "page", tuple.pageNumber.ToString() } }; +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Landing/YGetChildrenLandingBuilder.cs b/YandexMusic.API/Requests/Landing/YGetChildrenLandingBuilder.cs index 7ec065f..fe05313 100644 --- a/YandexMusic.API/Requests/Landing/YGetChildrenLandingBuilder.cs +++ b/YandexMusic.API/Requests/Landing/YGetChildrenLandingBuilder.cs @@ -1,17 +1,12 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Landing; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Landing; -[YApiRequest(WebRequestMethods.Http.Get, "children-landing/catalogue")] -public class YGetChildrenLandingBuilder : YRequestBuilder, object> +public class YGetChildrenLandingBuilder : YMusicRequestBuilder { - public YGetChildrenLandingBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YGetChildrenLandingBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "children-landing/catalogue"; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Landing/YGetLandingBuilder.cs b/YandexMusic.API/Requests/Landing/YGetLandingBuilder.cs index 98ee8a6..b428222 100644 --- a/YandexMusic.API/Requests/Landing/YGetLandingBuilder.cs +++ b/YandexMusic.API/Requests/Landing/YGetLandingBuilder.cs @@ -1,28 +1,17 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Landing; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Landing; -[YApiRequest(WebRequestMethods.Http.Get, "landing3")] -public class YGetLandingBuilder : YRequestBuilder, YLandingBlockType[]> +public class YGetLandingBuilder : YMusicRequestBuilder { - public YGetLandingBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) + public YGetLandingBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "landing3"; + protected override NameValueCollection GetQueryParams(YLandingBlockType[] blocks) { - } - - protected override NameValueCollection GetQueryParams(YLandingBlockType[] tuple) - { - string blocks = string.Join(",", tuple - .Select(b => SerializeJson(b).Replace("\"", string.Empty))); - - return new NameValueCollection { - { "blocks", blocks } - }; + string blocksStr = string.Join(",", blocks.Select(b => SerializeJson(b).Replace("\"", ""))); + return new NameValueCollection { { "blocks", blocksStr } }; } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Library/YGetLibraryRecentlyListenedBuilder.cs b/YandexMusic.API/Requests/Library/YGetLibraryRecentlyListenedBuilder.cs index 94d3ed1..2b6b9aa 100644 --- a/YandexMusic.API/Requests/Library/YGetLibraryRecentlyListenedBuilder.cs +++ b/YandexMusic.API/Requests/Library/YGetLibraryRecentlyListenedBuilder.cs @@ -1,36 +1,22 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Landing.Entity.Entities.Context; using YandexMusic.API.Models.Library; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Library; -[YApiRequest(WebRequestMethods.Http.Get, "/users/{uid}/contexts")] -public class YGetLibraryRecentlyListenedBuilder : YRequestBuilder, - (IEnumerable contextTypes, int trackCount, int contextCount)> +public class YGetLibraryRecentlyListenedBuilder : YMusicRequestBuilder contextTypes, int trackCount, int contextCount)> { - public YGetLibraryRecentlyListenedBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetLibraryRecentlyListenedBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "users/{uid}/contexts"; + protected override Dictionary GetSubstitutions((IEnumerable contextTypes, int trackCount, int contextCount) tuple) + => new() { { "uid", Api.Storage.User.Uid } }; protected override NameValueCollection GetQueryParams((IEnumerable contextTypes, int trackCount, int contextCount) tuple) - { - return new NameValueCollection { + => new() + { { "trackCount", tuple.trackCount.ToString() }, { "contextCount", tuple.contextCount.ToString() }, { "types", string.Join(",", tuple.contextTypes.Select(x => x.ToString().ToLowerInvariant())) } }; - } - - protected override Dictionary GetSubstitutions((IEnumerable contextTypes, int trackCount, int contextCount) tuple) - { - return new Dictionary { - { "uid", storage.User.Uid } - }; - } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Library/YGetLibrarySectionBuilder.cs b/YandexMusic.API/Requests/Library/YGetLibrarySectionBuilder.cs index eadd41f..b2feedd 100644 --- a/YandexMusic.API/Requests/Library/YGetLibrarySectionBuilder.cs +++ b/YandexMusic.API/Requests/Library/YGetLibrarySectionBuilder.cs @@ -1,26 +1,18 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Library; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Library; -[YApiRequest(WebRequestMethods.Http.Get, "users/{uid}/{type}/{section}")] -public class YGetLibrarySectionBuilder : YRequestBuilder, (YLibrarySection section, YLibrarySectionType type)> +public class YGetLibrarySectionBuilder : YMusicRequestBuilder { - public YGetLibrarySectionBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetLibrarySectionBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "users/{uid}/{type}/{section}"; protected override Dictionary GetSubstitutions((YLibrarySection section, YLibrarySectionType type) tuple) - { - return new Dictionary { - { "uid", storage.User.Uid }, + => new() + { + { "uid", Api.Storage.User.Uid }, { "type", tuple.type.ToString().ToLower() }, - { "section", tuple.section.ToString().ToLower() }, + { "section", tuple.section.ToString().ToLower() } }; - } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Library/YLibraryAddBuilder.cs b/YandexMusic.API/Requests/Library/YLibraryAddBuilder.cs index 24c1a2f..7671cdf 100644 --- a/YandexMusic.API/Requests/Library/YLibraryAddBuilder.cs +++ b/YandexMusic.API/Requests/Library/YLibraryAddBuilder.cs @@ -1,33 +1,20 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Library; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Library; -[YApiRequest(WebRequestMethods.Http.Post, "users/{uid}/{type}/{section}/add-multiple")] -public class YLibraryAddBuilder : YRequestBuilder, (string id, YLibrarySection section, YLibrarySectionType type)> +public class YLibraryAddBuilder : YMusicRequestBuilder { - public YLibraryAddBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YLibraryAddBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "users/{uid}/{type}/{section}/add-multiple"; protected override Dictionary GetSubstitutions((string id, YLibrarySection section, YLibrarySectionType type) tuple) - { - return new Dictionary { - { "uid", storage.User.Uid }, + => new() + { + { "uid", Api.Storage.User.Uid }, { "type", tuple.type.ToString().ToLower() }, - { "section", tuple.section.ToString().ToLower() }, + { "section", tuple.section.ToString().ToLower() } }; - } - - protected override HttpContent GetContent((string id, YLibrarySection section, YLibrarySectionType type) tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { $"{tuple.section.ToString().ToLower().TrimEnd('s')}-ids", tuple.id } - }); - } + protected override HttpContent? GetContent((string id, YLibrarySection section, YLibrarySectionType type) tuple) + => new FormUrlEncodedContent(new Dictionary { { $"{tuple.section.ToString().ToLower().TrimEnd('s')}-ids", tuple.id } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Library/YLibraryRemoveBuilder.cs b/YandexMusic.API/Requests/Library/YLibraryRemoveBuilder.cs index 0809d83..d400b75 100644 --- a/YandexMusic.API/Requests/Library/YLibraryRemoveBuilder.cs +++ b/YandexMusic.API/Requests/Library/YLibraryRemoveBuilder.cs @@ -1,33 +1,20 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Library; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Library; -[YApiRequest(WebRequestMethods.Http.Post, "users/{uid}/{type}/{section}/remove")] -public class YLibraryRemoveBuilder : YRequestBuilder, (string id, YLibrarySection section, YLibrarySectionType type)> +public class YLibraryRemoveBuilder : YMusicRequestBuilder { - public YLibraryRemoveBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YLibraryRemoveBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "users/{uid}/{type}/{section}/remove"; protected override Dictionary GetSubstitutions((string id, YLibrarySection section, YLibrarySectionType type) tuple) - { - return new Dictionary { - { "uid", storage.User.Uid }, + => new() + { + { "uid", Api.Storage.User.Uid }, { "type", tuple.type.ToString().ToLower() }, - { "section", tuple.section.ToString().ToLower() }, + { "section", tuple.section.ToString().ToLower() } }; - } - - protected override HttpContent GetContent((string id, YLibrarySection section, YLibrarySectionType type) tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { $"{tuple.section.ToString().ToLower().TrimEnd('s')}-ids", tuple.id } - }); - } + protected override HttpContent? GetContent((string id, YLibrarySection section, YLibrarySectionType type) tuple) + => new FormUrlEncodedContent(new Dictionary { { $"{tuple.section.ToString().ToLower().TrimEnd('s')}-ids", tuple.id } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Pins/YGetPinsBuilder.cs b/YandexMusic.API/Requests/Pins/YGetPinsBuilder.cs index 0621fcd..5a7f81e 100644 --- a/YandexMusic.API/Requests/Pins/YGetPinsBuilder.cs +++ b/YandexMusic.API/Requests/Pins/YGetPinsBuilder.cs @@ -1,17 +1,12 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Pins; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Pins; -[YApiRequest(WebRequestMethods.Http.Get, "pins")] -public class YGetPinsBuilder : YRequestBuilder, object> +public class YGetPinsBuilder : YMusicRequestBuilder { - public YGetPinsBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YGetPinsBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "pins"; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Playlist/YGetPlaylistBuilder.cs b/YandexMusic.API/Requests/Playlist/YGetPlaylistBuilder.cs index be05cbf..d070478 100644 --- a/YandexMusic.API/Requests/Playlist/YGetPlaylistBuilder.cs +++ b/YandexMusic.API/Requests/Playlist/YGetPlaylistBuilder.cs @@ -1,25 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Playlist; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Playlist; -[YApiRequest(WebRequestMethods.Http.Get, "users/{user}/playlists/{kind}")] -public class YGetPlaylistBuilder : YRequestBuilder, (string user, string kind)> +public class YGetPlaylistBuilder : YMusicRequestBuilder { - public YGetPlaylistBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetPlaylistBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "users/{user}/playlists/{kind}"; protected override Dictionary GetSubstitutions((string user, string kind) tuple) - { - return new Dictionary { - { "user", tuple.user }, - { "kind", tuple.kind }, - }; - } + => new() { { "user", tuple.user }, { "kind", tuple.kind } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Playlist/YGetPlaylistByUuidBuilder.cs b/YandexMusic.API/Requests/Playlist/YGetPlaylistByUuidBuilder.cs index cb816a0..5e6c075 100644 --- a/YandexMusic.API/Requests/Playlist/YGetPlaylistByUuidBuilder.cs +++ b/YandexMusic.API/Requests/Playlist/YGetPlaylistByUuidBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Playlist; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Playlist; -[YApiRequest(WebRequestMethods.Http.Get, "playlist/{uuid}")] -public class YGetPlaylistByUuidBuilder : YRequestBuilder, string> +public class YGetPlaylistByUuidBuilder : YMusicRequestBuilder { - public YGetPlaylistByUuidBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetPlaylistByUuidBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "playlist/{uuid}"; protected override Dictionary GetSubstitutions(string uuid) - { - return new Dictionary { - { "uuid", uuid }, - }; - } + => new() { { "uuid", uuid } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Playlist/YGetPlaylistFavoritesBuilder.cs b/YandexMusic.API/Requests/Playlist/YGetPlaylistFavoritesBuilder.cs index 6ca4bd9..82626bc 100644 --- a/YandexMusic.API/Requests/Playlist/YGetPlaylistFavoritesBuilder.cs +++ b/YandexMusic.API/Requests/Playlist/YGetPlaylistFavoritesBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Playlist; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Playlist; -[YApiRequest(WebRequestMethods.Http.Get, "users/{uid}/playlists/list")] -public class YGetPlaylistFavoritesBuilder : YRequestBuilder>, object> +public class YGetPlaylistFavoritesBuilder : YMusicRequestBuilder?, object> { - public YGetPlaylistFavoritesBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override Dictionary GetSubstitutions(object tuple) - { - return new Dictionary { - { "uid", storage.User.Uid } - }; - } + public YGetPlaylistFavoritesBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "users/{uid}/playlists/list"; + protected override Dictionary GetSubstitutions(object _) + => new() { { "uid", Api.Storage.User.Uid } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Playlist/YGetPlaylistsBuilder.cs b/YandexMusic.API/Requests/Playlist/YGetPlaylistsBuilder.cs index 901d595..cb71199 100644 --- a/YandexMusic.API/Requests/Playlist/YGetPlaylistsBuilder.cs +++ b/YandexMusic.API/Requests/Playlist/YGetPlaylistsBuilder.cs @@ -1,24 +1,16 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Playlist; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Playlist; -[YApiRequest(WebRequestMethods.Http.Post, "playlists/list")] -public class YGetPlaylistsBuilder : YRequestBuilder>, IEnumerable<(string User, string Kind)>> +public class YGetPlaylistsBuilder : YMusicRequestBuilder?, IEnumerable<(string User, string Kind)>> { - public YGetPlaylistsBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(IEnumerable<(string User, string Kind)> playlistIds) - { - return new FormUrlEncodedContent(new Dictionary { + public YGetPlaylistsBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "playlists/list"; + protected override HttpContent? GetContent(IEnumerable<(string User, string Kind)> playlistIds) + => new FormUrlEncodedContent(new Dictionary + { { "playlist-Ids", string.Join(",", playlistIds.Select(t => $"{t.User}:{t.Kind}")) } }); - } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Playlist/YPlaylistChangeBuilder.cs b/YandexMusic.API/Requests/Playlist/YPlaylistChangeBuilder.cs index 1d543da..2938919 100644 --- a/YandexMusic.API/Requests/Playlist/YPlaylistChangeBuilder.cs +++ b/YandexMusic.API/Requests/Playlist/YPlaylistChangeBuilder.cs @@ -1,34 +1,20 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Playlist; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Playlist; -[YApiRequest(WebRequestMethods.Http.Post, "users/{uid}/playlists/{kind}/change")] -public class YPlaylistChangeBuilder : YRequestBuilder, (YPlaylist playlist, IEnumerable changes)> +public class YPlaylistChangeBuilder : YMusicRequestBuilder changes)> { - public YPlaylistChangeBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YPlaylistChangeBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "users/{uid}/playlists/{kind}/change"; protected override Dictionary GetSubstitutions((YPlaylist playlist, IEnumerable changes) tuple) - { - return new Dictionary { - { "uid", storage.User.Uid }, - { "kind", tuple.playlist.Kind } - }; - } - - protected override HttpContent GetContent((YPlaylist playlist, IEnumerable changes) tuple) - { - return new FormUrlEncodedContent(new Dictionary { + => new() { { "uid", Api.Storage.User.Uid }, { "kind", tuple.playlist.Kind } }; + protected override HttpContent? GetContent((YPlaylist playlist, IEnumerable changes) tuple) + => new FormUrlEncodedContent(new Dictionary + { { "kind", tuple.playlist.Kind }, { "revision", tuple.playlist.Revision.ToString() }, { "diff", SerializeJson(tuple.changes) } }); - } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Playlist/YPlaylistCreateBuilder.cs b/YandexMusic.API/Requests/Playlist/YPlaylistCreateBuilder.cs index c835683..ae6b065 100644 --- a/YandexMusic.API/Requests/Playlist/YPlaylistCreateBuilder.cs +++ b/YandexMusic.API/Requests/Playlist/YPlaylistCreateBuilder.cs @@ -1,32 +1,15 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Playlist; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Playlist; -[YApiRequest(WebRequestMethods.Http.Post, "users/{uid}/playlists/create")] -public class YPlaylistCreateBuilder : YRequestBuilder, string> +public class YPlaylistCreateBuilder : YMusicRequestBuilder { - public YPlaylistCreateBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YPlaylistCreateBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "users/{uid}/playlists/create"; protected override Dictionary GetSubstitutions(string name) - { - return new Dictionary { - { "uid", storage.User.Uid } - }; - } - - protected override HttpContent GetContent(string name) - { - return new FormUrlEncodedContent(new Dictionary { - { "title", name }, - { "visibility", "public" } - }); - } + => new() { { "uid", Api.Storage.User.Uid } }; + protected override HttpContent? GetContent(string name) + => new FormUrlEncodedContent(new Dictionary { { "title", name }, { "visibility", "public" } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Playlist/YPlaylistRemoveBuilder.cs b/YandexMusic.API/Requests/Playlist/YPlaylistRemoveBuilder.cs index 19b8226..0194cb0 100644 --- a/YandexMusic.API/Requests/Playlist/YPlaylistRemoveBuilder.cs +++ b/YandexMusic.API/Requests/Playlist/YPlaylistRemoveBuilder.cs @@ -1,32 +1,12 @@ using System.Net; -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; -using YandexMusic.API.Models.Playlist; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; - namespace YandexMusic.API.Requests.Playlist; -[YApiRequest(WebRequestMethods.Http.Post, "users/{uid}/playlists/{kind}/name")] -public class YPlaylistRenameBuilder : YRequestBuilder, (string kind, string name)> +public class YPlaylistRemoveBuilder : YMusicRequestBuilder { - public YPlaylistRenameBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override Dictionary GetSubstitutions((string kind, string name) tuple) - { - return new Dictionary { - { "uid", storage.User.Uid }, - { "kind", tuple.kind } - }; - } - - protected override HttpContent GetContent((string kind, string name) tuple) - { - return new FormUrlEncodedContent(new Dictionary { - { "value", tuple.name } - }); - } + public YPlaylistRemoveBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "users/{uid}/playlists/{kind}/delete"; + protected override Dictionary GetSubstitutions(string kind) + => new() { { "uid", Api.Storage.User.Uid }, { "kind", kind } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Playlist/YPlaylistRenameBuilder.cs b/YandexMusic.API/Requests/Playlist/YPlaylistRenameBuilder.cs index 04dcb12..3b90522 100644 --- a/YandexMusic.API/Requests/Playlist/YPlaylistRenameBuilder.cs +++ b/YandexMusic.API/Requests/Playlist/YPlaylistRenameBuilder.cs @@ -1,23 +1,15 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; +using YandexMusic.API.Models.Playlist; namespace YandexMusic.API.Requests.Playlist; -[YApiRequest(WebRequestMethods.Http.Post, "users/{uid}/playlists/{kind}/delete")] -public class YPlaylistRemoveBuilder : YRequestBuilder +public class YPlaylistRenameBuilder : YMusicRequestBuilder { - public YPlaylistRemoveBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override Dictionary GetSubstitutions(string kind) - { - return new Dictionary { - { "uid", storage.User.Uid }, - { "kind", kind } - }; - } + public YPlaylistRenameBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "users/{uid}/playlists/{kind}/name"; + protected override Dictionary GetSubstitutions((string kind, string name) tuple) + => new() { { "uid", Api.Storage.User.Uid }, { "kind", tuple.kind } }; + protected override HttpContent? GetContent((string kind, string name) tuple) + => new FormUrlEncodedContent(new Dictionary { { "value", tuple.name } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Queue/YGetQueueBuilder.cs b/YandexMusic.API/Requests/Queue/YGetQueueBuilder.cs index 95da016..2c4df49 100644 --- a/YandexMusic.API/Requests/Queue/YGetQueueBuilder.cs +++ b/YandexMusic.API/Requests/Queue/YGetQueueBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Queue; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Queue; -[YApiRequest(WebRequestMethods.Http.Get, "queues/{queueId}")] -public class YGetQueueBuilder : YRequestBuilder, string> +public class YGetQueueBuilder : YMusicRequestBuilder { - public YGetQueueBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetQueueBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "queues/{queueId}"; protected override Dictionary GetSubstitutions(string queueId) - { - return new Dictionary { - { "queueId", queueId } - }; - } -} + => new() { { "queueId", queueId } }; +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Queue/YQueueCreateBuilder.cs b/YandexMusic.API/Requests/Queue/YQueueCreateBuilder.cs index 323998c..8c75597 100644 --- a/YandexMusic.API/Requests/Queue/YQueueCreateBuilder.cs +++ b/YandexMusic.API/Requests/Queue/YQueueCreateBuilder.cs @@ -4,40 +4,29 @@ using System.Net.Http.Json; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Queue; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Queue; -[YApiRequest(WebRequestMethods.Http.Post, "queues")] -public class YQueueCreateBuilder : YRequestBuilder, YQueue> +public class YQueueCreateBuilder : YMusicRequestBuilder { - public YQueueCreateBuilder(YandexMusicApi yandex, AuthStorage auth, string? device = null) : base(yandex, auth) + private string? _device; + public YQueueCreateBuilder(YandexMusicApi api, string? device = null) : base(api) => _device = device; + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "queues"; + protected override HttpContent? GetContent(YQueue queue) { - if (device != null) - this.device = device; - } - - protected override HttpContent GetContent(YQueue queue) - { - JsonSerializerOptions settings = new() + var options = new JsonSerializerOptions { - Converters = { - new JsonStringEnumConverter() - }, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase + Converters = { new JsonStringEnumConverter() } }; - - return JsonContent.Create(queue, new MediaTypeHeaderValue("application/json"), settings); + return JsonContent.Create(queue, new MediaTypeHeaderValue("application/json"), options); } - protected override void SetCustomHeaders(HttpRequestHeaders headers) { - headers.Add("X-Yandex-Music-Device", device); + if (!string.IsNullOrEmpty(_device)) + headers.Add("X-Yandex-Music-Device", _device); } -} +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Queue/YQueueUpdatePositionBuilder.cs b/YandexMusic.API/Requests/Queue/YQueueUpdatePositionBuilder.cs index c58990c..b02eddc 100644 --- a/YandexMusic.API/Requests/Queue/YQueueUpdatePositionBuilder.cs +++ b/YandexMusic.API/Requests/Queue/YQueueUpdatePositionBuilder.cs @@ -1,41 +1,27 @@ using System.Collections.Specialized; using System.Net; using System.Net.Http.Headers; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Queue; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Queue; -[YApiRequest(WebRequestMethods.Http.Post, "queues/{queueId}/update-position")] -public class YQueueUpdatePositionBuilder : YRequestBuilder, (string queueId, int currentIndex, bool isInteractive)> +public class YQueueUpdatePositionBuilder : YMusicRequestBuilder { - public YQueueUpdatePositionBuilder(YandexMusicApi yandex, AuthStorage auth, string device = null) : base(yandex, auth) - { - if (device != null) - this.device = device; - } - + private string? _device; + public YQueueUpdatePositionBuilder(YandexMusicApi api, string? device = null) : base(api) => _device = device; + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "queues/{queueId}/update-position"; protected override Dictionary GetSubstitutions((string queueId, int currentIndex, bool isInteractive) tuple) - { - return new Dictionary { - { "queueId", tuple.queueId }, - }; - } - - protected override void SetCustomHeaders(HttpRequestHeaders headers) - { - headers.Add("X-Yandex-Music-Device", device); - } - + => new() { { "queueId", tuple.queueId } }; protected override NameValueCollection GetQueryParams((string queueId, int currentIndex, bool isInteractive) tuple) - { - return new NameValueCollection { + => new() + { { "currentIndex", tuple.currentIndex.ToString() }, { "isInteractive", tuple.isInteractive.ToString().ToLower() } }; + protected override void SetCustomHeaders(HttpRequestHeaders headers) + { + if (!string.IsNullOrEmpty(_device)) + headers.Add("X-Yandex-Music-Device", _device); } -} +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Queue/YQueuesListBuilder.cs b/YandexMusic.API/Requests/Queue/YQueuesListBuilder.cs index 26f5ccd..7d45888 100644 --- a/YandexMusic.API/Requests/Queue/YQueuesListBuilder.cs +++ b/YandexMusic.API/Requests/Queue/YQueuesListBuilder.cs @@ -1,25 +1,18 @@ using System.Net; using System.Net.Http.Headers; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Queue; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Queue; -[YApiRequest(WebRequestMethods.Http.Get, "queues")] -public class YQueuesListBuilder : YRequestBuilder, string> +public class YQueuesListBuilder : YMusicRequestBuilder { - public YQueuesListBuilder(YandexMusicApi yandex, AuthStorage auth, string? device = null) : base(yandex, auth) - { - if (device != null) - this.device = device; - } - + private string? _device; + public YQueuesListBuilder(YandexMusicApi api, string? device = null) : base(api) => _device = device; + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "queues"; protected override void SetCustomHeaders(HttpRequestHeaders headers) { - headers.Add("X-Yandex-Music-Device", device); + if (!string.IsNullOrEmpty(_device)) + headers.Add("X-Yandex-Music-Device", _device); } -} +} \ No newline at end of file diff --git a/YandexMusic.API/Requests/Radio/YGetStationBuilder.cs b/YandexMusic.API/Requests/Radio/YGetStationBuilder.cs index 18497d8..7d4970f 100644 --- a/YandexMusic.API/Requests/Radio/YGetStationBuilder.cs +++ b/YandexMusic.API/Requests/Radio/YGetStationBuilder.cs @@ -1,25 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Radio; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Radio; -[YApiRequest(WebRequestMethods.Http.Get, "rotor/station/{type}:{tag}/info")] -public class YGetStationBuilder : YRequestBuilder>, (string type, string tag)> +public class YGetStationBuilder : YMusicRequestBuilder?, (string type, string tag)> { - public YGetStationBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetStationBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "rotor/station/{type}:{tag}/info"; protected override Dictionary GetSubstitutions((string type, string tag) tuple) - { - return new Dictionary { - { "type", tuple.type }, - { "tag", tuple.tag } - }; - } + => new() { { "type", tuple.type }, { "tag", tuple.tag } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Radio/YGetStationTracksBuilder.cs b/YandexMusic.API/Requests/Radio/YGetStationTracksBuilder.cs index 077600f..dfbdbe3 100644 --- a/YandexMusic.API/Requests/Radio/YGetStationTracksBuilder.cs +++ b/YandexMusic.API/Requests/Radio/YGetStationTracksBuilder.cs @@ -1,38 +1,21 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Radio; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Radio; -[YApiRequest(WebRequestMethods.Http.Get, "rotor/station/{type}:{tag}/tracks")] -public class YGetStationTracksBuilder : YRequestBuilder, (YStationDescription station, string prevTrackId)> +public class YGetStationTracksBuilder : YMusicRequestBuilder { - public YGetStationTracksBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetStationTracksBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "rotor/station/{type}:{tag}/tracks"; protected override Dictionary GetSubstitutions((YStationDescription station, string prevTrackId) tuple) - { - return new Dictionary { - { "type", tuple.station.Id.Type }, - { "tag", tuple.station.Id.Tag }, - }; - } - + => new() { { "type", tuple.station.Id.Type }, { "tag", tuple.station.Id.Tag } }; protected override NameValueCollection GetQueryParams((YStationDescription station, string prevTrackId) tuple) { - NameValueCollection query = new() { - { "settings2", "true" } - }; - + var query = new NameValueCollection { { "settings2", "true" } }; if (!string.IsNullOrEmpty(tuple.prevTrackId)) query.Add("queue", tuple.prevTrackId); - - return base.GetQueryParams(tuple); + return query; } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Radio/YGetStationsBuilder.cs b/YandexMusic.API/Requests/Radio/YGetStationsBuilder.cs index b70e315..8a76ac0 100644 --- a/YandexMusic.API/Requests/Radio/YGetStationsBuilder.cs +++ b/YandexMusic.API/Requests/Radio/YGetStationsBuilder.cs @@ -1,17 +1,12 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Radio; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Radio; -[YApiRequest(WebRequestMethods.Http.Get, "rotor/stations/list")] -public class YGetStationsBuilder : YRequestBuilder>, object> +public class YGetStationsBuilder : YMusicRequestBuilder?, object> { - public YGetStationsBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YGetStationsBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "rotor/stations/list"; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Radio/YGetStationsDashboardBuilder.cs b/YandexMusic.API/Requests/Radio/YGetStationsDashboardBuilder.cs index 27fc73e..23cba2e 100644 --- a/YandexMusic.API/Requests/Radio/YGetStationsDashboardBuilder.cs +++ b/YandexMusic.API/Requests/Radio/YGetStationsDashboardBuilder.cs @@ -1,17 +1,12 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Radio; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Radio; -[YApiRequest(WebRequestMethods.Http.Get, "rotor/stations/dashboard")] -public class YGetStationsDashboardBuilder : YRequestBuilder, object> +public class YGetStationsDashboardBuilder : YMusicRequestBuilder { - public YGetStationsDashboardBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YGetStationsDashboardBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "rotor/stations/dashboard"; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Radio/YSetSettings2Builder.cs b/YandexMusic.API/Requests/Radio/YSetSettings2Builder.cs index f77bee3..d0619b4 100644 --- a/YandexMusic.API/Requests/Radio/YSetSettings2Builder.cs +++ b/YandexMusic.API/Requests/Radio/YSetSettings2Builder.cs @@ -4,42 +4,26 @@ using System.Net.Http.Json; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Radio; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Radio; -[YApiRequest(WebRequestMethods.Http.Post, "rotor/station/{type}:{tag}/settings2")] -public class YSetSettings2Builder : YRequestBuilder, (YStationDescription station, YStationSettings2 settings2)> +public class YSetSettings2Builder : YMusicRequestBuilder { - public YSetSettings2Builder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YSetSettings2Builder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "rotor/station/{type}:{tag}/settings2"; protected override Dictionary GetSubstitutions((YStationDescription station, YStationSettings2 settings2) tuple) + => new() { { "type", tuple.station.Id.Type }, { "tag", tuple.station.Id.Tag } }; + protected override HttpContent? GetContent((YStationDescription station, YStationSettings2 settings2) tuple) { - return new Dictionary { - { "type", tuple.station.Id.Type }, - { "tag", tuple.station.Id.Tag }, - }; - } - - protected override HttpContent GetContent((YStationDescription station, YStationSettings2 settings2) tuple) - { - JsonSerializerOptions settings = new() + var options = new JsonSerializerOptions { - Converters = { - new JsonStringEnumConverter() - }, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = { new JsonStringEnumConverter() } }; - - return JsonContent.Create(tuple.settings2, new MediaTypeHeaderValue("application/json"), settings); + return JsonContent.Create(tuple.settings2, new MediaTypeHeaderValue("application/json"), options); } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Radio/YSetStationFeedbackBuilder.cs b/YandexMusic.API/Requests/Radio/YSetStationFeedbackBuilder.cs index a2611d0..602e68d 100644 --- a/YandexMusic.API/Requests/Radio/YSetStationFeedbackBuilder.cs +++ b/YandexMusic.API/Requests/Radio/YSetStationFeedbackBuilder.cs @@ -5,67 +5,45 @@ using System.Net.Http.Json; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Radio; using YandexMusic.API.Models.Track; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Radio; -[YApiRequest(WebRequestMethods.Http.Post, "rotor/station/{type}:{tag}/feedback")] -public class YSetStationFeedbackBuilder : YRequestBuilder +public class YSetStationFeedbackBuilder : YMusicRequestBuilder { - public YSetStationFeedbackBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) + public YSetStationFeedbackBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "rotor/station/{type}:{tag}/feedback"; + protected override Dictionary GetSubstitutions((YStationFeedbackType type, YStation station, YTrack? track, string batchId, double totalPlayedSeconds) tuple) + => new() { { "type", tuple.station.Station.Id.Type }, { "tag", tuple.station.Station.Id.Tag } }; + protected override NameValueCollection GetQueryParams((YStationFeedbackType type, YStation station, YTrack? track, string batchId, double totalPlayedSeconds) tuple) { + var query = new NameValueCollection(); + if (!string.IsNullOrWhiteSpace(tuple.batchId)) + query.Add("batch-id", tuple.batchId); + return query; } - - protected override Dictionary GetSubstitutions((YStationFeedbackType type, YStation station, YTrack track, string batchId, double totalPlayedSeconds) tuple) + protected override HttpContent? GetContent((YStationFeedbackType type, YStation station, YTrack? track, string batchId, double totalPlayedSeconds) tuple) { - return new Dictionary { - { "type", tuple.station.Station.Id.Type }, - { "tag", tuple.station.Station.Id.Tag } - }; - } - - protected override HttpContent GetContent((YStationFeedbackType type, YStation station, YTrack track, string batchId, double totalPlayedSeconds) tuple) - { - long timestamp = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(); - - JsonSerializerOptions settings = new() - { - Converters = { - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) - }, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault - }; - - YStationFeedback feedBack = new() + var feedback = new YStationFeedback { Type = tuple.type, From = tuple.station.Station.IdForFrom, - Timestamp = timestamp + Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds() }; - if (tuple.track != null) - feedBack.TrackId = tuple.track.Id; - + feedback.TrackId = tuple.track.Id; if (tuple.totalPlayedSeconds > 0) - feedBack.TotalPlayedSeconds = tuple.totalPlayedSeconds; + feedback.TotalPlayedSeconds = tuple.totalPlayedSeconds; - return JsonContent.Create(feedBack, new MediaTypeHeaderValue("application/json"), settings); ; - } - - protected override NameValueCollection GetQueryParams((YStationFeedbackType type, YStation station, YTrack track, string batchId, double totalPlayedSeconds) tuple) - { - NameValueCollection query = new(); - - if (!string.IsNullOrWhiteSpace(tuple.batchId)) - query.Add("batch-id", tuple.batchId); - - return query; + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } + }; + return JsonContent.Create(feedback, new MediaTypeHeaderValue("application/json"), options); } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Search/YSearchBuilder.cs b/YandexMusic.API/Requests/Search/YSearchBuilder.cs index e8f9689..0d11b30 100644 --- a/YandexMusic.API/Requests/Search/YSearchBuilder.cs +++ b/YandexMusic.API/Requests/Search/YSearchBuilder.cs @@ -1,28 +1,21 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Search; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Search; -[YApiRequest(WebRequestMethods.Http.Get, "search")] -public class YSearchBuilder : YRequestBuilder, (string searchText, YSearchType searchType, int page, int pageSize)> +public class YSearchBuilder : YMusicRequestBuilder { - public YSearchBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YSearchBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "search"; protected override NameValueCollection GetQueryParams((string searchText, YSearchType searchType, int page, int pageSize) tuple) - { - return new NameValueCollection { + => new() + { { "text", tuple.searchText }, { "type", tuple.searchType.ToString() }, { "page", tuple.page.ToString() }, { "pageSize", tuple.pageSize.ToString() } }; - } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Search/YSearchSuggestBuilder.cs b/YandexMusic.API/Requests/Search/YSearchSuggestBuilder.cs index 24356e2..7364ea5 100644 --- a/YandexMusic.API/Requests/Search/YSearchSuggestBuilder.cs +++ b/YandexMusic.API/Requests/Search/YSearchSuggestBuilder.cs @@ -1,25 +1,14 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Search; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Search; -[YApiRequest(WebRequestMethods.Http.Get, "search/suggest")] -public class YSearchSuggestBuilder : YRequestBuilder, string> +public class YSearchSuggestBuilder : YMusicRequestBuilder { - public YSearchSuggestBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YSearchSuggestBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "search/suggest"; protected override NameValueCollection GetQueryParams(string searchText) - { - return new NameValueCollection { - { "part", searchText } - }; - } + => new() { { "part", searchText } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Track/YGetTrackSimilarBuilder.cs b/YandexMusic.API/Requests/Track/YGetTrackSimilarBuilder.cs index a770e3e..a41c84c 100644 --- a/YandexMusic.API/Requests/Track/YGetTrackSimilarBuilder.cs +++ b/YandexMusic.API/Requests/Track/YGetTrackSimilarBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Track; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Track; -[YApiRequest(WebRequestMethods.Http.Get, "tracks/{trackId}/similar")] -public class YGetTrackSimilarBuilder : YRequestBuilder, string> +public class YGetTrackSimilarBuilder : YMusicRequestBuilder { - public YGetTrackSimilarBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetTrackSimilarBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "tracks/{trackId}/similar"; protected override Dictionary GetSubstitutions(string trackId) - { - return new Dictionary { - { "trackId", trackId } - }; - } + => new() { { "trackId", trackId } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Track/YGetTrackSupplementBuilder.cs b/YandexMusic.API/Requests/Track/YGetTrackSupplementBuilder.cs index 8bfaf8d..8d70694 100644 --- a/YandexMusic.API/Requests/Track/YGetTrackSupplementBuilder.cs +++ b/YandexMusic.API/Requests/Track/YGetTrackSupplementBuilder.cs @@ -1,24 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Track; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Track; -[YApiRequest(WebRequestMethods.Http.Get, "tracks/{trackId}/supplement")] -public class YGetTrackSupplementBuilder : YRequestBuilder, string> +public class YGetTrackSupplementBuilder : YMusicRequestBuilder { - public YGetTrackSupplementBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YGetTrackSupplementBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "tracks/{trackId}/supplement"; protected override Dictionary GetSubstitutions(string trackId) - { - return new Dictionary { - { "trackId", trackId } - }; - } + => new() { { "trackId", trackId } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Track/YGetTracksBuilder.cs b/YandexMusic.API/Requests/Track/YGetTracksBuilder.cs index ae1db7b..c5f8cca 100644 --- a/YandexMusic.API/Requests/Track/YGetTracksBuilder.cs +++ b/YandexMusic.API/Requests/Track/YGetTracksBuilder.cs @@ -1,25 +1,13 @@ using System.Net; - -using YandexMusic.API.Common; -using YandexMusic.API.Models.Common; using YandexMusic.API.Models.Track; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Track; -[YApiRequest(WebRequestMethods.Http.Post, "tracks")] -public class YGetTracksBuilder : YRequestBuilder>, IEnumerable> +public class YGetTracksBuilder : YMusicRequestBuilder?, IEnumerable> { - public YGetTracksBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - - protected override HttpContent GetContent(IEnumerable trackIds) - { - return new FormUrlEncodedContent(new Dictionary { - { "track-ids", string.Join(",", trackIds) }, - { "with-positions", "true" } - }); - } + public YGetTracksBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "tracks"; + protected override HttpContent? GetContent(IEnumerable trackIds) + => new FormUrlEncodedContent(new Dictionary { { "track-ids", string.Join(",", trackIds) }, { "with-positions", "true" } }); } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Track/YSendTrackInfoBuilder.cs b/YandexMusic.API/Requests/Track/YSendTrackInfoBuilder.cs index e74bed7..50a9f8e 100644 --- a/YandexMusic.API/Requests/Track/YSendTrackInfoBuilder.cs +++ b/YandexMusic.API/Requests/Track/YSendTrackInfoBuilder.cs @@ -1,37 +1,31 @@ using System.Globalization; using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Track; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Track; -[YApiRequest(WebRequestMethods.Http.Post, "play-audio")] -public class YSendTrackInfoBuilder : YRequestBuilder +public class YSendTrackInfoBuilder : YMusicRequestBuilder { - public YSendTrackInfoBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) + public YSendTrackInfoBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Post; + protected override string PathTemplate => "play-audio"; + protected override HttpContent? GetContent((YTrack track, string from, bool fromCache, string playId, string playlistId, double totalPlayedSeconds, double endPositionSeconds) tuple) { - } - - protected override HttpContent GetContent((YTrack track, string from, bool fromCache, string playId, string playlistId, double totalPlayedSeconds, double endPositionSeconds) tuple) - { - Dictionary formData = new() { + var form = new Dictionary + { { "track_id", tuple.track.Id }, { "from-cache", tuple.fromCache.ToString() }, { "play_id", tuple.playId }, - { "uid", storage.User.Uid }, + { "uid", Api.Storage.User.Uid }, { "timestamp", DateTime.Now.ToString("o", CultureInfo.InvariantCulture) }, { "client-now", DateTime.Now.ToString("o", CultureInfo.InvariantCulture) }, - { "album-id", tuple.track.Albums.FirstOrDefault()?.Id }, + { "album-id", tuple.track.Albums?.FirstOrDefault()?.Id ?? "" }, { "from", tuple.from }, { "playlist-id", tuple.playlistId }, - { "track-length-seconds", ((double)(tuple.track.DurationMs / 1000)).ToString(CultureInfo.InvariantCulture) }, + { "track-length-seconds", (tuple.track.DurationMs / 1000.0).ToString(CultureInfo.InvariantCulture) }, { "total-played-seconds", tuple.totalPlayedSeconds.ToString(CultureInfo.InvariantCulture) }, - { "end-position-seconds", tuple.endPositionSeconds.ToString(CultureInfo.InvariantCulture) }, + { "end-position-seconds", tuple.endPositionSeconds.ToString(CultureInfo.InvariantCulture) } }; - - return new FormUrlEncodedContent(formData); + return new FormUrlEncodedContent(form); } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Track/YStorageDownloadFileBuilder.cs b/YandexMusic.API/Requests/Track/YStorageDownloadFileBuilder.cs index f32d8ee..f36fc08 100644 --- a/YandexMusic.API/Requests/Track/YStorageDownloadFileBuilder.cs +++ b/YandexMusic.API/Requests/Track/YStorageDownloadFileBuilder.cs @@ -1,42 +1,34 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Common; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Track; -[YRequest(WebRequestMethods.Http.Get, "{src}")] -public class YStorageDownloadFileBuilder : YRequestBuilder +/// Особый запрос – не к api.music.yandex.net, а к произвольному URL. +public class YStorageDownloadFileBuilder : YJsonRequestBuilder { - public YStorageDownloadFileBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YStorageDownloadFileBuilder(YandexMusicApi api) : base(api) { } + protected override string BaseUrl => ""; // не используется, т.к. URL берётся из параметра + + protected override string Method => WebRequestMethods.Http.Get; + + protected override string PathTemplate => "{src}"; protected override Dictionary GetSubstitutions(string src) - { - return new Dictionary { - { "src", src.Split('?')[0] } - }; - } - + => new() { { "src", src.Split('?')[0] } }; protected override NameValueCollection GetQueryParams(string src) { - NameValueCollection query = new() { - { "format", "json" } - }; - - src.Split('?')[1] - .Split('&') - .ToList() - .ForEach(p => + var query = new NameValueCollection { { "format", "json" } }; + var parts = src.Split('?'); + if (parts.Length > 1) + { + foreach (var param in parts[1].Split('&')) { - string[] param = p.Split('='); - query.Add(param[0], param[1]); - }); - + var kv = param.Split('='); + if (kv.Length == 2) query.Add(kv[0], kv[1]); + } + } return query; } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Track/YTrackDownloadInfoBuilder.cs b/YandexMusic.API/Requests/Track/YTrackDownloadInfoBuilder.cs index 633bec2..07e9850 100644 --- a/YandexMusic.API/Requests/Track/YTrackDownloadInfoBuilder.cs +++ b/YandexMusic.API/Requests/Track/YTrackDownloadInfoBuilder.cs @@ -1,31 +1,16 @@ using System.Collections.Specialized; using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Common; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Track; -[YApiRequest(WebRequestMethods.Http.Get, "tracks/{trackKey}/download-info")] -public class YTrackDownloadInfoBuilder : YRequestBuilder>, (string trackKey, bool direct)> +public class YTrackDownloadInfoBuilder : YMusicRequestBuilder?, (string trackKey, bool direct)> { - public YTrackDownloadInfoBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + public YTrackDownloadInfoBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "tracks/{trackKey}/download-info"; protected override Dictionary GetSubstitutions((string trackKey, bool direct) tuple) - { - return new Dictionary { - { "trackKey", tuple.trackKey } - }; - } - + => new() { { "trackKey", tuple.trackKey } }; protected override NameValueCollection GetQueryParams((string trackKey, bool direct) tuple) - { - return new NameValueCollection { - { "direct", tuple.direct.ToString() } - }; - } + => new() { { "direct", tuple.direct.ToString() } }; } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Ugc/YUgcGetUploadLinkBuilder.cs b/YandexMusic.API/Requests/Ugc/YUgcGetUploadLinkBuilder.cs index c9d8dee..30bfdae 100644 --- a/YandexMusic.API/Requests/Ugc/YUgcGetUploadLinkBuilder.cs +++ b/YandexMusic.API/Requests/Ugc/YUgcGetUploadLinkBuilder.cs @@ -1,32 +1,24 @@ using System.Collections.Specialized; using System.Globalization; using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Playlist; using YandexMusic.API.Models.Ugc; -using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Ugc; -[YWebApiRequest(WebRequestMethods.Http.Get, "handlers/ugc-upload.jsx")] -public class YUgcGetUploadLinkBuilder : YRequestBuilder +public class YUgcGetUploadLinkBuilder : YMusicRequestBuilder { - private static readonly Lazy Random = new(() => new Random()); - - public YUgcGetUploadLinkBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } - + private static readonly Random _random = new(); + public YUgcGetUploadLinkBuilder(YandexMusicApi api) : base(api) { } + protected override string Method => WebRequestMethods.Http.Get; + protected override string PathTemplate => "handlers/ugc-upload.jsx"; protected override NameValueCollection GetQueryParams((YPlaylist playlist, string fileName) tuple) - { - return new NameValueCollection { + => new() + { { "filename", tuple.fileName }, { "kind", tuple.playlist.Kind }, { "visibility", "private" }, { "external-domain", "music.yandex.ru" }, - { "ncrnd", Random.Value.NextDouble().ToString(CultureInfo.InvariantCulture) } + { "ncrnd", _random.NextDouble().ToString(CultureInfo.InvariantCulture) } }; - } } \ No newline at end of file diff --git a/YandexMusic.API/Requests/Ugc/YUgcUploadBuilder.cs b/YandexMusic.API/Requests/Ugc/YUgcUploadBuilder.cs index d326612..eb05a58 100644 --- a/YandexMusic.API/Requests/Ugc/YUgcUploadBuilder.cs +++ b/YandexMusic.API/Requests/Ugc/YUgcUploadBuilder.cs @@ -1,31 +1,21 @@ using System.Net; - -using YandexMusic.API.Common; using YandexMusic.API.Models.Common; using YandexMusic.API.Requests.Common; -using YandexMusic.API.Requests.Common.Attributes; namespace YandexMusic.API.Requests.Ugc; -[YRequest(WebRequestMethods.Http.Post, "{postTargetLink}")] -public class YUgcUploadBuilder : YRequestBuilder, (string postTargetLink, byte[] fileBytes)> +/// Загрузка трека – специальный запрос на произвольный URL. +public class YUgcUploadBuilder : YJsonRequestBuilder?, (string postTargetLink, byte[] fileBytes)> { - public YUgcUploadBuilder(YandexMusicApi yandex, AuthStorage auth) : base(yandex, auth) - { - } + public YUgcUploadBuilder(YandexMusicApi api) : base(api) { } + protected override string BaseUrl => ""; + + protected override string Method => WebRequestMethods.Http.Post; + + protected override string PathTemplate => "{postTargetLink}"; protected override Dictionary GetSubstitutions((string postTargetLink, byte[] fileBytes) tuple) - { - return new Dictionary { - { "postTargetLink", tuple.postTargetLink } - }; - } - - protected override HttpContent GetContent((string postTargetLink, byte[] fileBytes) tuple) - { - return new MultipartFormDataContent { - { new ByteArrayContent(tuple.fileBytes), "file" } - }; - } - + => new() { { "postTargetLink", tuple.postTargetLink } }; + protected override HttpContent? GetContent((string postTargetLink, byte[] fileBytes) tuple) + => new MultipartFormDataContent { { new ByteArrayContent(tuple.fileBytes), "file" } }; } \ No newline at end of file diff --git a/YandexMusic.API/YandexMusicApi.cs b/YandexMusic.API/YandexMusicApi.cs index 43eda6f..b05b7ad 100644 --- a/YandexMusic.API/YandexMusicApi.cs +++ b/YandexMusic.API/YandexMusicApi.cs @@ -1,8 +1,15 @@ -namespace YandexMusic.API; +using YandexMusic.API.Common; + +namespace YandexMusic.API; /// Главный класс API Яндекс Музыки. Предоставляет доступ ко всем веткам API. public class YandexMusicApi { + /// HttpClient, используемый для всех запросов. + public HttpClient HttpClient { get; } + /// Хранилище данных авторизации. + public AuthStorage Storage { get; } + /// API для работы с альбомами. public YAlbumAPI Album { get; internal set; } = null!; /// API для работы с исполнителями. @@ -32,9 +39,16 @@ public class YandexMusicApi /// API для работы с протоколом Ynison (WebSocket). public YYnisonAPI Ynison { get; internal set; } = null!; - /// Создаёт экземпляр API с инициализацией всех подсистем. - public YandexMusicApi() + /// + /// Инициализирует новый экземпляр API. + /// + /// HttpClient для выполнения запросов. + /// Хранилище авторизации. + public YandexMusicApi(HttpClient httpClient, AuthStorage storage) { + HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + Storage = storage ?? throw new ArgumentNullException(nameof(storage)); + Album = new YAlbumAPI(this); Artist = new YArtistAPI(this); Label = new YLabelAPI(this); diff --git a/YandexMusic/YandexMusicClient.cs b/YandexMusic/YandexMusicClient.cs index bb0b748..8c175d3 100644 --- a/YandexMusic/YandexMusicClient.cs +++ b/YandexMusic/YandexMusicClient.cs @@ -22,6 +22,8 @@ public class YandexMusicClient : IDisposable { private readonly YandexMusicApi _api; private readonly AuthStorage _storage; + private readonly HttpClient _httpClient; + private readonly bool _ownsHttpClient; private YnisonPlayer? _player; /// Информация об аккаунте (после авторизации). @@ -33,463 +35,340 @@ public class YandexMusicClient : IDisposable /// Плеер Ynison (WebSocket) для синхронизации воспроизведения. public YnisonPlayer? Ynison => _player; - /// Создаёт новый экземпляр клиента Яндекс Музыки. - public YandexMusicClient() + /// HttpClient, используемый клиентом (можно получить для настройки кук). + public HttpClient HttpClient => _httpClient; + + /// Создаёт новый экземпляр клиента с собственным HttpClient. + public YandexMusicClient() : this(YandexMusicHttpClientFactory.CreateDefault()) { - _api = new YandexMusicApi(); + _ownsHttpClient = true; + } + + /// + /// Создаёт новый экземпляр клиента с указанным HttpClient. + /// + /// Экземпляр HttpClient (должен быть настроен с нужными куками, таймаутами). + /// Если true, клиент будет отвечать за освобождение HttpClient при Dispose. + public YandexMusicClient(HttpClient httpClient) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _storage = new AuthStorage(); + _api = new YandexMusicApi(_httpClient, _storage); } #region Авторизация - /// - /// Авторизация по токену - /// - /// Токен авторизации - /// + /// Авторизация по готовому OAuth-токену. public async Task Authorize(string token) { - await _api.User.AuthorizeAsync(_storage, token); + await _api.User.AuthorizeAsync(token); return _storage.IsAuthorized; } - /// - /// Создание сеанса и получение доступных методов авторизации - /// - /// Имя пользователя - /// - public Task CreateAuthSession(string userName) - { - return _api.User.CreateAuthSessionAsync(_storage, userName); - } + /// Создание сеанса и получение доступных методов авторизации. + public Task CreateAuthSession(string userName) + => _api.User.CreateAuthSessionAsync(userName); - /// - /// Получение ссылки на QR-код - /// - /// - public Task GetAuthQRLink() - { - return _api.User.GetAuthQRLinkAsync(_storage); - } + /// Получение ссылки на QR-код для авторизации. + public Task GetAuthQRLink() + => _api.User.GetAuthQRLinkAsync(); - /// - /// Авторизация по QR-коду - /// - /// - public Task AuthorizeByQR() - { - return _api.User.AuthorizeByQRAsync(_storage); - } + /// Авторизация по QR-коду (после сканирования). + public Task AuthorizeByQR() + => _api.User.AuthorizeByQRAsync(); - /// - /// Получение - /// - /// - public Task GetCaptcha() - { - return _api.User.GetCaptchaAsync(_storage); - } + /// Получение капчи. + public Task GetCaptcha() + => _api.User.GetCaptchaAsync(); - /// - /// Авторизация по captcha - /// - /// Значение captcha - /// - public Task AuthorizeByCaptcha(string captcha) - { - return _api.User.AuthorizeByCaptchaAsync(_storage, captcha); - } + /// Авторизация с вводом капчи. + public Task AuthorizeByCaptcha(string captcha) + => _api.User.AuthorizeByCaptchaAsync(captcha); - /// - /// Получение письма авторизации на почту пользователя - /// - /// - public Task GetAuthLetter() - { - return _api.User.GetAuthLetterAsync(_storage); - } + /// Запрос письма для авторизации. + public Task GetAuthLetter() + => _api.User.GetAuthLetterAsync(); - /// - /// Авторизация после подтверждения входа через письмо - /// - /// + /// Подтверждение авторизации по письму. public Task AuthorizeByLetter() - { - return _api.User.AuthorizeByLetterAsync(_storage); - } + => _api.User.AuthorizeByLetterAsync(); - /// - /// Авторизация с помощью пароля из приложения Яндекс - /// - /// Пароль - /// - public Task AuthorizeByAppPassword(string password) - { - return _api.User.AuthorizeByAppPasswordAsync(_storage, password); - } + /// Авторизация по паролю приложения. + public Task AuthorizeByAppPassword(string password) + => _api.User.AuthorizeByAppPasswordAsync(password); - /// - /// Получение после авторизации с помощью QR, e-mail, пароля из приложения - /// - public Task GetAccessToken() - { - return _api.User.GetAccessTokenAsync(_storage); - } + /// Получение AccessToken после успешной авторизации. + public Task GetAccessToken() + => _api.User.GetAccessTokenAsync(); - /// - /// Получение информации о пользователе через логин Яндекса - /// - public Task GetLoginInfo() - { - return _api.User.GetLoginInfoAsync(_storage); - } + /// Получение информации о пользователе через логин Яндекса. + public Task GetLoginInfo() + => _api.User.GetLoginInfoAsync(); - #endregion Авторизация + #endregion #region Треки + /// Получает трек по идентификатору. public async Task GetTrackAsync(string id) - { - var response = await _api.Track.GetAsync(_storage, id); - return response?.Result?.FirstOrDefault(); - } + => (await _api.Track.GetAsync(id)); /// Получает список треков по идентификаторам. public async Task> GetTracksAsync(IEnumerable ids) - { - var response = await _api.Track.GetAsync(_storage, ids); - return response?.Result ?? new List(); - } + => (await _api.Track.GetAsync(ids)) ?? new List(); + #endregion #region Альбомы + /// Получает альбом по идентификатору. - public async Task GetAlbumAsync(string id) - { - var response = await _api.Album.GetAsync(_storage, id); - return response?.Result; - } + public Task GetAlbumAsync(string id) + => _api.Album.GetAsync(id); /// Получает список альбомов по идентификаторам. public async Task> GetAlbumsAsync(IEnumerable ids) - { - var response = await _api.Album.GetAsync(_storage, ids); - return response?.Result ?? new List(); - } + => (await _api.Album.GetAsync(ids)) ?? new List(); + #endregion #region Главная страница + /// Получает персональные блоки лендинга. - public async Task GetLandingAsync(params YLandingBlockType[] blocks) - { - var response = await _api.Landing.GetAsync(_storage, blocks); - return response?.Result; - } + public Task GetLandingAsync(params YLandingBlockType[] blocks) + => _api.Landing.GetAsync(blocks); /// Получает ленту событий. - public async Task GetFeedAsync() - { - var response = await _api.Landing.GetFeedAsync(_storage); - return response?.Result; - } + public Task GetFeedAsync() + => _api.Landing.GetFeedAsync(); /// Получает детский лендинг. - public async Task GetChildrenLandingAsync() - { - var response = await _api.Landing.GetChildrenLandingAsync(_storage); - return response?.Result; - } + public Task GetChildrenLandingAsync() + => _api.Landing.GetChildrenLandingAsync(); + #endregion #region Исполнители + /// Получает информацию об исполнителе. - public async Task GetArtistAsync(string id) - { - var response = await _api.Artist.GetAsync(_storage, id); - return response?.Result; - } + public Task GetArtistAsync(string id) + => _api.Artist.GetAsync(id); /// Получает список исполнителей. public async Task> GetArtistsAsync(IEnumerable ids) - { - var response = await _api.Artist.GetAsync(_storage, ids); - return response?.Result ?? new List(); - } + => (await _api.Artist.GetAsync(ids)) ?? new List(); + #endregion #region Плейлисты + /// Получает плейлист по пользователю и идентификатору. - public async Task GetPlaylistAsync(string user, string id) - { - var response = await _api.Playlist.GetAsync(_storage, user, id); - return response?.Result; - } + public Task GetPlaylistAsync(string user, string id) + => _api.Playlist.GetAsync(user, id); /// Получает плейлист по UUID. - public async Task GetPlaylistAsync(string uuid) - { - var response = await _api.Playlist.GetAsync(_storage, uuid); - return response?.Result; - } + public Task GetPlaylistAsync(string uuid) + => _api.Playlist.GetAsync(uuid); /// Получает список плейлистов по списку пар (пользователь, идентификатор). public async Task> GetPlaylistsAsync(IEnumerable<(string user, string id)> ids) - { - var response = await _api.Playlist.GetAsync(_storage, ids); - return response?.Result ?? new List(); - } + => (await _api.Playlist.GetAsync(ids)) ?? new List(); /// Получает персональные плейлисты (с главной страницы). - public async Task> GetPersonalPlaylistsAsync() - { - var playlists = await _api.Playlist.GetPersonalPlaylistsAsync(_storage); - return playlists.Select(r => r.Result).Where(p => p != null).ToList()!; - } + public Task> GetPersonalPlaylistsAsync() + => _api.Playlist.GetPersonalPlaylistsAsync(); /// Получает избранные плейлисты. public async Task> GetFavoritesAsync() - { - var response = await _api.Playlist.FavoritesAsync(_storage); - return response?.Result ?? new List(); - } + => (await _api.Playlist.FavoritesAsync()) ?? new List(); /// Получает плейлист «Дежавю». - public async Task GetDejaVuAsync() - { - var response = await _api.Playlist.DejaVuAsync(_storage); - return response?.Result; - } + public Task GetDejaVuAsync() + => _api.Playlist.DejaVuAsync(); /// Получает плейлист «Тайник». - public async Task GetMissedAsync() - { - var response = await _api.Playlist.MissedAsync(_storage); - return response?.Result; - } + public Task GetMissedAsync() + => _api.Playlist.MissedAsync(); /// Получает плейлист дня. - public async Task GetOfTheDayAsync() - { - var response = await _api.Playlist.OfTheDayAsync(_storage); - return response?.Result; - } + public Task GetOfTheDayAsync() + => _api.Playlist.OfTheDayAsync(); /// Получает плейлист «Кинопоиск». - public async Task GetKinopoiskAsync() - { - var response = await _api.Playlist.KinopoiskAsync(_storage); - return response?.Result; - } + public Task GetKinopoiskAsync() + => _api.Playlist.KinopoiskAsync(); /// Получает плейлист «Премьера». - public async Task GetPremiereAsync() - { - var response = await _api.Playlist.PremiereAsync(_storage); - return response?.Result; - } + public Task GetPremiereAsync() + => _api.Playlist.PremiereAsync(); /// Создаёт новый плейлист с заданным именем. - public async Task CreatePlaylistAsync(string name) - { - var response = await _api.Playlist.CreateAsync(_storage, name); - return response?.Result; - } + public Task CreatePlaylistAsync(string name) + => _api.Playlist.CreateAsync(name); + #endregion #region Поиск + /// Выполняет поиск. - public async Task SearchAsync(string searchText, YSearchType searchType, int page = 0, int pageSize = 20) - { - var response = await _api.Search.SearchAsync(_storage, searchText, searchType, page, pageSize); - return response?.Result; - } + public Task SearchAsync(string searchText, YSearchType searchType, int page = 0, int pageSize = 20) + => _api.Search.SearchAsync(searchText, searchType, page, pageSize); /// Получает подсказки для поискового запроса. - public async Task GetSearchSuggestionsAsync(string searchText) - { - var response = await _api.Search.SuggestAsync(_storage, searchText); - return response?.Result; - } + public Task GetSearchSuggestionsAsync(string searchText) + => _api.Search.GetSearchSuggestionsAsync(searchText); + #endregion #region Библиотека + /// Получает лайкнутые треки. public async Task> GetLikedTracksAsync() { - var likes = await _api.Library.GetLikedTracksAsync(_storage); - var ids = likes.Result?.Library.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty(); + var likes = await _api.Library.GetLikedTracksAsync(); + var ids = likes?.Library?.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty(); if (ids.Length == 0) return new List(); - var response = await _api.Track.GetAsync(_storage, ids); - return response?.Result ?? new List(); + var tracks = await _api.Track.GetAsync(ids); + return tracks ?? new List(); } /// Получает дизлайкнутые треки. public async Task> GetDislikedTracksAsync() { - var dislikes = await _api.Library.GetDislikedTracksAsync(_storage); - var ids = dislikes.Result?.Library.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty(); + var dislikes = await _api.Library.GetDislikedTracksAsync(); + var ids = dislikes?.Library?.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty(); if (ids.Length == 0) return new List(); - var response = await _api.Track.GetAsync(_storage, ids); - return response?.Result ?? new List(); + var tracks = await _api.Track.GetAsync(ids); + return tracks ?? new List(); } /// Получает лайкнутые альбомы. public async Task> GetLikedAlbumsAsync() { - var albums = await _api.Library.GetLikedAlbumsAsync(_storage); - var ids = albums.Result?.Select(a => a.Id).ToArray() ?? Array.Empty(); + var albums = await _api.Library.GetLikedAlbumsAsync(); + var ids = albums?.Select(a => a.Id).ToArray() ?? Array.Empty(); if (ids.Length == 0) return new List(); - var response = await _api.Album.GetAsync(_storage, ids); - return response?.Result ?? new List(); + var result = await _api.Album.GetAsync(ids); + return result ?? new List(); } /// Получает лайкнутых исполнителей. public async Task> GetLikedArtistsAsync() { - var artists = await _api.Library.GetLikedArtistsAsync(_storage); - var ids = artists.Result?.Select(a => a.Id).ToArray() ?? Array.Empty(); + var artists = await _api.Library.GetLikedArtistsAsync(); + var ids = artists?.Select(a => a.Id).ToArray() ?? Array.Empty(); if (ids.Length == 0) return new List(); - var response = await _api.Artist.GetAsync(_storage, ids); - return response?.Result ?? new List(); + var result = await _api.Artist.GetAsync(ids); + return result ?? new List(); } /// Получает дизлайкнутых исполнителей. public async Task> GetDislikedArtistsAsync() { - var artists = await _api.Library.GetDislikedArtistsAsync(_storage); - var ids = artists.Result?.Select(a => a.Id).ToArray() ?? Array.Empty(); + var artists = await _api.Library.GetDislikedArtistsAsync(); + var ids = artists?.Select(a => a.Id).ToArray() ?? Array.Empty(); if (ids.Length == 0) return new List(); - var response = await _api.Artist.GetAsync(_storage, ids); - return response?.Result ?? new List(); + var result = await _api.Artist.GetAsync(ids); + return result ?? new List(); } /// Получает лайкнутые плейлисты. public async Task> GetLikedPlaylistsAsync() { - var playlists = await _api.Library.GetLikedPlaylistsAsync(_storage); - var ids = playlists.Result? + var playlists = await _api.Library.GetLikedPlaylistsAsync(); + var ids = playlists? .Select(p => (p.Playlist.Uid, p.Playlist.Kind)) .ToArray() ?? Array.Empty<(string, string)>(); if (ids.Length == 0) return new List(); - var response = await _api.Playlist.GetAsync(_storage, ids); - return response?.Result ?? new List(); + var result = await _api.Playlist.GetAsync(ids); + return result ?? new List(); } /// Получает список недавно прослушанных треков. - /// Типы контекстов (альбом, исполнитель, плейлист). - /// Количество треков на контекст. - /// Количество контекстов. public async Task> GetRecentlyListenedAsync( IEnumerable contextTypes, int trackCount = 50, int contextCount = 10) { - var response = await _api.Library.GetRecentlyListenedAsync(_storage, contextTypes, trackCount, contextCount); - return response?.Result?.Contexts ?? new List(); + var response = await _api.Library.GetRecentlyListenedAsync(contextTypes, trackCount, contextCount); + return response?.Contexts ?? new List(); } + #endregion #region Радио + /// Получает список рекомендованных радиостанций. public async Task> GetRadioDashboardAsync() { - var response = await _api.Radio.GetStationsDashboardAsync(_storage); - return response?.Result?.Stations ?? new List(); + var dashboard = await _api.Radio.GetStationsDashboardAsync(); + return dashboard?.Stations ?? new List(); } /// Получает список всех радиостанций. public async Task> GetRadioStationsAsync() - { - var response = await _api.Radio.GetStationsAsync(_storage); - return response?.Result ?? new List(); - } + => (await _api.Radio.GetStationsAsync()) ?? new List(); /// Получает информацию о радиостанции по идентификатору. public async Task GetRadioStationAsync(YStationId id) - { - var response = await _api.Radio.GetStationAsync(_storage, id); - return response?.Result?.FirstOrDefault(); - } + => (await _api.Radio.GetStationAsync(id))?.FirstOrDefault(); + #endregion #region Очереди + /// Получает все очереди с разных устройств. - public async Task GetQueuesAsync(string? device = null) - { - var response = await _api.Queue.ListAsync(_storage, device); - return response?.Result; - } + public Task GetQueuesAsync(string? device = null) + => _api.Queue.ListAsync(device); /// Получает очередь по идентификатору. - public async Task GetQueueAsync(string queueId) - { - var response = await _api.Queue.GetAsync(_storage, queueId); - return response?.Result; - } + public Task GetQueueAsync(string queueId) + => _api.Queue.GetAsync(queueId); /// Создаёт новую очередь. - public async Task CreateQueueAsync(YQueue queue, string? device = null) - { - var response = await _api.Queue.CreateAsync(_storage, queue, device); - return response?.Result; - } + public Task CreateQueueAsync(YQueue queue, string? device = null) + => _api.Queue.CreateAsync(queue, device); /// Обновляет позицию в очереди. - public async Task UpdateQueuePositionAsync(string queueId, int currentIndex, bool isInteractive, string? device = null) - { - var response = await _api.Queue.UpdatePositionAsync(_storage, queueId, currentIndex, isInteractive, device); - return response?.Result; - } + public Task UpdateQueuePositionAsync(string queueId, int currentIndex, bool isInteractive, string? device = null) + => _api.Queue.UpdatePositionAsync(queueId, currentIndex, isInteractive, device); + #endregion #region Загрузка треков (UGC) + /// Загружает трек из файла в плейлист. - public async Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, string filePath) - { - var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName); - var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, filePath); - return response?.Result; - } + public Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, string filePath) + => _api.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, filePath); /// Загружает трек из потока в плейлист. - public async Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, Stream stream) - { - var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName); - var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, stream); - return response?.Result; - } + public Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, Stream stream) + => _api.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, stream); /// Загружает трек из массива байтов в плейлист. - public async Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, byte[] file) - { - var uploadLink = await _api.UserGeneratedContent.GetUgcUploadLinkAsync(_storage, playlist, fileName); - var response = await _api.UserGeneratedContent.UploadUgcTrackAsync(_storage, uploadLink.PostTarget, file); - return response?.Result; - } + public Task UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, byte[] file) + => _api.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, file); + #endregion #region Лейблы + /// Получает альбомы лейбла с пагинацией. public async Task> GetAlbumsByLabelAsync(YLabel label, int page = 0) - { - var response = await _api.Label.GetAlbumsByLabelAsync(_storage, label, page); - return response?.Result?.Albums ?? new List(); - } + => (await _api.Label.GetAlbumsByLabelAsync(label, page))?.Albums ?? new List(); /// Получает артистов лейбла с пагинацией. public async Task> GetArtistsByLabelAsync(YLabel label, int page = 0) - { - var response = await _api.Label.GetArtistsByLabelAsync(_storage, label, page); - return response?.Result?.Artists ?? new List(); - } + => (await _api.Label.GetArtistsByLabelAsync(label, page))?.Artists ?? new List(); + #endregion #region Ynison (WebSocket плеер) + /// Подключается к Ynison и запускает синхронизацию состояния плеера. public async Task ConnectYnisonAsync() { if (_player == null) - _player = _api.Ynison.GetPlayer(_storage); + _player = _api.Ynison.GetPlayer(); await _player.ConnectAsync(); } @@ -499,9 +378,11 @@ public class YandexMusicClient : IDisposable if (_player != null) await _player.DisconnectAsync(); } + #endregion #region IDisposable + private bool _disposed; /// Освобождает ресурсы. @@ -509,8 +390,11 @@ public class YandexMusicClient : IDisposable { if (_disposed) return; _player?.Dispose(); + if (_ownsHttpClient) + _httpClient.Dispose(); _disposed = true; GC.SuppressFinalize(this); } + #endregion } \ No newline at end of file