feat: NetEase Cloud Music playlist download supports passing in multiple IDs.

Issue: https://github.com/real-zony/ZonyLrcToolsX/issues/139
This commit is contained in:
real-zony 2023-05-24 21:54:13 +08:00
parent 98c935ed93
commit 41cba02833
2 changed files with 50 additions and 32 deletions

View File

@ -69,6 +69,12 @@ macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命
例如获取地址 [https://music.163.com/#/playlist?id=158010361](https://music.163.com/#/playlist?id=158010361) 的歌单信息,那么歌单 ID 就应该传递 158010361。 例如获取地址 [https://music.163.com/#/playlist?id=158010361](https://music.163.com/#/playlist?id=158010361) 的歌单信息,那么歌单 ID 就应该传递 158010361。
如果你需要获取多个歌单的歌曲数据,请在传递歌单 ID 时使用分号 `;` 指定多个 ID例如下面的命令就是下载两个歌单的歌曲数据。
```shell
.\ZonyLrcTools.Cli.exe download -sc netease -o "D:\TempFiles" -s "7224428149;158010361" -l
```
由于网易云音乐的限制,要想获取完整的歌单信息,必须扫码登录程序,还是以最上面的为例,我需要下载歌单内的歌词数据,就必须扫码之后程序才会执行。 由于网易云音乐的限制,要想获取完整的歌单信息,必须扫码登录程序,还是以最上面的为例,我需要下载歌单内的歌词数据,就必须扫码之后程序才会执行。
![image-20230328223155280](./README.assets/image-20230328223155280.png) ![image-20230328223155280](./README.assets/image-20230328223155280.png)

View File

@ -37,11 +37,11 @@ public class NetEaseMusicSongListMusicScanner : ISingletonDependency
/// <summary> /// <summary>
/// 从网易云歌单获取需要下载的歌曲列表,调用这个 API 需要用户登录,否则获取的歌单数据不全。 /// 从网易云歌单获取需要下载的歌曲列表,调用这个 API 需要用户登录,否则获取的歌单数据不全。
/// </summary> /// </summary>
/// <param name="songListId">网易云音乐歌单的 ID。</param> /// <param name="songListIds">网易云音乐歌单的 ID。</param>
/// <param name="outputDirectory">歌词文件的输出路径。</param> /// <param name="outputDirectory">歌词文件的输出路径。</param>
/// <param name="pattern">输出的歌词文件格式,默认是 "{Artist} - {Title}.lrc" 的形式。</param> /// <param name="pattern">输出的歌词文件格式,默认是 "{Artist} - {Title}.lrc" 的形式。</param>
/// <returns>返回获取到的歌曲列表。</returns> /// <returns>返回获取到的歌曲列表。</returns>
public async Task<List<MusicInfo>> GetMusicInfoFromNetEaseMusicSongListAsync(string songListId, string outputDirectory, string pattern) public async Task<List<MusicInfo>> GetMusicInfoFromNetEaseMusicSongListAsync(string songListIds, string outputDirectory, string pattern)
{ {
if (string.IsNullOrEmpty(Cookie)) if (string.IsNullOrEmpty(Cookie))
{ {
@ -50,40 +50,52 @@ public class NetEaseMusicSongListMusicScanner : ISingletonDependency
CsrfToken = loginResponse.csrfToken ?? string.Empty; CsrfToken = loginResponse.csrfToken ?? string.Empty;
} }
var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16); async Task<List<MusicInfo>> GetMusicInfoBySongIdAsync(string songId)
var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
var response = await _warpHttpClient.PostAsync<GetMusicInfoFromNetEaseMusicSongListResponse>(
$"{Host}/weapi/v6/playlist/detail?csrf_token=e5044820d8b66e14b8c31d39f9651a98", requestOption:
request =>
{
request.Headers.Add("Cookie", Cookie);
request.Content = new FormUrlEncodedContent(HandleRequest(new
{
csrf_token = CsrfToken,
id = songListId,
n = 1000,
offset = 0,
total = true,
limit = 1000,
}, secretKey, encSecKey));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
});
if (response.Code != 200 || response.PlayList?.SongList == null)
{ {
throw new ErrorCodeException(ErrorCodes.NotSupportedFileEncoding); var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16);
var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
var response = await _warpHttpClient.PostAsync<GetMusicInfoFromNetEaseMusicSongListResponse>(
$"{Host}/weapi/v6/playlist/detail?csrf_token={CsrfToken}", requestOption:
request =>
{
request.Headers.Add("Cookie", Cookie);
request.Content = new FormUrlEncodedContent(HandleRequest(new
{
csrf_token = CsrfToken,
id = songId,
n = 1000,
offset = 0,
total = true,
limit = 1000,
}, secretKey, encSecKey));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
});
if (response.Code != 200 || response.PlayList?.SongList == null)
{
throw new ErrorCodeException(ErrorCodes.NotSupportedFileEncoding);
}
return response.PlayList.SongList
.Where(song => !string.IsNullOrEmpty(song.Name))
.Select(song =>
{
var artistName = song.Artists?.FirstOrDefault()?.Name ?? string.Empty;
var fakeFilePath = Path.Combine(outputDirectory, pattern.Replace("{Name}", song.Name).Replace("{Artist}", artistName));
return new MusicInfo(fakeFilePath, song.Name!, artistName);
}).ToList();
} }
return response.PlayList.SongList var musicInfoList = new List<MusicInfo>();
.Where(song => !string.IsNullOrEmpty(song.Name)) foreach (var songListId in songListIds.Split(';'))
.Select(song => {
{ _logger.LogInformation("正在获取歌单 {SongListId} 的歌曲列表。", songListId);
var artistName = song.Artists?.FirstOrDefault()?.Name ?? string.Empty; var musicInfos = await GetMusicInfoBySongIdAsync(songListId);
var fakeFilePath = Path.Combine(outputDirectory, pattern.Replace("{Name}", song.Name).Replace("{Artist}", artistName)); musicInfoList.AddRange(musicInfos);
}
var info = new MusicInfo(fakeFilePath, song.Name!, artistName); return musicInfoList;
return info;
}).ToList();
} }
/// <summary> /// <summary>