mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-07-01 12:11:13 +00:00
feat: We have improved the search and download logic for NetEase Cloud Music playlists and added support for logging in.
This commit is contained in:
parent
afe1a7013c
commit
b916323986
@ -72,6 +72,11 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
|
||||
protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
|
||||
{
|
||||
if (!DownloadAlbum && !DownloadLyric)
|
||||
{
|
||||
throw new ArgumentException("请至少指定一个下载选项,例如 -l(下载歌词) 或 -a(下载专辑图像)。");
|
||||
}
|
||||
|
||||
if (DownloadLyric)
|
||||
{
|
||||
await _lyricsDownloader.DownloadAsync(await GetMusicInfosAsync(Scanner), ParallelNumber);
|
||||
|
@ -11,7 +11,7 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
{
|
||||
protected virtual Task<int> OnExecuteAsync(CommandLineApplication app)
|
||||
{
|
||||
if (Environment.UserInteractive)
|
||||
if (!Environment.UserInteractive)
|
||||
{
|
||||
Console.WriteLine("请使用终端运行此程序,如果你不知道如何操作,请访问 https://soft.myzony.com 或添加 QQ 群 337656932 寻求帮助。");
|
||||
Console.ReadKey();
|
||||
|
@ -16,6 +16,8 @@ using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Common.Infrastructure.Exceptions;
|
||||
using ZonyLrcTools.Common.Infrastructure.Network;
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
|
||||
namespace ZonyLrcTools.Cli
|
||||
{
|
||||
[Command("lyric-tool")]
|
||||
@ -63,12 +65,12 @@ namespace ZonyLrcTools.Cli
|
||||
.WriteTo.Async(c => c.Console(theme: CustomConsoleTheme.Code))
|
||||
.WriteTo.Logger(lc =>
|
||||
{
|
||||
lc.Filter.ByIncludingOnly(lc => lc.Level == LogEventLevel.Warning)
|
||||
lc.Filter.ByIncludingOnly(warningLog => warningLog.Level == LogEventLevel.Warning)
|
||||
.WriteTo.Async(c => c.File("Logs/warnings.txt"));
|
||||
})
|
||||
.WriteTo.Logger(lc =>
|
||||
{
|
||||
lc.Filter.ByIncludingOnly(lc => lc.Level == LogEventLevel.Error)
|
||||
lc.Filter.ByIncludingOnly(errLog => errLog.Level == LogEventLevel.Error)
|
||||
.WriteTo.Async(c => c.File("Logs/errors.txt"));
|
||||
})
|
||||
.CreateLogger();
|
||||
|
@ -15,6 +15,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
|
@ -20,6 +20,7 @@ public static class NetEaseMusicEncryptionHelper
|
||||
public const string Nonce = "0CoJUm6Qyw8W8jud";
|
||||
public const string PubKey = "010001";
|
||||
public const string Vi = "0102030405060708";
|
||||
public static readonly byte[] ID_XOR_KEY_1 = "3go8&$8*3*3h0k(2)2"u8.ToArray();
|
||||
|
||||
public static string RsaEncode(string text)
|
||||
{
|
||||
@ -90,4 +91,19 @@ public static class NetEaseMusicEncryptionHelper
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string CloudMusicDllEncode(string deviceId)
|
||||
{
|
||||
var xored = new byte[deviceId.Length];
|
||||
for (var i = 0; i < deviceId.Length; i++)
|
||||
{
|
||||
xored[i] = (byte)(deviceId[i] ^ ID_XOR_KEY_1[i % ID_XOR_KEY_1.Length]);
|
||||
}
|
||||
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
var digest = md5.ComputeHash(xored);
|
||||
return Convert.ToBase64String(digest);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using QRCoder;
|
||||
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Common.Infrastructure.Encryption;
|
||||
using ZonyLrcTools.Common.Infrastructure.Exceptions;
|
||||
@ -8,28 +12,43 @@ using ZonyLrcTools.Common.MusicScanner.JsonModel;
|
||||
|
||||
namespace ZonyLrcTools.Common.MusicScanner;
|
||||
|
||||
public class NetEaseMusicSongListMusicScanner : ITransientDependency
|
||||
public class NetEaseMusicSongListMusicScanner : ISingletonDependency
|
||||
{
|
||||
private readonly IWarpHttpClient _warpHttpClient;
|
||||
private readonly ILogger<NetEaseMusicSongListMusicScanner> _logger;
|
||||
private const string Host = "https://music.163.com";
|
||||
|
||||
public NetEaseMusicSongListMusicScanner(IWarpHttpClient warpHttpClient)
|
||||
private string Cookie { get; set; } = string.Empty;
|
||||
private string CsrfToken { get; set; } = string.Empty;
|
||||
|
||||
public NetEaseMusicSongListMusicScanner(IWarpHttpClient warpHttpClient,
|
||||
ILogger<NetEaseMusicSongListMusicScanner> logger)
|
||||
{
|
||||
_warpHttpClient = warpHttpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<List<MusicInfo>> GetMusicInfoFromNetEaseMusicSongListAsync(string songListId, string outputDirectory, string pattern)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Cookie))
|
||||
{
|
||||
var loginResponse = await LoginViqQrCodeAsync();
|
||||
Cookie = loginResponse.cookieContainer?.GetCookieHeader(new Uri(Host)) ?? string.Empty;
|
||||
CsrfToken = loginResponse.csrfToken ?? string.Empty;
|
||||
}
|
||||
|
||||
var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16);
|
||||
var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
|
||||
|
||||
var response = await _warpHttpClient.PostAsync<GetMusicInfoFromNetEaseMusicSongListResponse>("https://music.163.com/weapi/v6/playlist/detail?csrf_token=", requestOption:
|
||||
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 = "",
|
||||
csrf_token = CsrfToken,
|
||||
id = songListId,
|
||||
n = 100000,
|
||||
n = 1000,
|
||||
offset = 0,
|
||||
total = true,
|
||||
limit = 1000,
|
||||
@ -66,4 +85,87 @@ public class NetEaseMusicSongListMusicScanner : ITransientDependency
|
||||
{ "encSecKey", encSecKey }
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(string? csrfToken, CookieContainer? cookieContainer)> LoginViqQrCodeAsync()
|
||||
{
|
||||
// Get unikey.
|
||||
var qrCodeKeyJson = await (await PostAsync($"{Host}/weapi/login/qrcode/unikey", new
|
||||
{
|
||||
type = 1
|
||||
})).Content.ReadAsStringAsync();
|
||||
var uniKey = JObject.Parse(qrCodeKeyJson).SelectToken("$.unikey")!.Value<string>();
|
||||
if (string.IsNullOrEmpty(uniKey)) return (null, null);
|
||||
|
||||
// Generate QR code.
|
||||
var qrGenerator = new QRCodeGenerator();
|
||||
var qrCodeData = qrGenerator.CreateQrCode($"{Host}/login?codekey={uniKey}",
|
||||
QRCodeGenerator.ECCLevel.L);
|
||||
var qrCode = new AsciiQRCode(qrCodeData);
|
||||
var asciiQrCodeString = qrCode.GetGraphic(1, drawQuietZones: false);
|
||||
|
||||
_logger.LogInformation("请使用网易云 APP 扫码登录:");
|
||||
_logger.LogInformation(asciiQrCodeString);
|
||||
|
||||
// Wait for login success.
|
||||
var isLogin = false;
|
||||
while (!isLogin)
|
||||
{
|
||||
var (isSuccess, cookieContainer) = await CheckIsLoginAsync(uniKey);
|
||||
isLogin = isSuccess;
|
||||
|
||||
if (!isLogin)
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (cookieContainer?.GetCookies(new Uri(Host))["__csrf"]?.Value, cookieContainer);
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private async Task<(bool isSuccess, CookieContainer? cookieContainer)> CheckIsLoginAsync(string uniKey)
|
||||
{
|
||||
var responseMessage = await PostAsync($"{Host}/weapi/login/qrcode/client/login", new
|
||||
{
|
||||
key = uniKey,
|
||||
type = 1
|
||||
});
|
||||
|
||||
var responseString = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseCode = JObject.Parse(responseString)["code"]?.Value<int>();
|
||||
|
||||
if (responseCode != 803)
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
if (!responseMessage.Headers.TryGetValues("Set-Cookie", out var cookies))
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var cookieContainer = new CookieContainer();
|
||||
foreach (var cookie in cookies)
|
||||
{
|
||||
cookieContainer.SetCookies(new Uri(Host), cookie);
|
||||
}
|
||||
|
||||
return (true, cookieContainer);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> PostAsync(string url, object @params)
|
||||
{
|
||||
var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16);
|
||||
var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
|
||||
|
||||
return await _warpHttpClient.PostReturnHttpResponseAsync(url, requestOption:
|
||||
request =>
|
||||
{
|
||||
request.Content = new FormUrlEncodedContent(HandleRequest(@params, secretKey, encSecKey));
|
||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||
});
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
<PackageReference Include="MusicDecrypto.Library" Version="2.2.0" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -2,6 +2,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using QRCoder;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZonyLrcTools.Common.Infrastructure.IO;
|
||||
@ -27,5 +28,14 @@ namespace ZonyLrcTools.Tests
|
||||
|
||||
File.Delete(tempMusicFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestConsole()
|
||||
{
|
||||
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
||||
QRCodeData qrCodeData = qrGenerator.CreateQrCode("https://y.music.163.com/m/login?codekey=2f0da1d0-759e-478b-9153-35058b3", QRCodeGenerator.ECCLevel.L);
|
||||
AsciiQRCode qrCode = new AsciiQRCode(qrCodeData);
|
||||
string qrCodeAsAsciiArt = qrCode.GetGraphic(1, drawQuietZones: false);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user