Skip to content

Commit

Permalink
Merge pull request #6 from SKProCH/dev
Browse files Browse the repository at this point in the history
Add YandexId type to handle cursed Yandex ids (long/guid)
  • Loading branch information
SKProCH authored Aug 9, 2024
2 parents 012a064 + ead9bf4 commit 554c5c2
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void DoPlaylistSearch() {
[InlineData("myprefix")]
public void TestPrefixes(string? prefix) {
#pragma warning disable 8625
var yandexMusicSearchResultLoader = new YandexMusicSearchResultLoader(YandexCredentialsProviderMock.Object, new HttpClient(), AutoMocker.Get<IYandexMusicPlaylistLoader>());
var yandexMusicSearchResultLoader = YandexMusicSearchResultLoader.CreateWithHttpClient(YandexCredentialsProviderMock.Object, new HttpClient(), AutoMocker.Get<IYandexMusicPlaylistLoader>());
yandexMusicSearchResultLoader.SetSearchPrefix(prefix);
#pragma warning restore 8625
prefix ??= "ymsearch";
Expand Down
4 changes: 2 additions & 2 deletions YandexMusicResolver.Tests/YandexMusicTrackLoaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ public class YandexMusicTrackLoaderTest : YandexTestBase {
[InlineData(9425747, 55561798)]
[InlineData(12033669, 70937156)]
public void GetTrackInfo(long albumId, long trackId) {
var yandexMusicTrackLoader = new YandexMusicTrackLoader(YandexCredentialsProviderMock.Object, new HttpClient());
var yandexMusicTrackLoader = YandexMusicTrackLoader.CreateWithHttpClient(YandexCredentialsProviderMock.Object, new HttpClient());
var trackInfo = yandexMusicTrackLoader.LoadTrack(trackId).GetAwaiter().GetResult();
Assert.NotNull(trackInfo);
Assert.Equal($"https://music.yandex.ru/album/{albumId}/track/{trackId}", trackInfo.Uri);
}

[Fact]
public void GetTracksInfo() {
var yandexMusicTrackLoader = new YandexMusicTrackLoader(YandexCredentialsProviderMock.Object, new HttpClient());
var yandexMusicTrackLoader = YandexMusicTrackLoader.CreateWithHttpClient(YandexCredentialsProviderMock.Object, new HttpClient());
var trackIds = new List<long>() { 55561798, 70937156 };
var trackInfo = yandexMusicTrackLoader.LoadTracks(trackIds).GetAwaiter().GetResult();
Assert.NotNull(trackInfo);
Expand Down
8 changes: 6 additions & 2 deletions YandexMusicResolver/AudioItems/YandexMusicArtist.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
namespace YandexMusicResolver.AudioItems {
using System.Text.Json.Serialization;
using YandexMusicResolver.Ids;

namespace YandexMusicResolver.AudioItems {
/// <summary>
/// Represent a artist in Yandex Music
/// </summary>
public class YandexMusicArtist {
/// <summary>
/// Artist ID
/// </summary>
public long Id { get; set; }
[JsonConverter(typeof(YandexIdConverter))]
public YandexId Id { get; set; }

/// <summary>
/// Artist name
Expand Down
5 changes: 3 additions & 2 deletions YandexMusicResolver/AudioItems/YandexMusicTrack.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using YandexMusicResolver.Ids;

namespace YandexMusicResolver.AudioItems {
/// <summary>
/// AudioTrackInfo wrapper to resolve track direct url
/// </summary>
public class YandexMusicTrack {
internal YandexMusicTrack(string title, List<YandexMusicArtist> authors, TimeSpan length, long id, string? uri, bool isAvailable, string? artworkUrl = null) {
internal YandexMusicTrack(string title, List<YandexMusicArtist> authors, TimeSpan length, YandexId id, string? uri, bool isAvailable, string? artworkUrl = null) {
Title = title;
Authors = authors;
Length = length;
Expand Down Expand Up @@ -40,7 +41,7 @@ internal YandexMusicTrack(string title, List<YandexMusicArtist> authors, TimeSpa
/// <summary>
/// Track id
/// </summary>
public long Id { get; }
public YandexId Id { get; }

/// <summary>
/// Track link
Expand Down
128 changes: 128 additions & 0 deletions YandexMusicResolver/Ids/YandexId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System;

namespace YandexMusicResolver.Ids;

/// <summary>
/// Holds the yandex track id
/// </summary>
/// <remarks>
/// Since Yandex fucked up as usual, it will return an <see cref="long"/> ids for yandex's track, but will return <see cref="Guid"/> ids for user uploaded tracks
/// </remarks>
public struct YandexId {
private long? _longId;
private Guid? _guidId;

/// <summary>
/// Gets the <see cref="Guid"/> id
/// </summary>
/// <exception cref="InvalidOperationException">Current Id is not an Guid type. You can look at <see cref="IdType"/> to determine proper id type</exception>
public Guid GuidId {
get => _guidId ?? throw new InvalidOperationException($"Current Id is not an Guid type. Current type is {IdType}");
set => _guidId = value;
}

/// <summary>
/// Gets the <see cref="long"/> id
/// </summary>
/// <exception cref="InvalidOperationException">Current Id is not an long type. You can look at <see cref="IdType"/> to determine proper id type</exception>
public long LongId {
get => _longId ?? throw new InvalidOperationException($"Current Id is not an long type. Current type is {IdType}");
set => _longId = value;
}

/// <summary>
/// Gets the current id type
/// </summary>
public YandexIdType IdType { get; }

/// <summary>
/// Creates <see cref="YandexId"/> with <see cref="Guid"/> id
/// </summary>
/// <param name="id">The <see cref="Guid"/> id</param>
public YandexId(Guid id) {
IdType = YandexIdType.Guid;
_guidId = id;
}

/// <summary>
/// Creates <see cref="YandexId"/> with <see cref="long"/> id
/// </summary>
/// <param name="id">The <see cref="long"/> id</param>
public YandexId(long id) {
IdType = YandexIdType.Long;
_longId = id;
}

/// <summary>
/// Allows to implicitly cast <see cref="YandexId"/> to <see cref="Guid"/>
/// </summary>
/// <param name="yandexId">Instance of <see cref="YandexId"/></param>
/// <returns>The <see cref="Guid"/> value</returns>
public static implicit operator Guid(YandexId yandexId) => yandexId.GuidId;

/// <summary>
/// Allows to implicitly cast <see cref="YandexId"/> to <see cref="long"/>
/// </summary>
/// <param name="yandexId">Instance of <see cref="YandexId"/></param>
/// <returns>The <see cref="long"/> value</returns>
public static implicit operator long(YandexId yandexId) => yandexId.LongId;

/// <summary>
/// Allows to implicitly cast <see cref="long"/> to <see cref="YandexId"/>
/// </summary>
/// <param name="id"><see cref="long"/> value</param>
/// <returns>The <see cref="YandexId"/> intance</returns>
public static implicit operator YandexId(long id) => new(id);

/// <summary>
/// Allows to implicitly cast <see cref="Guid"/> to <see cref="YandexId"/>
/// </summary>
/// <param name="id"><see cref="Guid"/> value</param>
/// <returns>The <see cref="YandexId"/> intance</returns>
public static implicit operator YandexId(Guid id) => new(id);

/// <inheritdoc />
public override string ToString() {
if (_longId is not null) {
return _longId.ToString();
}

if (_guidId is not null) {
return _guidId.ToString();
}

throw new NotSupportedException("Current Id type doesn't support to string conversion. Report this to the library author");
}

/// <summary>
/// Tries to parse <see cref="YandexId"/> from the string
/// </summary>
/// <param name="s"><see cref="string"/> to parse the Id</param>
/// <returns>Instance of <see cref="YandexId"/></returns>
/// <exception cref="NotSupportedException">Current string cannot be converted to any known Id type. Check the string</exception>
public static YandexId Parse(string s) {
if (Guid.TryParse(s, out var guid)) {
return new YandexId(guid);
}

if (long.TryParse(s, out var l)) {
return new YandexId(l);
}

throw new NotSupportedException("Current string cannot be converted to any known Id type. Check the string");
}

/// <summary>
/// Available types of Yandex ids
/// </summary>
public enum YandexIdType {
/// <summary>
/// Current id is long
/// </summary>
Long,
/// <summary>
/// Current id is Guid
/// </summary>
Guid
}
}
42 changes: 42 additions & 0 deletions YandexMusicResolver/Ids/YandexIdConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace YandexMusicResolver.Ids;

/// <summary>
/// Allows to properly serialize/deserialize the <see cref="YandexId"/> from JSON
/// </summary>
public class YandexIdConverter : JsonConverter<YandexId> {
/// <inheritdoc />
public override YandexId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
if (reader.TokenType == JsonTokenType.String) {
var s = reader.GetString();
if (long.TryParse(s, out var l)) {
return new YandexId(l);
}

if (Guid.TryParse(s, out var guid)) {
return new YandexId(guid);
}
}
else if (reader.TokenType == JsonTokenType.Number) {
var int64 = reader.GetInt64();
return new YandexId(int64);
}
throw new NotSupportedException("Current value seems doesn't look like any known YandexId type");
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, YandexId value, JsonSerializerOptions options) {
switch (value.IdType) {
case YandexId.YandexIdType.Long:
writer.WriteNumberValue(value.LongId);
break;
case YandexId.YandexIdType.Guid:
writer.WriteStringValue(value.GuidId);
break;
default:
throw new ArgumentOutOfRangeException(nameof(value.IdType), "Current YandexId type can't be wrote to JSON");
}
}
}
46 changes: 45 additions & 1 deletion YandexMusicResolver/Loaders/IYandexMusicTrackLoader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using YandexMusicResolver.AudioItems;
using YandexMusicResolver.Ids;

namespace YandexMusicResolver.Loaders {
/// <summary>
Expand All @@ -13,12 +15,54 @@ public interface IYandexMusicTrackLoader {
/// <param name="trackId">Target track id</param>
/// <returns>Instance of <see cref="YandexMusicTrack"/></returns>
Task<YandexMusicTrack?> LoadTrack(long trackId);

/// <summary>
/// Load track info
/// </summary>
/// <param name="trackId">Target track id</param>
/// <returns>Instance of <see cref="YandexMusicTrack"/></returns>
Task<YandexMusicTrack?> LoadTrack(Guid trackId);

/// <summary>
/// Load track info
/// </summary>
/// <param name="trackId">Target track id</param>
/// <returns>Instance of <see cref="YandexMusicTrack"/></returns>
Task<YandexMusicTrack?> LoadTrack(YandexId trackId);

/// <summary>
/// Load track info
/// </summary>
/// <param name="trackId">Target track id</param>
/// <returns>Instance of <see cref="YandexMusicTrack"/></returns>
Task<YandexMusicTrack?> LoadTrack(string trackId);

/// <summary>
/// Load track infos
/// </summary>
/// <param name="trackIds">Target track ids</param>
/// <returns>List of instances of <see cref="YandexMusicTrack"/></returns>
Task<IReadOnlyCollection<YandexMusicTrack>> LoadTracks(IEnumerable<long> trackIds);

/// <summary>
/// Load track infos
/// </summary>
/// <param name="trackIds">Target track ids</param>
/// <returns>List of instances of <see cref="YandexMusicTrack"/></returns>
Task<IReadOnlyCollection<YandexMusicTrack>> LoadTracks(IEnumerable<Guid> trackIds);

/// <summary>
/// Load track infos
/// </summary>
/// <param name="trackIds">Target track ids</param>
/// <returns>List of instances of <see cref="YandexMusicTrack"/></returns>
Task<IReadOnlyCollection<YandexMusicTrack>> LoadTracks(IEnumerable<YandexId> trackIds);

/// <summary>
/// Load track infos
/// </summary>
/// <param name="trackIds">Target track ids</param>
/// <returns>List of instances of <see cref="YandexMusicTrack"/></returns>
Task<IReadOnlyCollection<YandexMusicTrack>> LoadTracks(IEnumerable<string> trackIds);
}
}
12 changes: 7 additions & 5 deletions YandexMusicResolver/Loaders/YandexMusicDirectUrlLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ public class YandexMusicDirectUrlLoader : IYandexMusicDirectUrlLoader {
/// </summary>
/// <param name="credentialsProvider">Config instance for performing requests</param>
/// <param name="httpClientFactory">Factory for resolving HttpClient. Client name is <see cref="YandexMusicUtilities.HttpClientName"/></param>
public YandexMusicDirectUrlLoader(IYandexCredentialsProvider credentialsProvider, IHttpClientFactory httpClientFactory) {
_httpClient = httpClientFactory.GetYMusicHttpClient();
public YandexMusicDirectUrlLoader(IYandexCredentialsProvider credentialsProvider, IHttpClientFactory httpClientFactory)
: this(credentialsProvider, httpClientFactory.GetYMusicHttpClient()) { }

private YandexMusicDirectUrlLoader(IYandexCredentialsProvider credentialsProvider, HttpClient httpClient) {
_httpClient = httpClient;
_credentialsProvider = credentialsProvider;
}

Expand All @@ -32,9 +35,8 @@ public YandexMusicDirectUrlLoader(IYandexCredentialsProvider credentialsProvider
/// </summary>
/// <param name="credentialsProvider">Config instance for performing requests</param>
/// <param name="httpClient">HttpClient for performing requests. But preferred way is use another ctor and pass <see cref="IHttpClientFactory"/></param>
public YandexMusicDirectUrlLoader(IYandexCredentialsProvider credentialsProvider, HttpClient httpClient) {
_httpClient = httpClient;
_credentialsProvider = credentialsProvider;
public static YandexMusicDirectUrlLoader CreateWithHttpClient(IYandexCredentialsProvider credentialsProvider, HttpClient httpClient) {
return new YandexMusicDirectUrlLoader(credentialsProvider, httpClient);
}

/// <inheritdoc />
Expand Down
13 changes: 7 additions & 6 deletions YandexMusicResolver/Loaders/YandexMusicPlaylistLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ public class YandexMusicPlaylistLoader : IYandexMusicPlaylistLoader {
/// <param name="yandexCredentialsProvider">Config instance for performing requests</param>
/// <param name="httpClientFactory">Factory for resolving HttpClient. Client name is <see cref="YandexMusicUtilities.HttpClientName"/></param>
/// <param name="trackLoader">Instance of <see cref="YandexMusicTrackLoader"/> for resolving some strange playlists</param>
public YandexMusicPlaylistLoader(IYandexCredentialsProvider yandexCredentialsProvider, IHttpClientFactory httpClientFactory, IYandexMusicTrackLoader? trackLoader = null) {
_httpClient = httpClientFactory.GetYMusicHttpClient();
public YandexMusicPlaylistLoader(IYandexCredentialsProvider yandexCredentialsProvider, IHttpClientFactory httpClientFactory, IYandexMusicTrackLoader? trackLoader = null)
: this(yandexCredentialsProvider, httpClientFactory.GetYMusicHttpClient(), trackLoader) { }

private YandexMusicPlaylistLoader(IYandexCredentialsProvider yandexCredentialsProvider, HttpClient httpClient, IYandexMusicTrackLoader? trackLoader = null) {
_httpClient = httpClient;
_trackLoader = trackLoader;
_yandexCredentialsProvider = yandexCredentialsProvider;
}
Expand All @@ -33,10 +36,8 @@ public YandexMusicPlaylistLoader(IYandexCredentialsProvider yandexCredentialsPro
/// <param name="yandexCredentialsProvider">Config instance for performing requests</param>
/// <param name="httpClient">HttpClient for performing requests. But preferred way is use another ctor and pass <see cref="IHttpClientFactory"/></param>
/// <param name="trackLoader">Instance of <see cref="YandexMusicTrackLoader"/> for resolving some strange playlists</param>
public YandexMusicPlaylistLoader(IYandexCredentialsProvider yandexCredentialsProvider, HttpClient httpClient, IYandexMusicTrackLoader? trackLoader = null) {
_httpClient = httpClient;
_trackLoader = trackLoader;
_yandexCredentialsProvider = yandexCredentialsProvider;
public static YandexMusicPlaylistLoader CreateWithHttpClient(IYandexCredentialsProvider yandexCredentialsProvider, HttpClient httpClient, IYandexMusicTrackLoader? trackLoader = null) {
return new YandexMusicPlaylistLoader(yandexCredentialsProvider, httpClient, trackLoader);
}

/// <inheritdoc />
Expand Down
13 changes: 7 additions & 6 deletions YandexMusicResolver/Loaders/YandexMusicSearchResultLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ public class YandexMusicSearchResultLoader : IYandexMusicSearchResultLoader {
/// <param name="credentialsProvider">Config instance for performing requests</param>
/// <param name="httpClientFactory">Factory for resolving HttpClient. Client name is <see cref="YandexMusicUtilities.HttpClientName"/></param>
/// <param name="playlistLoader">Playlist loader instance for resolving albums and playlists</param>
public YandexMusicSearchResultLoader(IYandexCredentialsProvider credentialsProvider, IHttpClientFactory httpClientFactory, IYandexMusicPlaylistLoader playlistLoader) {
_httpClient = httpClientFactory.GetYMusicHttpClient();
public YandexMusicSearchResultLoader(IYandexCredentialsProvider credentialsProvider, IHttpClientFactory httpClientFactory, IYandexMusicPlaylistLoader playlistLoader)
: this(credentialsProvider, httpClientFactory.GetYMusicHttpClient(), playlistLoader) { }

private YandexMusicSearchResultLoader(IYandexCredentialsProvider credentialsProvider, HttpClient httpClient, IYandexMusicPlaylistLoader playlistLoader) {
_httpClient = httpClient;
_playlistLoader = playlistLoader;
_credentialsProvider = credentialsProvider;
}
Expand All @@ -43,10 +46,8 @@ public YandexMusicSearchResultLoader(IYandexCredentialsProvider credentialsProvi
/// <param name="credentialsProvider">Config instance for performing requests</param>
/// <param name="httpClient">HttpClient for performing requests. But preferred way is use another ctor and pass <see cref="IHttpClientFactory"/></param>
/// <param name="playlistLoader">Playlist loader instance for resolving albums and playlists</param>
public YandexMusicSearchResultLoader(IYandexCredentialsProvider credentialsProvider, HttpClient httpClient, IYandexMusicPlaylistLoader playlistLoader) {
_httpClient = httpClient;
_playlistLoader = playlistLoader;
_credentialsProvider = credentialsProvider;
public static YandexMusicSearchResultLoader CreateWithHttpClient(IYandexCredentialsProvider credentialsProvider, HttpClient httpClient, IYandexMusicPlaylistLoader playlistLoader) {
return new YandexMusicSearchResultLoader(credentialsProvider, httpClient, playlistLoader);
}

/// <inheritdoc />
Expand Down
Loading

0 comments on commit 554c5c2

Please sign in to comment.