Merge pull request #135 from real-zony/routine-maintenance

Routine maintenance - 2023.04.17
This commit is contained in:
Zony 2023-04-17 01:15:12 +08:00 committed by GitHub
commit 22f5bc69ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 115 additions and 31 deletions

View File

@ -47,7 +47,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
public bool DownloadAlbum { get; set; } public bool DownloadAlbum { get; set; }
[Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")] [Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")]
public int ParallelNumber { get; set; } = 2; public int ParallelNumber { get; set; } = 1;
#endregion #endregion

View File

@ -41,7 +41,7 @@ namespace ZonyLrcTools.Common.Album.NetEase
true, true,
_defaultOption); _defaultOption);
if (searchResult is not { StatusCode: 200 } || searchResult.Items?.SongCount <= 0) if (searchResult is not { StatusCode: 200 } || searchResult.Items is not { SongCount: > 0 })
{ {
throw new ErrorCodeException(ErrorCodes.NoMatchingSong); throw new ErrorCodeException(ErrorCodes.NoMatchingSong);
} }

View File

@ -18,6 +18,6 @@ namespace ZonyLrcTools.Common.Lyrics
/// <param name="sourceLyric">原始歌词数据。</param> /// <param name="sourceLyric">原始歌词数据。</param>
/// <param name="translationLyric">翻译歌词数据。</param> /// <param name="translationLyric">翻译歌词数据。</param>
/// <returns>构建完成的 <see cref="LyricsItemCollection"/> 对象。</returns> /// <returns>构建完成的 <see cref="LyricsItemCollection"/> 对象。</returns>
LyricsItemCollection Build(string sourceLyric, string translationLyric); LyricsItemCollection Build(string sourceLyric, string? translationLyric);
} }
} }

View File

