Skip to content

Commit

Permalink
#17 Add ability to configure logging using WithLogger method in main …
Browse files Browse the repository at this point in the history
…Client and Providers. Added Instrumental flag to the ResponseMessage.
  • Loading branch information
skuill committed Dec 16, 2023
1 parent 0c71753 commit 83de1cc
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 37 deletions.
33 changes: 28 additions & 5 deletions LyricsScraperNET.Client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using LyricsScraperNET.Models.Responses;
using System.Threading.Tasks;
using System;
using Microsoft.Extensions.Logging;

class Program
{
Expand All @@ -28,11 +29,23 @@ static async Task Main()
//var result = ExampleWithCertainProvider(artistToSearch, songToSearch);

//// Checking if something was found.
//// If not, search errors can be found in the logs.
//// May not be found in two cases:
//// 1) search errors can be found in the logs or in response fields like ResponseStatusCode and ResponseMessage.
//// 2) The requested song contains only the instrumental, no lyrics. In this case the flag will be true.
if (result.IsEmpty())
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Can't find lyrics for: {artistToSearch} - {songToSearch}");
if (result.Instrumental)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("This song is instrumental.\r\nIt does not contain any lyrics");
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Can't find lyrics for: [ {artistToSearch} - {songToSearch} ]. " +
$"Status code: [ {result.ResponseStatusCode} ]. " +
$"Response message: [ {result.ResponseMessage} ].");
}
Console.ResetColor();

Console.ReadLine();
Expand All @@ -41,13 +54,13 @@ static async Task Main()

//// Output result to console
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"{artistToSearch} - {songToSearch}\r\n");
Console.WriteLine($"[ {artistToSearch} - {songToSearch} ]\r\n");
Console.ResetColor();

Console.WriteLine(result.LyricText);

Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($"\r\nThis text was found by {result.ExternalProviderType}\r\n");
Console.WriteLine($"\r\nThis lyric was found by [ {result.ExternalProviderType} ]\r\n");
Console.ResetColor();

Console.ReadLine();
Expand Down Expand Up @@ -105,6 +118,16 @@ ILyricsScraperClient lyricsScraperClient
.WithSongLyrics()
.WithLyricFind();

// To enable library logging, the LoggerFactory must be configured and passed to the client.
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("LyricsScraperNET", LogLevel.Trace)
.AddConsole();
});
lyricsScraperClient.WithLogger(loggerFactory);

//// Another way to configure:
//// 1. First create instance of LyricScraperClient.
// ILyricsScraperClient lyricsScraperClient = new LyricsScraperClient();
Expand Down
6 changes: 6 additions & 0 deletions LyricsScraperNET/Extensions/SearchResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,11 @@ internal static SearchResult AppendResponseMessage(this SearchResult searchResul
: responseMessage;
return searchResult;
}

internal static SearchResult AddInstrumental(this SearchResult searchResult, bool instrumental)
{
searchResult.Instrumental = instrumental;
return searchResult;
}
}
}
7 changes: 7 additions & 0 deletions LyricsScraperNET/ILyricsScraperClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Providers.Abstract;
using LyricsScraperNET.Providers.Models;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace LyricsScraperNET
Expand Down Expand Up @@ -49,5 +50,11 @@ public interface ILyricsScraperClient
/// Calling the lyrics search method will return an empty result.
/// </summary>
void Disable();

/// <summary>
/// Creates a new ILogger instance from <paramref name="loggerFactory"/>.
/// Can be useful for error analysis if the lyric text is not found.
/// </summary>
void WithLogger(ILoggerFactory loggerFactory);
}
}
24 changes: 18 additions & 6 deletions LyricsScraperNET/LyricsScraperClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ namespace LyricsScraperNET
{
public sealed class LyricsScraperClient : ILyricsScraperClient
{

private readonly ILogger<LyricsScraperClient> _logger;
private ILoggerFactory _loggerFactory;
private ILogger<LyricsScraperClient> _logger;

private List<IExternalProvider> _externalProviders;
private readonly ILyricScraperClientConfig _lyricScraperClientConfig;
Expand Down Expand Up @@ -200,7 +200,11 @@ public void AddProvider(IExternalProvider provider)
if (IsEmptyProvidersList())
_externalProviders = new List<IExternalProvider>();
if (!_externalProviders.Contains(provider))
{
if (_loggerFactory != null)
provider.WithLogger(_loggerFactory);
_externalProviders.Add(provider);
}
else
_logger?.LogWarning($"External provider {provider} already added");
}
Expand All @@ -219,9 +223,7 @@ public void Enable()
return;

foreach (var provider in _externalProviders)
{
provider.Enable();
}
}

public void Disable()
Expand All @@ -230,9 +232,19 @@ public void Disable()
return;

foreach (var provider in _externalProviders)
{
provider.Disable();
}
}

public void WithLogger(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
_logger = loggerFactory.CreateLogger<LyricsScraperClient>();

if (IsEmptyProvidersList())
return;

foreach (var provider in _externalProviders)
provider.WithLogger(loggerFactory);
}

private bool IsEmptyProvidersList() => _externalProviders == null || !_externalProviders.Any();
Expand Down
5 changes: 5 additions & 0 deletions LyricsScraperNET/Models/Responses/SearchResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ internal SearchResult(string lyricText, ExternalProviderType externalProviderTyp
/// </summary>
public string ResponseMessage { get; internal set; } = string.Empty;

/// <summary>
/// The flag indicates that the search results are for music only, without text.
/// </summary>
public bool Instrumental { get; internal set; } = false;

/// <summary>
/// Returns true if the field <seealso cref="LyricText"/> is empty.
/// </summary>
Expand Down
7 changes: 6 additions & 1 deletion LyricsScraperNET/Providers/AZLyrics/AZLyricsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace LyricsScraperNET.Providers.AZLyrics
{
public sealed class AZLyricsProvider : ExternalProviderBase
{
private readonly ILogger<AZLyricsProvider> _logger;
private ILogger<AZLyricsProvider> _logger;
private readonly IExternalUriConverter _uriConverter;

private const string _lyricStart = "<!-- Usage of azlyrics.com content by any third-party lyrics provider is prohibited by our licensing agreement. Sorry about that. -->";
Expand Down Expand Up @@ -96,6 +96,11 @@ protected override async Task<SearchResult> SearchLyricAsync(Uri uri)

#endregion

public override void WithLogger(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AZLyricsProvider>();
}

private SearchResult PostProcessLyric(Uri uri, string text)
{
if (string.IsNullOrEmpty(text))
Expand Down
4 changes: 4 additions & 0 deletions LyricsScraperNET/Providers/Abstract/ExternalProviderBase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using LyricsScraperNET.Models.Requests;
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Network.Abstract;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

Expand Down Expand Up @@ -95,5 +96,8 @@ public void Disable()
if (Options != null)
Options.Enabled = false;
}

public virtual void WithLogger(ILoggerFactory loggerFactory)
=> throw new NotImplementedException();
}
}
3 changes: 3 additions & 0 deletions LyricsScraperNET/Providers/Abstract/IExternalProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using LyricsScraperNET.Models.Requests;
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Network.Abstract;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace LyricsScraperNET.Providers.Abstract
Expand All @@ -27,5 +28,7 @@ public interface IExternalProvider
void Enable();

void Disable();

void WithLogger(ILoggerFactory loggerFactory);
}
}
43 changes: 36 additions & 7 deletions LyricsScraperNET/Providers/Genius/GeniusProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace LyricsScraperNET.Providers.Genius
{
public sealed class GeniusProvider : ExternalProviderBase
{
private readonly ILogger<GeniusProvider> _logger;
private ILogger<GeniusProvider> _logger;
private readonly IExternalUriConverter _uriConverter;

// Format: "artist song". Example: "Parkway Drive Carrion".
Expand All @@ -26,6 +26,10 @@ public sealed class GeniusProvider : ExternalProviderBase
private const string _referentFragmentNodesXPath = "//a[contains(@class, 'ReferentFragmentVariantdesktop') or contains(@class, 'ReferentFragmentdesktop')]";
private const string _lyricsContainerNodesXPath = "//div[@data-lyrics-container]";

// In case of instrumental song without a lyric.
private const string _lyricsPlaceholderNodesXPath = "//div[contains(@class, 'LyricsPlaceholder')]";
private const string _instrumentalLyricText = "This song is an instrumental";

#region Constructors

public GeniusProvider()
Expand Down Expand Up @@ -71,7 +75,10 @@ protected override SearchResult SearchLyric(Uri uri)
{
var htmlPageBody = WebClient.Load(uri);

return new SearchResult(GetParsedLyricFromHtmlPageBody(htmlPageBody), Models.ExternalProviderType.Genius);
var lyricResult = GetParsedLyricFromHtmlPageBody(htmlPageBody, out var instrumental);

return new SearchResult(lyricResult, Models.ExternalProviderType.Genius)
.AddInstrumental(instrumental);
}

protected override SearchResult SearchLyric(string artist, string song)
Expand All @@ -94,7 +101,7 @@ protected override SearchResult SearchLyric(string artist, string song)

return !string.IsNullOrWhiteSpace(lyricUrl)
? SearchLyric(new Uri(lyricUrl))
: new SearchResult();
: new SearchResult(Models.ExternalProviderType.Genius);
}

#endregion
Expand All @@ -105,7 +112,10 @@ protected override async Task<SearchResult> SearchLyricAsync(Uri uri)
{
var htmlPageBody = await WebClient.LoadAsync(uri);

return new SearchResult(GetParsedLyricFromHtmlPageBody(htmlPageBody), Models.ExternalProviderType.Genius);
var lyricResult = GetParsedLyricFromHtmlPageBody(htmlPageBody, out var instrumental);

return new SearchResult(lyricResult, Models.ExternalProviderType.Genius)
.AddInstrumental(instrumental);
}

protected override async Task<SearchResult> SearchLyricAsync(string artist, string song)
Expand All @@ -128,11 +138,16 @@ protected override async Task<SearchResult> SearchLyricAsync(string artist, stri

return !string.IsNullOrWhiteSpace(lyricUrl)
? await SearchLyricAsync(new Uri(lyricUrl))
: new SearchResult();
: new SearchResult(Models.ExternalProviderType.Genius);
}

#endregion

public override void WithLogger(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<GeniusProvider>();
}

private string GetLyricUrlWithoutApiKey(string artist, string song)
{
var htmlPageBody = WebClient.Load(_uriConverter.GetLyricUri(artist, song));
Expand Down Expand Up @@ -160,8 +175,10 @@ private string GetLyricUrlWithoutApiKey(string artist, string song)
return string.Empty;
}

private string GetParsedLyricFromHtmlPageBody(string htmlPageBody)
private string GetParsedLyricFromHtmlPageBody(string htmlPageBody, out bool instrumental)
{
instrumental = false;

var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(htmlPageBody.Replace("https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js", ""));

Expand All @@ -175,8 +192,20 @@ private string GetParsedLyricFromHtmlPageBody(string htmlPageBody)
spanNode.Remove();

var lyricNodes = htmlDocument.DocumentNode.SelectNodes(_lyricsContainerNodesXPath);
if (lyricNodes == null)
{
// lyricNodes could be null in case of instrumental.
var instrumentalNodes = htmlDocument.DocumentNode.SelectNodes(_lyricsPlaceholderNodesXPath);
if (instrumentalNodes != null
&& instrumentalNodes.Any(node => node.InnerHtml.Contains(_instrumentalLyricText)))
{
instrumental = true;
return string.Empty;
}
_logger?.LogWarning($"Genius. Can't parse lyric from the page.");
}

return Parser.Parse(string.Join("", lyricNodes.Select(Node => Node.InnerHtml)));
return Parser.Parse(string.Join("", lyricNodes.Select(node => node.InnerHtml)));
}

private string GetLyricUrlFromSearchResponse(SearchResponse searchResponse, string artist, string song)
Expand Down
7 changes: 6 additions & 1 deletion LyricsScraperNET/Providers/LyricFind/LyricFindProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace LyricsScraperNET.Providers.LyricFind
{
public sealed class LyricFindProvider : ExternalProviderBase
{
private readonly ILogger<LyricFindProvider> _logger;
private ILogger<LyricFindProvider> _logger;
private readonly IExternalUriConverter _uriConverter;

private const string _lyricStart = "\"lyrics\"";
Expand Down Expand Up @@ -95,6 +95,11 @@ protected override async Task<SearchResult> SearchLyricAsync(Uri uri)

#endregion

public override void WithLogger(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<LyricFindProvider>();
}

private SearchResult PostProcessLyric(Uri uri, string text)
{
if (string.IsNullOrEmpty(text))
Expand Down
Loading

0 comments on commit 83de1cc

Please sign in to comment.