mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-07-01 20:30:41 +00:00
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:
parent
98c935ed93
commit
41cba02833
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
由于网易云音乐的限制,要想获取完整的歌单信息,必须扫码登录程序,还是以最上面的为例,我需要下载歌单内的歌词数据,就必须扫码之后程序才会执行。
|
由于网易云音乐的限制,要想获取完整的歌单信息,必须扫码登录程序,还是以最上面的为例,我需要下载歌单内的歌词数据,就必须扫码之后程序才会执行。
|
||||||
|
|
||||||

|

|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user