@ -1,5 +1,6 @@
using System.Text; using System.Text;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Polly;
using ZonyLrcTools.Common.Configuration; using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Infrastructure.DependencyInject; using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.Common.Infrastructure.Exceptions; using ZonyLrcTools.Common.Infrastructure.Exceptions;
@ -36,7 +37,7 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
} }
public async Task DownloadAsync(List<MusicInfo> needDownloadMusicInfos, public async Task DownloadAsync(List<MusicInfo> needDownloadMusicInfos,
int parallelCount = 2, int parallelCount = 1,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
await _logger.InfoAsync("开始下载歌词文件数据..."); await _logger.InfoAsync("开始下载歌词文件数据...");

View File

@ -30,7 +30,7 @@ namespace ZonyLrcTools.Common.Lyrics
return lyric; return lyric;
} }
public LyricsItemCollection Build(string sourceLyric, string translationLyric) public LyricsItemCollection Build(string sourceLyric, string? translationLyric)
{ {
var lyric = new LyricsItemCollection(_options.Provider.Lyric.Config); var lyric = new LyricsItemCollection(_options.Provider.Lyric.Config);
if (string.IsNullOrEmpty(sourceLyric)) if (string.IsNullOrEmpty(sourceLyric))

View File

@ -40,6 +40,11 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
var accessKeyResponse = await _warpHttpClient.GetAsync<GetLyricAccessKeyResponse>(KuGouGetLyricAccessKeyUrl, var accessKeyResponse = await _warpHttpClient.GetAsync<GetLyricAccessKeyResponse>(KuGouGetLyricAccessKeyUrl,
new GetLyricAccessKeyRequest(searchResult.Data.List[0].FileHash)); new GetLyricAccessKeyRequest(searchResult.Data.List[0].FileHash));
if (accessKeyResponse.AccessKeyDataObjects.Count == 0)
{
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
}
var accessKeyObject = accessKeyResponse.AccessKeyDataObjects[0]; var accessKeyObject = accessKeyResponse.AccessKeyDataObjects[0];
return await _warpHttpClient.GetAsync(KuGouGetLyricUrl, return await _warpHttpClient.GetAsync(KuGouGetLyricUrl,
new GetLyricRequest(accessKeyObject.Id, accessKeyObject.AccessKey)); new GetLyricRequest(accessKeyObject.Id, accessKeyObject.AccessKey));
@ -60,7 +65,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args) protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
{ {
if (response.ErrorCode != 0 && response.Status != 1 || response.Data.List == null) if ((response.ErrorCode != 0 && response.Status != 1) || response.Data.List.Count == 0)
{ {
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args); throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
} }

View File

@ -15,7 +15,7 @@ public class GetLyricsResponse
public class GetLyricsResponseInnerData public class GetLyricsResponseInnerData
{ {
[JsonProperty("lrclist")] public ICollection<GetLyricsItem> Lyrics { get; set; } [JsonProperty("lrclist")] public ICollection<GetLyricsItem>? Lyrics { get; set; }
} }
public class GetLyricsItem public class GetLyricsItem

View File

@ -60,6 +60,11 @@ public class KuWoLyricsProvider : LyricsProvider
await ValueTask.CompletedTask; await ValueTask.CompletedTask;
var lyricsResponse = (GetLyricsResponse)lyricsObject; var lyricsResponse = (GetLyricsResponse)lyricsObject;
if (lyricsResponse.Data.Lyrics == null)
{
return new LyricsItemCollection(null);
}
var items = lyricsResponse.Data.Lyrics.Select(l => var items = lyricsResponse.Data.Lyrics.Select(l =>
{ {
var position = double.Parse(l.Position); var position = double.Parse(l.Position);

View File

@ -8,7 +8,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
{ {
public GetLyricRequest(long songId) public GetLyricRequest(long songId)
{ {
OS = "ios"; OS = "pc";
Id = songId; Id = songId;
Lv = Kv = Tv = Rv = -1; Lv = Kv = Tv = Rv = -1;
} }

View File

@ -20,7 +20,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
/// 如果存在翻译歌词,则本项内容为翻译歌词。 /// 如果存在翻译歌词,则本项内容为翻译歌词。
/// </summary> /// </summary>
[JsonProperty("tlyric")] [JsonProperty("tlyric")]
public InnerLyric TranslationLyric { get; set; } public InnerLyric? TranslationLyric { get; set; }
/// <summary> /// <summary>
/// 如果存在罗马音歌词,则本项内容为罗马音歌词。 /// 如果存在罗马音歌词,则本项内容为罗马音歌词。

View File

@ -60,7 +60,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
var regex = new Regex(@"\([^)]*\)"); var regex = new Regex(@"\([^)]*\)");
musicName = regex.Replace(musicName, string.Empty); musicName = regex.Replace(musicName, string.Empty);
SearchKey = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8); SearchKey = $"{musicName}+{artistName}";
Limit = limit; Limit = limit;
} }
} }

View File

@ -4,7 +4,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
{ {
public class SongSearchResponse public class SongSearchResponse
{ {
[JsonProperty("result")] public InnerListItemModel Items { get; set; } [JsonProperty("result")] public InnerListItemModel? Items { get; set; }
[JsonProperty("code")] public int StatusCode { get; set; } [JsonProperty("code")] public int StatusCode { get; set; }
@ -27,7 +27,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
public class InnerListItemModel public class InnerListItemModel
{ {
[JsonProperty("songs")] public IList<SongModel> SongItems { get; set; } [JsonProperty("songs")] public IList<SongModel>? SongItems { get; set; }
[JsonProperty("songCount")] public int SongCount { get; set; } [JsonProperty("songCount")] public int SongCount { get; set; }
} }

View File

@ -2,6 +2,7 @@ using System.Net.Http.Headers;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using ZonyLrcTools.Common.Configuration; using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Infrastructure.Encryption;
using ZonyLrcTools.Common.Infrastructure.Exceptions; using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Infrastructure.Network; using ZonyLrcTools.Common.Infrastructure.Network;
using ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel; using ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel;
@ -16,11 +17,10 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase
private readonly ILyricsItemCollectionFactory _lyricsItemCollectionFactory; private readonly ILyricsItemCollectionFactory _lyricsItemCollectionFactory;
private readonly GlobalOptions _options; private readonly GlobalOptions _options;
private const string NetEaseSearchMusicUrl = @"https://music.163.com/api/search/get/web"; private const string NetEaseSearchMusicUrl = @"https://music.163.com/weapi/cloudsearch/get/web";
private const string NetEaseGetLyricUrl = @"https://music.163.com/api/song/lyric"; private const string NetEaseGetLyricUrl = @"https://music.163.com/weapi/song/lyric?csrf_token=";
private const string NetEaseRequestReferer = @"https://music.163.com"; private const string NetEaseRequestReferer = @"https://music.163.com";
private const string NetEaseRequestContentType = @"application/x-www-form-urlencoded";
public NetEaseLyricsProvider(IWarpHttpClient warpHttpClient, public NetEaseLyricsProvider(IWarpHttpClient warpHttpClient,
ILyricsItemCollectionFactory lyricsItemCollectionFactory, ILyricsItemCollectionFactory lyricsItemCollectionFactory,
@ -33,25 +33,30 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase
protected override async ValueTask<object> DownloadDataAsync(LyricsProviderArgs args) protected override async ValueTask<object> DownloadDataAsync(LyricsProviderArgs args)
{ {
var searchResult = await _warpHttpClient.PostAsync<SongSearchResponse>( var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16);
NetEaseSearchMusicUrl, var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
new SongSearchRequest(args.SongName, args.Artist, _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth),
true, var searchResult = await _warpHttpClient.PostAsync<SongSearchResponse>(NetEaseSearchMusicUrl,
msg => requestOption: request =>
{ {
msg.Headers.Referrer = new Uri(NetEaseRequestReferer); request.Headers.Referrer = new Uri(NetEaseRequestReferer);
if (msg.Content != null) request.Content = new FormUrlEncodedContent(HandleRequest(
{ new SongSearchRequest(args.SongName, args.Artist, _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth),
msg.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(NetEaseRequestContentType); secretKey,
} encSecKey));
}); });
ValidateSongSearchResponse(searchResult, args); ValidateSongSearchResponse(searchResult, args);
return await _warpHttpClient.GetAsync( return await _warpHttpClient.PostAsync(NetEaseGetLyricUrl,
NetEaseGetLyricUrl, requestOption: request =>
new GetLyricRequest(searchResult.GetFirstMatchSongId(args.SongName, args.Duration)), {
msg => msg.Headers.Referrer = new Uri(NetEaseRequestReferer)); request.Headers.Referrer = new Uri(NetEaseRequestReferer);
request.Content = new FormUrlEncodedContent(HandleRequest(
new GetLyricRequest(searchResult.GetFirstMatchSongId(args.SongName, args.Duration)),
secretKey,
encSecKey));
});
} }
protected override async ValueTask<LyricsItemCollection> GenerateLyricAsync(object lyricsObject, LyricsProviderArgs args) protected override async ValueTask<LyricsItemCollection> GenerateLyricAsync(object lyricsObject, LyricsProviderArgs args)
@ -71,7 +76,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase
return _lyricsItemCollectionFactory.Build( return _lyricsItemCollectionFactory.Build(
json.OriginalLyric.Text, json.OriginalLyric.Text,
json.TranslationLyric.Text); json.TranslationLyric?.Text);
} }
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args) protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
@ -81,10 +86,23 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, attachObj: args); throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, attachObj: args);
} }
if (response.Items?.SongCount <= 0) if (response.Items is not { SongCount: > 0 })
{ {
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args); throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
} }
} }
private Dictionary<string, string> HandleRequest(object srcParams, string secretKey, string encSecKey)
{
return new Dictionary<string, string>
{
{
"params", NetEaseMusicEncryptionHelper.AesEncode(
NetEaseMusicEncryptionHelper.AesEncode(
JsonConvert.SerializeObject(srcParams), NetEaseMusicEncryptionHelper.Nonce), secretKey)
},
{ "encSecKey", encSecKey }
};
}
} }
} }

