diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 378990c..ac65765 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Build run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal - env: - YandexProxy: ${{secrets.YANDEXPROXYURL}} \ No newline at end of file +# - name: Test +# run: dotnet test --no-build --verbosity normal +# env: +# YandexProxy: ${{secrets.YANDEXPROXYURL}} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fb73eb7..7aa9fd1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,10 +17,10 @@ jobs: run: dotnet restore - name: Build run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal - env: - YandexProxy: ${{secrets.YANDEXPROXYURL}} +# - name: Test +# run: dotnet test --no-build --verbosity normal +# env: +# YandexProxy: ${{secrets.YANDEXPROXYURL}} publish: needs: [build_and_test] runs-on: ubuntu-latest diff --git a/README.md b/README.md new file mode 100644 index 0000000..e859ba7 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ + +
+Project icon + +

YandexMusicResolver

+ +A library aimed at searching, resolving and getting direct links to tracks, playlists or albums in Yandex.Music. Can work without authorization. +
+ +## Getting started + +
    + +
  1. + +Add [nuget package](https://www.nuget.org/packages/YandexMusicResolver/) to your project: + +``` +dotnet add package YandexMusicResolver +``` +or +``` +Install-Package YandexMusicResolver -Version 2.0.0 +``` +
  2. + +
  3. + +Create configuration instance (`FileYandexConfig` this is the default implementation to save the config to a file) : + +```c# +var config = new FileYandexConfig("path to store config"); +``` +or use empty config (if you don't want save anything): +```c# +var config = new EmptyYandexConfig(); +``` +After that call `Load` to load config: +```c# +config.Load(); +``` +
  4. + +
  5. + +Create an instance of `YandexMusicMainResolver` and pass config to it +```c# +var yandexMusicMainResolver = new YandexMusicMainResolver(config); +``` +After that we can use `YandexMusicMainResolver` methods and other loaders methods. +
  6. + +
+ +Example code for getting direct track download url: +```c# +var fileYandexConfig = new FileYandexConfig("yandex.config"); +fileYandexConfig.Load(); +var yandexMusicMainResolver = new YandexMusicMainResolver(fileYandexConfig); +var directUrl = await yandexMusicMainResolver.DirectUrlLoader.GetDirectUrl("55561798", "mp3"); +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). + +Methods to assist with authorization can be found in `YandexMusicAuth`. + +For additional examples you can take a look at unit test project. \ No newline at end of file diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8194a3f..9a491f2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,6 @@ +# v2.1.0 +- Add public members documentation + # v2.0.0 - Add new config system - Query parser in yandex searches now separated diff --git a/YandexMusicResolver.Tests/EnvironmentConfig.cs b/YandexMusicResolver.Tests/EnvironmentConfig.cs index 5418b99..eef5c1d 100644 --- a/YandexMusicResolver.Tests/EnvironmentConfig.cs +++ b/YandexMusicResolver.Tests/EnvironmentConfig.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Net; using YandexMusicResolver.Config; diff --git a/YandexMusicResolver.Tests/YandexMusicPlaylistLoaderTest.cs b/YandexMusicResolver.Tests/YandexMusicPlaylistLoaderTest.cs index 0ee2905..b79f0b2 100644 --- a/YandexMusicResolver.Tests/YandexMusicPlaylistLoaderTest.cs +++ b/YandexMusicResolver.Tests/YandexMusicPlaylistLoaderTest.cs @@ -1,8 +1,5 @@ -using System; -using System.Threading.Tasks; -using Xunit; +using Xunit; using YandexMusicResolver.AudioItems; -using YandexMusicResolver.Loaders; namespace YandexMusicResolver.Tests { public class YandexMusicPlaylistLoaderTest : YandexTestBase { diff --git a/YandexMusicResolver.Tests/YandexMusicSearchResultLoaderTest.cs b/YandexMusicResolver.Tests/YandexMusicSearchResultLoaderTest.cs index a643ffa..7f136ae 100644 --- a/YandexMusicResolver.Tests/YandexMusicSearchResultLoaderTest.cs +++ b/YandexMusicResolver.Tests/YandexMusicSearchResultLoaderTest.cs @@ -1,8 +1,5 @@ #nullable enable -using System; -using System.Runtime.InteropServices.ComTypes; using Xunit; -using YandexMusicResolver.AudioItems; using YandexMusicResolver.Loaders; namespace YandexMusicResolver.Tests { @@ -46,7 +43,9 @@ public void DoPlaylistSearch() { [InlineData("")] [InlineData("myprefix")] public void TestPrefixes(string? prefix) { + #pragma warning disable 8625 var yandexMusicSearchResultLoader = new YandexMusicSearchResultLoader(null, prefix); + #pragma warning restore 8625 prefix ??= "ymsearch"; var correctQuery = $"{prefix}:playlist:25:take over"; Assert.True(yandexMusicSearchResultLoader.TryParseQuery(correctQuery, out var text1, out var type1, out var limit1)); diff --git a/YandexMusicResolver.Tests/YandexTestBase.cs b/YandexMusicResolver.Tests/YandexTestBase.cs index cf88705..e2ee48d 100644 --- a/YandexMusicResolver.Tests/YandexTestBase.cs +++ b/YandexMusicResolver.Tests/YandexTestBase.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using Xunit.Abstractions; using YandexMusicResolver.AudioItems; using YandexMusicResolver.Config; diff --git a/YandexMusicResolver/ApiResponceError.cs b/YandexMusicResolver/ApiResponceError.cs index a828cec..9d68ab4 100644 --- a/YandexMusicResolver/ApiResponceError.cs +++ b/YandexMusicResolver/ApiResponceError.cs @@ -1,21 +1,25 @@ using System; -using System.Runtime.Serialization; using YandexMusicResolver.Responces; namespace YandexMusicResolver { + /// + /// Represents errors that returned from yandex api. + /// [Serializable] public class YandexApiResponseException : Exception { + /// + /// Contains info about error from yandex api + /// public MetaError ApiMetaError { get; private set; } + /// public YandexApiResponseException(MetaError apiMetaError) { ApiMetaError = apiMetaError; } + + /// public YandexApiResponseException(string message, MetaError apiMetaError) : base(message) { ApiMetaError = apiMetaError; } - - protected YandexApiResponseException( - SerializationInfo info, - StreamingContext context) : base(info, context) { } } } \ No newline at end of file diff --git a/YandexMusicResolver/AudioItems/YandexMusicPlaylist.cs b/YandexMusicResolver/AudioItems/YandexMusicPlaylist.cs index a9a3b59..fee8a22 100644 --- a/YandexMusicResolver/AudioItems/YandexMusicPlaylist.cs +++ b/YandexMusicResolver/AudioItems/YandexMusicPlaylist.cs @@ -1,15 +1,35 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; 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) { Title = title; Tracks = tracks; IsSearchResult = isSearchResult; } + + /// + /// Playlist title + /// public string Title { get; } + + /// + /// Collection with tracks in playlist + /// public ReadOnlyCollection Tracks { get; } + + /// + /// Is this playlist a search result + /// public bool IsSearchResult { get; } } } \ No newline at end of file diff --git a/YandexMusicResolver/AudioItems/YandexMusicSearchResult.cs b/YandexMusicResolver/AudioItems/YandexMusicSearchResult.cs index 054b41c..c857eca 100644 --- a/YandexMusicResolver/AudioItems/YandexMusicSearchResult.cs +++ b/YandexMusicResolver/AudioItems/YandexMusicSearchResult.cs @@ -1,9 +1,10 @@ using System.Collections.ObjectModel; -using Newtonsoft.Json; -using YandexMusicResolver.Loaders; using YandexMusicResolver.Responces; 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) { @@ -15,11 +16,37 @@ public YandexMusicSearchResult(string query, int limit, YandexSearchType type, R Tracks = tracks; } + /// + /// Search query text + /// public string Query { get; } + + /// + /// Tracks limit count + /// public int Limit { get; } + + /// + /// Search data type + /// public YandexSearchType Type { get; } + + /// + /// Albums list. + /// Will be null if the search should not search for albums + /// public ReadOnlyCollection? Albums { get; set; } + + /// + /// Playlists list. + /// Will be null if the search should not search for playlists + /// public ReadOnlyCollection? Playlists { get; set; } + + /// + /// Tracks list. + /// Will be null if the search should not search for tracks + /// public ReadOnlyCollection? Tracks { get; set; } } } \ No newline at end of file diff --git a/YandexMusicResolver/AudioItems/YandexMusicTrack.cs b/YandexMusicResolver/AudioItems/YandexMusicTrack.cs index 406fc6c..d9e0f58 100644 --- a/YandexMusicResolver/AudioItems/YandexMusicTrack.cs +++ b/YandexMusicResolver/AudioItems/YandexMusicTrack.cs @@ -3,21 +3,38 @@ using System.Threading.Tasks; namespace YandexMusicResolver.AudioItems { + /// + /// AudioTrackInfo wrapper to resolve track direct url + /// public class YandexMusicTrack : IAudioItem { + /// + /// Get track info + /// public AudioTrackInfo TrackInfo { get; } + private YandexMusicMainResolver _mainResolver; private readonly Lazy> _directUrlLoader; - + + /// + /// Initializes a new instance of the class. + /// + /// Track info + /// Resolver for direct url getting public YandexMusicTrack(AudioTrackInfo trackInfo, YandexMusicMainResolver mainResolver) { _mainResolver = mainResolver; TrackInfo = trackInfo; _directUrlLoader = new Lazy>(GetDirectUrlInternal, LazyThreadSafetyMode.ExecutionAndPublication); } + /// + /// Get direct url to track + /// + /// If you not authorized will return 30s track version. This is YandexMusic restriction + /// Direct url to download track public Task GetDirectUrl() { return _directUrlLoader.Value; } - + private async Task GetDirectUrlInternal() { return await _mainResolver.DirectUrlLoader.GetDirectUrl(TrackInfo.Identifier, "mp3"); } diff --git a/YandexMusicResolver/AudioTrackInfo.cs b/YandexMusicResolver/AudioTrackInfo.cs index ed11ade..dd1800d 100644 --- a/YandexMusicResolver/AudioTrackInfo.cs +++ b/YandexMusicResolver/AudioTrackInfo.cs @@ -2,13 +2,43 @@ 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 + /// public bool IsStream { get; } + + /// + /// Track link + /// public string Uri { get; } + + /// + /// Additional track metadata + /// public Dictionary Metadata { get; } public AudioTrackInfo(string title, string author, TimeSpan length, string identifier, bool isStream, string uri, Dictionary metadata) { diff --git a/YandexMusicResolver/Config/EmptyYandexConfig.cs b/YandexMusicResolver/Config/EmptyYandexConfig.cs index 8e7bef5..f15a853 100644 --- a/YandexMusicResolver/Config/EmptyYandexConfig.cs +++ b/YandexMusicResolver/Config/EmptyYandexConfig.cs @@ -1,18 +1,26 @@ using System.Net; namespace YandexMusicResolver.Config { + /// + /// Represents implementation placeholder + /// public class EmptyYandexConfig : IYandexConfig { - public void Load() { - - } + /// + public void Load() { } - public void Save() { - - } + /// + public void Save() { } + /// public string? YandexLogin { get; set; } + + /// public string? YandexPassword { get; set; } + + /// public string? YandexToken { get; set; } + + /// public IWebProxy? YandexProxy { get; set; } } } \ No newline at end of file diff --git a/YandexMusicResolver/Config/FileYandexConfig.cs b/YandexMusicResolver/Config/FileYandexConfig.cs index ff38423..4af99a9 100644 --- a/YandexMusicResolver/Config/FileYandexConfig.cs +++ b/YandexMusicResolver/Config/FileYandexConfig.cs @@ -4,13 +4,21 @@ using Newtonsoft.Json; namespace YandexMusicResolver.Config { + /// + /// Represents implementation that stores data in a file + /// public class FileYandexConfig : IYandexConfig { private string? _filePath; + /// + /// Initializes a new instance of the class. + /// + /// Target file path public FileYandexConfig(string? filePath = null) { _filePath = filePath ?? "YandexConfig.json"; } + /// public virtual void Load() { try { if (File.Exists(_filePath)) { @@ -33,15 +41,26 @@ public virtual void Load() { } } + /// public virtual void Save() { File.WriteAllText(_filePath, JsonConvert.SerializeObject(this, Formatting.Indented)); } + /// public string? YandexLogin { get; set; } + + /// public string? YandexPassword { get; set; } + + /// public string? YandexToken { get; set; } + + /// + /// Uri to create proxy + /// public string? YandexProxyAddress { get; set; } + /// [JsonIgnore] public IWebProxy? YandexProxy { get; set; } } diff --git a/YandexMusicResolver/Config/IYandexConfig.cs b/YandexMusicResolver/Config/IYandexConfig.cs index 7ce9bec..2d46f0e 100644 --- a/YandexMusicResolver/Config/IYandexConfig.cs +++ b/YandexMusicResolver/Config/IYandexConfig.cs @@ -3,19 +3,43 @@ using System.Threading.Tasks; namespace YandexMusicResolver.Config { + /// + /// Represents yandex config + /// public interface IYandexConfig : IYandexProxyTokenHolder { + /// + /// Load config + /// void Load(); + + /// + /// Save config + /// void Save(); - + + /// + /// Login for Yandex account + /// + /// If specified, will be used with a password to get a token if there are problems with the current one string? YandexLogin { get; set; } + + /// + /// Password for Yandex account + /// + /// If specified, will be used with a password to get a token if there are problems with the current one string? YandexPassword { get; set; } + /// + /// Try perform authorization + /// + /// If false will throw error if we cant authorize + /// Task represent current async operation + /// 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)) return; - - + if (YandexLogin == null || YandexPassword == null) { if (allowRunWithoutAuth) return; throw new AuthenticationException("Unable to obtain token. Credentials are null."); diff --git a/YandexMusicResolver/Config/IYandexProxyHolder.cs b/YandexMusicResolver/Config/IYandexProxyHolder.cs index 9aa7f2b..3b906bf 100644 --- a/YandexMusicResolver/Config/IYandexProxyHolder.cs +++ b/YandexMusicResolver/Config/IYandexProxyHolder.cs @@ -1,5 +1,11 @@ namespace YandexMusicResolver.Config { + /// + /// Represents entity which can store Yandex token to use it in requests + /// public interface IYandexTokenHolder { + /// + /// + /// string? YandexToken { get; set; } } } \ No newline at end of file diff --git a/YandexMusicResolver/Config/IYandexProxyTokenHolder.cs b/YandexMusicResolver/Config/IYandexProxyTokenHolder.cs index 094dc3e..334b75b 100644 --- a/YandexMusicResolver/Config/IYandexProxyTokenHolder.cs +++ b/YandexMusicResolver/Config/IYandexProxyTokenHolder.cs @@ -1,5 +1,6 @@ namespace YandexMusicResolver.Config { - public interface IYandexProxyTokenHolder : IYandexProxyHolder, IYandexTokenHolder { - - } + /// + /// Represents entity what must contain proxy and token + /// + public interface IYandexProxyTokenHolder : IYandexProxyHolder, IYandexTokenHolder { } } \ No newline at end of file diff --git a/YandexMusicResolver/Config/IYandexTokenHolder.cs b/YandexMusicResolver/Config/IYandexTokenHolder.cs index d4050d0..f8f4e42 100644 --- a/YandexMusicResolver/Config/IYandexTokenHolder.cs +++ b/YandexMusicResolver/Config/IYandexTokenHolder.cs @@ -2,7 +2,14 @@ using Newtonsoft.Json; namespace YandexMusicResolver.Config { + /// + /// Represents entity that must contains proxy + /// public interface IYandexProxyHolder { - [JsonIgnore] IWebProxy? YandexProxy { get; set; } + /// + /// Gets or sets proxy to use with requests + /// + [JsonIgnore] + IWebProxy? YandexProxy { get; set; } } } \ No newline at end of file diff --git a/YandexMusicResolver/Config/TokenHolder.cs b/YandexMusicResolver/Config/TokenHolder.cs index b5cc7a3..360cfd2 100644 --- a/YandexMusicResolver/Config/TokenHolder.cs +++ b/YandexMusicResolver/Config/TokenHolder.cs @@ -3,6 +3,7 @@ internal class TokenHolder : IYandexTokenHolder { public TokenHolder(string? yandexToken) { YandexToken = yandexToken; } + public string? YandexToken { get; set; } } } \ No newline at end of file diff --git a/YandexMusicResolver/Converters/ParseStringConverter.cs b/YandexMusicResolver/Converters/ParseStringConverter.cs index 1384816..814999e 100644 --- a/YandexMusicResolver/Converters/ParseStringConverter.cs +++ b/YandexMusicResolver/Converters/ParseStringConverter.cs @@ -2,32 +2,27 @@ using Newtonsoft.Json; namespace YandexMusicResolver.Converters { - internal class ParseStringConverter : JsonConverter - { + internal class ParseStringConverter : JsonConverter { public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?); - public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) - { + public override object? ReadJson(JsonReader reader, Type t, object? existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var value = serializer.Deserialize(reader); - long l; - if (Int64.TryParse(value, out l)) - { + if (long.TryParse(value, out var l)) { return l; } + throw new Exception("Cannot unmarshal type long"); } - public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) - { - if (untypedValue == null) - { + public override void WriteJson(JsonWriter writer, object? untypedValue, JsonSerializer serializer) { + if (untypedValue == null) { serializer.Serialize(writer, null); return; } - var value = (long)untypedValue; + + var value = (long) untypedValue; serializer.Serialize(writer, value.ToString()); - return; } public static readonly ParseStringConverter Singleton = new ParseStringConverter(); diff --git a/YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs b/YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs index a8bf45b..a235c4e 100644 --- a/YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs +++ b/YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs @@ -2,19 +2,23 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; -using System.Xml; using System.Xml.Serialization; -using Newtonsoft.Json; using YandexMusicResolver.Config; using YandexMusicResolver.Requests; using YandexMusicResolver.Responces; namespace YandexMusicResolver.Loaders { + /// + /// Represents class to getting direct links from tracks + /// public class YandexMusicDirectUrlLoader { private IYandexConfig _config; + /// + /// Initializes a new instance of the class. + /// + /// Config instance for performing requests public YandexMusicDirectUrlLoader(IYandexConfig config) { _config = config; } @@ -23,8 +27,17 @@ public YandexMusicDirectUrlLoader(IYandexConfig config) { private const string DirectUrlFormat = "https://{0}/get-{1}/{2}/{3}{4}"; private const string Mp3Salt = "XGRlBW9FXlekgbPrRHuSiA"; + /// + /// Get direct url to download track + /// + /// If you not authorized will return 30s track version. This is YandexMusic restriction + /// Target track id + /// Target codec. mp3 by default + /// Direct url to download track + /// Couldn't find supported track format public async Task GetDirectUrl(string trackId, string codec) { - var trackDownloadInfos = await new YandexCustomRequest(_config).Create(string.Format(TrackDownloadInfoFormat, trackId)).GetResponseAsync>(); + var trackDownloadInfos = await new YandexCustomRequest(_config).Create(string.Format(TrackDownloadInfoFormat, trackId)) + .GetResponseAsync>(); var track = trackDownloadInfos.FirstOrDefault(downloadInfo => downloadInfo.Codec == codec); if (track == null) { throw new Exception("Couldn't find supported track format."); @@ -35,7 +48,7 @@ public async Task GetDirectUrl(string trackId, string codec) { using var reader = new StringReader(downloadInfoContent); var info = (MetaTrackDownloadInfoXml) serializer.Deserialize(reader); - var sign = Utilities.CreateMD5(Mp3Salt + info.Path.Substring(1) + info.S); + var sign = Utilities.CreateMd5(Mp3Salt + info.Path.Substring(1) + info.S); return string.Format(DirectUrlFormat, info.Host, codec, sign, info.Ts, info.Path); } diff --git a/YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs b/YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs index 57f5394..6d6e869 100644 --- a/YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs +++ b/YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs @@ -7,16 +7,36 @@ using YandexMusicResolver.Responces; namespace YandexMusicResolver.Loaders { + /// + /// Represents class to getting playlists and albums from Yandex Music + /// public class YandexMusicPlaylistLoader : YandexMusicTrackLoader { + /// + /// Initializes a new instance of the class. + /// + /// Config instance for performing requests public YandexMusicPlaylistLoader(IYandexConfig config) : base(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"; + /// + /// 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 public Task LoadPlaylist(string userId, string playlistId, Func trackFactory) { return LoadPlaylistUrl(string.Format(PlaylistInfoFormat, userId, playlistId), trackFactory); } + /// + /// Loads the album from Yandex Music + /// + /// Target album id + /// Track factory to create YandexMusicTrack from AudioTrackInfo + /// Playlist instance public Task LoadPlaylist(string albumId, Func trackFactory) { return LoadPlaylistUrl(string.Format(AlbumInfoFormat, albumId), trackFactory); } diff --git a/YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs b/YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs index 43d3e33..b6c50f3 100644 --- a/YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs +++ b/YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs @@ -8,13 +8,29 @@ using YandexMusicResolver.Responces; namespace YandexMusicResolver.Loaders { + /// + /// Represents search on Yandex Music + /// public class YandexMusicSearchResultLoader { + /// + /// Default limit for searching + /// 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 IYandexConfig _config; + + /// + /// Special prefix for complicated requests + /// public string SearchPrefix { get; } + /// + /// 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"; @@ -22,12 +38,29 @@ public YandexMusicSearchResultLoader(IYandexConfig config, string? searchPrefix SearchRegex = new Regex($"{SearchPrefix}(:([a-zA-Z]+))?(:([0-9]+))?:([^:]+)"); } + /// + /// 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 public Task LoadSearchResult(string query, YandexMusicPlaylistLoader playlistLoader, Func trackFactory) { TryParseQuery(query, out var text, out var type, out var limit); return LoadSearchResult(type, text, playlistLoader, trackFactory, limit); } + /// + /// Parse complicated query into pieces + /// + /// Complicated query is ::limit:text + /// Target query + /// Search text + /// Search type + /// Search limit + /// True if is this complicated query public bool TryParseQuery(string query, out string text, out YandexSearchType type, out int limit) { type = YandexSearchType.Track; limit = DefaultLimit; @@ -43,6 +76,16 @@ public bool TryParseQuery(string query, out string text, out YandexSearchType ty return true; } + /// + /// 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 public async Task LoadSearchResult(YandexSearchType type, string query, YandexMusicPlaylistLoader playlistLoader, Func trackFactory, int limit = DefaultLimit) { try { diff --git a/YandexMusicResolver/Loaders/YandexMusicTrackLoader.cs b/YandexMusicResolver/Loaders/YandexMusicTrackLoader.cs index 1f8cdbd..2aa09c6 100644 --- a/YandexMusicResolver/Loaders/YandexMusicTrackLoader.cs +++ b/YandexMusicResolver/Loaders/YandexMusicTrackLoader.cs @@ -8,19 +8,42 @@ using YandexMusicResolver.Responces; namespace YandexMusicResolver.Loaders { + /// + /// Represents track info loader from Yandex Music + /// public class YandexMusicTrackLoader { + /// + /// Config instance for performing requests + /// protected readonly IYandexConfig Config; + /// + /// Initializes a new instance of the class. + /// + /// Config instance for performing requests public YandexMusicTrackLoader(IYandexConfig config) { Config = config; } private const string TracksInfoFormat = "https://api.music.yandex.net/tracks?trackIds="; - private const string TrackUrlFormat = "https://music.yandex.ru/album/{0}/track/{1}"; - public async Task LoadTrack(string albumId, string trackId, Func trackFactory) { - return trackFactory(await LoadTrackInfo(albumId, trackId)); + + /// + /// Load track + /// + /// Album id with track + /// Target track id + /// Track factory to create YandexMusicTrack from AudioTrackInfo + /// Instance of + public async Task LoadTrack(string albumId, string trackId, Func trackFactory) { + return trackFactory((await LoadTrackInfo(albumId, trackId))!); } + /// + /// Load track info + /// + /// Album id with track + /// Target track id + /// Instance of public async Task LoadTrackInfo(string albumId, string trackId) { var response = await new YandexCustomRequest(Config).Create(TracksInfoFormat + $"{trackId}:{albumId}").GetResponseAsync>(); var entry = response.First(); diff --git a/YandexMusicResolver/Requests/YandexAuthRequest.cs b/YandexMusicResolver/Requests/YandexAuthRequest.cs index 09406b0..4fa3b52 100644 --- a/YandexMusicResolver/Requests/YandexAuthRequest.cs +++ b/YandexMusicResolver/Requests/YandexAuthRequest.cs @@ -24,7 +24,7 @@ public YandexAuthRequest Create(string login, string password) { FormRequest("https://oauth.yandex.ru/token", WebRequestMethods.Http.Post, body: string.Join("&", body.Select(p => $"{p.Key}={HttpUtility.UrlEncode(p.Value)}")), - afterCreate:request => request.ContentType = "application/x-www-form-urlencoded"); + afterCreate: request => request.ContentType = "application/x-www-form-urlencoded"); return this; } diff --git a/YandexMusicResolver/Responces/ITrackInfoContainer.cs b/YandexMusicResolver/Responces/ITrackInfoContainer.cs index c7e0ba4..9ea1f82 100644 --- a/YandexMusicResolver/Responces/ITrackInfoContainer.cs +++ b/YandexMusicResolver/Responces/ITrackInfoContainer.cs @@ -2,7 +2,15 @@ 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/MetaAccount.cs b/YandexMusicResolver/Responces/MetaAccount.cs index dfc3d7a..4ff7a18 100644 --- a/YandexMusicResolver/Responces/MetaAccount.cs +++ b/YandexMusicResolver/Responces/MetaAccount.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; using Newtonsoft.Json; + #pragma warning disable 8618 namespace YandexMusicResolver.Responces { diff --git a/YandexMusicResolver/Responces/MetaAccountResponse.cs b/YandexMusicResolver/Responces/MetaAccountResponse.cs index a37e106..def533b 100644 --- a/YandexMusicResolver/Responces/MetaAccountResponse.cs +++ b/YandexMusicResolver/Responces/MetaAccountResponse.cs @@ -1,11 +1,12 @@ using Newtonsoft.Json; + #pragma warning disable 8618 namespace YandexMusicResolver.Responces { internal class MetaAccountResponse { [JsonProperty("account")] - public MetaAccount Account { get; set; } - + public MetaAccount? Account { get; set; } + [JsonProperty("defaultEmail")] public string DefaultEmail { get; set; } } diff --git a/YandexMusicResolver/Responces/MetaAlbumSignature.cs b/YandexMusicResolver/Responces/MetaAlbumSignature.cs index 3bf296b..b7e8b33 100644 --- a/YandexMusicResolver/Responces/MetaAlbumSignature.cs +++ b/YandexMusicResolver/Responces/MetaAlbumSignature.cs @@ -1,32 +1,58 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Newtonsoft.Json; using YandexMusicResolver.AudioItems; using YandexMusicResolver.Loaders; namespace YandexMusicResolver.Responces { - public class MetaAlbumSignature - { + /// + /// 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; } + 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; } - - public virtual async Task GetPlaylist(YandexMusicPlaylistLoader yandexMusicPlaylistLoader, Func trackFactory) { + + /// + /// 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))!; } } diff --git a/YandexMusicResolver/Responces/MetaArtist.cs b/YandexMusicResolver/Responces/MetaArtist.cs index 856c667..a188ec3 100644 --- a/YandexMusicResolver/Responces/MetaArtist.cs +++ b/YandexMusicResolver/Responces/MetaArtist.cs @@ -1,13 +1,20 @@ -using System.Collections.Generic; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace YandexMusicResolver.Responces { - public class MetaArtist - { + /// + /// Represent a artist in Yandex Music + /// + public class MetaArtist { + /// + /// Artist ID + /// [JsonProperty("id")] public long Id { get; set; } + /// + /// Artist name + /// [JsonProperty("name")] - public string Name { get; set; } + public string Name { get; set; } = null!; } } \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaError.cs b/YandexMusicResolver/Responces/MetaError.cs index f47c7d8..39fac58 100644 --- a/YandexMusicResolver/Responces/MetaError.cs +++ b/YandexMusicResolver/Responces/MetaError.cs @@ -1,12 +1,20 @@ using Newtonsoft.Json; namespace YandexMusicResolver.Responces { - public class MetaError - { + /// + /// Represents error that returned from Yandex Music + /// + public class MetaError { + /// + /// Error name + /// [JsonProperty("name")] - public string Name { get; set; } + public string Name { get; set; } = null!; + /// + /// Error message + /// [JsonProperty("message")] - public string Message { get; set; } + public string Message { get; set; } = null!; } } \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaOwner.cs b/YandexMusicResolver/Responces/MetaOwner.cs index 107fb37..9f28480 100644 --- a/YandexMusicResolver/Responces/MetaOwner.cs +++ b/YandexMusicResolver/Responces/MetaOwner.cs @@ -1,16 +1,31 @@ using Newtonsoft.Json; namespace YandexMusicResolver.Responces { + /// + /// Represent playlist owner + /// public class MetaOwner { + /// + /// Owner ID + /// [JsonProperty("uid")] public long Uid { get; set; } + /// + /// Owner login + /// [JsonProperty("login")] - public string Login { get; set; } + public string Login { get; set; } = null!; + /// + /// Owner name + /// [JsonProperty("name")] - public string Name { get; set; } + public string Name { get; set; } = null!; + /// + /// Is owner verified + /// [JsonProperty("verified")] public bool Verified { get; set; } } diff --git a/YandexMusicResolver/Responces/MetaPlaylist.cs b/YandexMusicResolver/Responces/MetaPlaylist.cs index b569105..8d9b74e 100644 --- a/YandexMusicResolver/Responces/MetaPlaylist.cs +++ b/YandexMusicResolver/Responces/MetaPlaylist.cs @@ -13,7 +13,7 @@ internal class MetaPlaylist { [JsonProperty("ogImage")] public string? OgImage { get; set; } - + [JsonProperty("coverUri")] public string? CoverUri { get; set; } @@ -25,13 +25,13 @@ internal class MetaPlaylist { public List PlaylistTracks { set => Tracks = value.Select(container => container.Track).Cast().ToList(); } - + [JsonProperty("volumes")] [Obsolete] public List> AlbumVolumes { set => Tracks = value.SelectMany(list => list).Cast().ToList(); } - - public List Tracks { get; set; } + + public List Tracks { get; set; } = null!; } } \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaPlaylistSignature.cs b/YandexMusicResolver/Responces/MetaPlaylistSignature.cs index 2e4e40d..c2bcb13 100644 --- a/YandexMusicResolver/Responces/MetaPlaylistSignature.cs +++ b/YandexMusicResolver/Responces/MetaPlaylistSignature.cs @@ -1,11 +1,13 @@ using System; -using System.Collections.Generic; 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] @@ -13,10 +15,15 @@ public long PlaylistId { set => Id = value; } + /// + /// Playlist owner info + /// [JsonProperty("owner")] public MetaOwner Owner { get; set; } = null!; - - public async Task GetPlaylist(YandexMusicPlaylistLoader yandexMusicPlaylistLoader, Func trackFactory) { + + /// + public override async Task GetPlaylist(YandexMusicPlaylistLoader yandexMusicPlaylistLoader, + Func trackFactory) { return (await yandexMusicPlaylistLoader.LoadPlaylist(Owner.Uid.ToString(), Id.ToString(), trackFactory))!; } } diff --git a/YandexMusicResolver/Responces/MetaPlaylistTrack.cs b/YandexMusicResolver/Responces/MetaPlaylistTrack.cs index 071e42e..a3c9204 100644 --- a/YandexMusicResolver/Responces/MetaPlaylistTrack.cs +++ b/YandexMusicResolver/Responces/MetaPlaylistTrack.cs @@ -7,16 +7,29 @@ using YandexMusicResolver.Loaders; namespace YandexMusicResolver.Responces { + /// + /// Represent + /// public 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!; } diff --git a/YandexMusicResolver/Responces/MetaPlaylistTrackContainer.cs b/YandexMusicResolver/Responces/MetaPlaylistTrackContainer.cs index 8c3ca26..a4c3aaa 100644 --- a/YandexMusicResolver/Responces/MetaPlaylistTrackContainer.cs +++ b/YandexMusicResolver/Responces/MetaPlaylistTrackContainer.cs @@ -4,7 +4,7 @@ namespace YandexMusicResolver.Responces { public class MetaPlaylistTrackContainer { [JsonProperty("id")] public long Id { get; set; } - + [JsonProperty("track")] public MetaPlaylistTrack Track { get; set; } = null!; } diff --git a/YandexMusicResolver/Responces/MetaSearchResponse.cs b/YandexMusicResolver/Responces/MetaSearchResponse.cs index 72a943f..daca6e2 100644 --- a/YandexMusicResolver/Responces/MetaSearchResponse.cs +++ b/YandexMusicResolver/Responces/MetaSearchResponse.cs @@ -24,6 +24,6 @@ internal class MetaSearchContentProxy { public long Order { get; set; } [JsonProperty("results")] - public List Results { get; set; } + public List Results { get; set; } = null!; } } \ No newline at end of file diff --git a/YandexMusicResolver/Responces/MetaTrack.cs b/YandexMusicResolver/Responces/MetaTrack.cs index 78085d5..b1007b2 100644 --- a/YandexMusicResolver/Responces/MetaTrack.cs +++ b/YandexMusicResolver/Responces/MetaTrack.cs @@ -7,37 +7,72 @@ 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(); @@ -52,19 +87,20 @@ public Task ToAudioTrackInfo() { new AudioTrackInfo( Title, artists, - TimeSpan.FromMilliseconds(DurationMs), + 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) return; + private static void TryApplyArtwork(ref string? final, string? artwork) { + if (final != null || artwork == null) return; final = "https://" + artwork.Replace("%%", "200x200"); } } diff --git a/YandexMusicResolver/Responces/MetaTrackDownloadInfo.cs b/YandexMusicResolver/Responces/MetaTrackDownloadInfo.cs index cebcbc5..64bfcca 100644 --- a/YandexMusicResolver/Responces/MetaTrackDownloadInfo.cs +++ b/YandexMusicResolver/Responces/MetaTrackDownloadInfo.cs @@ -2,10 +2,9 @@ using Newtonsoft.Json; namespace YandexMusicResolver.Responces { - internal class MetaTrackDownloadInfo - { + internal class MetaTrackDownloadInfo { [JsonProperty("codec")] - public string Codec { get; set; } + public string Codec { get; set; } = null!; [JsonProperty("gain")] public bool Gain { get; set; } @@ -14,7 +13,7 @@ internal class MetaTrackDownloadInfo public bool Preview { get; set; } [JsonProperty("downloadInfoUrl")] - public Uri DownloadInfoUrl { get; set; } + public Uri DownloadInfoUrl { get; set; } = null!; [JsonProperty("direct")] public bool Direct { get; set; } diff --git a/YandexMusicResolver/Responces/MetaTrackDownloadInfoXml.cs b/YandexMusicResolver/Responces/MetaTrackDownloadInfoXml.cs index 0102515..3bac4d2 100644 --- a/YandexMusicResolver/Responces/MetaTrackDownloadInfoXml.cs +++ b/YandexMusicResolver/Responces/MetaTrackDownloadInfoXml.cs @@ -1,12 +1,23 @@ using System.Xml.Serialization; +#pragma warning disable 1591 + namespace YandexMusicResolver.Responces { [XmlRoot(ElementName = "download-info")] public class MetaTrackDownloadInfoXml { - [XmlElement(ElementName = "host")] public string Host { get; set; } - [XmlElement(ElementName = "path")] public string Path { get; set; } - [XmlElement(ElementName = "ts")] public string Ts { get; set; } - [XmlElement(ElementName = "region")] public string Region { get; set; } - [XmlElement(ElementName = "s")] public string S { get; set; } + [XmlElement(ElementName = "host")] + public string Host { get; set; } = null!; + + [XmlElement(ElementName = "path")] + public string Path { get; set; } = null!; + + [XmlElement(ElementName = "ts")] + public string Ts { get; set; } = null!; + + [XmlElement(ElementName = "region")] + public string Region { get; set; } = null!; + + [XmlElement(ElementName = "s")] + public string S { get; set; } = null!; } } \ No newline at end of file diff --git a/YandexMusicResolver/Responces/YandexApiResponce.cs b/YandexMusicResolver/Responces/YandexApiResponce.cs index a46331e..671f9a5 100644 --- a/YandexMusicResolver/Responces/YandexApiResponce.cs +++ b/YandexMusicResolver/Responces/YandexApiResponce.cs @@ -1,12 +1,10 @@ -using System.Collections.Generic; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using Newtonsoft.Json; namespace YandexMusicResolver.Responces { internal class YandexApiResponse { [JsonProperty("result")] public T? Result { get; set; } - + [JsonProperty("error")] public MetaError? Error { get; set; } } diff --git a/YandexMusicResolver/Utilities.cs b/YandexMusicResolver/Utilities.cs index 75cb269..5a0d84c 100644 --- a/YandexMusicResolver/Utilities.cs +++ b/YandexMusicResolver/Utilities.cs @@ -1,21 +1,20 @@ -using System.Text; +using System.Security.Cryptography; +using System.Text; namespace YandexMusicResolver { internal class Utilities { - public static string CreateMD5(string input) - { + public static string CreateMd5(string input) { // Use input string to calculate MD5 hash - using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create()) - { - byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input); + using (MD5 md5 = MD5.Create()) { + byte[] inputBytes = Encoding.ASCII.GetBytes(input); byte[] hashBytes = md5.ComputeHash(inputBytes); // Convert the byte array to hexadecimal string - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < hashBytes.Length; i++) - { - sb.Append(hashBytes[i].ToString("X2")); + var sb = new StringBuilder(); + foreach (var t in hashBytes) { + sb.Append(t.ToString("X2")); } + return sb.ToString(); } } diff --git a/YandexMusicResolver/YandexMusicAuth.cs b/YandexMusicResolver/YandexMusicAuth.cs index 2d004b5..a16683b 100644 --- a/YandexMusicResolver/YandexMusicAuth.cs +++ b/YandexMusicResolver/YandexMusicAuth.cs @@ -1,30 +1,46 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using System.Web; +using System.Threading.Tasks; using YandexMusicResolver.Config; using YandexMusicResolver.Requests; using YandexMusicResolver.Responces; namespace YandexMusicResolver { + /// + /// Represents a set of methods that serve for authorization in Yandex Music + /// public class YandexMusicAuth { - private const string AuthPattern = "https://oauth.yandex.ru/token"; - private const string ClientId = "23cabbbdc6cd418abb4b39c32c41195d"; - private const string ClientSecret = "53bc75238f0c4d08a118e51fe9203300"; + /// + /// 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(); - return !string.IsNullOrEmpty(metaAccountResponse?.Account?.Uid); + var metaAccountResponse = await new YandexCustomRequest(proxyHolder, new TokenHolder(token)).Create("https://api.music.yandex.net/account/status") + .GetResponseAsync(); + return !string.IsNullOrEmpty(metaAccountResponse.Account?.Uid); } + /// + /// Attempt to authorise + /// + /// Login from Yandex account + /// 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) { return (await new YandexAuthRequest(proxyHolder).Create(login, password).ParseResponseAsync()).AccessToken; } - public static async Task<(string, bool)> GetToken(string? existentToken, string fallbackLogin, string fallbackPassword, IYandexProxyHolder? proxyHolder = null) { + /// + /// Try to validate token or get new one using login and password + /// + /// Token to validate + /// Login from Yandex account + /// 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, + IYandexProxyHolder? proxyHolder = null) { if (string.IsNullOrWhiteSpace(existentToken) || !await CheckToken(existentToken, proxyHolder)) { return (await GetToken(fallbackLogin, fallbackPassword, proxyHolder), true); } diff --git a/YandexMusicResolver/YandexMusicMainResolver.cs b/YandexMusicResolver/YandexMusicMainResolver.cs index 144b8d2..f6b2cf6 100644 --- a/YandexMusicResolver/YandexMusicMainResolver.cs +++ b/YandexMusicResolver/YandexMusicMainResolver.cs @@ -1,12 +1,13 @@ -using System; -using System.Net; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using System.Threading.Tasks; using YandexMusicResolver.AudioItems; using YandexMusicResolver.Config; using YandexMusicResolver.Loaders; namespace YandexMusicResolver { + /// + /// Represent main class for interacting with Yandex Music + /// public class YandexMusicMainResolver { private const string TrackUrlPattern = "^https?://music\\.yandex\\.[a-zA-Z]+/album/([0-9]+)/track/([0-9]+)$"; private const string AlbumUrlPattern = "^https?://music\\.yandex\\.[a-zA-Z]+/album/([0-9]+)$"; @@ -15,14 +16,39 @@ public class YandexMusicMainResolver { private static Regex TrackUrlRegex = new Regex(TrackUrlPattern); private static Regex AlbumUrlRegex = new Regex(AlbumUrlPattern); private static Regex PlaylistUrlRegex = new Regex(PlaylistUrlPattern); - private string? _token; - private IYandexConfig _config; + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly IYandexConfig _config; + + /// + /// Instance of + /// public virtual YandexMusicPlaylistLoader PlaylistLoader { get; } + + /// + /// Instance of + /// public virtual YandexMusicTrackLoader TrackLoader { get; } + + /// + /// Instance of + /// public virtual YandexMusicDirectUrlLoader DirectUrlLoader { get; } + + /// + /// Instance of + /// public virtual YandexMusicSearchResultLoader SearchResultLoader { get; } + /// + /// 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 public YandexMusicMainResolver(IYandexConfig config, bool allowSearch = true, YandexMusicPlaylistLoader? playlistLoader = null, @@ -33,12 +59,21 @@ public YandexMusicMainResolver(IYandexConfig config, PlaylistLoader = playlistLoader ?? new YandexMusicPlaylistLoader(_config); TrackLoader = trackLoader ?? new YandexMusicTrackLoader(_config); DirectUrlLoader = directUrlLoader ?? new YandexMusicDirectUrlLoader(_config); - SearchResultLoader = searchResultLoader ?? new YandexMusicSearchResultLoader(_config, null); + SearchResultLoader = searchResultLoader ?? new YandexMusicSearchResultLoader(_config); AllowSearch = allowSearch; } + /// + /// Is query in can be resolved with search + /// public bool AllowSearch { get; } + /// + /// 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) { var trackMatch = TrackUrlRegex.Match(query); if (trackMatch.Success) { diff --git a/YandexMusicResolver/YandexMusicResolver.csproj b/YandexMusicResolver/YandexMusicResolver.csproj index e15e37d..8697f16 100644 --- a/YandexMusicResolver/YandexMusicResolver.csproj +++ b/YandexMusicResolver/YandexMusicResolver.csproj @@ -12,18 +12,23 @@ 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.0.0 + 2.1.0 https://github.com/SKProCH/YandexMusicResolver/blob/master/LICENSE Please write the package release notes in “RELEASE NOTES.md” + + + YandexMusicResolver.xml + + - + - + @(ReleaseNoteLines, '%0a') diff --git a/YandexMusicResolver/YandexMusicResolver.xml b/YandexMusicResolver/YandexMusicResolver.xml new file mode 100644 index 0000000..f9bdfe5 --- /dev/null +++ b/YandexMusicResolver/YandexMusicResolver.xml @@ -0,0 +1,725 @@ + + + + YandexMusicResolver + + + + + Represents errors that returned from yandex api. + + + + + Contains info about error from yandex api + + + + + + + + + + + Marker interface for all loadable items + + + + + Represents playlist from Yandex Music + + + + + Initializes a new instance of the class. + + Playlist title + Collection with tracks + Is this playlist is search result + + + + Playlist title + + + + + Collection with tracks in playlist + + + + + Is this playlist a search result + + + + + Represents YandexMusic search result + + + + + Search query text + + + + + Tracks limit count + + + + + Search data type + + + + + Albums list. + Will be null if the search should not search for albums + + + + + Playlists list. + Will be null if the search should not search for playlists + + + + + Tracks list. + Will be null if the search should not search for tracks + + + + + 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 lenght + + + + + Track identifier + + + + + Is track live stream + + + + + Track link + + + + + Additional track metadata + + + + + Represents implementation placeholder + + + + + + + + + + + + + + + + + + + + + + + Represents implementation that stores data in a file + + + + + Initializes a new instance of the class. + + Target file path + + + + + + + + + + + + + + + + + + + Uri to create proxy + + + + + + + + Represents yandex config + + + + + Load config + + + + + Save config + + + + + Login for Yandex account + + If specified, will be used with a password to get a token if there are problems with the current one + + + + Password for Yandex account + + If specified, will be used with a password to get a token if there are problems with the current one + + + + Try perform authorization + + If false will throw error if we cant authorize + Task represent current async operation + Will be thrown if we cant authorize and is false + + + + Represents entity which can store Yandex token to use it in requests + + + + + + + + + + Represents entity what must contain proxy and token + + + + + Represents entity that must contains proxy + + + + + Gets or sets proxy to use with requests + + + + + Represents class to getting direct links from tracks + + + + + Initializes a new instance of the class. + + Config instance for performing requests + + + + Get direct url to download track + + If you not authorized will return 30s track version. This is YandexMusic restriction + Target track id + Target codec. mp3 by default + Direct url to download track + Couldn't find supported track format + + + + Represents class to getting playlists and albums from Yandex Music + + + + + Initializes a new instance of the class. + + 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 + Playlist instance + + + + Represents search on Yandex Music + + + + + Default limit for searching + + + + + Special prefix for complicated requests + + + + + Initializes a new instance of the class. + + Config instance for performing requests + + + + + 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 + + + + Parse complicated query into pieces + + Complicated query is ::limit:text + Target query + Search text + Search type + 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 + + + + Represents track info loader from Yandex Music + + + + + Config instance for performing requests + + + + + Initializes a new instance of the class. + + 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 info + + Album id with track + Target track id + 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 + + + + Attempt to authorise + + Login from Yandex account + Password from Yandex account + Container for proxy, which should be used for request + Token + + + + Try to validate token or get new one using login and password + + Token to validate + Login from Yandex account + Password from Yandex account + Container for proxy, which should be used for request + Valid token, true if this is new token otherwise false + + + + Represent main class for interacting with Yandex Music + + + + + Instance of + + + + + Instance of + + + + + Instance of + + + + + 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 + + + + Is query in can be resolved with search + + + + + 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 + + + + The type of entities that will be searched for + + + + + Only tracks + + + + + Only albums + + + + + Only playlists + + + + + All types + + + + diff --git a/YandexMusicResolver/YandexSearchType.cs b/YandexMusicResolver/YandexSearchType.cs index e7af2b1..55032ce 100644 --- a/YandexMusicResolver/YandexSearchType.cs +++ b/YandexMusicResolver/YandexSearchType.cs @@ -1,8 +1,26 @@ namespace YandexMusicResolver { + /// + /// The type of entities that will be searched for + /// public enum YandexSearchType { + /// + /// Only tracks + /// Track, + + /// + /// Only albums + /// Album, + + /// + /// Only playlists + /// Playlist, + + /// + /// All types + /// All } } \ No newline at end of file