feat: Added KuWo(酷我) Music lyrics source (incomplete).

This commit is contained in:
real-zony 2022-10-26 17:05:44 +08:00
parent 6b72f919b8
commit b7b1f36bf5
15 changed files with 219 additions and 13 deletions

View File

@ -5,7 +5,6 @@ using ZonyLrcTools.Common.Album;
using ZonyLrcTools.Common.Lyrics;
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable MemberCanBePrivate.Global
namespace ZonyLrcTools.Cli.Commands.SubCommand

View File

@ -43,6 +43,9 @@ globalOption:
- name: KuGou # 基于酷狗音乐的歌词下载器。
priority: 3
depth: 10
- name: KuWo # 基于酷我音乐的歌词下载器。
priority: 4
depth: 10
# 歌词下载的一些共有配置参数。
config:
isOneLine: true # 双语歌词是否合并为一行展示。

View File

@ -15,7 +15,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Exceptions
/// <param name="errorCode">错误码,参考 <see cref="ErrorCodes"/> 类的定义。</param>
/// <param name="message">错误信息。</param>
/// <param name="attachObj">附加的对象数据。</param>
public ErrorCodeException(int errorCode, string message = null, object attachObj = null) : base(message)
public ErrorCodeException(int errorCode, string? message = null, object attachObj = null) : base(message)
{
ErrorCode = errorCode;
AttachObject = attachObj;

View File

@ -19,5 +19,10 @@ namespace ZonyLrcTools.Common.Lyrics
/// 酷狗音乐歌词下载器。
/// </summary>
public const string KuGou = nameof(KuGou);
/// <summary>
/// 酷我音乐歌词下载器。
/// </summary>
public const string KuWo = nameof(KuWo);
}
}

View File

@ -0,0 +1,13 @@
using Newtonsoft.Json;
namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
public class GetLyricsRequest
{
[JsonProperty("musicId")] public long MusicId { get; }
public GetLyricsRequest(long musicId)
{
MusicId = musicId;
}
}

View File

@ -0,0 +1,21 @@
using Newtonsoft.Json;
namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
public class GetLyricsResponse
{
[JsonProperty("status")] public int Status { get; set; }
[JsonProperty("lrclist")] public ICollection<GetLyricsItem> Lyrics { get; set; }
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
[JsonProperty("msgs")] public string? ErrorMessage2 { get; set; }
}
public class GetLyricsItem
{
[JsonProperty("lineLyric")] public string Text { get; set; }
[JsonProperty("time")] public string Position { get; set; }
}

View File

@ -0,0 +1,19 @@
using Newtonsoft.Json;
namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
public class SongSearchRequest
{
[JsonProperty("key")] public string Keyword { get; set; }
[JsonProperty("pn")] public int PageNumber { get; }
[JsonProperty("rn")] public int PageSize { get; }
public SongSearchRequest(string name, string artist, int pageNumber = 1, int pageSize = 20)
{
Keyword = $"{name} {artist}";
PageNumber = pageNumber;
PageSize = pageSize;
}
}

View File

@ -0,0 +1,46 @@
using Newtonsoft.Json;
namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
public class SongSearchResponse
{
[JsonProperty("code")] public int Code { get; set; }
[JsonProperty("data")] public SongSearchResponseInnerData InnerData { get; set; }
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
public long GetMatchedMusicId(string musicName, string artistName, long? duration)
{
var prefectMatch = InnerData.SongItems.FirstOrDefault(x => x.Name == musicName && x.Artist == artistName);
if (prefectMatch != null)
{
return prefectMatch.MusicId;
}
if (duration is null or 0)
{
return InnerData.SongItems.First().MusicId;
}
return InnerData.SongItems.OrderBy(t => Math.Abs(t.Duration - duration.Value)).First().MusicId;
}
}
public class SongSearchResponseInnerData
{
[JsonProperty("total")] public string Total { get; set; }
[JsonProperty("list")] public ICollection<SongSearchResponseDetail> SongItems { get; set; }
}
public class SongSearchResponseDetail
{
[JsonProperty("artist")] public string? Artist { get; set; }
[JsonProperty("name")] public string? Name { get; set; }
[JsonProperty("rid")] public long MusicId { get; set; }
[JsonProperty("duration")] public long Duration { get; set; }
}

View File

