diff --git a/README.md b/README.md index e859ba7..57ab3b3 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,6 @@ Add [nuget package](https://www.nuget.org/packages/YandexMusicResolver/) to your ``` dotnet add package YandexMusicResolver ``` -or -``` -Install-Package YandexMusicResolver -Version 2.0.0 -```
  • @@ -57,7 +53,7 @@ Example code for getting direct track download url: var fileYandexConfig = new FileYandexConfig("yandex.config"); fileYandexConfig.Load(); var yandexMusicMainResolver = new YandexMusicMainResolver(fileYandexConfig); -var directUrl = await yandexMusicMainResolver.DirectUrlLoader.GetDirectUrl("55561798", "mp3"); +var directUrl = await yandexMusicMainResolver.DirectUrlLoader.GetDirectUrl("55561798"); Console.WriteLine(directUrl); ``` **Warn:** Yandex will return a link to a 30-seconds track if you do not log in (do not use a config with a valid token). diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 74114ad..d1e9049 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,9 @@ +# v3.0.0 +- ***BREAKING CHANGES***: `LoadPlaylist` -> `LoadAlbum`, remove `almubId` from `LoadTrack`, remove track `Metadata` `IsStream`, rework `YandexMusicMainResolver` and `YandexMusicSearchResultLoader` ctors, new `YandexMusicAuth` method names, ***`Load` in config now called by loaders and can be called multiple times*** +- Global type system rework: playlist now differs from album, no more meta classes as public API, new load on demand system for tracks in playlists and albums, remove `AudioTrackInfo`, now `YandexMusicTrack` are standalone, remove `IAudioItem` - `ResolveQueue` now return `YandexMusicSearchResult` and other changes +- Extend MainResolver `ResolveQuery` functionality + + # v2.2.1 - Add some missing xml docs diff --git a/YandexMusicResolver.Tests/EnvironmentConfig.cs b/YandexMusicResolver.Tests/EnvironmentConfig.cs index eef5c1d..5c3f410 100644 --- a/YandexMusicResolver.Tests/EnvironmentConfig.cs +++ b/YandexMusicResolver.Tests/EnvironmentConfig.cs @@ -5,7 +5,9 @@ namespace YandexMusicResolver.Tests { public class EnvironmentConfig : IYandexConfig { + private bool isLoaded; public void Load() { + if (isLoaded) return; YandexLogin = Environment.GetEnvironmentVariable("YandexLogin"); YandexPassword = Environment.GetEnvironmentVariable("YandexPassword"); YandexToken = Environment.GetEnvironmentVariable("YandexToken"); @@ -14,6 +16,8 @@ public void Load() { if (proxyUrl != null) { YandexProxy = new WebProxy(proxyUrl); } + + isLoaded = true; } public void Save() { } diff --git a/YandexMusicResolver.Tests/YandexMusicMainResolverTest.cs b/YandexMusicResolver.Tests/YandexMusicMainResolverTest.cs index 7a4b32d..108f1e5 100644 --- a/YandexMusicResolver.Tests/YandexMusicMainResolverTest.cs +++ b/YandexMusicResolver.Tests/YandexMusicMainResolverTest.cs @@ -8,7 +8,12 @@ public class YandexMusicMainResolverTest : YandexTestBase{ public void GetTrack(string url) { var audioItem = MainResolver.ResolveQuery(url).GetAwaiter().GetResult(); Assert.NotNull(audioItem); - Assert.IsType(audioItem); + Assert.False(audioItem.IsSearchResult); + Assert.NotNull(audioItem.Tracks); + Assert.Null(audioItem.Playlists); + Assert.Null(audioItem.Albums); + Assert.NotEmpty(audioItem.Tracks); + Assert.Equal(YandexSearchType.Track, YandexSearchType.Track); } [Theory] @@ -16,7 +21,12 @@ public void GetTrack(string url) { public void GetAlbum(string url) { var audioItem = MainResolver.ResolveQuery(url).GetAwaiter().GetResult(); Assert.NotNull(audioItem); - Assert.IsType(audioItem); + Assert.False(audioItem.IsSearchResult); + Assert.NotNull(audioItem.Albums); + Assert.Null(audioItem.Tracks); + Assert.Null(audioItem.Playlists); + Assert.NotEmpty(audioItem.Albums); + Assert.Equal(YandexSearchType.Album, audioItem.Type); } [Theory] @@ -24,16 +34,32 @@ public void GetAlbum(string url) { public void GetPlaylist(string url) { var audioItem = MainResolver.ResolveQuery(url).GetAwaiter().GetResult(); Assert.NotNull(audioItem); - Assert.IsType(audioItem); + Assert.False(audioItem.IsSearchResult); + Assert.NotNull(audioItem.Playlists); + Assert.Null(audioItem.Tracks); + Assert.Null(audioItem.Albums); + Assert.NotEmpty(audioItem.Playlists); + Assert.Equal(YandexSearchType.Playlist, audioItem.Type); } - [Fact] - public void TestDisabledSearch() { - var yandexMusicMainResolver = new YandexMusicMainResolver(Config, false); - var audioItem = yandexMusicMainResolver.ResolveQuery("Take Over").GetAwaiter().GetResult(); + [Theory] + [InlineData("Take over")] + [InlineData("ymsearch:Track:10:Take over")] + public void TestDisabledSearch(string query) { + var yandexMusicMainResolver = new YandexMusicMainResolver(Config) {AllowSearch = false}; + var audioItem = yandexMusicMainResolver.ResolveQuery(query).GetAwaiter().GetResult(); Assert.Null(audioItem); } + [Theory] + [InlineData("Take over", true)] + [InlineData("ymsearch:Track:10:Take over", false)] + public void TestDisabledPlainTextSearch(string query, bool isPlainText) { + var yandexMusicMainResolver = new YandexMusicMainResolver(Config) {PlainTextIsSearchQuery = false}; + var audioItem = yandexMusicMainResolver.ResolveQuery(query).GetAwaiter().GetResult(); + Assert.Equal(isPlainText, audioItem == null); + } + [Theory] [InlineData("Take Over")] public void TestSearch(string url) { diff --git a/YandexMusicResolver.Tests/YandexMusicPlaylistLoaderTest.cs b/YandexMusicResolver.Tests/YandexMusicPlaylistLoaderTest.cs index b79f0b2..94aeaf5 100644 --- a/YandexMusicResolver.Tests/YandexMusicPlaylistLoaderTest.cs +++ b/YandexMusicResolver.Tests/YandexMusicPlaylistLoaderTest.cs @@ -7,21 +7,20 @@ public class YandexMusicPlaylistLoaderTest : YandexTestBase { [InlineData("9425747", "Renovatio", 12)] [InlineData("12033669", "Take Over", 1)] public void LoadAlbum(string albumId, string expectedName, int trackCount) { - TrackFactory = info => new YandexMusicTrack(info, MainResolver); - var playlist = MainResolver.PlaylistLoader.LoadPlaylist(albumId, TrackFactory).GetAwaiter().GetResult(); - Assert.NotNull(playlist); - Assert.Equal(expectedName, playlist.Title); - Assert.Equal(trackCount, playlist.Tracks.Count); + var album = MainResolver.PlaylistLoader.LoadAlbum(albumId).GetAwaiter().GetResult(); + Assert.NotNull(album); + Assert.Equal(expectedName, album.Title); + Assert.Equal(trackCount, album.Data.Count); } [Theory] [InlineData("enlivenbot", "1000", "Test1", 60)] [InlineData("enlivenbot", "1001", "Test2", 36)] public void LoadPlaylist(string userId, string playlistId, string expectedName, int trackCount) { - var playlist = MainResolver.PlaylistLoader.LoadPlaylist(userId, playlistId, TrackFactory).GetAwaiter().GetResult(); + var playlist = MainResolver.PlaylistLoader.LoadPlaylist(userId, playlistId).GetAwaiter().GetResult(); Assert.NotNull(playlist); Assert.Equal(expectedName, playlist.Title); - Assert.Equal(trackCount, playlist.Tracks.Count); + Assert.Equal(trackCount, playlist.Data.Count); } } } \ No newline at end of file diff --git a/YandexMusicResolver.Tests/YandexMusicSearchResultLoaderTest.cs b/YandexMusicResolver.Tests/YandexMusicSearchResultLoaderTest.cs index 7f136ae..da0db66 100644 --- a/YandexMusicResolver.Tests/YandexMusicSearchResultLoaderTest.cs +++ b/YandexMusicResolver.Tests/YandexMusicSearchResultLoaderTest.cs @@ -1,29 +1,27 @@ #nullable enable using Xunit; +using YandexMusicResolver.Config; using YandexMusicResolver.Loaders; namespace YandexMusicResolver.Tests { public class YandexMusicSearchResultLoaderTest : YandexTestBase { [Fact] public void DoTrackSearch() { - var trackSearchResult = MainResolver.SearchResultLoader.LoadSearchResult(YandexSearchType.Track, "Take Over", - MainResolver.PlaylistLoader, TrackFactory).GetAwaiter().GetResult(); + var trackSearchResult = MainResolver.SearchResultLoader.LoadSearchResult(YandexSearchType.Track, "Take Over").GetAwaiter().GetResult(); Assert.NotNull(trackSearchResult); Assert.NotNull(trackSearchResult?.Tracks); } [Fact] public void DoAlbumSearch() { - var trackSearchResult = MainResolver.SearchResultLoader.LoadSearchResult(YandexSearchType.Album, "Take Over", - MainResolver.PlaylistLoader, TrackFactory).GetAwaiter().GetResult(); + var trackSearchResult = MainResolver.SearchResultLoader.LoadSearchResult(YandexSearchType.Album, "Take Over").GetAwaiter().GetResult(); Assert.NotNull(trackSearchResult); Assert.NotNull(trackSearchResult?.Albums); } [Fact] public void DoAllSearch() { - var trackSearchResult = MainResolver.SearchResultLoader.LoadSearchResult(YandexSearchType.All, "Take Over", - MainResolver.PlaylistLoader, TrackFactory).GetAwaiter().GetResult(); + var trackSearchResult = MainResolver.SearchResultLoader.LoadSearchResult(YandexSearchType.All, "Take Over").GetAwaiter().GetResult(); Assert.NotNull(trackSearchResult); Assert.NotNull(trackSearchResult?.Albums); Assert.NotNull(trackSearchResult?.Playlists); @@ -32,8 +30,7 @@ public void DoAllSearch() { [Fact] public void DoPlaylistSearch() { - var trackSearchResult = MainResolver.SearchResultLoader.LoadSearchResult(YandexSearchType.Playlist, "Take Over", - MainResolver.PlaylistLoader, TrackFactory).GetAwaiter().GetResult(); + var trackSearchResult = MainResolver.SearchResultLoader.LoadSearchResult(YandexSearchType.Playlist, "Take Over").GetAwaiter().GetResult(); Assert.NotNull(trackSearchResult); Assert.NotNull(trackSearchResult?.Playlists); } @@ -44,7 +41,8 @@ public void DoPlaylistSearch() { [InlineData("myprefix")] public void TestPrefixes(string? prefix) { #pragma warning disable 8625 - var yandexMusicSearchResultLoader = new YandexMusicSearchResultLoader(null, prefix); + var yandexMusicSearchResultLoader = new YandexMusicSearchResultLoader(new EmptyYandexConfig(), null); + yandexMusicSearchResultLoader.SetSearchPrefix(prefix); #pragma warning restore 8625 prefix ??= "ymsearch"; var correctQuery = $"{prefix}:playlist:25:take over"; diff --git a/YandexMusicResolver.Tests/YandexMusicTrackLoaderTest.cs b/YandexMusicResolver.Tests/YandexMusicTrackLoaderTest.cs index 9237cc7..2a78893 100644 --- a/YandexMusicResolver.Tests/YandexMusicTrackLoaderTest.cs +++ b/YandexMusicResolver.Tests/YandexMusicTrackLoaderTest.cs @@ -8,7 +8,7 @@ public class YandexMusicTrackLoaderTest : YandexTestBase { [InlineData("12033669", "70937156")] public void GetTracksInfo(string albumId, string trackId) { var yandexMusicTrackLoader = new YandexMusicTrackLoader(Config); - var trackInfo = yandexMusicTrackLoader.LoadTrackInfo(albumId, trackId).GetAwaiter().GetResult(); + var trackInfo = yandexMusicTrackLoader.LoadTrack(trackId).GetAwaiter().GetResult(); Assert.NotNull(trackInfo); Assert.Equal($"https://music.yandex.ru/album/{albumId}/track/{trackId}", trackInfo.Uri); } diff --git a/YandexMusicResolver.Tests/YandexTestBase.cs b/YandexMusicResolver.Tests/YandexTestBase.cs index 0fc5c80..e224985 100644 --- a/YandexMusicResolver.Tests/YandexTestBase.cs +++ b/YandexMusicResolver.Tests/YandexTestBase.cs @@ -7,7 +7,6 @@ namespace YandexMusicResolver.Tests { public class YandexTestBase { public IYandexConfig Config; public YandexMusicMainResolver MainResolver; - public Func TrackFactory; public YandexTestBase() { if (File.Exists("TestData.json")) { @@ -17,9 +16,7 @@ public YandexTestBase() { Config = new EnvironmentConfig(); } - Config.Load(); - MainResolver = new YandexMusicMainResolver(Config, true); - TrackFactory = info => new YandexMusicTrack(info, MainResolver); + MainResolver = new YandexMusicMainResolver(Config); } } } \ No newline at end of file diff --git a/YandexMusicResolver/ApiResponceError.cs b/YandexMusicResolver/ApiResponceError.cs index 9d68ab4..db01519 100644 --- a/YandexMusicResolver/ApiResponceError.cs +++ b/YandexMusicResolver/ApiResponceError.cs @@ -1,5 +1,5 @@ using System; -using YandexMusicResolver.Responces; +using YandexMusicResolver.Responses; namespace YandexMusicResolver { /// diff --git a/YandexMusicResolver/AudioItems/IAudioItem.cs b/YandexMusicResolver/AudioItems/IAudioItem.cs deleted file mode 100644 index f390a6b..0000000 --- a/YandexMusicResolver/AudioItems/IAudioItem.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace YandexMusicResolver.AudioItems { - /// - /// Marker interface for all loadable items - /// - public interface IAudioItem { } -} \ No newline at end of file diff --git a/YandexMusicResolver/AudioItems/YandexMusicAlbum.cs b/YandexMusicResolver/AudioItems/YandexMusicAlbum.cs new file mode 100644 index 0000000..e9b0d26 --- /dev/null +++ b/YandexMusicResolver/AudioItems/YandexMusicAlbum.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using YandexMusicResolver.Loaders; + +namespace YandexMusicResolver.AudioItems { + public class YandexMusicAlbum : YandexMusicDataContainer> { + internal YandexMusicAlbum(long id, long year, List artists, string? artworkUrl, long trackCount, string genre, string title, + YandexMusicPlaylistLoader loader) : base(async () => (await loader.LoadAlbum(id.ToString()))!.Data.ToList()) { + Id = id; + Year = year; + Artists = artists; + ArtworkUrl = artworkUrl; + TrackCount = trackCount; + Genre = genre; + Title = title; + } + + internal YandexMusicAlbum(long id, long year, List artists, string? artworkUrl, long trackCount, string genre, string title, + List tracks) : base(tracks) { + Id = id; + Year = year; + Artists = artists; + ArtworkUrl = artworkUrl; + TrackCount = trackCount; + Genre = genre; + Title = title; + } + + + /// + /// Album id + /// + public long Id { get; } + + /// + /// Album release year + /// + public long Year { get; } + + /// + /// Album artists + /// + public List Artists { get; } + + /// + /// Track image uri + /// + public string? ArtworkUrl { get; } + + /// + /// Album tracks count + /// + public long TrackCount { get; } + + /// + /// Album genre + /// + public string Genre { get; } + + /// + /// Track title + /// + public string Title { get; } + } +} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaArtist.cs b/YandexMusicResolver/AudioItems/YandexMusicArtist.cs similarity index 83% rename from YandexMusicResolver/Responces/MetaArtist.cs rename to YandexMusicResolver/AudioItems/YandexMusicArtist.cs index a188ec3..f5d1725 100644 --- a/YandexMusicResolver/Responces/MetaArtist.cs +++ b/YandexMusicResolver/AudioItems/YandexMusicArtist.cs @@ -1,10 +1,10 @@ using Newtonsoft.Json; -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.AudioItems { /// /// Represent a artist in Yandex Music /// - public class MetaArtist { + public class YandexMusicArtist { /// /// Artist ID /// diff --git a/YandexMusicResolver/AudioItems/YandexMusicDataContainer.cs b/YandexMusicResolver/AudioItems/YandexMusicDataContainer.cs new file mode 100644 index 0000000..f807e1f --- /dev/null +++ b/YandexMusicResolver/AudioItems/YandexMusicDataContainer.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; + +namespace YandexMusicResolver.AudioItems { + /// + /// Represents class that contains data which could be loaded + /// + /// + public abstract class YandexMusicDataContainer { + private Func> loadDataFactory; + private Task? _loadDataTask; + + /// + /// Create instance of + /// + /// Data creation factory + public YandexMusicDataContainer(Func> loadDataFactory) { + this.loadDataFactory = loadDataFactory; + } + + /// + /// Create instance of + /// + /// Target data + public YandexMusicDataContainer(T data) { + var task = Task.FromResult(data); + loadDataFactory = () => task; + _loadDataTask = task; + } + + private Task LoadDataTask => _loadDataTask ??= loadDataFactory(); + + /// + /// Load target data + /// + /// Task + public Task LoadDataAsync() { + return LoadDataTask; + } + + /// + /// Return true if data already loaded + /// + public bool IsDataLoaded => _loadDataTask?.IsCompleted ?? false; + + /// + /// Synchronously wait for data loading + /// + public T Data => LoadDataAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + } +} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaOwner.cs b/YandexMusicResolver/AudioItems/YandexMusicOwner.cs similarity index 89% rename from YandexMusicResolver/Responces/MetaOwner.cs rename to YandexMusicResolver/AudioItems/YandexMusicOwner.cs index 9f28480..2da3c9e 100644 --- a/YandexMusicResolver/Responces/MetaOwner.cs +++ b/YandexMusicResolver/AudioItems/YandexMusicOwner.cs @@ -1,10 +1,10 @@ using Newtonsoft.Json; -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.AudioItems { /// /// Represent playlist owner /// - public class MetaOwner { + public class YandexMusicOwner { /// /// Owner ID /// diff --git a/YandexMusicResolver/AudioItems/YandexMusicPlaylist.cs b/YandexMusicResolver/AudioItems/YandexMusicPlaylist.cs index fee8a22..285070e 100644 --- a/YandexMusicResolver/AudioItems/YandexMusicPlaylist.cs +++ b/YandexMusicResolver/AudioItems/YandexMusicPlaylist.cs @@ -1,35 +1,62 @@ -using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using YandexMusicResolver.Loaders; namespace YandexMusicResolver.AudioItems { /// /// Represents playlist from Yandex Music /// - public class YandexMusicPlaylist : IAudioItem { - /// - /// Initializes a new instance of the class. - /// - /// Playlist title - /// Collection with tracks - /// Is this playlist is search result - public YandexMusicPlaylist(string title, ReadOnlyCollection tracks, bool isSearchResult) { + public class YandexMusicPlaylist : YandexMusicDataContainer> { + internal YandexMusicPlaylist(long uid, long kind, long trackCount, string title, YandexMusicOwner owner, string? artworkUrl, + YandexMusicPlaylistLoader loader) : + base(async () => (await loader.LoadPlaylist(owner.Login, kind.ToString()))!.Data.ToList()) { + Uid = uid; + Kind = kind; + TrackCount = trackCount; Title = title; - Tracks = tracks; - IsSearchResult = isSearchResult; + Owner = owner; + ArtworkUrl = artworkUrl; } + internal YandexMusicPlaylist(long uid, long kind, long trackCount, string title, YandexMusicOwner owner, string? artworkUrl, + List tracks) : base(tracks) { + Uid = uid; + Kind = kind; + TrackCount = trackCount; + Title = title; + Owner = owner; + ArtworkUrl = artworkUrl; + } + + /// + /// Playlist UID + /// + public long Uid { get; } + + /// + /// Playlist kind (something like the user's playlist index) + /// + public long Kind { get; } + + /// + /// Playlist tracks count + /// + public long TrackCount { get; } + /// /// Playlist title /// public string Title { get; } /// - /// Collection with tracks in playlist + /// Playlist owner /// - public ReadOnlyCollection Tracks { get; } + public YandexMusicOwner Owner { get; } /// - /// Is this playlist a search result + /// Playlist artwork url /// - public bool IsSearchResult { get; } + public string? ArtworkUrl { get; } } } \ No newline at end of file diff --git a/YandexMusicResolver/AudioItems/YandexMusicSearchResult.cs b/YandexMusicResolver/AudioItems/YandexMusicSearchResult.cs index c857eca..93d7db9 100644 --- a/YandexMusicResolver/AudioItems/YandexMusicSearchResult.cs +++ b/YandexMusicResolver/AudioItems/YandexMusicSearchResult.cs @@ -1,19 +1,25 @@ -using System.Collections.ObjectModel; -using YandexMusicResolver.Responces; +using System.Collections.Generic; +using System.Collections.ObjectModel; namespace YandexMusicResolver.AudioItems { /// /// Represents YandexMusic search result /// - public class YandexMusicSearchResult : IAudioItem { - public YandexMusicSearchResult(string query, int limit, YandexSearchType type, ReadOnlyCollection? albums, - ReadOnlyCollection? playlists, ReadOnlyCollection? tracks) { + public class YandexMusicSearchResult { + internal YandexMusicSearchResult(string query, + bool isSearchResult, + YandexSearchType type, + IReadOnlyCollection? albums, + IReadOnlyCollection? playlists, + IReadOnlyCollection? tracks, + int? limit = null) { Query = query; Limit = limit; Type = type; Albums = albums; Playlists = playlists; Tracks = tracks; + IsSearchResult = isSearchResult; } /// @@ -22,31 +28,37 @@ public YandexMusicSearchResult(string query, int limit, YandexSearchType type, R public string Query { get; } /// - /// Tracks limit count + /// Tracks limit count. + /// Will be null if the is false /// - public int Limit { get; } + public int? Limit { get; } /// /// Search data type /// public YandexSearchType Type { get; } + /// + /// Is this playlist a search result + /// + public bool IsSearchResult { get; } + /// /// Albums list. /// Will be null if the search should not search for albums /// - public ReadOnlyCollection? Albums { get; set; } + public IReadOnlyCollection? Albums { get; set; } /// /// Playlists list. /// Will be null if the search should not search for playlists /// - public ReadOnlyCollection? Playlists { get; set; } + public IReadOnlyCollection? Playlists { get; set; } /// /// Tracks list. /// Will be null if the search should not search for tracks /// - public ReadOnlyCollection? Tracks { get; set; } + public IReadOnlyCollection? Tracks { get; set; } } } \ No newline at end of file diff --git a/YandexMusicResolver/AudioItems/YandexMusicTrack.cs b/YandexMusicResolver/AudioItems/YandexMusicTrack.cs index d9e0f58..3c5b5a6 100644 --- a/YandexMusicResolver/AudioItems/YandexMusicTrack.cs +++ b/YandexMusicResolver/AudioItems/YandexMusicTrack.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -6,37 +8,49 @@ namespace YandexMusicResolver.AudioItems { /// /// AudioTrackInfo wrapper to resolve track direct url /// - public class YandexMusicTrack : IAudioItem { + public class YandexMusicTrack { + internal YandexMusicTrack(string title, List authors, TimeSpan length, string id, string uri, string? artworkUrl = null) { + Title = title; + Authors = authors; + Length = length; + Id = id; + Uri = uri; + ArtworkUrl = artworkUrl; + } + /// - /// Get track info + /// Track title /// - public AudioTrackInfo TrackInfo { get; } + public string Title { get; } - private YandexMusicMainResolver _mainResolver; - private readonly Lazy> _directUrlLoader; + /// + /// Track authors + /// + public List Authors { get; } /// - /// Initializes a new instance of the class. + /// Compose names into single string /// - /// Track info - /// Resolver for direct url getting - public YandexMusicTrack(AudioTrackInfo trackInfo, YandexMusicMainResolver mainResolver) { - _mainResolver = mainResolver; - TrackInfo = trackInfo; - _directUrlLoader = new Lazy>(GetDirectUrlInternal, LazyThreadSafetyMode.ExecutionAndPublication); - } + public string Author => string.Join(", ", Authors.Select(artist => artist.Name)); /// - /// Get direct url to track + /// Track lenght /// - /// If you not authorized will return 30s track version. This is YandexMusic restriction - /// Direct url to download track - public Task GetDirectUrl() { - return _directUrlLoader.Value; - } + public TimeSpan Length { get; } - private async Task GetDirectUrlInternal() { - return await _mainResolver.DirectUrlLoader.GetDirectUrl(TrackInfo.Identifier, "mp3"); - } + /// + /// Track id + /// + public string Id { get; } + + /// + /// Track link + /// + public string Uri { get; } + + /// + /// Track image uri + /// + public string? ArtworkUrl { get; } } } \ No newline at end of file diff --git a/YandexMusicResolver/AudioTrackInfo.cs b/YandexMusicResolver/AudioTrackInfo.cs deleted file mode 100644 index 859cc1c..0000000 --- a/YandexMusicResolver/AudioTrackInfo.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace YandexMusicResolver { - /// - /// Contains info about track - /// - public class AudioTrackInfo { - /// - /// Track title - /// - public string Title { get; } - - /// - /// Track author - /// - public string Author { get; } - - /// - /// Track lenght - /// - public TimeSpan Length { get; } - - /// - /// Track identifier - /// - public string Identifier { get; } - - /// - /// Is track live stream - /// - [Obsolete("There is no streams support in library. \nWill removed in 3.0")] - public bool IsStream { get; } - - /// - /// Track link - /// - public string Uri { get; } - - /// - /// Additional track metadata - /// - [Obsolete("Metadata used only for image uri storing. Use ArtworkUrl property instead. \nWill be removed in 3.0")] - public Dictionary Metadata { get; } - - /// - /// Track image uri - /// - public string? ArtworkUrl { get; } - - public AudioTrackInfo(string title, string author, TimeSpan length, string identifier, bool isStream, string uri, Dictionary metadata) { - Title = title; - Author = author; - Length = length; - Identifier = identifier; - IsStream = isStream; - Uri = uri; - Metadata = metadata; - if (Metadata.TryGetValue("artworkUrl", out var artworkUrl)) { - ArtworkUrl = artworkUrl; - } - } - - public AudioTrackInfo(string title, string author, TimeSpan length, string identifier, bool isStream, string uri, string? artworkUrl = null) { - Title = title; - Author = author; - Length = length; - Identifier = identifier; - IsStream = isStream; - Uri = uri; - Metadata = new Dictionary(); - if (artworkUrl != null) { - Metadata.Add("artworkUrl", artworkUrl); - ArtworkUrl = artworkUrl; - } - } - } -} \ No newline at end of file diff --git a/YandexMusicResolver/Config/FileYandexConfig.cs b/YandexMusicResolver/Config/FileYandexConfig.cs index 4af99a9..de3bb9c 100644 --- a/YandexMusicResolver/Config/FileYandexConfig.cs +++ b/YandexMusicResolver/Config/FileYandexConfig.cs @@ -8,7 +8,8 @@ namespace YandexMusicResolver.Config { /// Represents implementation that stores data in a file /// public class FileYandexConfig : IYandexConfig { - private string? _filePath; + private string _filePath; + private bool isLoaded; /// /// Initializes a new instance of the class. @@ -20,6 +21,7 @@ public FileYandexConfig(string? filePath = null) { /// public virtual void Load() { + if (isLoaded) return; try { if (File.Exists(_filePath)) { var fileYandexConfig = JsonConvert.DeserializeObject(File.ReadAllText(_filePath)); @@ -39,6 +41,8 @@ public virtual void Load() { catch (Exception) { // ignored } + + isLoaded = true; } /// diff --git a/YandexMusicResolver/Config/IYandexConfig.cs b/YandexMusicResolver/Config/IYandexConfig.cs index 2d46f0e..abd9df7 100644 --- a/YandexMusicResolver/Config/IYandexConfig.cs +++ b/YandexMusicResolver/Config/IYandexConfig.cs @@ -8,7 +8,7 @@ namespace YandexMusicResolver.Config { /// public interface IYandexConfig : IYandexProxyTokenHolder { /// - /// Load config + /// Load config. This method can be called multiple times /// void Load(); @@ -37,7 +37,7 @@ public interface IYandexConfig : IYandexProxyTokenHolder { /// Will be thrown if we cant authorize and is false public async Task AuthorizeAsync(bool allowRunWithoutAuth = true) { if (YandexToken != null) - if (await YandexMusicAuth.CheckToken(YandexToken, this)) + if (await YandexMusicAuth.ValidateTokenAsync(YandexToken, this)) return; if (YandexLogin == null || YandexPassword == null) { @@ -46,7 +46,7 @@ public async Task AuthorizeAsync(bool allowRunWithoutAuth = true) { } try { - YandexToken = await YandexMusicAuth.GetToken(YandexLogin, YandexPassword, this); + YandexToken = await YandexMusicAuth.LoginAsync(YandexLogin, YandexPassword, this); Save(); } catch (Exception e) { diff --git a/YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs b/YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs index 9c465a4..eb6ffda 100644 --- a/YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs +++ b/YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs @@ -6,7 +6,7 @@ using System.Xml.Serialization; using YandexMusicResolver.Config; using YandexMusicResolver.Requests; -using YandexMusicResolver.Responces; +using YandexMusicResolver.Responses; namespace YandexMusicResolver.Loaders { /// @@ -20,6 +20,7 @@ public class YandexMusicDirectUrlLoader { /// /// Config instance for performing requests public YandexMusicDirectUrlLoader(IYandexConfig config) { + config.Load(); _config = config; } diff --git a/YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs b/YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs index 075186f..0d73b10 100644 --- a/YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs +++ b/YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs @@ -4,18 +4,22 @@ using YandexMusicResolver.AudioItems; using YandexMusicResolver.Config; using YandexMusicResolver.Requests; -using YandexMusicResolver.Responces; +using YandexMusicResolver.Responses; namespace YandexMusicResolver.Loaders { /// /// Represents class to getting playlists and albums from Yandex Music /// - public class YandexMusicPlaylistLoader : YandexMusicTrackLoader { + public class YandexMusicPlaylistLoader { + protected readonly IYandexConfig Config; + /// /// Initializes a new instance of the class. /// /// Config instance for performing requests - public YandexMusicPlaylistLoader(IYandexConfig config) : base(config) { } + public YandexMusicPlaylistLoader(IYandexConfig config) { + Config = config; + } private const string PlaylistInfoFormat = "https://api.music.yandex.net/users/{0}/playlists/{1}"; private const string AlbumInfoFormat = "https://api.music.yandex.net/albums/{0}/with-tracks"; @@ -24,42 +28,31 @@ public YandexMusicPlaylistLoader(IYandexConfig config) : base(config) { } /// Loads the playlist from Yandex Music /// /// Id of user who created the playlist - /// Target playlist id - /// Track factory to create YandexMusicTrack from AudioTrackInfo + /// Target playlist id /// Playlist instance - public Task LoadPlaylist(string userId, string playlistId, Func trackFactory) { - return LoadPlaylistUrl(string.Format(PlaylistInfoFormat, userId, playlistId), trackFactory); - } + public async Task LoadPlaylist(string userId, string playlistKind) { + string url = string.Format(PlaylistInfoFormat, userId, playlistKind); + var playlistData = await new YandexCustomRequest(Config).Create(url).GetResponseAsync(); + if (playlistData.Tracks == null) { + throw new Exception("Empty playlist found."); + } - /// - /// Loads the album from Yandex Music - /// - /// Target album id - /// Track factory to create YandexMusicTrack from AudioTrackInfo - /// Playlist instance - [Obsolete("For album loading use LoadAlbum method. \nWill be removed in 3.0")] - public Task LoadPlaylist(string albumId, Func trackFactory) { - return LoadPlaylistUrl(string.Format(AlbumInfoFormat, albumId), trackFactory); + return playlistData.ToYaPlaylist(this); } /// /// Loads the album from Yandex Music /// /// Target album id - /// Track factory to create YandexMusicTrack from AudioTrackInfo /// Playlist instance - public Task LoadAlbum(string albumId, Func trackFactory) { - return LoadPlaylistUrl(string.Format(AlbumInfoFormat, albumId), trackFactory); - } - - private async Task LoadPlaylistUrl(string url, Func trackFactory) { - var playlistData = await new YandexCustomRequest(Config).Create(url).GetResponseAsync(); + public async Task LoadAlbum(string albumId) { + string url = string.Format(AlbumInfoFormat, albumId); + var playlistData = await new YandexCustomRequest(Config).Create(url).GetResponseAsync(); if (playlistData.Tracks == null) { throw new Exception("Empty album or playlist found."); } - var tracks = await Task.WhenAll(playlistData.Tracks.Select(async container => trackFactory(await container.ToAudioTrackInfo(this)))); - return new YandexMusicPlaylist(playlistData.Title, tracks.ToList().AsReadOnly(), false); + return playlistData.ToYmAlbum(this); } } } \ No newline at end of file diff --git a/YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs b/YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs index b6c50f3..7c15b73 100644 --- a/YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs +++ b/YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs @@ -5,7 +5,7 @@ using YandexMusicResolver.AudioItems; using YandexMusicResolver.Config; using YandexMusicResolver.Requests; -using YandexMusicResolver.Responces; +using YandexMusicResolver.Responses; namespace YandexMusicResolver.Loaders { /// @@ -18,24 +18,35 @@ public class YandexMusicSearchResultLoader { public const int DefaultLimit = 10; private const string TracksInfoFormat = "https://api.music.yandex.net/search?type={0}&page=0&text={1}"; - private Regex SearchRegex; + private Regex SearchRegex = new Regex($"ymsearch(:([a-zA-Z]+))?(:([0-9]+))?:([^:]+)"); private IYandexConfig _config; + private string _searchPrefix = "ymsearch"; + #pragma warning disable 1591 + protected YandexMusicPlaylistLoader PlaylistLoader; + #pragma warning restore 1591 /// /// Special prefix for complicated requests /// - public string SearchPrefix { get; } + public string SearchPrefix => _searchPrefix; /// /// Initializes a new instance of the class. /// /// Config instance for performing requests - /// - public YandexMusicSearchResultLoader(IYandexConfig config, string? searchPrefix = null) { - // ReSharper disable once StringLiteralTypo - SearchPrefix = searchPrefix ?? "ymsearch"; + public YandexMusicSearchResultLoader(IYandexConfig config, YandexMusicPlaylistLoader playlistLoader) { + PlaylistLoader = playlistLoader; + config.Load(); _config = config; - SearchRegex = new Regex($"{SearchPrefix}(:([a-zA-Z]+))?(:([0-9]+))?:([^:]+)"); + } + + /// + /// Set a new search prefix for complicated queries + /// + /// New prefix. null will be replaced with "ymsearch" + public void SetSearchPrefix(string? prefix = null) { + _searchPrefix = prefix ?? "ymsearch"; + SearchRegex = new Regex($"{_searchPrefix}(:([a-zA-Z]+))?(:([0-9]+))?:([^:]+)"); } /// @@ -43,13 +54,10 @@ public YandexMusicSearchResultLoader(IYandexConfig config, string? searchPrefix /// /// Complicated query is ::limit:text /// Search query. May be complicated or default values will be used - /// Playlist loader instance - /// Track factory to create YandexMusicTrack from AudioTrackInfo /// Instance of YandexMusicSearchResult - public Task LoadSearchResult(string query, YandexMusicPlaylistLoader playlistLoader, - Func trackFactory) { + public Task LoadSearchResult(string query) { TryParseQuery(query, out var text, out var type, out var limit); - return LoadSearchResult(type, text, playlistLoader, trackFactory, limit); + return LoadSearchResult(type, text, limit); } /// @@ -82,24 +90,23 @@ public bool TryParseQuery(string query, out string text, out YandexSearchType ty /// Search type /// Search text /// Playlist loader instance - /// Track factory to create YandexMusicTrack from AudioTrackInfo /// Search results limit count /// Instance of YandexMusicSearchResult /// Throws exception if something went wrong - public async Task LoadSearchResult(YandexSearchType type, string query, YandexMusicPlaylistLoader playlistLoader, - Func trackFactory, int limit = DefaultLimit) { + public async Task LoadSearchResult(YandexSearchType type, string query, int limit = DefaultLimit) { try { var searchResponse = await new YandexCustomRequest(_config) .Create(string.Format(TracksInfoFormat, type, query)) .GetResponseAsync(); - var albums = searchResponse.Albums?.Results.Take(limit); - var playlists = searchResponse.Playlists?.Results.Take(limit); - var tracks = searchResponse.Tracks?.Results.Take(limit); + var albums = searchResponse.Albums?.Results.Take(limit).Select(signature => signature.ToYmAlbum(PlaylistLoader)); + var playlists = searchResponse.Playlists?.Results.Take(limit).Select(signature => signature.ToYaPlaylist(PlaylistLoader)); + var tracks = searchResponse.Tracks?.Results.Take(limit).Select(track => track.ToYmTrack()); - return new YandexMusicSearchResult(query, limit, type, + return new YandexMusicSearchResult(query, true, type, albums?.ToList().AsReadOnly(), playlists?.ToList().AsReadOnly(), - tracks?.ToList().AsReadOnly()); + tracks?.ToList().AsReadOnly(), + limit); } catch (Exception e) { throw new Exception("Could not load search results", e); diff --git a/YandexMusicResolver/Loaders/YandexMusicTrackLoader.cs b/YandexMusicResolver/Loaders/YandexMusicTrackLoader.cs index af304d9..817859d 100644 --- a/YandexMusicResolver/Loaders/YandexMusicTrackLoader.cs +++ b/YandexMusicResolver/Loaders/YandexMusicTrackLoader.cs @@ -5,7 +5,7 @@ using YandexMusicResolver.AudioItems; using YandexMusicResolver.Config; using YandexMusicResolver.Requests; -using YandexMusicResolver.Responces; +using YandexMusicResolver.Responses; namespace YandexMusicResolver.Loaders { /// @@ -22,53 +22,20 @@ public class YandexMusicTrackLoader { /// /// Config instance for performing requests public YandexMusicTrackLoader(IYandexConfig config) { + config.Load(); Config = config; } private const string TracksInfoFormat = "https://api.music.yandex.net/tracks?trackIds="; - /// - /// Load track - /// - /// Album id with track - /// Target track id - /// Track factory to create YandexMusicTrack from AudioTrackInfo - /// Instance of - [Obsolete("We do not need an album ID to load track information. \nWill be removed in 3.0")] - public Task LoadTrack(string albumId, string trackId, Func trackFactory) { - return LoadTrack(trackId, trackFactory); - } - - /// - /// Load track - /// - /// Target track id - /// Track factory to create YandexMusicTrack from AudioTrackInfo - /// Instance of - public async Task LoadTrack(string trackId, Func trackFactory) { - return trackFactory((await LoadTrackInfo(trackId))!); - } - - /// - /// Load track info - /// - /// Album id with track - /// Target track id - /// Instance of - [Obsolete("We do not need an album ID to load track information. \nWill be removed in 3.0")] - public Task LoadTrackInfo(string albumId, string trackId) { - return LoadTrackInfo(trackId); - } - /// /// Load track info /// /// Target track id /// Instance of - public async Task LoadTrackInfo(string trackId) { + public async Task LoadTrack(string trackId) { var response = await new YandexCustomRequest(Config).Create(TracksInfoFormat + trackId).GetResponseAsync>(); - var entry = response.First(); - return await entry.ToAudioTrackInfo(this); + return response.FirstOrDefault()?.ToYmTrack(); } } } \ No newline at end of file diff --git a/YandexMusicResolver/Requests/YandexAuthRequest.cs b/YandexMusicResolver/Requests/YandexAuthRequest.cs index 4fa3b52..8839d79 100644 --- a/YandexMusicResolver/Requests/YandexAuthRequest.cs +++ b/YandexMusicResolver/Requests/YandexAuthRequest.cs @@ -5,7 +5,7 @@ using System.Web; using Newtonsoft.Json; using YandexMusicResolver.Config; -using YandexMusicResolver.Responces; +using YandexMusicResolver.Responses; namespace YandexMusicResolver.Requests { internal class YandexAuthRequest : YandexRequest { diff --git a/YandexMusicResolver/Requests/YandexRequest.cs b/YandexMusicResolver/Requests/YandexRequest.cs index b3ab847..32981c4 100644 --- a/YandexMusicResolver/Requests/YandexRequest.cs +++ b/YandexMusicResolver/Requests/YandexRequest.cs @@ -9,7 +9,7 @@ using System.Web; using Newtonsoft.Json; using YandexMusicResolver.Config; -using YandexMusicResolver.Responces; +using YandexMusicResolver.Responses; namespace YandexMusicResolver.Requests { internal class YandexRequest { diff --git a/YandexMusicResolver/Responces/ITrackInfoContainer.cs b/YandexMusicResolver/Responces/ITrackInfoContainer.cs deleted file mode 100644 index 9ea1f82..0000000 --- a/YandexMusicResolver/Responces/ITrackInfoContainer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; -using YandexMusicResolver.Loaders; - -namespace YandexMusicResolver.Responces { - /// - /// Represents entity from which we can get - /// - public interface ITrackInfoContainer { - /// - /// Get related - /// - /// Track loader instance - /// Instance of - Task ToAudioTrackInfo(YandexMusicTrackLoader loader); - } -} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaAlbumSignature.cs b/YandexMusicResolver/Responces/MetaAlbumSignature.cs deleted file mode 100644 index b7e8b33..0000000 --- a/YandexMusicResolver/Responces/MetaAlbumSignature.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using YandexMusicResolver.AudioItems; -using YandexMusicResolver.Loaders; - -namespace YandexMusicResolver.Responces { - /// - /// Represents data to resolve album - /// - public class MetaAlbumSignature { - /// - /// Id of this entity - /// - [JsonProperty("id")] - public long Id { get; set; } - - /// - /// Title of this album - /// - [JsonProperty("title")] - public string Title { get; set; } = null!; - - /// - /// Cover link - /// - [JsonProperty("coverUri")] - public string? CoverUri { get; set; } - - /// - /// Opengraph image (alternative cover) url - /// - [JsonProperty("ogImage")] - public string? OgImage { get; set; } - - /// - /// Count of tracks - /// - [JsonProperty("trackCount")] - public long TrackCount { get; set; } - - /// - /// Is this playlist available now - /// - [JsonProperty("available")] - public bool Available { get; set; } - - /// - /// Get full playlist with tracks - /// - /// Instance of playlist loader to load playlist - /// Track factory to create YandexMusicTrack from AudioTrackInfo - /// Playlist with tracks - public virtual async Task GetPlaylist(YandexMusicPlaylistLoader yandexMusicPlaylistLoader, - Func trackFactory) { - return (await yandexMusicPlaylistLoader.LoadPlaylist(Id.ToString(), trackFactory))!; - } - } -} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaPlaylist.cs b/YandexMusicResolver/Responces/MetaPlaylist.cs deleted file mode 100644 index 5f86f60..0000000 --- a/YandexMusicResolver/Responces/MetaPlaylist.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; - -namespace YandexMusicResolver.Responces { - internal class MetaPlaylist { - [JsonProperty("title")] - public string Title { get; set; } = null!; - - [JsonProperty("trackCount")] - public long TrackCount { get; set; } - - [JsonProperty("ogImage")] - public string? OgImage { get; set; } - - [JsonProperty("coverUri")] - public string? CoverUri { get; set; } - - [JsonProperty("available")] - public bool Available { get; set; } = true; - - [JsonProperty("tracks")] - private List PlaylistTracks { - set => Tracks = value.Select(container => container.Track).Cast().ToList(); - } - - [JsonProperty("volumes")] - private List> AlbumVolumes { - set => Tracks = value.SelectMany(list => list).Cast().ToList(); - } - - public List Tracks { get; set; } = null!; - } -} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaPlaylistSignature.cs b/YandexMusicResolver/Responces/MetaPlaylistSignature.cs deleted file mode 100644 index c2bcb13..0000000 --- a/YandexMusicResolver/Responces/MetaPlaylistSignature.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using YandexMusicResolver.AudioItems; -using YandexMusicResolver.Loaders; - -namespace YandexMusicResolver.Responces { - /// - /// Represents data to resolve playlist - /// - public class MetaPlaylistSignature : MetaAlbumSignature { - [JsonProperty("uid")] - [Obsolete] - public long PlaylistId { - set => Id = value; - } - - /// - /// Playlist owner info - /// - [JsonProperty("owner")] - public MetaOwner Owner { get; set; } = null!; - - /// - public override async Task GetPlaylist(YandexMusicPlaylistLoader yandexMusicPlaylistLoader, - Func trackFactory) { - return (await yandexMusicPlaylistLoader.LoadPlaylist(Owner.Uid.ToString(), Id.ToString(), trackFactory))!; - } - } -} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaPlaylistTrack.cs b/YandexMusicResolver/Responces/MetaPlaylistTrack.cs deleted file mode 100644 index ca1a3ef..0000000 --- a/YandexMusicResolver/Responces/MetaPlaylistTrack.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; -using YandexMusicResolver.AudioItems; -using YandexMusicResolver.Loaders; - -namespace YandexMusicResolver.Responces { - /// - /// Represent - /// - internal class MetaPlaylistTrack : ITrackInfoContainer { - /// - /// Track id - /// - [JsonProperty("id")] - public long Id { get; set; } - - /// - /// List of albums that contain this track - /// - [JsonProperty("albums")] - public List Albums { get; set; } = null!; - - /// - /// Creation timestamp - /// - [JsonProperty("timestamp")] - public DateTimeOffset Timestamp { get; set; } - - /// - public async Task ToAudioTrackInfo(YandexMusicTrackLoader loader) { - return (await loader.LoadTrack(Albums.First().Id.ToString(), Id.ToString(), TrackFactory))?.TrackInfo!; - } - - private YandexMusicTrack TrackFactory(AudioTrackInfo arg) { - // This is fake for retrieving track info - return new YandexMusicTrack(arg, null!); - } - } -} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaTrack.cs b/YandexMusicResolver/Responces/MetaTrack.cs deleted file mode 100644 index b1007b2..0000000 --- a/YandexMusicResolver/Responces/MetaTrack.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; -using YandexMusicResolver.Converters; -using YandexMusicResolver.Loaders; - -namespace YandexMusicResolver.Responces { - /// - /// Track data from Yandex Music - /// - public class MetaTrack : ITrackInfoContainer { - private const string TrackUrlFormat = "https://music.yandex.ru/album/{0}/track/{1}"; - - /// - /// Track ID - /// - [JsonProperty("id")] - [JsonConverter(typeof(ParseStringConverter))] - public long Id { get; set; } - - /// - /// Track title - /// - [JsonProperty("title")] - public string Title { get; set; } = null!; - - /// - /// Is track available - /// - /// If false, then most likely you are trying to get this information while not in the CIS. At the moment, you need to use a proxy in the CIS or log in with an account that has Yandex Plus (a subscription from Yandex) - [JsonProperty("available")] - public bool Available { get; set; } - - /// - /// Track duration in ms - /// - [JsonProperty("durationMs")] - public long DurationMs { get; set; } - - /// - /// Track authors list - /// - [JsonProperty("artists")] - public MetaArtist[] Artists { get; set; } = null!; - - /// - /// List of albums that contain this track - /// - [JsonProperty("albums")] - public MetaAlbumSignature[] Albums { get; set; } = null!; - - /// - /// Cover link - /// - [JsonProperty("coverUri")] - public string? CoverUri { get; set; } - - /// - /// Opengraph image (alternative cover) link - /// - [JsonProperty("ogImage")] - public string? OgImage { get; set; } - - /// - /// Is lyrics available for this track - /// - [JsonProperty("lyricsAvailable")] - public bool LyricsAvailable { get; set; } - - /// - /// Convert this meta class to - /// - /// Instance of - public Task ToAudioTrackInfo() { - var artists = string.Join(", ", Artists.Select(artist => artist.Name)); - var album = Albums.First(); - - string? artworkUrl = null; - TryApplyArtwork(ref artworkUrl, CoverUri); - TryApplyArtwork(ref artworkUrl, OgImage); - TryApplyArtwork(ref artworkUrl, album.CoverUri); - TryApplyArtwork(ref artworkUrl, album.OgImage); - - return Task.FromResult( - new AudioTrackInfo( - Title, - artists, - TimeSpan.FromMilliseconds(DurationMs), - Id.ToString(), - false, - string.Format(TrackUrlFormat, album.Id, Id), - new Dictionary {{"artworkUrl", artworkUrl!}})); - } - - /// - public Task ToAudioTrackInfo(YandexMusicTrackLoader loader) { - return ToAudioTrackInfo(); - } - - private static void TryApplyArtwork(ref string? final, string? artwork) { - if (final != null || artwork == null) return; - final = "https://" + artwork.Replace("%%", "200x200"); - } - } -} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaAccount.cs b/YandexMusicResolver/Responses/MetaAccount.cs similarity index 95% rename from YandexMusicResolver/Responces/MetaAccount.cs rename to YandexMusicResolver/Responses/MetaAccount.cs index 4ff7a18..9e5a61d 100644 --- a/YandexMusicResolver/Responces/MetaAccount.cs +++ b/YandexMusicResolver/Responses/MetaAccount.cs @@ -3,7 +3,7 @@ #pragma warning disable 8618 -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { internal class MetaAccount { [JsonProperty("now")] public DateTimeOffset Now { get; set; } diff --git a/YandexMusicResolver/Responces/MetaAccountResponse.cs b/YandexMusicResolver/Responses/MetaAccountResponse.cs similarity index 86% rename from YandexMusicResolver/Responces/MetaAccountResponse.cs rename to YandexMusicResolver/Responses/MetaAccountResponse.cs index def533b..320376a 100644 --- a/YandexMusicResolver/Responces/MetaAccountResponse.cs +++ b/YandexMusicResolver/Responses/MetaAccountResponse.cs @@ -2,7 +2,7 @@ #pragma warning disable 8618 -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { internal class MetaAccountResponse { [JsonProperty("account")] public MetaAccount? Account { get; set; } diff --git a/YandexMusicResolver/Responses/MetaAlbum.cs b/YandexMusicResolver/Responses/MetaAlbum.cs new file mode 100644 index 0000000..c1a1452 --- /dev/null +++ b/YandexMusicResolver/Responses/MetaAlbum.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using YandexMusicResolver.AudioItems; +using YandexMusicResolver.Loaders; + +namespace YandexMusicResolver.Responses { + internal class MetaAlbum : MetaAlbumSignature { + [JsonProperty("volumes", NullValueHandling = NullValueHandling.Ignore)] + public List> Tracks { get; set; } + + [JsonProperty("ogImage")] + public string OgImage { get; set; } + + public override YandexMusicAlbum ToYmAlbum(YandexMusicPlaylistLoader loader) { + var artwork = CoverUri; + if (string.IsNullOrEmpty(artwork)) { + artwork = OgImage; + } + + return new YandexMusicAlbum(Id, Year, Artists, artwork, TrackCount, Genre, Title, + Tracks.SelectMany(list => list).Select(track => track.ToYmTrack()).ToList()); + } + } +} \ No newline at end of file diff --git a/YandexMusicResolver/Responses/MetaAlbumSignature.cs b/YandexMusicResolver/Responses/MetaAlbumSignature.cs new file mode 100644 index 0000000..d8f0cb2 --- /dev/null +++ b/YandexMusicResolver/Responses/MetaAlbumSignature.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using YandexMusicResolver.AudioItems; +using YandexMusicResolver.Loaders; + +namespace YandexMusicResolver.Responses { + internal class MetaAlbumSignature + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("year")] + public long Year { get; set; } + + [JsonProperty("artists")] + public List Artists { get; set; } = null!; + + [JsonProperty("coverUri")] + public string? CoverUri { get; set; } + + [JsonProperty("trackCount")] + public long TrackCount { get; set; } + + [JsonProperty("genre")] + public string Genre { get; set; } = null!; + + [JsonProperty("available")] + public bool Available { get; set; } + + [JsonProperty("availableForPremiumUsers")] + public bool AvailableForPremiumUsers { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + public virtual YandexMusicAlbum ToYmAlbum(YandexMusicPlaylistLoader loader) { + return new(Id, Year, Artists, CoverUri, TrackCount, Genre, Title, loader); + } + } +} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaAuthResponse.cs b/YandexMusicResolver/Responses/MetaAuthResponse.cs similarity index 90% rename from YandexMusicResolver/Responces/MetaAuthResponse.cs rename to YandexMusicResolver/Responses/MetaAuthResponse.cs index bc6c607..fbf8484 100644 --- a/YandexMusicResolver/Responces/MetaAuthResponse.cs +++ b/YandexMusicResolver/Responses/MetaAuthResponse.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { internal class MetaAuthResponse { [JsonProperty("access_token")] public string AccessToken { get; set; } = null!; diff --git a/YandexMusicResolver/Responses/MetaCover.cs b/YandexMusicResolver/Responses/MetaCover.cs new file mode 100644 index 0000000..8fedb1f --- /dev/null +++ b/YandexMusicResolver/Responses/MetaCover.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace YandexMusicResolver.Responses { + internal class MetaCover { + [JsonProperty("type")] + public string Type { get; set; } = null!; + + [JsonProperty("dir")] + public string? Dir { get; set; } + + [JsonProperty("version")] + public string? Version { get; set; } + + [JsonProperty("uri")] + public string? Uri { get; set; } + + [JsonProperty("custom")] + public bool Custom { get; set; } + + public string? GetCoverUrl() { + if (Uri == null) return null; + return "https://" + Uri.Replace("%%", "200x200"); + } + } +} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaError.cs b/YandexMusicResolver/Responses/MetaError.cs similarity index 91% rename from YandexMusicResolver/Responces/MetaError.cs rename to YandexMusicResolver/Responses/MetaError.cs index 39fac58..fb21301 100644 --- a/YandexMusicResolver/Responces/MetaError.cs +++ b/YandexMusicResolver/Responses/MetaError.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { /// /// Represents error that returned from Yandex Music /// diff --git a/YandexMusicResolver/Responses/MetaPlaylist.cs b/YandexMusicResolver/Responses/MetaPlaylist.cs new file mode 100644 index 0000000..b166fe7 --- /dev/null +++ b/YandexMusicResolver/Responses/MetaPlaylist.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using YandexMusicResolver.AudioItems; +using YandexMusicResolver.Loaders; + +namespace YandexMusicResolver.Responses { + internal class MetaPlaylist : MetaPlaylistSignature { + [JsonProperty("tracks")] + private List PlaylistTracks { + set => Tracks = value.Select(container => container.Track).ToList(); + } + + public List Tracks { get; set; } = null!; + public override YandexMusicPlaylist ToYaPlaylist(YandexMusicPlaylistLoader loader) { + return new(Uid, Kind, TrackCount, Title, Owner, Cover.GetCoverUrl(), Tracks.Select(track => track.ToYmTrack()).ToList()); + } + } +} \ No newline at end of file diff --git a/YandexMusicResolver/Responses/MetaPlaylistSignature.cs b/YandexMusicResolver/Responses/MetaPlaylistSignature.cs new file mode 100644 index 0000000..c76b05d --- /dev/null +++ b/YandexMusicResolver/Responses/MetaPlaylistSignature.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using YandexMusicResolver.AudioItems; +using YandexMusicResolver.Loaders; + +namespace YandexMusicResolver.Responses { + internal class MetaPlaylistSignature { + [JsonProperty("uid")] + public long Uid { get; set; } + + [JsonProperty("kind")] + public long Kind { get; set; } + + [JsonProperty("trackCount")] + public long TrackCount { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("owner")] + public YandexMusicOwner Owner { get; set; } + + [JsonProperty("cover")] + public MetaCover Cover { get; set; } + + public virtual YandexMusicPlaylist ToYaPlaylist(YandexMusicPlaylistLoader loader) { + return new(Uid, Kind, TrackCount, Title, Owner, Cover.GetCoverUrl(), loader); + } + } +} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaPlaylistTrackContainer.cs b/YandexMusicResolver/Responses/MetaPlaylistTrackContainer.cs similarity index 63% rename from YandexMusicResolver/Responces/MetaPlaylistTrackContainer.cs rename to YandexMusicResolver/Responses/MetaPlaylistTrackContainer.cs index c7da8e0..9f971cf 100644 --- a/YandexMusicResolver/Responces/MetaPlaylistTrackContainer.cs +++ b/YandexMusicResolver/Responses/MetaPlaylistTrackContainer.cs @@ -1,11 +1,11 @@ using Newtonsoft.Json; -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { internal class MetaPlaylistTrackContainer { [JsonProperty("id")] public long Id { get; set; } [JsonProperty("track")] - public MetaPlaylistTrack Track { get; set; } = null!; + public MetaTrack Track { get; set; } = null!; } } \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaSearchResponse.cs b/YandexMusicResolver/Responses/MetaSearchResponse.cs similarity index 95% rename from YandexMusicResolver/Responces/MetaSearchResponse.cs rename to YandexMusicResolver/Responses/MetaSearchResponse.cs index daca6e2..d0f2dad 100644 --- a/YandexMusicResolver/Responces/MetaSearchResponse.cs +++ b/YandexMusicResolver/Responses/MetaSearchResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { internal class MetaSearchResponse { [JsonProperty("albums")] public MetaSearchContentProxy? Albums { get; set; } diff --git a/YandexMusicResolver/Responses/MetaTrack.cs b/YandexMusicResolver/Responses/MetaTrack.cs new file mode 100644 index 0000000..50258a8 --- /dev/null +++ b/YandexMusicResolver/Responses/MetaTrack.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using YandexMusicResolver.AudioItems; +using YandexMusicResolver.Converters; +using YandexMusicResolver.Loaders; + +namespace YandexMusicResolver.Responses { + /// + /// Track data from Yandex Music + /// + internal class MetaTrack { + private const string TrackUrlFormat = "https://music.yandex.ru/album/{0}/track/{1}"; + + [JsonProperty("id")] + [JsonConverter(typeof(ParseStringConverter))] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } = null!; + + [JsonProperty("available")] + public bool Available { get; set; } + + [JsonProperty("durationMs")] + public long DurationMs { get; set; } + + [JsonProperty("artists")] + public List Artists { get; set; } = null!; + + [JsonProperty("albums")] + public List Albums { get; set; } = null!; + + [JsonProperty("coverUri")] + public string? CoverUri { get; set; } + + [JsonProperty("ogImage")] + public string? OgImage { get; set; } + + [JsonProperty("lyricsAvailable")] + public bool LyricsAvailable { get; set; } + + public YandexMusicTrack ToYmTrack() { + var album = Albums.First(); + + string? artworkUrl = null; + TryApplyArtwork(ref artworkUrl, CoverUri); + TryApplyArtwork(ref artworkUrl, OgImage); + TryApplyArtwork(ref artworkUrl, album.CoverUri); + // TryApplyArtwork(ref artworkUrl, album.OgImage); + + return new YandexMusicTrack(Title, Artists, + TimeSpan.FromMilliseconds(DurationMs), Id.ToString(), + string.Format(TrackUrlFormat, album.Id, Id), artworkUrl); + } + + private static void TryApplyArtwork(ref string? final, string? artwork) { + if (final != null || artwork == null) return; + final = "https://" + artwork.Replace("%%", "200x200"); + } + } +} \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaTrackDownloadInfo.cs b/YandexMusicResolver/Responses/MetaTrackDownloadInfo.cs similarity index 93% rename from YandexMusicResolver/Responces/MetaTrackDownloadInfo.cs rename to YandexMusicResolver/Responses/MetaTrackDownloadInfo.cs index 64bfcca..57ebb68 100644 --- a/YandexMusicResolver/Responces/MetaTrackDownloadInfo.cs +++ b/YandexMusicResolver/Responses/MetaTrackDownloadInfo.cs @@ -1,7 +1,7 @@ using System; using Newtonsoft.Json; -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { internal class MetaTrackDownloadInfo { [JsonProperty("codec")] public string Codec { get; set; } = null!; diff --git a/YandexMusicResolver/Responces/MetaTrackDownloadInfoXml.cs b/YandexMusicResolver/Responses/MetaTrackDownloadInfoXml.cs similarity index 93% rename from YandexMusicResolver/Responces/MetaTrackDownloadInfoXml.cs rename to YandexMusicResolver/Responses/MetaTrackDownloadInfoXml.cs index 3bac4d2..2c2f767 100644 --- a/YandexMusicResolver/Responces/MetaTrackDownloadInfoXml.cs +++ b/YandexMusicResolver/Responses/MetaTrackDownloadInfoXml.cs @@ -2,7 +2,7 @@ #pragma warning disable 1591 -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { [XmlRoot(ElementName = "download-info")] public class MetaTrackDownloadInfoXml { [XmlElement(ElementName = "host")] diff --git a/YandexMusicResolver/Responces/YandexApiResponce.cs b/YandexMusicResolver/Responses/YandexApiResponce.cs similarity index 84% rename from YandexMusicResolver/Responces/YandexApiResponce.cs rename to YandexMusicResolver/Responses/YandexApiResponce.cs index 671f9a5..b28dfbf 100644 --- a/YandexMusicResolver/Responces/YandexApiResponce.cs +++ b/YandexMusicResolver/Responses/YandexApiResponce.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace YandexMusicResolver.Responces { +namespace YandexMusicResolver.Responses { internal class YandexApiResponse { [JsonProperty("result")] public T? Result { get; set; } diff --git a/YandexMusicResolver/YandexMusicAuth.cs b/YandexMusicResolver/YandexMusicAuth.cs index a16683b..cfae0e0 100644 --- a/YandexMusicResolver/YandexMusicAuth.cs +++ b/YandexMusicResolver/YandexMusicAuth.cs @@ -1,22 +1,23 @@ using System.Threading.Tasks; using YandexMusicResolver.Config; using YandexMusicResolver.Requests; -using YandexMusicResolver.Responces; +using YandexMusicResolver.Responses; namespace YandexMusicResolver { /// /// Represents a set of methods that serve for authorization in Yandex Music /// - public class YandexMusicAuth { + public static class YandexMusicAuth { /// /// Validates token /// /// Token to validate /// Container for proxy, which should be used for request - /// True if token correct - public static async Task CheckToken(string token, IYandexProxyHolder? proxyHolder = null) { - var metaAccountResponse = await new YandexCustomRequest(proxyHolder, new TokenHolder(token)).Create("https://api.music.yandex.net/account/status") - .GetResponseAsync(); + /// True if token valid + public static async Task ValidateTokenAsync(string token, IYandexProxyHolder? proxyHolder = null) { + var metaAccountResponse = await new YandexCustomRequest(proxyHolder, new TokenHolder(token)) + .Create("https://api.music.yandex.net/account/status") + .GetResponseAsync(); return !string.IsNullOrEmpty(metaAccountResponse.Account?.Uid); } @@ -27,7 +28,7 @@ public static async Task CheckToken(string token, IYandexProxyHolder? prox /// Password from Yandex account /// Container for proxy, which should be used for request /// Token - public static async Task GetToken(string login, string password, IYandexProxyHolder? proxyHolder = null) { + public static async Task LoginAsync(string login, string password, IYandexProxyHolder? proxyHolder = null) { return (await new YandexAuthRequest(proxyHolder).Create(login, password).ParseResponseAsync()).AccessToken; } @@ -39,13 +40,13 @@ public static async Task GetToken(string login, string password, IYandex /// Password from Yandex account /// Container for proxy, which should be used for request /// Valid token, true if this is new token otherwise false - public static async Task<(string, bool)> GetToken(string? existentToken, string fallbackLogin, string fallbackPassword, + public static async Task ValidateOrLoginAsync(string? existentToken, string fallbackLogin, string fallbackPassword, IYandexProxyHolder? proxyHolder = null) { - if (string.IsNullOrWhiteSpace(existentToken) || !await CheckToken(existentToken, proxyHolder)) { - return (await GetToken(fallbackLogin, fallbackPassword, proxyHolder), true); + if (string.IsNullOrWhiteSpace(existentToken) || !await ValidateTokenAsync(existentToken, proxyHolder)) { + return await LoginAsync(fallbackLogin, fallbackPassword, proxyHolder); } - return (existentToken, false); + return existentToken; } } } \ No newline at end of file diff --git a/YandexMusicResolver/YandexMusicMainResolver.cs b/YandexMusicResolver/YandexMusicMainResolver.cs index 3326294..71b9d71 100644 --- a/YandexMusicResolver/YandexMusicMainResolver.cs +++ b/YandexMusicResolver/YandexMusicMainResolver.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading.Tasks; using YandexMusicResolver.AudioItems; @@ -14,9 +15,9 @@ public class YandexMusicMainResolver { private const string AlbumUrlPattern = "^https?://music\\.yandex\\.[a-zA-Z]+/album/([0-9]+)$"; private const string PlaylistUrlPattern = "^https?://music\\.yandex\\.[a-zA-Z]+/users/(.+)/playlists/([0-9]+)$"; - private static readonly Regex TrackUrlRegex = new Regex(TrackUrlPattern); - private static readonly Regex AlbumUrlRegex = new Regex(AlbumUrlPattern); - private static readonly Regex PlaylistUrlRegex = new Regex(PlaylistUrlPattern); + private static readonly Regex TrackUrlRegex = new(TrackUrlPattern); + private static readonly Regex AlbumUrlRegex = new(AlbumUrlPattern); + private static readonly Regex PlaylistUrlRegex = new(PlaylistUrlPattern); // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable private readonly IYandexConfig _config; @@ -45,82 +46,93 @@ public class YandexMusicMainResolver { /// Initializes a new instance of the class. /// /// Yandex config instance - /// Is query in can be resolved with search /// Instance of /// Instance of /// Instance of /// Instance of - [Obsolete("Use another ctor and set AllowSearch property after that. \nWill be removed in 3.0")] public YandexMusicMainResolver(IYandexConfig config, - bool allowSearch = true, YandexMusicPlaylistLoader? playlistLoader = null, YandexMusicTrackLoader? trackLoader = null, YandexMusicDirectUrlLoader? directUrlLoader = null, YandexMusicSearchResultLoader? searchResultLoader = null) { + config.Load(); _config = config; PlaylistLoader = playlistLoader ?? new YandexMusicPlaylistLoader(_config); TrackLoader = trackLoader ?? new YandexMusicTrackLoader(_config); DirectUrlLoader = directUrlLoader ?? new YandexMusicDirectUrlLoader(_config); - SearchResultLoader = searchResultLoader ?? new YandexMusicSearchResultLoader(_config); - AllowSearch = allowSearch; + SearchResultLoader = searchResultLoader ?? new YandexMusicSearchResultLoader(_config, PlaylistLoader); } - + /// - /// Initializes a new instance of the class. + /// Is complicated query in can be resolved /// - /// Yandex config instance - /// Instance of - /// Instance of - /// Instance of - /// Instance of - public YandexMusicMainResolver(IYandexConfig config, - YandexMusicPlaylistLoader? playlistLoader = null, - YandexMusicTrackLoader? trackLoader = null, - YandexMusicDirectUrlLoader? directUrlLoader = null, - YandexMusicSearchResultLoader? searchResultLoader = null) { - _config = config; - PlaylistLoader = playlistLoader ?? new YandexMusicPlaylistLoader(_config); - TrackLoader = trackLoader ?? new YandexMusicTrackLoader(_config); - DirectUrlLoader = directUrlLoader ?? new YandexMusicDirectUrlLoader(_config); - SearchResultLoader = searchResultLoader ?? new YandexMusicSearchResultLoader(_config); - } + public bool AllowSearch { get; set; } = true; /// - /// Is query in can be resolved with search + /// If we pass plain text to it will be interpreted as search query with this search type. Set to false to disable this /// - public bool AllowSearch { get; set; } = true; + public YandexSearchType PlainTextIsSearchQueryType { get; set; } = YandexSearchType.Track; + + /// + /// Will plain text be interpreted as a search query in + /// + public bool PlainTextIsSearchQuery { get; set; } = true; /// /// Resolves yandex query. Can directly resolve playlists, albums, tracks by url and search queries /// /// Direct url or search query /// Is query in can be resolved with search. This parameter overrides - /// Instance of - public async Task ResolveQuery(string query, bool? allowSearchOverride = null) { + /// Will plain text be interpreted as a search query in + /// If we pass plain text to it will be interpreted as search query with this search type + /// Instance of . Null if track will now an valid url and is false. + public async Task ResolveQuery(string query, bool? allowSearchOverride = null, + bool? plainTextIsSearchQueryOverride = null, + YandexSearchType? plainTextAsSearchQueryTypeOverride = null) { var trackMatch = TrackUrlRegex.Match(query); if (trackMatch.Success) { - return await TrackLoader.LoadTrack(trackMatch.Groups[1].Value, trackMatch.Groups[2].Value, GetTrack); + var tracks = new List(); + + var yandexMusicTrack = await TrackLoader.LoadTrack(trackMatch.Groups[2].Value); + if (yandexMusicTrack != null) tracks.Add(yandexMusicTrack); + + return new YandexMusicSearchResult(query, false, YandexSearchType.Track, null, null, tracks.AsReadOnly()); } var playlistMatch = PlaylistUrlRegex.Match(query); if (playlistMatch.Success) { - return await PlaylistLoader.LoadPlaylist(playlistMatch.Groups[1].Value, playlistMatch.Groups[2].Value, GetTrack); + var playlists = new List(); + + var playlist = await PlaylistLoader.LoadPlaylist(playlistMatch.Groups[1].Value, playlistMatch.Groups[2].Value); + if (playlist != null) playlists.Add(playlist); + + return new YandexMusicSearchResult(query, false, YandexSearchType.Playlist, null, playlists.AsReadOnly(), null); } var albumMatch = AlbumUrlRegex.Match(query); if (albumMatch.Success) { - return await PlaylistLoader.LoadPlaylist(albumMatch.Groups[1].Value, GetTrack); + var albums = new List(); + + var album = await PlaylistLoader.LoadAlbum(albumMatch.Groups[1].Value); + if (album != null) albums.Add(album); + + return new YandexMusicSearchResult(query, false, YandexSearchType.Album, albums.AsReadOnly(), null, null); } - if (allowSearchOverride ?? AllowSearch) { - return await SearchResultLoader.LoadSearchResult(query, PlaylistLoader, GetTrack); + if (!(allowSearchOverride ?? AllowSearch)) return null; + string searchText = query; + var searchType = plainTextAsSearchQueryTypeOverride ?? PlainTextIsSearchQueryType; + var searchLimit = 10; + var needSearch = plainTextIsSearchQueryOverride ?? PlainTextIsSearchQuery; + if (SearchResultLoader.TryParseQuery(query, out var text, out var type, out var limit)) { + searchText = text; + searchType = type; + searchLimit = limit; + needSearch = true; } + if (needSearch) return await SearchResultLoader.LoadSearchResult(searchType, searchText, searchLimit); return null; } - - private YandexMusicTrack GetTrack(AudioTrackInfo arg) { - return new(arg, this); - } } } \ No newline at end of file diff --git a/YandexMusicResolver/YandexMusicResolver.csproj b/YandexMusicResolver/YandexMusicResolver.csproj index bb8731b..4692828 100644 --- a/YandexMusicResolver/YandexMusicResolver.csproj +++ b/YandexMusicResolver/YandexMusicResolver.csproj @@ -12,7 +12,7 @@ A library aimed at searching, resolving and getting direct links to tracks, playlists or albums in Yandex.Music. Can work without authorization. Git https://github.com/SKProCH/YandexMusicResolver - 2.2.1 + 3.0.0 https://github.com/SKProCH/YandexMusicResolver/blob/master/LICENSE Please write the package release notes in “RELEASE NOTES.md” @@ -23,12 +23,18 @@ - + + + + + + + - + @(ReleaseNoteLines, '%0a') diff --git a/YandexMusicResolver/YandexMusicResolver.xml b/YandexMusicResolver/YandexMusicResolver.xml index dc65616..42a995d 100644 --- a/YandexMusicResolver/YandexMusicResolver.xml +++ b/YandexMusicResolver/YandexMusicResolver.xml @@ -14,15 +14,119 @@ Contains info about error from yandex api - + - + - + - Marker interface for all loadable items + Album id + + + + + Album release year + + + + + Album artists + + + + + Track image uri + + + + + Album tracks count + + + + + Album genre + + + + + Track title + + + + + Represent a artist in Yandex Music + + + + + Artist ID + + + + + Artist name + + + + + Represents class that contains data which could be loaded + + + + + + Create instance of + + Data creation factory + + + + Create instance of + + Target data + + + + Load target data + + Task + + + + Return true if data already loaded + + + + + Synchronously wait for data loading + + + + + Represent playlist owner + + + + + Owner ID + + + + + Owner login + + + + + Owner name + + + + + Is owner verified @@ -30,27 +134,34 @@ Represents playlist from Yandex Music - + + + Playlist UID + + + + + Playlist kind (something like the user's playlist index) + + + - Initializes a new instance of the class. + Playlist tracks count - Playlist title - Collection with tracks - Is this playlist is search result Playlist title - + - Collection with tracks in playlist + Playlist owner - + - Is this playlist a search result + Playlist artwork url @@ -65,7 +176,8 @@ - Tracks limit count + Tracks limit count. + Will be null if the is false @@ -73,6 +185,11 @@ Search data type + + + Is this playlist a search result + + Albums list. @@ -96,66 +213,37 @@ AudioTrackInfo wrapper to resolve track direct url - - - Get track info - - - - - Initializes a new instance of the class. - - Track info - Resolver for direct url getting - - - - Get direct url to track - - If you not authorized will return 30s track version. This is YandexMusic restriction - Direct url to download track - - - - Contains info about track - - - + Track title - + - Track author + Track authors - + - Track lenght + Compose names into single string - + - Track identifier + Track lenght - + - Is track live stream + Track id - + Track link - - - Additional track metadata - - - + Track image uri @@ -224,7 +312,7 @@ - Load config + Load config. This method can be called multiple times @@ -309,29 +397,19 @@ Config instance for performing requests - + Loads the playlist from Yandex Music Id of user who created the playlist - Target playlist id - Track factory to create YandexMusicTrack from AudioTrackInfo - Playlist instance - - - - Loads the album from Yandex Music - - Target album id - Track factory to create YandexMusicTrack from AudioTrackInfo + Target playlist id Playlist instance - + Loads the album from Yandex Music Target album id - Track factory to create YandexMusicTrack from AudioTrackInfo Playlist instance @@ -349,21 +427,24 @@ Special prefix for complicated requests - + Initializes a new instance of the class. Config instance for performing requests - - + + + Set a new search prefix for complicated queries + + New prefix. null will be replaced with "ymsearch" + + Perform search request on Yandex Music Complicated query is ::limit:text Search query. May be complicated or default values will be used - Playlist loader instance - Track factory to create YandexMusicTrack from AudioTrackInfo Instance of YandexMusicSearchResult @@ -377,14 +458,13 @@ Search limit True if is this complicated query - + Perform search request on Yandex Music Search type Search text Playlist loader instance - Track factory to create YandexMusicTrack from AudioTrackInfo Search results limit count Instance of YandexMusicSearchResult Throws exception if something went wrong @@ -405,258 +485,47 @@ Config instance for performing requests - - - Load track - - Album id with track - Target track id - Track factory to create YandexMusicTrack from AudioTrackInfo - Instance of - - - - Load track - - Target track id - Track factory to create YandexMusicTrack from AudioTrackInfo - Instance of - - - - Load track info - - Album id with track - Target track id - Instance of - - + Load track info Target track id - Instance of + Instance of - - - Represents entity from which we can get - - - - - Get related - - Track loader instance - Instance of - - - - Represents data to resolve album - - - - - Id of this entity - - - - - Title of this album - - - - - Cover link - - - - - Opengraph image (alternative cover) url - - - - - Count of tracks - - - - - Is this playlist available now - - - - - Get full playlist with tracks - - Instance of playlist loader to load playlist - Track factory to create YandexMusicTrack from AudioTrackInfo - Playlist with tracks - - - - Represent a artist in Yandex Music - - - - - Artist ID - - - - - Artist name - - - + Represents error that returned from Yandex Music - + Error name - + Error message - - - Represent playlist owner - - - - - Owner ID - - - - - Owner login - - - - - Owner name - - - - - Is owner verified - - - - - Represents data to resolve playlist - - - - - Playlist owner info - - - - - - - - Represent - - - - - Track id - - - - - List of albums that contain this track - - - - - Creation timestamp - - - - - - + Track data from Yandex Music - - - Track ID - - - - - Track title - - - - - Is track available - - If false, then most likely you are trying to get this information while not in the CIS. At the moment, you need to use a proxy in the CIS or log in with an account that has Yandex Plus (a subscription from Yandex) - - - - Track duration in ms - - - - - Track authors list - - - - - List of albums that contain this track - - - - - Cover link - - - - - Opengraph image (alternative cover) link - - - - - Is lyrics available for this track - - - - - Convert this meta class to - - Instance of - - - - Represents a set of methods that serve for authorization in Yandex Music - + Validates token Token to validate Container for proxy, which should be used for request - True if token correct + True if token valid - + Attempt to authorise @@ -665,7 +534,7 @@ Container for proxy, which should be used for request Token - + Try to validate token or get new one using login and password @@ -700,39 +569,40 @@ Instance of - + Initializes a new instance of the class. Yandex config instance - Is query in can be resolved with search Instance of Instance of Instance of Instance of - + - Initializes a new instance of the class. + Is complicated query in can be resolved - Yandex config instance - Instance of - Instance of - Instance of - Instance of - + + + If we pass plain text to it will be interpreted as search query with this search type. Set to false to disable this + + + - Is query in can be resolved with search + Will plain text be interpreted as a search query in - + Resolves yandex query. Can directly resolve playlists, albums, tracks by url and search queries Direct url or search query - Is query in can be resolved with search. This parameter overrides - Instance of + Is query in can be resolved with search. This parameter overrides + Will plain text be interpreted as a search query in + If we pass plain text to it will be interpreted as search query with this search type + Instance of . Null if track will now an valid url and is false.