From ce97f92bf5a9db7f98417d173afffac8f18fd59f Mon Sep 17 00:00:00 2001 From: 10092023-G49 <10092023-G49@deccansoftstudents.onmicrosoft.com> Date: Tue, 31 Dec 2024 16:49:08 +1100 Subject: [PATCH 1/2] Issue 38: Initial commit for Lyrics Freak Provider --- LyricsScraperNET.Client/appsettings.json | 4 + .../ILyricScraperClientConfig.cs | 1 + .../Configuration/LyricScraperClientConfig.cs | 6 +- .../ServiceCollectionExtensions.cs | 2 + .../LyricsScraperClientExtensions.cs | 10 +- .../Extensions/StringExtensions.cs | 20 + .../LyricsFreak/LyricsFreakOptions.cs | 31 + .../LyricsFreak/LyricsFreakParser.cs | 21 + .../LyricsFreak/LyricsFreakProvider.cs | 160 ++ .../LyricsFreak/LyricsFreakUriConverter.cs | 28 + .../Providers/Models/ExternalProviderType.cs | 3 +- .../LyricsScraperNET.IntegrationTest.csproj | 9 + .../LyricsFreak/LyricsFreakProviderTest.cs | 106 + .../Resources/Lyrics_Result_01.txt | 48 + .../Resources/Lyrics_Result_02.txt | 56 + .../LyricsFreak/lyric_test_data.json | 20 + .../Extensions/ExternalProviderExtensions.cs | 12 + .../TestModel/LyricsTestData.cs | 2 + .../ServiceCollectionExtensionsTest.cs | 3 +- .../LyricsScraperNET.UnitTest.csproj | 12 + .../LyricsFreak/LyricsFreakProviderTest.cs | 37 + .../Resources/Lyrics_HtmlPage_01.txt | 1780 +++++++++++++++++ .../Resources/Lyrics_HtmlPage_02.txt | 1240 ++++++++++++ .../Resources/Lyrics_Result_01.txt | 48 + .../LyricsFreak/lyric_test_data.json | 10 + .../Resources/full_test_settings.json | 4 + 26 files changed, 3669 insertions(+), 4 deletions(-) create mode 100644 LyricsScraperNET/Providers/LyricsFreak/LyricsFreakOptions.cs create mode 100644 LyricsScraperNET/Providers/LyricsFreak/LyricsFreakParser.cs create mode 100644 LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs create mode 100644 LyricsScraperNET/Providers/LyricsFreak/LyricsFreakUriConverter.cs create mode 100644 Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/LyricsFreakProviderTest.cs create mode 100644 Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/Resources/Lyrics_Result_01.txt create mode 100644 Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/Resources/Lyrics_Result_02.txt create mode 100644 Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/lyric_test_data.json create mode 100644 Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/LyricsFreakProviderTest.cs create mode 100644 Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_HtmlPage_01.txt create mode 100644 Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_HtmlPage_02.txt create mode 100644 Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_Result_01.txt create mode 100644 Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/lyric_test_data.json diff --git a/LyricsScraperNET.Client/appsettings.json b/LyricsScraperNET.Client/appsettings.json index 4c49b6e..d696b7c 100644 --- a/LyricsScraperNET.Client/appsettings.json +++ b/LyricsScraperNET.Client/appsettings.json @@ -22,6 +22,10 @@ "LyricFindOptions": { "SearchPriority": 1, "Enabled": true + }, + "LyricsFreakOptions": { + "SearchPriority": 5, + "Enabled": true } } } \ No newline at end of file diff --git a/LyricsScraperNET/Configuration/ILyricScraperClientConfig.cs b/LyricsScraperNET/Configuration/ILyricScraperClientConfig.cs index b95c3bf..63ebd34 100644 --- a/LyricsScraperNET/Configuration/ILyricScraperClientConfig.cs +++ b/LyricsScraperNET/Configuration/ILyricScraperClientConfig.cs @@ -24,5 +24,6 @@ public interface ILyricScraperClientConfig IExternalProviderOptions SongLyricsOptions { get; } IExternalProviderOptions LyricFindOptions { get; } + IExternalProviderOptions LyricsFreakOptions { get; } } } diff --git a/LyricsScraperNET/Configuration/LyricScraperClientConfig.cs b/LyricsScraperNET/Configuration/LyricScraperClientConfig.cs index 2471888..f9f20fc 100644 --- a/LyricsScraperNET/Configuration/LyricScraperClientConfig.cs +++ b/LyricsScraperNET/Configuration/LyricScraperClientConfig.cs @@ -2,6 +2,7 @@ using LyricsScraperNET.Providers.AZLyrics; using LyricsScraperNET.Providers.Genius; using LyricsScraperNET.Providers.LyricFind; +using LyricsScraperNET.Providers.LyricsFreak; using LyricsScraperNET.Providers.Musixmatch; using LyricsScraperNET.Providers.SongLyrics; using System.Text.Json.Serialization; @@ -23,6 +24,8 @@ public sealed class LyricScraperClientConfig : ILyricScraperClientConfig public IExternalProviderOptions LyricFindOptions { get; set; } = new LyricFindOptions(); + public IExternalProviderOptions LyricsFreakOptions { get; set; } = new LyricsFreakOptions(); + /// public bool UseParallelSearch { get; set; } = false; @@ -31,6 +34,7 @@ public sealed class LyricScraperClientConfig : ILyricScraperClientConfig || GeniusOptions.Enabled || MusixmatchOptions.Enabled || SongLyricsOptions.Enabled - || LyricFindOptions.Enabled; + || LyricFindOptions.Enabled + || LyricsFreakOptions.Enabled; } } diff --git a/LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs b/LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs index a47110d..d268f30 100644 --- a/LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs +++ b/LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using LyricsScraperNET.Providers.LyricFind; using LyricsScraperNET.Providers.Musixmatch; using LyricsScraperNET.Providers.SongLyrics; +using LyricsScraperNET.Providers.LyricsFreak; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -20,6 +21,7 @@ public static IServiceCollection AddLyricScraperClientService( var lyricScraperClientConfig = configuration.GetSection(LyricScraperClientConfig.ConfigurationSectionName); if (lyricScraperClientConfig.Exists()) { + services.AddProvider(lyricScraperClientConfig); services.AddProvider(lyricScraperClientConfig); services.AddProvider(lyricScraperClientConfig); services.AddProvider(lyricScraperClientConfig); diff --git a/LyricsScraperNET/Extensions/LyricsScraperClientExtensions.cs b/LyricsScraperNET/Extensions/LyricsScraperClientExtensions.cs index 6f0197c..cb5aa30 100644 --- a/LyricsScraperNET/Extensions/LyricsScraperClientExtensions.cs +++ b/LyricsScraperNET/Extensions/LyricsScraperClientExtensions.cs @@ -1,6 +1,7 @@ using LyricsScraperNET.Providers.AZLyrics; using LyricsScraperNET.Providers.Genius; using LyricsScraperNET.Providers.LyricFind; +using LyricsScraperNET.Providers.LyricsFreak; using LyricsScraperNET.Providers.Models; using LyricsScraperNET.Providers.Musixmatch; using LyricsScraperNET.Providers.SongLyrics; @@ -39,6 +40,12 @@ public static ILyricsScraperClient WithLyricFind(this ILyricsScraperClient lyric return lyricsScraperClient; } + public static ILyricsScraperClient WithLyricsFreak(this ILyricsScraperClient lyricsScraperClient) + { + lyricsScraperClient.AddProvider(new LyricsFreakProvider()); + return lyricsScraperClient; + } + /// /// Configure LyricsScraperClient with all available providers in . /// Search lyrics enabled by default for all providers. @@ -50,7 +57,8 @@ public static ILyricsScraperClient WithAllProviders(this ILyricsScraperClient ly .WithAZLyrics() .WithMusixmatch() .WithSongLyrics() - .WithLyricFind(); + .WithLyricFind() + .WithLyricsFreak(); } } } diff --git a/LyricsScraperNET/Extensions/StringExtensions.cs b/LyricsScraperNET/Extensions/StringExtensions.cs index 4fc1303..79665b9 100644 --- a/LyricsScraperNET/Extensions/StringExtensions.cs +++ b/LyricsScraperNET/Extensions/StringExtensions.cs @@ -71,5 +71,25 @@ public static string СonvertToDashedFormat(this string input, bool useException return result; } + + public static string СonvertSpaceToPlusFormat(this string input, bool removeProhibitedSymbols = false) + { + if (string.IsNullOrWhiteSpace(input)) + return input; + + var result = input.ToLowerInvariant().Trim(); + + if (removeProhibitedSymbols) + result = new string(result.Where(x => char.IsLetterOrDigit(x) || char.IsWhiteSpace(x) ).ToArray()); + + result = Regex.Replace(new string(result.Select(x => + { + return (char.IsWhiteSpace(x)) + ? '+' + : x; + }).ToArray()), "\\++", "+").Trim('+'); + + return result; + } } } diff --git a/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakOptions.cs b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakOptions.cs new file mode 100644 index 0000000..6cc6688 --- /dev/null +++ b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakOptions.cs @@ -0,0 +1,31 @@ +using LyricsScraperNET.Providers.Abstract; +using LyricsScraperNET.Providers.Models; + +namespace LyricsScraperNET.Providers.LyricsFreak +{ + public sealed class LyricsFreakOptions : IExternalProviderOptions + { + public ExternalProviderType ExternalProviderType => ExternalProviderType.LyricsFreak; + + public bool Enabled { get; set; } + public int SearchPriority { get; set; } = 5; + + public string ConfigurationSectionName { get; } = "LyricsFreakOptions"; + + public override bool Equals(object? obj) + { + return obj is LyricsFreakOptions options && + ExternalProviderType == options.ExternalProviderType; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 31) + ExternalProviderType.GetHashCode(); + return hash; + } + } + } +} diff --git a/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakParser.cs b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakParser.cs new file mode 100644 index 0000000..9f21c06 --- /dev/null +++ b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakParser.cs @@ -0,0 +1,21 @@ +using LyricsScraperNET.Providers.Abstract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace LyricsScraperNET.Providers.LyricsFreak +{ + internal sealed class LyricsFreakParser : IExternalProviderLyricParser + { + public string Parse(string lyric) + { + lyric = WebUtility.HtmlDecode(lyric); + + return lyric?.Trim() ?? string.Empty; + + } + } +} diff --git a/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs new file mode 100644 index 0000000..3e74c80 --- /dev/null +++ b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs @@ -0,0 +1,160 @@ +using Genius.Models.Song; +using HtmlAgilityPack; +using LyricsScraperNET.Helpers; +using LyricsScraperNET.Models.Responses; +using LyricsScraperNET.Network; +using LyricsScraperNET.Providers.Abstract; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace LyricsScraperNET.Providers.LyricsFreak +{ + internal class LyricsFreakProvider : ExternalProviderBase + { + private ILogger? _logger; + private readonly IExternalUriConverter _uriConverter; + private readonly string LyricsHrefXPath = "//a[translate(@title, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = '{0} lyrics']"; + private const string LyricsDivXPath = "//div[@data-container-id='lyrics']"; + + public override IExternalProviderOptions Options { get; } + + #region Constructors + public LyricsFreakProvider() + { + Parser = new LyricsFreakParser(); + WebClient = new HtmlAgilityWebClient(); + Options = new LyricsFreakOptions() { Enabled = true }; + _uriConverter = new LyricsFreakUriConverter(); + } + public LyricsFreakProvider(ILogger logger, LyricsFreakOptions options) + : this() + { + _logger = logger; + Ensure.ArgumentNotNull(options, nameof(options)); + Options = options; + } + + public LyricsFreakProvider(ILogger logger, IOptionsSnapshot options) + : this(logger, options.Value) + { + Ensure.ArgumentNotNull(options, nameof(options)); + } + + public LyricsFreakProvider(LyricsFreakOptions options) + : this(NullLogger.Instance, options) + { + Ensure.ArgumentNotNull(options, nameof(options)); + } + + public LyricsFreakProvider(IOptionsSnapshot options) + : this(NullLogger.Instance, options.Value) + { + Ensure.ArgumentNotNull(options, nameof(options)); + } + #endregion + #region Sync + protected override SearchResult SearchLyric(string artist, string song, CancellationToken cancellationToken = default) + { + return SearchLyricAsync(artist, song, cancellationToken).GetAwaiter().GetResult(); + } + protected override SearchResult SearchLyric(Uri uri, CancellationToken cancellationToken = default) + { + return SearchLyricAsync(uri, cancellationToken).GetAwaiter().GetResult(); + } + #endregion + #region Async + protected override async Task SearchLyricAsync(string artist, string song, CancellationToken cancellationToken = default) + { + + try + { + var artistUri = _uriConverter.GetLyricUri(artist, song); + + + if (WebClient == null || Parser == null) + { + _logger?.LogWarning($"LyricFind. Please set up WebClient and Parser first"); + return new SearchResult(Models.ExternalProviderType.LyricsFreak); + } + var htmlResponse = await WebClient.LoadAsync(artistUri, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + var songUri = ParseForSongUri(htmlResponse, song); + + if (string.IsNullOrEmpty(songUri)) + { + _logger?.LogWarning($"LyricFind. Can't find song Uri for song: [{song}]"); + return new SearchResult(Models.ExternalProviderType.LyricsFreak); + } + var songUriResult = await SearchLyricAsync(new Uri(LyricsFreakUriConverter.BaseUrl + songUri), cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + if (string.IsNullOrEmpty(songUriResult?.LyricText)) + { + _logger?.LogWarning($"LyricFind. Can't find song lyrics for song : [{song}]"); + return new SearchResult(Models.ExternalProviderType.LyricFind); + } + return new SearchResult(songUriResult.LyricText, Models.ExternalProviderType.LyricsFreak); + } + catch (Exception ex) + { + _logger?.LogError(ex, $"LyricsFreak. Error searching for lyrics for artist: [{artist}], song: [{song}]"); + return new SearchResult(Models.ExternalProviderType.LyricsFreak); + } + + + } + protected async override Task SearchLyricAsync(Uri uri, CancellationToken cancellationToken = default) + { + var text = await WebClient.LoadAsync(uri, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + var songHtmlLyrics = ParseForSongLyrics(text); + if (string.IsNullOrEmpty(songHtmlLyrics)) + { + _logger?.LogWarning($"LyricFind. Can't find song lyrics for song uri: [{uri.AbsoluteUri}]"); + return new SearchResult(Models.ExternalProviderType.LyricFind); + } + var lyricsText = Parser.Parse(songHtmlLyrics); + return new SearchResult(lyricsText, Models.ExternalProviderType.LyricsFreak); + } + #endregion + #region Private methods + private string ParseForSongUri(string htmlBody, string song) + { + + var linkNode = GetNode(htmlBody, string.Format(LyricsHrefXPath, song.ToLower())); + if (linkNode == null) + { + return string.Empty; + } + + string hrefSong = linkNode.GetAttributeValue("href", string.Empty); + return hrefSong; + + } + private string ParseForSongLyrics(string htmlBody) + { + var lyricsNode = GetNode(htmlBody, LyricsDivXPath); + if (lyricsNode == null) + { + return string.Empty; + + } + string lyricsText = lyricsNode.InnerText.Trim(); + return lyricsText; + } + + private HtmlNode? GetNode(string htmlBody, string xPath) + { + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(htmlBody); + return htmlDoc.DocumentNode.SelectSingleNode(xPath); + } + + #endregion + } +} diff --git a/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakUriConverter.cs b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakUriConverter.cs new file mode 100644 index 0000000..5a75e12 --- /dev/null +++ b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakUriConverter.cs @@ -0,0 +1,28 @@ +using LyricsScraperNET.Extensions; +using LyricsScraperNET.Providers.Abstract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LyricsScraperNET.Providers.LyricsFreak +{ + internal sealed class LyricsFreakUriConverter : IExternalUriConverter + { + public const string BaseUrl = "https://www.lyricsfreak.com"; + // 0 - artist, 1 - song + private const string uriArtistPathFormat = BaseUrl + "/{0}/{1}"; + public Uri GetLyricUri(string artist, string song) + { + var artistFormatted = artist.ToLowerInvariant().СonvertSpaceToPlusFormat(removeProhibitedSymbols: true); + return GetArtistUri(artistFormatted); + } + // Example for Artist parkway drive https://www.lyricsfreak.com/p/parkway+drive/ + private static Uri GetArtistUri(string artist) + { + return new Uri(string.Format(uriArtistPathFormat, artist.Length > 0 ? artist[0] : string.Empty, artist)); + + } + } +} diff --git a/LyricsScraperNET/Providers/Models/ExternalProviderType.cs b/LyricsScraperNET/Providers/Models/ExternalProviderType.cs index 03be0b4..22bf168 100644 --- a/LyricsScraperNET/Providers/Models/ExternalProviderType.cs +++ b/LyricsScraperNET/Providers/Models/ExternalProviderType.cs @@ -7,6 +7,7 @@ public enum ExternalProviderType Genius, Musixmatch, SongLyrics, - LyricFind + LyricFind, + LyricsFreak } } diff --git a/Tests/LyricsScraperNET.IntegrationTest/LyricsScraperNET.IntegrationTest.csproj b/Tests/LyricsScraperNET.IntegrationTest/LyricsScraperNET.IntegrationTest.csproj index 401f0ac..4ba8bf9 100644 --- a/Tests/LyricsScraperNET.IntegrationTest/LyricsScraperNET.IntegrationTest.csproj +++ b/Tests/LyricsScraperNET.IntegrationTest/LyricsScraperNET.IntegrationTest.csproj @@ -50,6 +50,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/LyricsFreakProviderTest.cs b/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/LyricsFreakProviderTest.cs new file mode 100644 index 0000000..ee06309 --- /dev/null +++ b/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/LyricsFreakProviderTest.cs @@ -0,0 +1,106 @@ +using LyricsScraperNET.Models.Requests; +using LyricsScraperNET.Models.Responses; +using LyricsScraperNET.Providers.LyricsFreak; +using LyricsScraperNET.Providers.Models; +using LyricsScraperNET.TestShared.Attributes; +using LyricsScraperNET.TestShared.Providers; +using LyricsScraperNET.TestShared.TestModel; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + + +namespace LyricsScraperNET.IntegrationTest.Providers.LyricsFreak +{ + public class LyricsFreakProviderTest : ProviderTestBase + { + #region Sync + [Theory] + [MemberData(nameof(GetTestData), parameters: "Providers\\LyricsFreak\\lyric_test_data.json")] + public void SearchLyric_IntegrationDynamicData_Success(LyricsTestData testData) + { + // Arrange + var lyricsClient = new LyricsFreakProvider(); + SearchRequest searchRequest = CreateSearchRequest(testData); + CancellationToken cancellationToken = CancellationToken.None; + + // Act + var searchResult = lyricsClient.SearchLyric(searchRequest, cancellationToken); + + // Assert + Assert.NotNull(searchResult); + Assert.False(searchResult.IsEmpty()); + Assert.Equal(ResponseStatusCode.Success, searchResult.ResponseStatusCode); + Assert.True(string.IsNullOrEmpty(searchResult.ResponseMessage)); + Assert.Equal(ExternalProviderType.LyricsFreak, searchResult.ExternalProviderType); + Assert.Equal(testData.LyricResultData.Replace("\r\n", "\n"), searchResult.LyricText.Replace("\r\n", "\n")); + Assert.False(searchResult.Instrumental); + } + + [Theory] + [InlineData("asdfasdfasdfasdf", "asdfasdfasdfasdf")] + public void SearchLyric_NotExistsLyrics_ShouldReturnNoDataFoundStatus(string artist, string song) + { + // Arrange + var lyricsClient = new LyricsFreakProvider(); + var searchRequest = new ArtistAndSongSearchRequest(artist, song); + CancellationToken cancellationToken = CancellationToken.None; + + // Act + var searchResult = lyricsClient.SearchLyric(searchRequest, cancellationToken); + + // Assert + Assert.NotNull(searchResult); + Assert.True(searchResult.IsEmpty()); + Assert.Equal(ResponseStatusCode.NoDataFound, searchResult.ResponseStatusCode); + Assert.Equal(ExternalProviderType.LyricsFreak, searchResult.ExternalProviderType); + Assert.True(string.IsNullOrEmpty(searchResult.ResponseMessage)); + Assert.False(searchResult.Instrumental); + } + #endregion + + #region Async + [Theory] + [MemberData(nameof(GetTestData), parameters: "Providers\\LyricsFreak\\lyric_test_data.json")] + public async Task SearchLyricAsync_IntegrationDynamicData_Success(LyricsTestData testData) + { + // Arrange + var lyricsClient = new LyricsFreakProvider(); + SearchRequest searchRequest = CreateSearchRequest(testData); + CancellationToken cancellationToken = CancellationToken.None; + + // Act + var searchResult = await lyricsClient.SearchLyricAsync(searchRequest, cancellationToken); + + // Assert + Assert.NotNull(searchResult); + Assert.False(searchResult.IsEmpty()); + Assert.Equal(ResponseStatusCode.Success, searchResult.ResponseStatusCode); + Assert.True(string.IsNullOrEmpty(searchResult.ResponseMessage)); + Assert.Equal(ExternalProviderType.LyricsFreak, searchResult.ExternalProviderType); + Assert.Equal(testData.LyricResultData.Replace("\r\n", "\n"), searchResult.LyricText.Replace("\r\n", "\n")); + } + + [Theory] + [InlineData("asdfasdfasdfasdf", "asdfasdfasdfasdf")] + public async Task SearchLyricAsync_NotExistsLyrics_ShouldReturnNoDataFoundStatus(string artist, string song) + { + // Arrange + var lyricsClient = new LyricsFreakProvider(); + var searchRequest = new ArtistAndSongSearchRequest(artist, song); + CancellationToken cancellationToken = CancellationToken.None; + + // Act + var searchResult = await lyricsClient.SearchLyricAsync(searchRequest, cancellationToken); + + // Assert + Assert.NotNull(searchResult); + Assert.True(searchResult.IsEmpty()); + Assert.Equal(ResponseStatusCode.NoDataFound, searchResult.ResponseStatusCode); + Assert.Equal(ExternalProviderType.LyricsFreak, searchResult.ExternalProviderType); + Assert.True(string.IsNullOrEmpty(searchResult.ResponseMessage)); + Assert.False(searchResult.Instrumental); + } + #endregion + } +} diff --git a/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/Resources/Lyrics_Result_01.txt b/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/Resources/Lyrics_Result_01.txt new file mode 100644 index 0000000..e561dc9 --- /dev/null +++ b/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/Resources/Lyrics_Result_01.txt @@ -0,0 +1,48 @@ +Now, +Your heroes have fallen +Champion-less +The seas are rising + +So torch every banner, +Every hope of surviving +Lifetime is waking +Security has left you +Treading water + +Now taste the fear +Tasting the uncertainty + +What will you do? (What will you do?) +When there's nothing left for you to cling to +What will you do? (What will you do?) +When your life has rot +Thrive in your emptiness + +Burn all you love +There's no hope for the weak +Your heroes have died + +There is... +No Hope. + +For it still flies +In the Abyss (In the Abyss) +I'll find one +Beg for a way +Out from the nest + +Can you hear it? +Can you hear the sound? +As our broken Idols +Come crashing down. + +Now taste the fear +Now, taste the fear + +Burn all you love +There's no hope for the weak +Your Heroes have died +Burn all you love +There's no hope for the weak + +Burn all you love \ No newline at end of file diff --git a/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/Resources/Lyrics_Result_02.txt b/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/Resources/Lyrics_Result_02.txt new file mode 100644 index 0000000..02b82ba --- /dev/null +++ b/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/Resources/Lyrics_Result_02.txt @@ -0,0 +1,56 @@ +[Intro] +I spoke a vow today and asked if God would come and play +I've dug a shallow hole for him to sleep +But I swear he just won't answer me +I call on out is he afraid, I'll bury him down with the ones he keeps +And if the devil is listening, I'll come for him as well +If I suspect he had a hand to play +And if I see his face in town, there's room for two down underground +Nothing's gonna stop me till I'm done +Until I'm done! +Until I'm done! +Until I'm done! +Until I'm done! +'Cause tonight I'm killing gods! +Killing gods! + +[Verse 1] +It's guilt and frustration, it's everything between +The silence and the absence hitting home +Peripheral glances and the chasing of a sound +I never knew I'd miss until it's gone +So ask me how I'm coping, and I'll smile and tell you: "I'm just fine" +While down inside I'm drowning in the fucking rain +Because when everything is empty and your heart is set to cave +Sometimes all you wish for is a place, is a place to place the blame + +[Chorus] +Burn your heaven, flood your hell +Drown you in your wishing wells +Burn your heaven, flood your hell +Damn you all 'cause tonight I'm killing gods + +[Verse 2] +The incendiary shock wave scorched earth policy +The devastation that only loss will leave +The chants and incantations of daily rituals +A subtle lapse in brutal honesty +So ask me how I'm coping, and I'll smile and tell you: "I'm just fine" +While down inside I'm screaming till I fucking bleed +Because when everyone's expendable and your heart can't take the wait +The last thing that you wish for is the face, is the face to face the pain + +[Chorus] +Burn your heaven, flood your hell +Drown you in your wishing wells +Burn your heaven, flood your hell +Damn you all 'cause tonight I'm killing gods + +[Outro] +I spoke a vow today and asked if God would come and play +'Cause I just wanna cut that fucker down +Face me, face me, face me, face me +Face me, face me, none survive +Face me, face me, none survive, go +None survive +The Devil and God have died inside me \ No newline at end of file diff --git a/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/lyric_test_data.json b/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/lyric_test_data.json new file mode 100644 index 0000000..9c0447e --- /dev/null +++ b/Tests/LyricsScraperNET.IntegrationTest/Providers/LyricsFreak/lyric_test_data.json @@ -0,0 +1,20 @@ +[ + { + "LyricResultPath": "Providers/LyricsFreak/Resources/Lyrics_Result_01.txt", + "ArtistName": "Parkway Drive", + "SongName": "Idols And Anchors", + "SongUri": null + }, + { + "LyricResultPath": "Providers/LyricsFreak/Resources/Lyrics_Result_02.txt", + "ArtistName": "Parkway Drive", + "SongName": "Wishing Wells", + "SongUri": null + }, + { + "LyricResultPath": "Providers/LyricsFreak/Resources/Lyrics_Result_01.txt", + "ArtistName": null, + "SongName": null, + "SongUri": "http://www.lyricsfreak.com/p/parkway+drive/idols+and+anchors_20657144.html" + } +] \ No newline at end of file diff --git a/Tests/LyricsScraperNET.TestShared/Extensions/ExternalProviderExtensions.cs b/Tests/LyricsScraperNET.TestShared/Extensions/ExternalProviderExtensions.cs index 0ab0cb8..d21eae5 100644 --- a/Tests/LyricsScraperNET.TestShared/Extensions/ExternalProviderExtensions.cs +++ b/Tests/LyricsScraperNET.TestShared/Extensions/ExternalProviderExtensions.cs @@ -18,5 +18,17 @@ public static IExternalProvider ConfigureExternalProvider(this IExternalProvider externalProvider.WithWebClient(mockWebClient); return externalProvider; } + + public static IExternalProvider ConfigureExternalProviderWithArtist(this IExternalProvider externalProvider, LyricsTestData testData) + { + var mockWebClient = A.Fake(); + A.CallTo(() => mockWebClient.Load(A._, A._)) + .ReturnsNextFromSequence(testData.ArtistPageData, testData.LyricPageData); + A.CallTo(() => mockWebClient.LoadAsync(A._, A._)) + .ReturnsNextFromSequence(testData.ArtistPageData, testData.LyricPageData); + + externalProvider.WithWebClient(mockWebClient); + return externalProvider; + } } } diff --git a/Tests/LyricsScraperNET.TestShared/TestModel/LyricsTestData.cs b/Tests/LyricsScraperNET.TestShared/TestModel/LyricsTestData.cs index ba195b7..2387603 100644 --- a/Tests/LyricsScraperNET.TestShared/TestModel/LyricsTestData.cs +++ b/Tests/LyricsScraperNET.TestShared/TestModel/LyricsTestData.cs @@ -6,11 +6,13 @@ public class LyricsTestData { public string LyricPagePath { get; set; } public string LyricResultPath { get; set; } + public string ArtistPagePath { get; set; } public string ArtistName { get; set; } public string SongName { get; set; } public string SongUri { get; set; } public string LyricPageData => ReadFileData(LyricPagePath); + public string ArtistPageData => ReadFileData(ArtistPagePath); public string LyricResultData => ReadFileData(LyricResultPath); diff --git a/Tests/LyricsScraperNET.UnitTest/Configuration/ServiceCollectionExtensionsTest.cs b/Tests/LyricsScraperNET.UnitTest/Configuration/ServiceCollectionExtensionsTest.cs index 00e19e1..bd1805d 100644 --- a/Tests/LyricsScraperNET.UnitTest/Configuration/ServiceCollectionExtensionsTest.cs +++ b/Tests/LyricsScraperNET.UnitTest/Configuration/ServiceCollectionExtensionsTest.cs @@ -67,7 +67,8 @@ public void IocContainer_GetService_LyricsScraperClient_FullSetup() { ExternalProviderType.AZLyrics, 22}, { ExternalProviderType.Musixmatch, 33}, { ExternalProviderType.SongLyrics, 44}, - { ExternalProviderType.LyricFind, 55} + { ExternalProviderType.LyricFind, 55}, + { ExternalProviderType.LyricsFreak, 66} }; string settingsPath = "Resources\\full_test_settings.json"; diff --git a/Tests/LyricsScraperNET.UnitTest/LyricsScraperNET.UnitTest.csproj b/Tests/LyricsScraperNET.UnitTest/LyricsScraperNET.UnitTest.csproj index f737637..ff2b087 100644 --- a/Tests/LyricsScraperNET.UnitTest/LyricsScraperNET.UnitTest.csproj +++ b/Tests/LyricsScraperNET.UnitTest/LyricsScraperNET.UnitTest.csproj @@ -94,6 +94,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/LyricsFreakProviderTest.cs b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/LyricsFreakProviderTest.cs new file mode 100644 index 0000000..a8cd8c8 --- /dev/null +++ b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/LyricsFreakProviderTest.cs @@ -0,0 +1,37 @@ +using LyricsScraperNET.Models.Requests; +using LyricsScraperNET.Models.Responses; +using LyricsScraperNET.Providers.LyricsFreak; +using LyricsScraperNET.Providers.Models; +using LyricsScraperNET.TestShared.Extensions; +using LyricsScraperNET.TestShared.Providers; +using LyricsScraperNET.TestShared.TestModel; +using System.Threading; +using Xunit; + +namespace LyricsScraperNET.UnitTest.Providers.LyricsFreak +{ + public class LyricsFreakProviderTest : ProviderTestBase + { + [Theory] + [MemberData(nameof(GetTestData), parameters: "Providers\\LyricsFreak\\lyric_test_data.json")] + public void SearchLyric_UnitDynamicData_Success(LyricsTestData testData) + { + // Arrange + var lyricsClient = new LyricsFreakProvider(); + lyricsClient.ConfigureExternalProviderWithArtist(testData); + + SearchRequest searchRequest = CreateSearchRequest(testData); + CancellationToken cancellationToken = CancellationToken.None; + + // Act + var searchResult = lyricsClient.SearchLyric(searchRequest, cancellationToken); + + // Assert + Assert.NotNull(searchResult); + Assert.Equal(ResponseStatusCode.Success, searchResult.ResponseStatusCode); + Assert.True(string.IsNullOrEmpty(searchResult.ResponseMessage)); + Assert.Equal(ExternalProviderType.LyricsFreak, searchResult.ExternalProviderType); + Assert.Equal(testData.LyricResultData.Replace("\r\n", "\n"), searchResult.LyricText.Replace("\r\n", "\n")); + } + } +} diff --git a/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_HtmlPage_01.txt b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_HtmlPage_01.txt new file mode 100644 index 0000000..3f8a278 --- /dev/null +++ b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_HtmlPage_01.txt @@ -0,0 +1,1780 @@ + + + + + + + + + + + + + + + Parkway Drive lyrics, songs and albums | LyricsFreak + + + + + + + + + + + + + + + + + +
+
+
+
+ top 100 + · + top new + · + updates + · + submit lyrics +
+ + +
+ +
+
+

