121 lines
3.8 KiB
C#

using System.Threading.Tasks;
using AngleSharp;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using YouTubeMusicAPI.Client;
using System.Text.Json;
using Acornima.Ast;
using AngleSharp.Text;
using System.Text.RegularExpressions;
using System.Text.Json.Serialization;
public class NavidromeValidator : SongValidatorBase
{
private YouTubeMusicClient youtubeClient;
public NavidromeValidator(ILogger logger, SpotifyApiClient spotifyApiClient) : base(logger, spotifyApiClient)
{
youtubeClient = new("AT");
}
public override async Task<bool> CanValidateUriAsync(Uri songUri)
{
// Check if behind this URL there is a public navidrome share
var config = Configuration.Default.WithDefaultLoader();
var address = songUri.ToString();
var context = BrowsingContext.New(config);
var document = await context.OpenAsync(address);
var titleCell = (document.DocumentElement.GetDescendants().First() as HtmlElement)
.GetElementsByTagName("title").First();
return "Navidrome".Equals(titleCell.TextContent);
}
public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
{
return await this.CanValidateUriAsync(songUri);
}
public override SongProvider GetSongProvider()
{
return SongProvider.NavidromeSharedLink;
}
private static Stream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
public override async Task<Song> ValidateAsync(Uri songUri)
{
var config = Configuration.Default.WithDefaultLoader();
var address = songUri.ToString();
var context = BrowsingContext.New(config);
var document = await context.OpenAsync(address);
var infoScriptNode = document.GetElementsByTagName("script").Where(e => e.TextContent.Contains("__SHARE_INFO__")).First();
var manipulatedValue = infoScriptNode.TextContent.Replace("window.__SHARE_INFO__ = \"", "").StripLeadingTrailingSpaces().StripLineBreaks();
manipulatedValue = manipulatedValue.Remove(manipulatedValue.Length - 1);
var infoScriptJsonData = Regex.Unescape(Regex.Unescape(manipulatedValue));
var title = string.Empty;
var artist = string.Empty;
using (var stream = GenerateStreamFromString(infoScriptJsonData))
{
var jsonContent = await JsonSerializer.DeserializeAsync<NavidromeShareInfoData>(stream);
title = jsonContent.Tracks[0].Title;
artist = jsonContent.Tracks[0].Artist;
}
var song = new Song
{
Name = title,
Artist = artist,
Url = songUri.ToString(),
Provider = SongProvider.NavidromeSharedLink,
SpotifyId = await this.LookupSpotifyIdAsync(title, artist)
};
return song;
}
}
public class NavidromeShareInfoData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("downloadable")]
public bool Downloadable { get; set; }
[JsonPropertyName("tracks")]
public List<NavidromeTrackInfoData> Tracks { get; set; }
}
public class NavidromeTrackInfoData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("artist")]
public string Artist { get; set; }
[JsonPropertyName("album")]
public string Album { get; set; }
[JsonPropertyName("updatedAt")]
public DateTime UpdatedAt { get; set; }
[JsonPropertyName("duration")]
public float Duration { get; set; }
}