feat: implement song submission support, refs #5
This commit is contained in:
23
song_of_the_day/SongValidators/Base64UrlImageBuilder.cs
Normal file
23
song_of_the_day/SongValidators/Base64UrlImageBuilder.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
public class Base64UrlImageBuilder
|
||||
{
|
||||
public string ContentType { set; get; }
|
||||
|
||||
public string Url
|
||||
{
|
||||
set
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
var response = (httpClient.GetAsync(new Uri($"{value}"))).Result;
|
||||
var bytes = (response.Content.ReadAsByteArrayAsync()).Result;
|
||||
FileContents = Convert.ToBase64String(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
private string FileContents { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
//return $"data:{ContentType};base64,{FileContents}";
|
||||
return $"{FileContents}";
|
||||
}
|
||||
}
|
@@ -5,4 +5,6 @@ public interface ISongValidator
|
||||
bool CanValidateUri(Uri songUri);
|
||||
|
||||
Task<bool> CanExtractSongMetadataAsync(Uri songUri);
|
||||
|
||||
SongProvider GetSongProvider();
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SpotifyAPI.Web;
|
||||
|
||||
public class SongResolver
|
||||
{
|
||||
@@ -6,16 +7,20 @@ public class SongResolver
|
||||
|
||||
private readonly ILogger<SongResolver> logger;
|
||||
|
||||
public SongResolver(ILogger<SongResolver> logger)
|
||||
private SpotifyApiClient spotifyApiClient;
|
||||
|
||||
public SongResolver(ILogger<SongResolver> logger, ILoggerFactory loggerFactory, SpotifyApiClient spotifyApiClient)
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
this._songValidators = new List<ISongValidator>();
|
||||
this.spotifyApiClient = spotifyApiClient;
|
||||
|
||||
foreach (Type mytype in System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(mytype => mytype.GetInterfaces().Contains(typeof(ISongValidator)) && !(mytype.Name.EndsWith("Base"))))
|
||||
.Where(mytype => { return (mytype.GetInterfaces().Contains(typeof(ISongValidator)) && !(mytype.Name.Split("`")[0].EndsWith("Base"))); }))
|
||||
{
|
||||
if (Activator.CreateInstance(mytype) is ISongValidator validator)
|
||||
var typedLogger = loggerFactory.CreateLogger(mytype);
|
||||
if (Activator.CreateInstance(mytype, typedLogger, spotifyApiClient) is ISongValidator validator)
|
||||
{
|
||||
logger.LogDebug("Registering song validator: {ValidatorType}", mytype.Name);
|
||||
this._songValidators = this._songValidators.Append(validator);
|
||||
@@ -65,4 +70,15 @@ public class SongResolver
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<Base64UrlImageBuilder> GetPreviewImageAsync(string spotifyId)
|
||||
{
|
||||
var track = await spotifyApiClient.GetTrackByIdAsync(spotifyId);
|
||||
var url = track.Album.Images.FirstOrDefault().Url;
|
||||
return new Base64UrlImageBuilder()
|
||||
{
|
||||
Url = url,
|
||||
ContentType = "image/jpeg"
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public abstract class SongValidatorBase : ISongValidator
|
||||
{
|
||||
@@ -8,9 +9,22 @@ public abstract class SongValidatorBase : ISongValidator
|
||||
|
||||
public abstract bool CanValidateUri(Uri songUri);
|
||||
|
||||
protected string LookupSpotifyId(string songName, string songArtist)
|
||||
public abstract SongProvider GetSongProvider();
|
||||
|
||||
protected SpotifyApiClient _spotifyApiClient;
|
||||
|
||||
protected ILogger _logger;
|
||||
|
||||
public SongValidatorBase(ILogger logger, SpotifyApiClient spotifyApiClient)
|
||||
{
|
||||
// TODO: Implement Spotify ID lookup logic
|
||||
return songName + " by " + songArtist;
|
||||
_spotifyApiClient = spotifyApiClient;
|
||||
_logger = logger;
|
||||
|
||||
}
|
||||
|
||||
protected async Task<string> LookupSpotifyIdAsync(string songName, string songArtist)
|
||||
{
|
||||
var candidates = await _spotifyApiClient.GetTrackCandidatesAsync(songName, songArtist);
|
||||
return candidates.Any() ? candidates[0].Id : "";
|
||||
}
|
||||
}
|
@@ -7,24 +7,25 @@ public class SpotifyValidator : UriBasedSongValidatorBase
|
||||
{
|
||||
public override string UriValidatorRegex => @"^(https?://)?open.spotify.com/track/([a-zA-Z0-9_-]{22})(\?si=[a-zA-Z0-9_-]+)?$";
|
||||
|
||||
private SpotifyApiClient spotifyApiClient;
|
||||
|
||||
public SpotifyValidator()
|
||||
{
|
||||
spotifyApiClient = new SpotifyApiClient();
|
||||
}
|
||||
public SpotifyValidator(ILogger _logger, SpotifyApiClient spotifyApiClient) : base(_logger, spotifyApiClient)
|
||||
{}
|
||||
|
||||
public override Task<bool> CanExtractSongMetadataAsync(Uri songUri)
|
||||
{
|
||||
return Task.FromResult(this.CanValidateUri(songUri));
|
||||
}
|
||||
|
||||
public override SongProvider GetSongProvider()
|
||||
{
|
||||
return SongProvider.Spotify;
|
||||
}
|
||||
|
||||
public override async Task<Song> ValidateAsync(Uri songUri)
|
||||
{
|
||||
var regexp = new Regex(UriValidatorRegex, RegexOptions.IgnoreCase);
|
||||
var trackIdMatch = regexp.Match(songUri.ToString()).Groups[2].Value;
|
||||
|
||||
var track = await spotifyApiClient.GetTrackByIdAsync(trackIdMatch);
|
||||
var track = await _spotifyApiClient.GetTrackByIdAsync(trackIdMatch);
|
||||
|
||||
var song = new Song
|
||||
{
|
||||
|
@@ -4,9 +4,17 @@ public abstract class UriBasedSongValidatorBase : SongValidatorBase
|
||||
{
|
||||
public abstract string UriValidatorRegex { get; }
|
||||
|
||||
public override bool CanValidateUri(Uri songUri)
|
||||
public UriBasedSongValidatorBase(ILogger logger, SpotifyApiClient spotifyApiClient) : base(logger, spotifyApiClient)
|
||||
{}
|
||||
|
||||
public Match GetUriMatch(Uri songUri)
|
||||
{
|
||||
var regexp = new Regex(UriValidatorRegex, RegexOptions.IgnoreCase);
|
||||
return regexp.Match(songUri.ToString()).Success;
|
||||
return regexp.Match(songUri.ToString());
|
||||
}
|
||||
|
||||
public override bool CanValidateUri(Uri songUri)
|
||||
{
|
||||
return GetUriMatch(songUri).Success;
|
||||
}
|
||||
}
|
@@ -1,9 +1,22 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using YouTubeMusicAPI.Client;
|
||||
|
||||
public class YoutubeMusicValidator : UriBasedSongValidatorBase
|
||||
{
|
||||
public override string UriValidatorRegex => @"^(https?://)?(music\.youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})";
|
||||
public override string UriValidatorRegex => @"^(https?://)?(music\.youtube\.com/playlist\?list=)([a-zA-Z0-9_-]+)";
|
||||
|
||||
private YouTubeMusicClient youtubeClient;
|
||||
|
||||
public YoutubeMusicValidator(ILogger logger, SpotifyApiClient spotifyApiClient) : base(logger, spotifyApiClient)
|
||||
{
|
||||
youtubeClient = new YouTubeMusicClient(logger, "AT", null, null, null);
|
||||
}
|
||||
|
||||
public override SongProvider GetSongProvider()
|
||||
{
|
||||
return SongProvider.YoutubeMusic;
|
||||
}
|
||||
|
||||
public override Task<bool> CanExtractSongMetadataAsync(Uri songUri)
|
||||
{
|
||||
@@ -12,35 +25,23 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
|
||||
|
||||
public override async Task<Song> ValidateAsync(Uri songUri)
|
||||
{
|
||||
var title = string.Empty;
|
||||
var artist = string.Empty;
|
||||
var match = this.GetUriMatch(songUri);
|
||||
var playlistId = match.Groups[3].Value;
|
||||
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
var response = await httpClient.GetAsync(songUri);
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var context = BrowsingContext.New(config);
|
||||
using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
|
||||
{
|
||||
// document.getElementsByTagName("ytmusic-player-queue-item")[0].getElementsByClassName("song-title")[0].innerHTML
|
||||
title = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".song-title")?.InnerHtml;
|
||||
// document.getElementsByTagName("ytmusic-player-queue-item")[0].getElementsByClassName("byline")[0].innerHTML
|
||||
artist = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".byline")?.InnerHtml;
|
||||
}
|
||||
}
|
||||
var playlistResult = await youtubeClient.GetCommunityPlaylistInfoAsync(playlistId);
|
||||
var songData = playlistResult.Songs[0];
|
||||
|
||||
var title = songData.Name;
|
||||
var artist = songData.Artists[0].Name;
|
||||
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
var song = new Song
|
||||
{
|
||||
Name = title,
|
||||
Artist = artist,
|
||||
Url = songUri.ToString(),
|
||||
Provider = SongProvider.YouTube,
|
||||
SpotifyId = this.LookupSpotifyId(title, artist)
|
||||
Provider = SongProvider.YoutubeMusic,
|
||||
SpotifyId = await this.LookupSpotifyIdAsync(title, artist)
|
||||
};
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
|
||||
return song;
|
||||
}
|
||||
|
@@ -1,60 +1,47 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Dom;
|
||||
using YouTubeMusicAPI.Client;
|
||||
|
||||
public class YoutubeValidator : UriBasedSongValidatorBase
|
||||
{
|
||||
private YouTubeMusicClient youtubeClient;
|
||||
|
||||
public YoutubeValidator(ILogger logger, SpotifyApiClient spotifyApiClient) : base(logger, spotifyApiClient)
|
||||
{
|
||||
youtubeClient = new("AT");
|
||||
}
|
||||
|
||||
public override string UriValidatorRegex => @"^(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})";
|
||||
|
||||
public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
|
||||
{
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
var response = await httpClient.GetAsync(songUri);
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var context = BrowsingContext.New(config);
|
||||
using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
|
||||
{
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
var documentContents = (document.ChildNodes[1] as HtmlElement).InnerHtml;
|
||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
|
||||
var titleElement = document.QuerySelectorAll(".yt-video-attribute-view-model__title")[0];
|
||||
var artistParentElement = document.QuerySelectorAll(".yt-video-attribute-view-model__secondary-subtitle")[0];
|
||||
return this.CanValidateUri(songUri);
|
||||
}
|
||||
|
||||
return titleElement != null && artistParentElement != null && artistParentElement.Children.Length > 0;
|
||||
}
|
||||
}
|
||||
public override SongProvider GetSongProvider()
|
||||
{
|
||||
return SongProvider.YouTube;
|
||||
}
|
||||
|
||||
public override async Task<Song> ValidateAsync(Uri songUri)
|
||||
{
|
||||
var title = string.Empty;
|
||||
var artist = string.Empty;
|
||||
var match = this.GetUriMatch(songUri);
|
||||
var songId = match.Groups[4].Value;
|
||||
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
var response = await httpClient.GetAsync(songUri);
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var context = BrowsingContext.New(config);
|
||||
using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
|
||||
{
|
||||
title = document.QuerySelectorAll(".yt-video-attribute-view-model__title")[0]?.InnerHtml;
|
||||
artist = document.QuerySelectorAll(".yt-video-attribute-view-model__secondary-subtitle")[0]?.Children[0]?.InnerHtml;
|
||||
}
|
||||
}
|
||||
var songData = await youtubeClient.GetSongVideoInfoAsync(songId);
|
||||
|
||||
var title = songData.Name;
|
||||
var artist = songData.Artists[0].Name;
|
||||
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
var song = new Song
|
||||
{
|
||||
Name = title,
|
||||
Artist = artist,
|
||||
Url = songUri.ToString(),
|
||||
Provider = SongProvider.YouTube,
|
||||
SpotifyId = this.LookupSpotifyId(title, artist)
|
||||
SpotifyId = await this.LookupSpotifyIdAsync(title, artist)
|
||||
};
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
#pragma warning restore CS8604 // Possible null reference argument.
|
||||
|
||||
return song;
|
||||
}
|
||||
|
Reference in New Issue
Block a user