Parkway Drive original lyrics

+ +
+
+
+
+
+ +
+

All Parkway Drive lyrics A-Z

+
+ +
+
+
+
+ Parkway Drive lyrics +
+
+ Time +
+
+ Stars +
+
+ + + + +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ + +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+
+
+
+ + + + +
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + + + +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ + +
+ +
+
+
+
+
+ + + +
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + +
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ + + +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ + +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+

All Parkway Drive albums

+
+
+
+
+ Cover +
+
+ Parkway Drive albums +
+
+ Year +
+
+ Tracks count +
+
+
+
+ +
+ +
+ 2015
+
+ 11
+
+
+
+ +
+ +
+ 2012
+
+ 12
+
+
+
+ +
+ +
+ 2005
+
+ 11
+
+
+
+
+ +
+
+
+
+ + + + + + + + +
+ +
+ +
+ + + + + + + diff --git a/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_HtmlPage_02.txt b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_HtmlPage_02.txt new file mode 100644 index 0000000..9e41ad9 --- /dev/null +++ b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_HtmlPage_02.txt @@ -0,0 +1,1240 @@ + + + + + + + + + + + + + + + Parkway Drive - Idols And Anchors lyrics | LyricsFreak + + + + + + + + + + + + + + + + + + + +
+
+
+
+ top 100 + · + top new + · + updates + · + submit lyrics +
+ + +
+ +
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+ Correct  |  + Mail  |  + Print  |  + Vote +
+

