-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue 38: Initial commit for Lyrics Freak Provider #45
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a test in the |
||
{ | ||
lyricsScraperClient.AddProvider(new LyricsFreakProvider()); | ||
return lyricsScraperClient; | ||
} | ||
|
||
/// <summary> | ||
/// Configure LyricsScraperClient with all available providers in <seealso cref="ExternalProviderType"/>. | ||
/// 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(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a couple unit tests in the: |
||
{ | ||
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; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
using Genius.Models.Song; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pleasse sort and remove unused usings |
||
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<LyricsFreakProvider>? _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<LyricsFreakProvider> logger, LyricsFreakOptions options) | ||
: this() | ||
{ | ||
_logger = logger; | ||
Ensure.ArgumentNotNull(options, nameof(options)); | ||
Options = options; | ||
} | ||
public LyricsFreakProvider(ILogger<LyricsFreakProvider> logger, IOptionsSnapshot<LyricsFreakOptions> options) | ||
: this(logger, options.Value) | ||
{ | ||
Ensure.ArgumentNotNull(options, nameof(options)); | ||
} | ||
public LyricsFreakProvider(LyricsFreakOptions options) | ||
: this(NullLogger<LyricsFreakProvider>.Instance, options) | ||
{ | ||
Ensure.ArgumentNotNull(options, nameof(options)); | ||
} | ||
public LyricsFreakProvider(IOptionsSnapshot<LyricsFreakOptions> options) | ||
: this(NullLogger<LyricsFreakProvider>.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<SearchResult> 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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a copy-paste issue in the log messages. For instance, "LyricFind" appears incorrectly here and in other places in this file. |
||
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<SearchResult> 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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Plaese Add Unit Tests for |
||
{ | ||
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)); | ||
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ public enum ExternalProviderType | |
Genius, | ||
Musixmatch, | ||
SongLyrics, | ||
LyricFind | ||
LyricFind, | ||
LyricsFreak | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add an empty line before this for better readability