View File

@ -14,6 +14,7 @@
<PackageReference Include="MusicDecrypto.Library" Version="2.2.0" /> <PackageReference Include="MusicDecrypto.Library" Version="2.2.0" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.0.0" /> <PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="QRCoder" Version="1.4.3" /> <PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="TagLibSharp" Version="2.3.0" /> <PackageReference Include="TagLibSharp" Version="2.3.0" />
</ItemGroup> </ItemGroup>

View File

@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Lyrics; using ZonyLrcTools.Common.Lyrics;
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
@ -25,5 +26,18 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
lyric.ShouldNotBeNull(); lyric.ShouldNotBeNull();
lyric.IsPruneMusic.ShouldBe(false); lyric.IsPruneMusic.ShouldBe(false);
} }
[Fact]
public async Task DownloadAsync_Issue133_Test()
{
await Should.ThrowAsync<ErrorCodeException>(_lyricsProvider.DownloadAsync("天ノ弱", "漆柚").AsTask);
}
[Fact]
public async Task DownloadAsync_Index_Exception_Test()
{
var lyric = await _lyricsProvider.DownloadAsync("40'z", "ZOOLY");
lyric.ToString().ShouldNotBeNullOrEmpty();
}
} }
} }

View File

@ -25,4 +25,13 @@ public class KuWoLyricsProviderTests : TestBase
lyric.ShouldNotBeNull(); lyric.ShouldNotBeNull();
lyric.IsPruneMusic.ShouldBeFalse(); lyric.IsPruneMusic.ShouldBeFalse();
} }
[Fact]
public async Task DownloadAsync_Source_Null_Test()
{
var lyric = await _kuwoLyricsProvider.DownloadAsync("Concerto for Piano and Orchestra No. 12 in A major, K414 - 1. Allegro",
"Wolfgang Amadeus Mozart");
lyric.IsPruneMusic.ShouldBeTrue();
}
} }

