Skip to content

Commit

Permalink
feat: NetEase Cloud Music playlist download supports passing in multi…
Browse files Browse the repository at this point in the history
…ple IDs.

Issue: #139
  • Loading branch information
real-zony committed May 24, 2023
1 parent 98c935e commit 41cba02
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 30 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命

例如获取地址 [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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ public class NetEaseMusicSongListMusicScanner : ISingletonDependency
/// <summary>
/// 从网易云歌单获取需要下载的歌曲列表,调用这个 API 需要用户登录,否则获取的歌单数据不全。
/// </summary>
/// <param name="songListId">网易云音乐歌单的 ID。</param>
/// <param name="songListIds">网易云音乐歌单的 ID。</param>
/// <param name="outputDirectory">歌词文件的输出路径。</param>
/// <param name="pattern">输出的歌词文件格式,默认是 "{Artist} - {Title}.lrc" 的形式。</param>
/// <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))
{
Expand All @@ -50,40 +50,52 @@ public async Task<List<MusicInfo>> GetMusicInfoFromNetEaseMusicSongListAsync(str
CsrfToken = loginResponse.csrfToken ?? string.Empty;
}

var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16);
var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
var response = await _warpHttpClient.PostAsync<GetMusicInfoFromNetEaseMusicSongListResponse>(
$"{Host}/weapi/v6/playlist/detail?csrf_token=e5044820d8b66e14b8c31d39f9651a98", requestOption:
request =>
async Task<List<MusicInfo>> GetMusicInfoBySongIdAsync(string songId)
{
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)
{
request.Headers.Add("Cookie", Cookie);
request.Content = new FormUrlEncodedContent(HandleRequest(new
throw new ErrorCodeException(ErrorCodes.NotSupportedFileEncoding);
}

return response.PlayList.SongList
.Where(song => !string.IsNullOrEmpty(song.Name))
.Select(song =>
{
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");
});
var artistName = song.Artists?.FirstOrDefault()?.Name ?? string.Empty;
var fakeFilePath = Path.Combine(outputDirectory, pattern.Replace("{Name}", song.Name).Replace("{Artist}", artistName));
if (response.Code != 200 || response.PlayList?.SongList == null)
{
throw new ErrorCodeException(ErrorCodes.NotSupportedFileEncoding);
return new MusicInfo(fakeFilePath, song.Name!, artistName);
}).ToList();
}

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));
var musicInfoList = new List<MusicInfo>();
foreach (var songListId in songListIds.Split(';'))
{
_logger.LogInformation("正在获取歌单 {SongListId} 的歌曲列表。", songListId);
var musicInfos = await GetMusicInfoBySongIdAsync(songListId);
musicInfoList.AddRange(musicInfos);
}

var info = new MusicInfo(fakeFilePath, song.Name!, artistName);
return info;
}).ToList();
return musicInfoList;
}

/// <summary>
Expand Down

0 comments on commit 41cba02

Please sign in to comment.