Skip to content

Commit

Permalink
Add YandexId type to handle cursed Yandex ids (long/guid)
Browse files Browse the repository at this point in the history
  • Loading branch information
SKProCH committed Jan 14, 2024
1 parent fcd76c4 commit 1d1ed72
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 4 deletions.
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
}
}
36 changes: 36 additions & 0 deletions YandexMusicResolver/Ids/YandexIdConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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.TryGetInt64(out var l)) {
return new YandexId(l);
}

if (reader.TryGetGuid(out var guid)) {
return new YandexId(guid);
}

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");
}
}
}
5 changes: 3 additions & 2 deletions YandexMusicResolver/Responses/MetaTrack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text.Json.Serialization;
using YandexMusicResolver.AudioItems;
using YandexMusicResolver.Ids;

namespace YandexMusicResolver.Responses {
/// <summary>
Expand All @@ -11,8 +12,8 @@ namespace YandexMusicResolver.Responses {
internal class MetaTrack {
private const string TrackUrlFormat = "https://music.yandex.ru/album/{0}/track/{1}";

[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public long Id { get; set; }
[JsonConverter(typeof(YandexIdConverter))]
public YandexId Id { get; set; }

public string Title { get; set; } = null!;

Expand Down

0 comments on commit 1d1ed72

Please sign in to comment.