View File

@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
using ZonyLrcTools.Common.Configuration; using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Lyrics; using ZonyLrcTools.Common.Lyrics;
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
@ -105,5 +106,28 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
var lyric = await _lyricsProvider.DownloadAsync("橄榄树", "苏曼"); var lyric = await _lyricsProvider.DownloadAsync("橄榄树", "苏曼");
lyric.ToString().ShouldNotBeNullOrEmpty(); lyric.ToString().ShouldNotBeNullOrEmpty();
} }
[Fact]
public async Task DownloadAsync_Issue133_Test()
{
var lyric = await _lyricsProvider.DownloadAsync("Everything", "Yinyues");
lyric.ToString().ShouldNotBeNullOrEmpty();
}
[Fact]
public async Task DownloadAsync_NullException_Test()
{
var result = await Should.ThrowAsync<ErrorCodeException>(_lyricsProvider.DownloadAsync("創世記", "りりィ").AsTask);
result.ErrorCode.ShouldBe(ErrorCodes.NoMatchingSong);
}
[Fact]
public async Task DownloadAsync_Source_Null_Test()
{
var lyric = await _lyricsProvider.DownloadAsync("Concerto for Piano and Orchestra No. 12 in A major, K414 - 1. Allegro",
"Wolfgang Amadeus Mozart");
lyric.IsPruneMusic.ShouldBeTrue();
}
} }
} }

View File

@ -37,5 +37,12 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
lyric.IsPruneMusic.ShouldBeFalse(); lyric.IsPruneMusic.ShouldBeFalse();
lyric.ToString().ShouldContain("你好像快要不能呼吸"); lyric.ToString().ShouldContain("你好像快要不能呼吸");
} }
[Fact]
public async Task DownloadAsync_Issue133_Test()
{
var lyric = await _lyricsProvider.DownloadAsync("天ノ弱", "漆柚");
lyric.ToString().ShouldNotBeNullOrEmpty();
}
} }
} }