diff --git a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricAccessKeyRequest.cs b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricAccessKeyRequest.cs new file mode 100644 index 0000000..c325ca4 --- /dev/null +++ b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricAccessKeyRequest.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel +{ + public class GetLyricAccessKeyRequest + { + [JsonProperty("ver")] + public int UnknownParameters1 { get; } + + [JsonProperty("man")] + public string UnknownParameters2 { get; } + + [JsonProperty("client")] + public string UnknownParameters3 { get; } + + [JsonProperty("hash")] + public string FileHash { get; } + + public GetLyricAccessKeyRequest(string fileHash) + { + UnknownParameters1 = 1; + UnknownParameters2 = "yes"; + UnknownParameters3 = "mobi"; + FileHash = fileHash; + } + } +} \ No newline at end of file diff --git a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricAccessKeyResponse.cs b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricAccessKeyResponse.cs new file mode 100644 index 0000000..92dc775 --- /dev/null +++ b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricAccessKeyResponse.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel +{ + public class GetLyricAccessKeyResponse + { + [JsonProperty("status")] + public int Status { get; set; } + + [JsonProperty("errcode")] + public int ErrorCode { get; set; } + + [JsonProperty("candidates")] + public List AccessKeyDataObjects { get; set; } + } + + public class GetLyricAccessKeyDataObject + { + [JsonProperty("accesskey")] + public string AccessKey { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricRequest.cs b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricRequest.cs new file mode 100644 index 0000000..8cfe042 --- /dev/null +++ b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/GetLyricRequest.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; + +namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel +{ + public class GetLyricRequest + { + [JsonProperty("ver")] public int UnknownParameters1 { get; } + + [JsonProperty("client")] public string UnknownParameters2 { get; } + + [JsonProperty("fmt")] public string UnknownParameters3 { get; } + + [JsonProperty("charset")] public string UnknownParameters4 { get; } + + [JsonProperty("id")] public string Id { get; } + + [JsonProperty("accesskey")] public string AccessKey { get; } + + public GetLyricRequest(string id, string accessKey) + { + UnknownParameters1 = 1; + UnknownParameters2 = "iphone"; + UnknownParameters3 = "lrc"; + UnknownParameters4 = "utf8"; + Id = id; + AccessKey = accessKey; + } + } + + public class GetLyricResponse + { + } +} \ No newline at end of file diff --git a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/SongSearchRequest.cs b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/SongSearchRequest.cs new file mode 100644 index 0000000..a5e629e --- /dev/null +++ b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/SongSearchRequest.cs @@ -0,0 +1,25 @@ +using System.Text; +using System.Web; +using Newtonsoft.Json; + +namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel +{ + public class SongSearchRequest + { + [JsonProperty("filter")] + public int Filter { get; } + + [JsonProperty("platform")] + public string Platform { get; } + + [JsonProperty("keyword")] + public string Keyword { get; } + + public SongSearchRequest(string musicName, string artistName) + { + Filter = 2; + Platform = "WebFilter"; + Keyword = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8); + } + } +} \ No newline at end of file diff --git a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/SongSearchResponse.cs b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/SongSearchResponse.cs new file mode 100644 index 0000000..661d3b8 --- /dev/null +++ b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/JsonModel/SongSearchResponse.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel +{ + public class SongSearchResponse + { + [JsonProperty("status")] + public int Status { get; set; } + + [JsonProperty("data")] + public SongSearchResponseInnerData Data { get; set; } + + [JsonProperty("error_code")] + public int ErrorCode { get; set; } + + [JsonProperty("error_msg")] + public string ErrorMessage { get; set; } + } + + public class SongSearchResponseInnerData + { + [JsonProperty("lists")] + public List List { get; set; } + } + + public class SongSearchResponseSongDetail + { + public string FileHash { get; set; } + } +} \ No newline at end of file diff --git a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/KuGourLyricDownloader.cs b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/KuGourLyricDownloader.cs index 71fe40c..44bfdb2 100644 --- a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/KuGourLyricDownloader.cs +++ b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/KuGou/KuGourLyricDownloader.cs @@ -1,4 +1,9 @@ +using System; +using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using ZonyLrcTools.Cli.Infrastructure.Exceptions; +using ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel; using ZonyLrcTools.Cli.Infrastructure.Network; namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou @@ -10,6 +15,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou private readonly IWarpHttpClient _warpHttpClient; private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory; + private const string KuGouSearchMusicUrl = @"https://songsearch.kugou.com/song_search_v2"; + private const string KuGouGetLyricAccessKeyUrl = @"http://lyrics.kugou.com/search"; + private const string KuGouGetLyricUrl = @"http://lyrics.kugou.com/download"; + public KuGourLyricDownloader(IWarpHttpClient warpHttpClient, ILyricItemCollectionFactory lyricItemCollectionFactory) { @@ -17,14 +26,43 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou _lyricItemCollectionFactory = lyricItemCollectionFactory; } - protected override ValueTask DownloadDataAsync(LyricDownloaderArgs args) + protected override async ValueTask DownloadDataAsync(LyricDownloaderArgs args) { - throw new System.NotImplementedException(); + var searchResult = await _warpHttpClient.GetAsync(KuGouSearchMusicUrl, + new SongSearchRequest(args.SongName, args.Artist)); + + ValidateSongSearchResponse(searchResult, args); + + // 获得特殊的 AccessToken 与 Id,真正请求歌词数据。 + var accessKeyResponse = await _warpHttpClient.GetAsync(KuGouGetLyricAccessKeyUrl, + new GetLyricAccessKeyRequest(searchResult.Data.List[0].FileHash)); + + var accessKeyObject = accessKeyResponse.AccessKeyDataObjects[0]; + var lyricResponse = await _warpHttpClient.GetAsync(KuGouGetLyricUrl, + new GetLyricRequest(accessKeyObject.Id, accessKeyObject.AccessKey)); + + return Encoding.UTF8.GetBytes(lyricResponse); } - protected override ValueTask GenerateLyricAsync(byte[] data, LyricDownloaderArgs args) + protected override async ValueTask GenerateLyricAsync(byte[] data, LyricDownloaderArgs args) { - throw new System.NotImplementedException(); + await ValueTask.CompletedTask; + var lyricJsonObj = JObject.Parse(Encoding.UTF8.GetString(data)); + if (lyricJsonObj.SelectToken("$.status").Value() != 200) + { + throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args); + } + + var lyricText = Encoding.UTF8.GetString(Convert.FromBase64String(lyricJsonObj.SelectToken("$.content").Value())); + return _lyricItemCollectionFactory.Build(lyricText); + } + + protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricDownloaderArgs args) + { + if (response.ErrorCode != 0 && response.Status != 1 || response.Data.List == null) + { + throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args); + } } } } \ No newline at end of file diff --git a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/NetEase/NetEaseLyricDownloader.cs b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/NetEase/NetEaseLyricDownloader.cs index ee82f63..44ba48d 100644 --- a/src/ZonyLrcTools.Cli/Infrastructure/Lyric/NetEase/NetEaseLyricDownloader.cs +++ b/src/ZonyLrcTools.Cli/Infrastructure/Lyric/NetEase/NetEaseLyricDownloader.cs @@ -3,7 +3,6 @@ using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; -using ZonyLrcTools.Cli.Infrastructure.DependencyInject; using ZonyLrcTools.Cli.Infrastructure.Exceptions; using ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel; using ZonyLrcTools.Cli.Infrastructure.Network; diff --git a/tests/ZonyLrcTools.Tests/Infrastructure/Lyric/KuGouLyricDownloaderTests.cs b/tests/ZonyLrcTools.Tests/Infrastructure/Lyric/KuGouLyricDownloaderTests.cs new file mode 100644 index 0000000..ad6b338 --- /dev/null +++ b/tests/ZonyLrcTools.Tests/Infrastructure/Lyric/KuGouLyricDownloaderTests.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; +using ZonyLrcTools.Cli.Infrastructure.Lyric; + +namespace ZonyLrcTools.Tests.Infrastructure.Lyric +{ + public class KuGouLyricDownloaderTests : TestBase + { + [Fact] + public async Task DownloadAsync_Test() + { + var downloaderList = ServiceProvider.GetRequiredService>(); + var kuGouDownloader = downloaderList.FirstOrDefault(t => t.DownloaderName == InternalLyricDownloaderNames.KuGou); + + kuGouDownloader.ShouldNotBeNull(); + var lyric = await kuGouDownloader.DownloadAsync("东方红", null); + lyric.ShouldNotBeNull(); + lyric.IsPruneMusic.ShouldBe(false); + } + } +} \ No newline at end of file