diff --git a/LyricsScraperNET.Client/appsettings.json b/LyricsScraperNET.Client/appsettings.json index 8d4bd05..3bec804 100644 --- a/LyricsScraperNET.Client/appsettings.json +++ b/LyricsScraperNET.Client/appsettings.json @@ -26,6 +26,10 @@ "KPopLyricsOptions": { "SearchPriority": 3, "Enabled": true + }, + "LyricsFreakOptions": { + "SearchPriority": 6, + "Enabled": true } } } \ No newline at end of file diff --git a/LyricsScraperNET/Configuration/ILyricScraperClientConfig.cs b/LyricsScraperNET/Configuration/ILyricScraperClientConfig.cs index a5513d5..29855b0 100644 --- a/LyricsScraperNET/Configuration/ILyricScraperClientConfig.cs +++ b/LyricsScraperNET/Configuration/ILyricScraperClientConfig.cs @@ -24,6 +24,9 @@ public interface ILyricScraperClientConfig IExternalProviderOptions SongLyricsOptions { get; } IExternalProviderOptions LyricFindOptions { get; } + IExternalProviderOptions KPopLyricsOptions { get; } + + IExternalProviderOptions LyricsFreakOptions { get; } } } diff --git a/LyricsScraperNET/Configuration/LyricScraperClientConfig.cs b/LyricsScraperNET/Configuration/LyricScraperClientConfig.cs index ce00825..8127895 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; @@ -26,6 +27,8 @@ public sealed class LyricScraperClientConfig : ILyricScraperClientConfig public IExternalProviderOptions KPopLyricsOptions { get; set; } = new KPopLyricsOptions(); + public IExternalProviderOptions LyricsFreakOptions { get; set; } = new LyricsFreakOptions(); + /// public bool UseParallelSearch { get; set; } = false; @@ -35,6 +38,7 @@ public sealed class LyricScraperClientConfig : ILyricScraperClientConfig || MusixmatchOptions.Enabled || SongLyricsOptions.Enabled || LyricFindOptions.Enabled - || KPopLyricsOptions.Enabled; + || KPopLyricsOptions.Enabled + || LyricsFreakOptions.Enabled; } } diff --git a/LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs b/LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs index f0d51cf..15e0fb7 100644 --- a/LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs +++ b/LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using LyricsScraperNET.Providers.Musixmatch; using LyricsScraperNET.Providers.SongLyrics; using LyricsScraperNET.Providers.KPopLyrics; +using LyricsScraperNET.Providers.LyricsFreak; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -21,6 +22,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 ab871e1..3334b86 100644 --- a/LyricsScraperNET/Extensions/LyricsScraperClientExtensions.cs +++ b/LyricsScraperNET/Extensions/LyricsScraperClientExtensions.cs @@ -2,6 +2,7 @@ using LyricsScraperNET.Providers.Genius; using LyricsScraperNET.Providers.KPopLyrics; using LyricsScraperNET.Providers.LyricFind; +using LyricsScraperNET.Providers.LyricsFreak; using LyricsScraperNET.Providers.Models; using LyricsScraperNET.Providers.Musixmatch; using LyricsScraperNET.Providers.SongLyrics; @@ -46,6 +47,12 @@ public static ILyricsScraperClient WithKPopLyrics(this ILyricsScraperClient lyri 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. @@ -58,7 +65,8 @@ public static ILyricsScraperClient WithAllProviders(this ILyricsScraperClient ly .WithMusixmatch() .WithSongLyrics() .WithLyricFind() - .WithKPopLyrics(); + .WithKPopLyrics() + .WithLyricsFreak(); } } } diff --git a/LyricsScraperNET/Extensions/StringExtensions.cs b/LyricsScraperNET/Extensions/StringExtensions.cs index ae4cbd5..c36c493 100644 --- a/LyricsScraperNET/Extensions/StringExtensions.cs +++ b/LyricsScraperNET/Extensions/StringExtensions.cs @@ -104,5 +104,25 @@ public static string CreateCombinedUrlSlug(string artist, string songTitle) return slug.ToLower(); } + + 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..0f737b1 --- /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; } = 6; + + 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..b71c80e --- /dev/null +++ b/LyricsScraperNET/Providers/LyricsFreak/LyricsFreakProvider.cs @@ -0,0 +1,156 @@ +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($"LyricsFreak. 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($"LyricsFreak. 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 (songUriResult is null || string.IsNullOrEmpty(songUriResult?.LyricText)) + { + _logger?.LogWarning($"LyricsFreak. Can't find song lyrics for song : [{song}]"); + return new SearchResult(Models.ExternalProviderType.LyricsFreak); + } + 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($"LyricsFreak. Can't find song lyrics for song uri: [{uri.AbsoluteUri}]"); + return new SearchResult(Models.ExternalProviderType.LyricsFreak); + } + var lyricsText = Parser.Parse(songHtmlLyrics); + return new SearchResult(lyricsText, Models.ExternalProviderType.LyricsFreak); + } + #endregion + #region Private methods + private string ParseForSongUri(string htmlBody, string song) + { + string formattedXPath = string.Format(LyricsHrefXPath, EncodedSong(song)); + var linkNode = GetNode(htmlBody, formattedXPath); + 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); + } + private string EncodedSong(string song) + { + string encodedSong = System.Net.WebUtility.HtmlEncode(song).ToLowerInvariant(); + encodedSong = encodedSong.Replace("'", "'"); + return encodedSong; + } + #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 986b624..69d5c44 100644 --- a/LyricsScraperNET/Providers/Models/ExternalProviderType.cs +++ b/LyricsScraperNET/Providers/Models/ExternalProviderType.cs @@ -8,6 +8,7 @@ public enum ExternalProviderType Musixmatch, SongLyrics, LyricFind, - KPopLyrics + KPopLyrics, + LyricsFreak } } diff --git a/README.md b/README.md index dc7144e..2142986 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The library currently supports the following providers: - [MusixMatch](https://www.musixmatch.com/) - [SongLyrics](https://www.songlyrics.com/) - [LyricFind](https://www.lyricfind.com/) -- [LyricsFreak](https://www.lyricsfreak.com/) (**Coming soon** 🚧. [Issue #38](https://github.com/skuill/LyricsScraperNET/issues/38)) +- [LyricsFreak](https://www.lyricsfreak.com/) (added by [@ajay201402](https://github.com/ajay201402)) - [kpoplyrics](https://www.kpoplyrics.net/) (added by [@Lukeuke](https://github.com/Lukeuke)) - [Letras.mus.br](https://www.letras.mus.br/) (**Coming soon** 🚧. [Issue #40](https://github.com/skuill/LyricsScraperNET/issues/40)) - [darklyrics](http://www.darklyrics.com/) (**Coming soon** 🚧. [Issue #41](https://github.com/skuill/LyricsScraperNET/issues/41)) diff --git a/Tests/LyricsScraperNET.IntegrationTest/LyricsScraperNET.IntegrationTest.csproj b/Tests/LyricsScraperNET.IntegrationTest/LyricsScraperNET.IntegrationTest.csproj index 75c7a60..8d4d015 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 56b7f63..2cc8d8a 100644 --- a/Tests/LyricsScraperNET.UnitTest/Configuration/ServiceCollectionExtensionsTest.cs +++ b/Tests/LyricsScraperNET.UnitTest/Configuration/ServiceCollectionExtensionsTest.cs @@ -69,6 +69,7 @@ public void IocContainer_GetService_LyricsScraperClient_FullSetup() { ExternalProviderType.SongLyrics, 44}, { ExternalProviderType.LyricFind, 55}, { ExternalProviderType.KPopLyrics, 66}, + { ExternalProviderType.LyricsFreak, 77} }; string settingsPath = "Resources\\full_test_settings.json"; diff --git a/Tests/LyricsScraperNET.UnitTest/Extensions/LyricsScraperClientExtensionsTest.cs b/Tests/LyricsScraperNET.UnitTest/Extensions/LyricsScraperClientExtensionsTest.cs index 31388f6..99adb0d 100644 --- a/Tests/LyricsScraperNET.UnitTest/Extensions/LyricsScraperClientExtensionsTest.cs +++ b/Tests/LyricsScraperNET.UnitTest/Extensions/LyricsScraperClientExtensionsTest.cs @@ -9,6 +9,21 @@ public class LyricsScraperClientExtensionsTest { private ILyricsScraperClient _lyricsScraperClient => new LyricsScraperClient(); + [Fact] + public void LyricsScraperClient_WithLyricsFreak_ReturnsIsEnabled() + { + // Act + var lyricsScraperClient = _lyricsScraperClient.WithLyricsFreak(); + var externalTypeProvider = lyricsScraperClient[ExternalProviderType.LyricsFreak]; + + // Assert + Assert.NotNull(lyricsScraperClient); + Assert.True(lyricsScraperClient.IsEnabled); + Assert.NotNull(externalTypeProvider); + Assert.True(externalTypeProvider.IsEnabled); + Assert.Equal(6, externalTypeProvider.SearchPriority); + } + [Fact] public void LyricsScraperClient_WithAZLyrics_ReturnsIsEnabled() { diff --git a/Tests/LyricsScraperNET.UnitTest/Extensions/StringExtensionsTest.cs b/Tests/LyricsScraperNET.UnitTest/Extensions/StringExtensionsTest.cs index 27a83b2..1e56086 100644 --- a/Tests/LyricsScraperNET.UnitTest/Extensions/StringExtensionsTest.cs +++ b/Tests/LyricsScraperNET.UnitTest/Extensions/StringExtensionsTest.cs @@ -73,5 +73,21 @@ public void GenerateCombinedUrlSlug_Tests(string artist, string title, string ex Assert.Equal(expected, slug); } + + [Theory] + [InlineData("Attack Attack!", "attack+attack")] + [InlineData("I Swear I'll Change", "i+swear+ill+change")] + [InlineData("Summer of '69", "summer+of+69")] + [InlineData(" Of Mice & Men ", "of+mice+men")] + [InlineData("", "")] + [InlineData(null, null)] + public void СonvertToPlusFormat_MultipleInputs_ShouldBeParse(string input, string expected) + { + // Act + var actual = StringExtensions.СonvertSpaceToPlusFormat(input, true); + + // Assert + Assert.Equal(expected, actual); + } } } diff --git a/Tests/LyricsScraperNET.UnitTest/LyricsScraperNET.UnitTest.csproj b/Tests/LyricsScraperNET.UnitTest/LyricsScraperNET.UnitTest.csproj index 410b0f2..993129a 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/LyricsFreakUriConverterTests.cs b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/LyricsFreakUriConverterTests.cs new file mode 100644 index 0000000..ebdb739 --- /dev/null +++ b/Tests/LyricsScraperNET.UnitTest/Providers/LyricsFreak/LyricsFreakUriConverterTests.cs @@ -0,0 +1,27 @@ +using LyricsScraperNET.Providers.LyricsFreak; +using System; +using Xunit; + +namespace LyricsScraperNET.UnitTest.Providers.LyricsFreak +{ + public class LyricsFreakUriConverterTests + { + [Theory] + [InlineData("Bryan Adams", "Summer of '69", "https://www.lyricsfreak.com/b/bryan+adams")] + [InlineData("Patty & the Emblems", "Mixed+Up, Shook+Up Girl", "https://www.lyricsfreak.com/p/patty+the+emblems")] + [InlineData("Mac DeMarco", "Me and Jon, Hanging On", "https://www.lyricsfreak.com/m/mac+demarco")] + [InlineData("Of Mice & Men", "You're Not Alone", "https://www.lyricsfreak.com/o/of+mice+men")] + [InlineData("Attack Attack!", "I Swear I'll Change", "https://www.lyricsfreak.com/a/attack+attack")] + public void GetLyricUri_MultipleInputs_ShouldBeParse(string artistName, string songName, string expectedUri) + { + // Arrange + var uriConverter = new LyricsFreakUriConverter(); + + // Act + var actual = uriConverter.GetLyricUri(artistName, songName); + + // Assert + Assert.Equal(new Uri(expectedUri), actual); + } + } +} 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 952cb16..635a9a4 100644 --- a/Tests/LyricsScraperNET.UnitTest/Resources/full_test_settings.json +++ b/Tests/LyricsScraperNET.UnitTest/Resources/full_test_settings.json @@ -26,6 +26,10 @@ "KPopLyricsOptions": { "SearchPriority": 66, "Enabled": true + }, + "LyricsFreakOptions": { + "SearchPriority": 77, + "Enabled": true } } } \ No newline at end of file