Idols And Anchors Lyrics

+ +
+
+
+
+ +
+ + +
+
+

+ Parkway Drive – Idols And Anchors Lyrics

+
+
+ +
+ +
+
+ Now,
+Your heroes have fallen
+Champion-less
+The seas are rising
+
+So torch every banner,
+Every hope of surviving
+Lifetime is waking
+Security has left you
+Treading water
+
+Now taste the fear
+Tasting the uncertainty
+
+What will you do? (What will you do?)
+When there's nothing left for you to cling to
+What will you do? (What will you do?)
+When your life has rot
+Thrive in your emptiness
+
+Burn all you love
+There's no hope for the weak
+Your heroes have died
+
+There is...
+No Hope.
+
+For it still flies
+In the Abyss (In the Abyss)
+I'll find one
+Beg for a way
+Out from the nest
+
+Can you hear it?
+Can you hear the sound?
+As our broken Idols
+Come crashing down.
+
+Now taste the fear
+Now, taste the fear
+
+Burn all you love
+There's no hope for the weak
+Your Heroes have died
+Burn all you love
+There's no hope for the weak
+
+Burn all you love
+
+ Share lyrics +
+
+
+
+
+
+
×
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+

+ Idols And Anchors comments +

+
+
+
+ + +
+
+
+ + + + + + + + +
+ +
+ +
+ + + + + + + diff --git a/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_Result_01.txt b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_Result_01.txt new file mode 100644 index 0000000..e561dc9 --- /dev/null +++ b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/Resources/Lyrics_Result_01.txt @@ -0,0 +1,48 @@ +Now, +Your heroes have fallen +Champion-less +The seas are rising + +So torch every banner, +Every hope of surviving +Lifetime is waking +Security has left you +Treading water + +Now taste the fear +Tasting the uncertainty + +What will you do? (What will you do?) +When there's nothing left for you to cling to +What will you do? (What will you do?) +When your life has rot +Thrive in your emptiness + +Burn all you love +There's no hope for the weak +Your heroes have died + +There is... +No Hope. + +For it still flies +In the Abyss (In the Abyss) +I'll find one +Beg for a way +Out from the nest + +Can you hear it? +Can you hear the sound? +As our broken Idols +Come crashing down. + +Now taste the fear +Now, taste the fear + +Burn all you love +There's no hope for the weak +Your Heroes have died +Burn all you love +There's no hope for the weak + +Burn all you love \ No newline at end of file diff --git a/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/lyric_test_data.json b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/lyric_test_data.json new file mode 100644 index 0000000..a0e5a88 --- /dev/null +++ b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/lyric_test_data.json @@ -0,0 +1,10 @@ +[ + { + "ArtistPagePath": "Providers/LyricsFreak/Resources/Lyrics_HtmlPage_01.txt", + "LyricPagePath": "Providers/LyricsFreak/Resources/Lyrics_HtmlPage_02.txt", + "LyricResultPath": "Providers/LyricsFreak/Resources/Lyrics_Result_01.txt", + "ArtistName": "Parkway Drive", + "SongName": "Idols And Anchors", + "SongUri": null + } +] \ No newline at end of file diff --git a/Tests/LyricsScraperNET.UnitTest/Resources/full_test_settings.json b/Tests/LyricsScraperNET.UnitTest/Resources/full_test_settings.json index 943af3a..d6199db 100644 --- a/Tests/LyricsScraperNET.UnitTest/Resources/full_test_settings.json +++ b/Tests/LyricsScraperNET.UnitTest/Resources/full_test_settings.json @@ -22,6 +22,10 @@ "LyricFindOptions": { "SearchPriority": 55, "Enabled": true + }, + "LyricsFreakOptions": { + "SearchPriority": 66, + "Enabled": true } } } \ No newline at end of file From ed3810e4f8e384013eebf83e22b57b762c1e757b Mon Sep 17 00:00:00 2001 From: 10092023-G49 <10092023-G49@deccansoftstudents.onmicrosoft.com> Date: Tue, 31 Dec 2024 17:08:11 +1100 Subject: [PATCH 2/2] Fixing empty line issues --- .../Providers/LyricsFreak/LyricsFreakProvider.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs index 3e74c80..cfa2fff 100644 --- a/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs +++ b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs @@ -37,19 +37,16 @@ public LyricsFreakProvider(ILogger logger, LyricsFreakOptio Ensure.ArgumentNotNull(options, nameof(options)); Options = options; } - public LyricsFreakProvider(ILogger logger, IOptionsSnapshot options) : this(logger, options.Value) { Ensure.ArgumentNotNull(options, nameof(options)); } - public LyricsFreakProvider(LyricsFreakOptions options) : this(NullLogger.Instance, options) { Ensure.ArgumentNotNull(options, nameof(options)); } - public LyricsFreakProvider(IOptionsSnapshot options) : this(NullLogger.Instance, options.Value) { @@ -69,7 +66,6 @@ protected override SearchResult SearchLyric(Uri uri, CancellationToken cancellat #region Async protected override async Task SearchLyricAsync(string artist, string song, CancellationToken cancellationToken = default) { - try { var artistUri = _uriConverter.GetLyricUri(artist, song); @@ -104,8 +100,6 @@ protected override async Task SearchLyricAsync(string artist, stri _logger?.LogError(ex, $"LyricsFreak. Error searching for lyrics for artist: [{artist}], song: [{song}]"); return new SearchResult(Models.ExternalProviderType.LyricsFreak); } - - } protected async override Task SearchLyricAsync(Uri uri, CancellationToken cancellationToken = default) { @@ -125,7 +119,6 @@ protected async override Task SearchLyricAsync(Uri uri, Cancellati #region Private methods private string ParseForSongUri(string htmlBody, string song) { - var linkNode = GetNode(htmlBody, string.Format(LyricsHrefXPath, song.ToLower())); if (linkNode == null) { @@ -134,7 +127,6 @@ private string ParseForSongUri(string htmlBody, string song) string hrefSong = linkNode.GetAttributeValue("href", string.Empty); return hrefSong; - } private string ParseForSongLyrics(string htmlBody) { @@ -147,14 +139,12 @@ private string ParseForSongLyrics(string htmlBody) string lyricsText = lyricsNode.InnerText.Trim(); return lyricsText; } - private HtmlNode? GetNode(string htmlBody, string xPath) { var htmlDoc = new HtmlDocument(); htmlDoc.LoadHtml(htmlBody); return htmlDoc.DocumentNode.SelectSingleNode(xPath); } - #endregion } }