@ -0,0 +1,72 @@
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Infrastructure.Network;
using ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo;
public class KuWoLyricsProvider : LyricsProvider
{
public override string DownloaderName => InternalLyricsProviderNames.KuWo;
private const string KuWoSearchMusicUrl = @"https://www.kuwo.cn/api/www/search/searchMusicBykeyWord";
private const string KuWoSearchLyricsUrl = @"https://m.kuwo.cn/newh5/singles/songinfoandlrc";
private const string KuWoDefaultToken = "ABCDE12345";
private readonly IWarpHttpClient _warpHttpClient;
private readonly ILyricsItemCollectionFactory _lyricsItemCollectionFactory;
private readonly GlobalOptions _options;
private static readonly ProductInfoHeaderValue UserAgent = new("Chrome", "81.0.4044.138");
public KuWoLyricsProvider(IWarpHttpClient warpHttpClient,
ILyricsItemCollectionFactory lyricsItemCollectionFactory,
IOptions<GlobalOptions> options)
{
_warpHttpClient = warpHttpClient;
_lyricsItemCollectionFactory = lyricsItemCollectionFactory;
_options = options.Value;
}
protected override async ValueTask<byte[]> DownloadDataAsync(LyricsProviderArgs args)
{
var songSearchResponse = await _warpHttpClient.GetAsync<SongSearchResponse>(KuWoSearchMusicUrl,
new SongSearchRequest(args.SongName, args.Artist, pageSize: _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth),
op =>
{
op.Headers.UserAgent.Add(UserAgent);
op.Headers.Referrer = new Uri("https://kuwo.cn");
op.Headers.Add("csrf", KuWoDefaultToken);
op.Headers.Add("Cookie", $"kw_token={KuWoDefaultToken}");
});
ValidateSongSearchResponse(songSearchResponse, args);
var songLyricsResponse = await _warpHttpClient.GetAsync<GetLyricsResponse>(KuWoSearchLyricsUrl,
new GetLyricsRequest(songSearchResponse.GetMatchedMusicId(args.SongName, args.Artist, args.Duration)),
op =>
{
op.Headers.UserAgent.Add(UserAgent);
op.Headers.Referrer = new Uri("https://m.kuwo.cn/yinyue/");
});
return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(songLyricsResponse.Lyrics));
}
protected override ValueTask<LyricsItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args)
{
throw new NotImplementedException();
}
protected void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
{
if (response.Code != 200)
{
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, response.ErrorMessage, args);
}
}
}

View File

@ -5,13 +5,13 @@ using Shouldly;
using Xunit;
using ZonyLrcTools.Common.Lyrics;
namespace ZonyLrcTools.Tests.Infrastructure.Lyric
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
{
public class KuGouLyricDownloaderTests : TestBase
public class KuGouLyricProviderTests : TestBase
{
private readonly ILyricsProvider _lyricsProvider;
public KuGouLyricDownloaderTests()
public KuGouLyricProviderTests()
{
_lyricsProvider = GetService<IEnumerable<ILyricsProvider>>()
.FirstOrDefault(t => t.DownloaderName == InternalLyricsProviderNames.KuGou);

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
using ZonyLrcTools.Common.Lyrics;
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics;
public class KuWoLyricsProviderTests : TestBase
{
private readonly ILyricsProvider _kuwoLyricsProvider;
public KuWoLyricsProviderTests()
{
_kuwoLyricsProvider = GetService<IEnumerable<ILyricsProvider>>()
.FirstOrDefault(t => t.DownloaderName == InternalLyricsProviderNames.KuWo);
}
[Fact]
public async Task DownloadAsync_Test()
{
var lyric = await _kuwoLyricsProvider.DownloadAsync("告白气球", "周杰伦");
lyric.ShouldNotBeNull();
lyric.IsPruneMusic.ShouldBeFalse();
}
}

View File

@ -3,7 +3,7 @@ using Xunit;
using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Lyrics;
namespace ZonyLrcTools.Tests.Infrastructure.Lyric
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
{
public class LyricCollectionTests : TestBase
{

View File

@ -8,13 +8,13 @@ using Xunit;
using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Lyrics;
namespace ZonyLrcTools.Tests.Infrastructure.Lyric
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
{
public class NetEaseLyricDownloaderTests : TestBase
public class NetEaseLyricsProviderTests : TestBase
{
private readonly ILyricsProvider _lyricsProvider;
public NetEaseLyricDownloaderTests()
public NetEaseLyricsProviderTests()
{
_lyricsProvider = GetService<IEnumerable<ILyricsProvider>>()
.FirstOrDefault(t => t.DownloaderName == InternalLyricsProviderNames.NetEase);

View File

@ -5,13 +5,13 @@ using Shouldly;
using Xunit;
using ZonyLrcTools.Common.Lyrics;
namespace ZonyLrcTools.Tests.Infrastructure.Lyric
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
{
public class QQLyricDownloaderTests : TestBase
public class QQLyricsProviderTests : TestBase
{
private readonly ILyricsProvider _lyricsProvider;
public QQLyricDownloaderTests()
public QQLyricsProviderTests()
{
_lyricsProvider = GetService<IEnumerable<ILyricsProvider>>()
.FirstOrDefault(t => t.DownloaderName == InternalLyricsProviderNames.QQ);

View File

@ -1,6 +1,7 @@
New Features:
- CLI 提供了一个更新检查功能,在程序启动的时候会检测最新的版本。
- 支持从酷我音乐搜索歌词。
Breaking Changes: None
Enhancement: