Skip to content
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

#21. Fixed the Musixmatch provider that fails after certain requests #22

Merged
merged 1 commit into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions LyricsScraperNET/Configuration/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public static IServiceCollection AddMusixmatchService(
{
services.Configure<MusixmatchOptions>(configurationSection);

services.AddSingleton<IMusixmatchTokenCache, MusixmatchTokenCache>();
services.AddScoped<IMusixmatchClientWrapper, MusixmatchClientWrapper>();
services.AddScoped(typeof(IExternalProvider), typeof(MusixmatchProvider));
}

Expand Down
4 changes: 2 additions & 2 deletions LyricsScraperNET/LyricsScraperNET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@

<ItemGroup>
<PackageReference Include="Genius.NET" Version="4.0.1" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.58" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
<PackageReference Include="MusixmatchClientLib" Version="1.1.3" />
<PackageReference Include="MusixmatchClientLib" Version="1.1.5" />
<PackageReference Include="Sigil" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.2" />
</ItemGroup>
Expand Down
15 changes: 15 additions & 0 deletions LyricsScraperNET/Providers/Musixmatch/IMusixmatchClientWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using LyricsScraperNET.Models.Responses;
using System.Threading.Tasks;

namespace LyricsScraperNET.Providers.Musixmatch
{
/// <summary>
/// Decorator for the <seealso cref="MusixmatchClientLib.MusixmatchClient"/>
/// </summary>
public interface IMusixmatchClientWrapper
{
SearchResult SearchLyric(string artist, string song, bool regenerateToken = false);

Task<SearchResult> SearchLyricAsync(string artist, string song, bool regenerateToken = false);
}
}
15 changes: 15 additions & 0 deletions LyricsScraperNET/Providers/Musixmatch/IMusixmatchTokenCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace LyricsScraperNET.Providers.Musixmatch
{
/// <summary>
/// Token cache provider
/// </summary>
public interface IMusixmatchTokenCache
{
/// <summary>
/// Get or create token from cache.
/// </summary>
/// <param name="regenerate">If true, then the token will be created again.</param>
/// <returns></returns>
string GetOrCreateToken(bool regenerate = false);
}
}
104 changes: 104 additions & 0 deletions LyricsScraperNET/Providers/Musixmatch/MusixmatchClientWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using LyricsScraperNET.Extensions;
using LyricsScraperNET.Models.Responses;
using Microsoft.Extensions.Logging;
using MusixmatchClientLib;
using MusixmatchClientLib.API.Model.Types;
using MusixmatchClientLib.Types;
using System.Linq;
using System.Threading.Tasks;

namespace LyricsScraperNET.Providers.Musixmatch
{
public sealed class MusixmatchClientWrapper : IMusixmatchClientWrapper
{
private ILogger<MusixmatchClientWrapper> _logger;
private IMusixmatchTokenCache _tokenCache;

public MusixmatchClientWrapper()
{
}

public MusixmatchClientWrapper(IMusixmatchTokenCache tokenCache)
{
_tokenCache = tokenCache;
}

public MusixmatchClientWrapper(ILogger<MusixmatchClientWrapper> logger, IMusixmatchTokenCache tokenCache)
: this(tokenCache)
{
_logger = logger;
}

public SearchResult SearchLyric(string artist, string song, bool regenerateToken = false)
{
var client = GetMusixmatchClient(regenerateToken);
var trackSearchParameters = GetTrackSearchParameters(artist, song);

var trackId = client.SongSearch(trackSearchParameters)?.FirstOrDefault()?.TrackId;
if (trackId != null)
{
Lyrics lyrics = client.GetTrackLyrics(trackId.Value);

// lyrics.LyricsBody is null when the track is instrumental
if (lyrics.Instrumental != 1)
return new SearchResult(lyrics.LyricsBody, Models.ExternalProviderType.Musixmatch);

// Instrumental music without lyric
return new SearchResult(Models.ExternalProviderType.Musixmatch)
.AddInstrumental(true);
}
else
{
_logger?.LogWarning($"Musixmatch. Can't find any information about artist {artist} and song {song}");
return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
}

public async Task<SearchResult> SearchLyricAsync(string artist, string song, bool regenerateToken = false)
{
var client = GetMusixmatchClient(regenerateToken);
var trackSearchParameters = GetTrackSearchParameters(artist, song);

var songSearchTask = await client.SongSearchAsync(trackSearchParameters);

var trackId = songSearchTask?.FirstOrDefault()?.TrackId;
if (trackId != null)
{
Lyrics lyrics = await client.GetTrackLyricsAsync(trackId.Value);

// lyrics.LyricsBody is null when the track is instrumental
if (lyrics.Instrumental != 1)
return new SearchResult(lyrics.LyricsBody, Models.ExternalProviderType.Musixmatch);

// Instrumental music without lyric
return new SearchResult(Models.ExternalProviderType.Musixmatch)
.AddInstrumental(true);
}
else
{
_logger?.LogWarning($"Musixmatch. Can't find any information about artist {artist} and song {song}");
return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
}

private MusixmatchClient GetMusixmatchClient(bool regenerateToken = false)
{
var musixmatchToken = _tokenCache.GetOrCreateToken(regenerateToken);
return new MusixmatchClient(musixmatchToken);
}

private TrackSearchParameters GetTrackSearchParameters(string artist, string song)
{
return new TrackSearchParameters
{
Artist = artist,
Title = song, // Track name
//Query = $"{artist} - {song}", // Search query, covers all the search parameters above
//HasLyrics = false, // Only search for tracks with lyrics
//HasSubtitles = false, // Only search for tracks with synced lyrics
//Language = "", // Only search for tracks with lyrics in specified language
Sort = TrackSearchParameters.SortStrategy.TrackRatingDesc // List sorting strategy
};
}
}
}
134 changes: 18 additions & 116 deletions LyricsScraperNET/Providers/Musixmatch/MusixmatchProvider.cs
Original file line number Diff line number Diff line change
@@ -1,73 +1,55 @@
using LyricsScraperNET.Extensions;
using LyricsScraperNET.Helpers;
using LyricsScraperNET.Helpers;
using LyricsScraperNET.Models.Responses;
using LyricsScraperNET.Providers.Abstract;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MusixmatchClientLib;
using MusixmatchClientLib.API.Model.Exceptions;
using MusixmatchClientLib.API.Model.Types;
using MusixmatchClientLib.Auth;
using MusixmatchClientLib.Types;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace LyricsScraperNET.Providers.Musixmatch
{
public sealed class MusixmatchProvider : ExternalProviderBase
{
private ILogger<MusixmatchProvider> _logger;

// Musixmatch Token memory cache
private static readonly IMemoryCache _memoryCache;
private static readonly MemoryCacheEntryOptions _memoryCacheEntryOptions;

private static readonly string MusixmatchTokenKey = "MusixmatchToken";
private IMusixmatchClientWrapper _clientWrapper;

private readonly int _searchRetryAmount = 2;

#region Constructors

static MusixmatchProvider()
{
_memoryCache = new MemoryCache(new MemoryCacheOptions()
{
SizeLimit = 1024,
});
_memoryCacheEntryOptions = new MemoryCacheEntryOptions()
{
Size = 1
};
}

public MusixmatchProvider()

Check warning on line 22 in LyricsScraperNET/Providers/Musixmatch/MusixmatchProvider.cs

View workflow job for this annotation

GitHub Actions / lyrics_scraper_net-cicd

Non-nullable field '_logger' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
{
Options = new MusixmatchOptions() { Enabled = true };

var tokenCache = new MusixmatchTokenCache();
_clientWrapper = new MusixmatchClientWrapper(tokenCache);
}

public MusixmatchProvider(ILogger<MusixmatchProvider> logger, MusixmatchOptions options)
public MusixmatchProvider(ILogger<MusixmatchProvider> logger, MusixmatchOptions options, IMusixmatchClientWrapper clientWrapper)
{
_logger = logger;
Ensure.ArgumentNotNull(options, nameof(options));
Options = options;
Ensure.ArgumentNotNull(clientWrapper, nameof(clientWrapper));
_clientWrapper = clientWrapper;
}

public MusixmatchProvider(ILogger<MusixmatchProvider> logger, IOptionsSnapshot<MusixmatchOptions> options)
: this(logger, options.Value)
public MusixmatchProvider(ILogger<MusixmatchProvider> logger, IOptionsSnapshot<MusixmatchOptions> options, IMusixmatchClientWrapper clientWrapper)
: this(logger, options.Value, clientWrapper)
{
Ensure.ArgumentNotNull(options, nameof(options));
}

public MusixmatchProvider(MusixmatchOptions options)
: this(null, options)
public MusixmatchProvider(MusixmatchOptions options, IMusixmatchClientWrapper clientWrapper)
: this(null, options, clientWrapper)

Check warning on line 46 in LyricsScraperNET/Providers/Musixmatch/MusixmatchProvider.cs

View workflow job for this annotation

GitHub Actions / lyrics_scraper_net-cicd

Cannot convert null literal to non-nullable reference type.
{
Ensure.ArgumentNotNull(options, nameof(options));
}

public MusixmatchProvider(IOptionsSnapshot<MusixmatchOptions> options)
: this(null, options.Value)
public MusixmatchProvider(IOptionsSnapshot<MusixmatchOptions> options, IMusixmatchClientWrapper clientWrapper)
: this(null, options.Value, clientWrapper)
{
Ensure.ArgumentNotNull(options, nameof(options));
}
Expand All @@ -86,32 +68,13 @@

protected override SearchResult SearchLyric(string artist, string song)
{
int? trackId;
bool regenerateToken = false;
for (int i = 1; i <= _searchRetryAmount; i++)
{
var client = GetMusixmatchClient(regenerateToken);
var trackSearchParameters = GetTrackSearchParameters(artist, song);
try
{
trackId = client.SongSearch(trackSearchParameters)?.FirstOrDefault()?.TrackId;
if (trackId != null)
{
Lyrics lyrics = client.GetTrackLyrics(trackId.Value);

// lyrics.LyricsBody is null when the track is instrumental
if (lyrics.Instrumental != 1)
return new SearchResult(lyrics.LyricsBody, Models.ExternalProviderType.Musixmatch);

// Instrumental music without lyric
return new SearchResult(Models.ExternalProviderType.Musixmatch)
.AddInstrumental(true);
}
else
{
_logger?.LogWarning($"Musixmatch. Can't find any information about artist {artist} and song {song}");
return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
var result = _clientWrapper.SearchLyric(artist, song, regenerateToken);
return result;
}
catch (MusixmatchRequestException requestException) when (requestException.StatusCode == StatusCode.AuthFailed)
{
Expand All @@ -137,30 +100,10 @@
bool regenerateToken = false;
for (int i = 1; i <= _searchRetryAmount; i++)
{
var client = GetMusixmatchClient(regenerateToken);
var trackSearchParameters = GetTrackSearchParameters(artist, song);
try
{
var songSearchTask = await client.SongSearchAsync(trackSearchParameters);

var trackId = songSearchTask?.FirstOrDefault()?.TrackId;
if (trackId != null)
{
Lyrics lyrics = await client.GetTrackLyricsAsync(trackId.Value);

// lyrics.LyricsBody is null when the track is instrumental
if (lyrics.Instrumental != 1)
return new SearchResult(lyrics.LyricsBody, Models.ExternalProviderType.Musixmatch);

// Instrumental music without lyric
return new SearchResult(Models.ExternalProviderType.Musixmatch)
.AddInstrumental(true);
}
else
{
_logger?.LogWarning($"Musixmatch. Can't find any information about artist {artist} and song {song}");
return new SearchResult(Models.ExternalProviderType.Musixmatch);
}
var result = await _clientWrapper.SearchLyricAsync(artist, song, regenerateToken);
return result;
}
catch (MusixmatchRequestException requestException) when (requestException.StatusCode == StatusCode.AuthFailed)
{
Expand All @@ -177,46 +120,5 @@
{
_logger = loggerFactory.CreateLogger<MusixmatchProvider>();
}

private MusixmatchClient GetMusixmatchClient(bool regenerateToken = false)
{
// TODO: uncomment after the fix of https://github.com/Eimaen/MusixmatchClientLib/issues/21
//if (Options.TryGetApiKeyFromOptions(out var apiKey))
//{
// _logger.LogInformation("Use MusixmatchToken from options.");
// return new MusixmatchClient(apiKey);
//}
//else
//{
if (regenerateToken)
_memoryCache.Remove(MusixmatchTokenKey);

_logger?.LogDebug("Musixmatch. Use default MusixmatchToken.");
string musixmatchTokenValue;
if (!_memoryCache.TryGetValue(MusixmatchTokenKey, out musixmatchTokenValue))
{
_logger?.LogDebug("Musixmatch. Generate new token.");
var musixmatchToken = new MusixmatchToken();
musixmatchTokenValue = musixmatchToken.Token;
_memoryCache.Set(MusixmatchTokenKey, musixmatchTokenValue, _memoryCacheEntryOptions);
}
(Options as IExternalProviderOptionsWithApiKey).ApiKey = musixmatchTokenValue;
return new MusixmatchClient(musixmatchTokenValue);
//}
}

private TrackSearchParameters GetTrackSearchParameters(string artist, string song)
{
return new TrackSearchParameters
{
Artist = artist,
Title = song, // Track name
//Query = $"{artist} - {song}", // Search query, covers all the search parameters above
//HasLyrics = false, // Only search for tracks with lyrics
//HasSubtitles = false, // Only search for tracks with synced lyrics
//Language = "", // Only search for tracks with lyrics in specified language
Sort = TrackSearchParameters.SortStrategy.TrackRatingDesc // List sorting strategy
};
}
}
}
Loading
Loading