mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-09-05 21:16:52 +00:00
Compare commits
12 Commits
ZonyLrcToo
...
ZonyLrcToo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1186369d16 | ||
![]() |
b240564cf7 | ||
![]() |
6e2848a9de | ||
![]() |
6361bf9265 | ||
![]() |
383e2c5939 | ||
![]() |
1e5c41852f | ||
![]() |
41cba02833 | ||
![]() |
98c935ed93 | ||
![]() |
62d08df735 | ||
![]() |
1f312c749d | ||
![]() |
30e10f4c01 | ||
![]() |
35468cfb71 |
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
# 简介
|
# 简介
|
||||||
|
|
||||||
ZonyLrcToolX 4 是一个基于 CEF 的跨平台歌词下载工具。
|
ZonyLrcToolX 4 是一个基于 CEF 的跨平台歌词下载工具,**QQ 群:337656932**,群文件里面有详细的视频教程。
|
||||||
|
|
||||||
🚧 当前版本正在开发当中。
|
🚧 当前版本正在开发当中。
|
||||||
🚧 如果你想查看可以工作的代码,请切换到 dev 分支。
|
🚧 如果你想查看可以工作的代码,请切换到 dev 分支。
|
||||||
@@ -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
|
||||||
|
```
|
||||||
|
|
||||||
由于网易云音乐的限制,要想获取完整的歌单信息,必须扫码登录程序,还是以最上面的为例,我需要下载歌单内的歌词数据,就必须扫码之后程序才会执行。
|
由于网易云音乐的限制,要想获取完整的歌单信息,必须扫码登录程序,还是以最上面的为例,我需要下载歌单内的歌词数据,就必须扫码之后程序才会执行。
|
||||||
|
|
||||||

|

|
||||||
|
@@ -65,7 +65,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
|||||||
[Option("-f|--file", Description = "指定 CSV 文件的路径。")]
|
[Option("-f|--file", Description = "指定 CSV 文件的路径。")]
|
||||||
public string CsvFilePath { get; set; }
|
public string CsvFilePath { get; set; }
|
||||||
|
|
||||||
[Option("-s|--song-list-id", Description = "指定网易云音乐歌单的 ID。")]
|
[Option("-s|--song-list-id", Description = "指定网易云音乐歌单的 ID,如果有多个歌单,请使用 ';' 分割 ID。")]
|
||||||
public string SongListId { get; set; }
|
public string SongListId { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
|
@@ -13,6 +13,6 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 屏蔽词字典文件,用于替换歌曲名或者歌手名称。
|
/// 屏蔽词字典文件,用于替换歌曲名或者歌手名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FilePath { get; set; }
|
public string FilePath { get; set; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -5,16 +5,16 @@ namespace ZonyLrcTools.Common.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 支持的音乐文件后缀集合。
|
/// 支持的音乐文件后缀集合。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> SupportFileExtensions { get; set; }
|
public List<string> SupportFileExtensions { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 网络代理相关的配置信息。
|
/// 网络代理相关的配置信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public NetworkOptions NetworkOptions { get; set; }
|
public NetworkOptions NetworkOptions { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 定义下载器的相关配置信息。
|
/// 定义下载器的相关配置信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ProviderOptions Provider { get; set; }
|
public ProviderOptions Provider { get; set; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -2,12 +2,12 @@ namespace ZonyLrcTools.Common.Configuration;
|
|||||||
|
|
||||||
public class LyricsOptions
|
public class LyricsOptions
|
||||||
{
|
{
|
||||||
public IEnumerable<LyricsProviderOptions> Plugin { get; set; }
|
public IEnumerable<LyricsProviderOptions> Plugin { get; set; } = null!;
|
||||||
|
|
||||||
public GlobalLyricsConfigOptions Config { get; set; }
|
public GlobalLyricsConfigOptions Config { get; set; } = null!;
|
||||||
|
|
||||||
public LyricsProviderOptions GetLyricProviderOption(string name)
|
public LyricsProviderOptions GetLyricProviderOption(string name)
|
||||||
{
|
{
|
||||||
return Plugin.FirstOrDefault(x => x.Name == name);
|
return Plugin.FirstOrDefault(x => x.Name == name)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -5,7 +5,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词下载器的唯一标识。
|
/// 歌词下载器的唯一标识。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词下载时的优先级,当值为 -1 时是禁用。
|
/// 歌词下载时的优先级,当值为 -1 时是禁用。
|
||||||
|
@@ -13,7 +13,7 @@ namespace ZonyLrcTools.Common.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 代理服务器的 Ip。
|
/// 代理服务器的 Ip。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Ip { get; set; }
|
public string Ip { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 代理服务器的 端口。
|
/// 代理服务器的 端口。
|
||||||
|
@@ -5,10 +5,10 @@ public class ProviderOptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 标签加载器相关的配置属性。
|
/// 标签加载器相关的配置属性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TagInfoOptions Tag { get; set; }
|
public TagInfoOptions Tag { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词下载相关的配置信息。
|
/// 歌词下载相关的配置信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LyricsOptions Lyric { get; set; }
|
public LyricsOptions Lyric { get; set; } = null!;
|
||||||
}
|
}
|
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
public class TagInfoOptions
|
public class TagInfoOptions
|
||||||
{
|
{
|
||||||
public IEnumerable<TagInfoProviderOptions> Plugin { get; set; }
|
public IEnumerable<TagInfoProviderOptions> Plugin { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 屏蔽词功能相关配置。
|
/// 屏蔽词功能相关配置。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BlockWordOptions BlockWord { get; set; }
|
public BlockWordOptions BlockWord { get; set; } = null!;
|
||||||
}
|
}
|
@@ -2,10 +2,10 @@ namespace ZonyLrcTools.Common.Configuration
|
|||||||
{
|
{
|
||||||
public class TagInfoProviderOptions
|
public class TagInfoProviderOptions
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, string> Extensions { get; set; }
|
public Dictionary<string, string> Extensions { get; set; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -15,7 +15,7 @@ namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配置工具会用到的服务。
|
/// 配置工具会用到的服务。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection ConfigureToolService(this IServiceCollection services)
|
public static IServiceCollection? ConfigureToolService(this IServiceCollection? services)
|
||||||
{
|
{
|
||||||
if (services == null)
|
if (services == null)
|
||||||
{
|
{
|
||||||
|
@@ -7,7 +7,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
|||||||
{
|
{
|
||||||
public int ErrorCode { get; }
|
public int ErrorCode { get; }
|
||||||
|
|
||||||
public object AttachObject { get; }
|
public object? AttachObject { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建一个新的 <see cref="ErrorCodeException"/> 对象。
|
/// 构建一个新的 <see cref="ErrorCodeException"/> 对象。
|
||||||
@@ -15,7 +15,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
|||||||
/// <param name="errorCode">错误码,参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
/// <param name="errorCode">错误码,参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
||||||
/// <param name="message">错误信息。</param>
|
/// <param name="message">错误信息。</param>
|
||||||
/// <param name="attachObj">附加的对象数据。</param>
|
/// <param name="attachObj">附加的对象数据。</param>
|
||||||
public ErrorCodeException(int errorCode, string? message = null, object attachObj = null) : base(message)
|
public ErrorCodeException(int errorCode, string? message = null, object? attachObj = null) : base(message)
|
||||||
{
|
{
|
||||||
ErrorCode = errorCode;
|
ErrorCode = errorCode;
|
||||||
AttachObject = attachObj;
|
AttachObject = attachObj;
|
||||||
|
@@ -33,7 +33,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
|||||||
var errors = jsonObj.SelectTokens("$.Error.*");
|
var errors = jsonObj.SelectTokens("$.Error.*");
|
||||||
var warnings = jsonObj.SelectTokens("$.Warning.*");
|
var warnings = jsonObj.SelectTokens("$.Warning.*");
|
||||||
errors.Union(warnings).Select(m => m.Parent).OfType<JProperty>().ToList()
|
errors.Union(warnings).Select(m => m.Parent).OfType<JProperty>().ToList()
|
||||||
.ForEach(m => ErrorMessages.Add(int.Parse(m.Name), m.Value.Value<string>()));
|
.ForEach(m => ErrorMessages.Add(int.Parse(m.Name), m.Value.Value<string>() ?? string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetMessage(int errorCode) => ErrorMessages[errorCode];
|
public static string GetMessage(int errorCode) => ErrorMessages[errorCode];
|
||||||
|
@@ -17,7 +17,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Extensions
|
|||||||
/// <param name="logger">日志记录器实例。</param>
|
/// <param name="logger">日志记录器实例。</param>
|
||||||
/// <param name="errorCode">错误码,具体请参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
/// <param name="errorCode">错误码,具体请参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
||||||
/// <param name="e">异常实例,可为空。</param>
|
/// <param name="e">异常实例,可为空。</param>
|
||||||
public static void LogWarningWithErrorCode(this IWarpLogger logger, int errorCode, Exception e = null)
|
public static void LogWarningWithErrorCode(this IWarpLogger logger, int errorCode, Exception? e = null)
|
||||||
{
|
{
|
||||||
logger.WarnAsync($"错误代码: {errorCode}\n堆栈异常: {e?.StackTrace}").GetAwaiter().GetResult();
|
logger.WarnAsync($"错误代码: {errorCode}\n堆栈异常: {e?.StackTrace}").GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
@@ -19,9 +19,9 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<string> PostAsync(string url,
|
public async ValueTask<string> PostAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
using var responseMessage = await PostReturnHttpResponseAsync(url, parameters, isQueryStringParam, requestOption);
|
using var responseMessage = await PostReturnHttpResponseAsync(url, parameters, isQueryStringParam, requestOption);
|
||||||
var responseContentString = await responseMessage.Content.ReadAsStringAsync();
|
var responseContentString = await responseMessage.Content.ReadAsStringAsync();
|
||||||
@@ -30,18 +30,18 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<TResponse> PostAsync<TResponse>(string url,
|
public async ValueTask<TResponse> PostAsync<TResponse>(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
var responseString = await PostAsync(url, parameters, isQueryStringParam, requestOption);
|
var responseString = await PostAsync(url, parameters, isQueryStringParam, requestOption);
|
||||||
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<HttpResponseMessage> PostReturnHttpResponseAsync(string url,
|
public async ValueTask<HttpResponseMessage> PostReturnHttpResponseAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
var parametersStr = isQueryStringParam ? BuildQueryString(parameters) : BuildJsonBodyString(parameters);
|
var parametersStr = isQueryStringParam ? BuildQueryString(parameters) : BuildJsonBodyString(parameters);
|
||||||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
|
||||||
@@ -53,8 +53,8 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<string> GetAsync(string url,
|
public async ValueTask<string> GetAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
var requestParamsStr = BuildQueryString(parameters);
|
var requestParamsStr = BuildQueryString(parameters);
|
||||||
var requestMsg = new HttpRequestMessage(HttpMethod.Get, new Uri($"{url}?{requestParamsStr}"));
|
var requestMsg = new HttpRequestMessage(HttpMethod.Get, new Uri($"{url}?{requestParamsStr}"));
|
||||||
@@ -67,8 +67,8 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<TResponse> GetAsync<TResponse>(string url,
|
public async ValueTask<TResponse> GetAsync<TResponse>(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
var responseString = await GetAsync(url, parameters, requestOption);
|
var responseString = await GetAsync(url, parameters, requestOption);
|
||||||
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
||||||
@@ -79,7 +79,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
return _httpClientFactory.CreateClient(HttpClientNameConstant);
|
return _httpClientFactory.CreateClient(HttpClientNameConstant);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildQueryString(object parameters)
|
private string BuildQueryString(object? parameters)
|
||||||
{
|
{
|
||||||
if (parameters == null)
|
if (parameters == null)
|
||||||
{
|
{
|
||||||
@@ -89,7 +89,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
var type = parameters.GetType();
|
var type = parameters.GetType();
|
||||||
if (type == typeof(string))
|
if (type == typeof(string))
|
||||||
{
|
{
|
||||||
return parameters as string;
|
return parameters as string ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var properties = type.GetProperties();
|
var properties = type.GetProperties();
|
||||||
@@ -106,7 +106,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
return paramBuilder.ToString().TrimEnd('&');
|
return paramBuilder.ToString().TrimEnd('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildJsonBodyString(object parameters)
|
private string BuildJsonBodyString(object? parameters)
|
||||||
{
|
{
|
||||||
if (parameters == null) return string.Empty;
|
if (parameters == null) return string.Empty;
|
||||||
if (parameters is string result) return result;
|
if (parameters is string result) return result;
|
||||||
@@ -122,7 +122,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
||||||
/// <returns>如果响应正常,则返回具体的响应内容。</returns>
|
/// <returns>如果响应正常,则返回具体的响应内容。</returns>
|
||||||
/// <exception cref="ErrorCodeException">如果 Http 响应不正常,则可能抛出本异常。</exception>
|
/// <exception cref="ErrorCodeException">如果 Http 响应不正常,则可能抛出本异常。</exception>
|
||||||
private string ValidateHttpResponse(HttpResponseMessage responseMessage, object requestParameters, string responseString)
|
private string ValidateHttpResponse(HttpResponseMessage responseMessage, object? requestParameters, string responseString)
|
||||||
{
|
{
|
||||||
return responseMessage.StatusCode switch
|
return responseMessage.StatusCode switch
|
||||||
{
|
{
|
||||||
@@ -139,7 +139,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
||||||
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
||||||
/// <exception cref="ErrorCodeException">如果反序列化失败,则可能抛出本异常。</exception>
|
/// <exception cref="ErrorCodeException">如果反序列化失败,则可能抛出本异常。</exception>
|
||||||
private TResponse ConvertHttpResponseToObject<TResponse>(object requestParameters, string responseString)
|
private TResponse ConvertHttpResponseToObject<TResponse>(object? requestParameters, string responseString)
|
||||||
{
|
{
|
||||||
var throwException = new ErrorCodeException(ErrorCodes.HttpResponseConvertJsonFailed, attachObj: new { requestParameters, responseString });
|
var throwException = new ErrorCodeException(ErrorCodes.HttpResponseConvertJsonFailed, attachObj: new { requestParameters, responseString });
|
||||||
|
|
||||||
|
@@ -14,9 +14,9 @@
|
|||||||
/// <param name="requestOption">请求时的配置动作。</param>
|
/// <param name="requestOption">请求时的配置动作。</param>
|
||||||
/// <returns>服务端的响应结果。</returns>
|
/// <returns>服务端的响应结果。</returns>
|
||||||
ValueTask<string> PostAsync(string url,
|
ValueTask<string> PostAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据指定的配置执行 POST 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
/// 根据指定的配置执行 POST 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
||||||
@@ -28,14 +28,14 @@
|
|||||||
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
||||||
/// <returns>服务端的响应结果。</returns>
|
/// <returns>服务端的响应结果。</returns>
|
||||||
ValueTask<TResponse> PostAsync<TResponse>(string url,
|
ValueTask<TResponse> PostAsync<TResponse>(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
|
|
||||||
ValueTask<HttpResponseMessage> PostReturnHttpResponseAsync(string url,
|
ValueTask<HttpResponseMessage> PostReturnHttpResponseAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据指定的配置执行 GET 请求,并以 <see cref="string"/> 作为返回值。
|
/// 根据指定的配置执行 GET 请求,并以 <see cref="string"/> 作为返回值。
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
/// <param name="requestOption">请求时的配置动作。</param>
|
/// <param name="requestOption">请求时的配置动作。</param>
|
||||||
/// <returns>服务端的响应结果。</returns>
|
/// <returns>服务端的响应结果。</returns>
|
||||||
ValueTask<string> GetAsync(string url,
|
ValueTask<string> GetAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据指定的配置执行 GET 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
/// 根据指定的配置执行 GET 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
/// <returns>服务端的响应结果。</returns>
|
/// <returns>服务端的响应结果。</returns>
|
||||||
ValueTask<TResponse> GetAsync<TResponse>(
|
ValueTask<TResponse> GetAsync<TResponse>(
|
||||||
string url,
|
string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -18,6 +18,6 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// <param name="sourceLyric">原始歌词数据。</param>
|
/// <param name="sourceLyric">原始歌词数据。</param>
|
||||||
/// <param name="translationLyric">翻译歌词数据。</param>
|
/// <param name="translationLyric">翻译歌词数据。</param>
|
||||||
/// <returns>构建完成的 <see cref="LyricsItemCollection"/> 对象。</returns>
|
/// <returns>构建完成的 <see cref="LyricsItemCollection"/> 对象。</returns>
|
||||||
LyricsItemCollection Build(string sourceLyric, string? translationLyric);
|
LyricsItemCollection Build(string? sourceLyric, string? translationLyric);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -12,7 +12,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// <param name="artist">歌曲的作者。</param>
|
/// <param name="artist">歌曲的作者。</param>
|
||||||
/// <param name="duration">歌曲的时长。</param>
|
/// <param name="duration">歌曲的时长。</param>
|
||||||
/// <returns>歌曲的歌词数据对象。</returns>
|
/// <returns>歌曲的歌词数据对象。</returns>
|
||||||
ValueTask<LyricsItemCollection> DownloadAsync(string? songName, string? artist, long? duration = null);
|
ValueTask<LyricsItemCollection> DownloadAsync(string songName, string artist, long? duration = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 下载器的名称。
|
/// 下载器的名称。
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
using System.Text;
|
using System.Collections.Immutable;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Polly;
|
|
||||||
using ZonyLrcTools.Common.Configuration;
|
using ZonyLrcTools.Common.Configuration;
|
||||||
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||||
using ZonyLrcTools.Common.Infrastructure.Exceptions;
|
using ZonyLrcTools.Common.Infrastructure.Exceptions;
|
||||||
@@ -67,7 +67,10 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
|||||||
|
|
||||||
await Task.WhenAll(downloadTasks);
|
await Task.WhenAll(downloadTasks);
|
||||||
|
|
||||||
await _logger.InfoAsync($"歌词数据下载完成,成功: {needDownloadMusicInfos.Count(m => m.IsSuccessful)} 失败{needDownloadMusicInfos.Count(m => m.IsSuccessful == false)}。");
|
var successfulCount = needDownloadMusicInfos.Count(m => m is { IsSuccessful: true, IsPruneMusic: false });
|
||||||
|
var skippedCount = needDownloadMusicInfos.Count(m => m is { IsSuccessful: true, IsPruneMusic: true });
|
||||||
|
var failedCount = needDownloadMusicInfos.Count(m => m.IsSuccessful == false);
|
||||||
|
await _logger.InfoAsync($"歌词数据下载完成,成功: {successfulCount} 跳过(纯音乐): {skippedCount} 失败{failedCount}。");
|
||||||
await LogFailedSongFilesInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"歌词下载失败列表_{DateTime.Now:yyyyMMddHHmmss}.txt"), needDownloadMusicInfos);
|
await LogFailedSongFilesInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"歌词下载失败列表_{DateTime.Now:yyyyMMddHHmmss}.txt"), needDownloadMusicInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +83,7 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
|||||||
if (lyrics.IsPruneMusic)
|
if (lyrics.IsPruneMusic)
|
||||||
{
|
{
|
||||||
info.IsSuccessful = true;
|
info.IsSuccessful = true;
|
||||||
|
info.IsPruneMusic = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,12 +138,18 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
|||||||
return Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(_options.Provider.Lyric.Config.FileEncoding), lyrics.GetUtf8Bytes());
|
return Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(_options.Provider.Lyric.Config.FileEncoding), lyrics.GetUtf8Bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LogFailedSongFilesInfo(string outFilePath, List<MusicInfo> musicInfos)
|
private async Task LogFailedSongFilesInfo(string outFilePath, IEnumerable<MusicInfo> musicInfos)
|
||||||
{
|
{
|
||||||
|
var failedSongList = musicInfos.Where(m => m.IsSuccessful == false).ToImmutableList();
|
||||||
|
if (!failedSongList.Any())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var failedSongFiles = new StringBuilder();
|
var failedSongFiles = new StringBuilder();
|
||||||
failedSongFiles.AppendLine("歌曲名,歌手,路径");
|
failedSongFiles.AppendLine("歌曲名,歌手,路径");
|
||||||
|
|
||||||
foreach (var failedSongFile in musicInfos.Where(m => m.IsSuccessful == false))
|
foreach (var failedSongFile in failedSongList)
|
||||||
{
|
{
|
||||||
failedSongFiles.AppendLine($"{failedSongFile.Name},{failedSongFile.Artist},{failedSongFile.FilePath}");
|
failedSongFiles.AppendLine($"{failedSongFile.Name},{failedSongFile.Artist},{failedSongFile.FilePath}");
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词文本数据。
|
/// 歌词文本数据。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LyricText { get; }
|
public string? LyricText { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词所在的时间(分)。
|
/// 歌词所在的时间(分)。
|
||||||
@@ -55,21 +55,21 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// <param name="minute">歌词所在的时间(分)。</param>
|
/// <param name="minute">歌词所在的时间(分)。</param>
|
||||||
/// <param name="second">歌词所在的时间(秒)。</param>
|
/// <param name="second">歌词所在的时间(秒)。</param>
|
||||||
/// <param name="lyricText">歌词文本数据。</param>
|
/// <param name="lyricText">歌词文本数据。</param>
|
||||||
public LyricsItem(int minute, double second, string lyricText)
|
public LyricsItem(int minute, double second, string? lyricText)
|
||||||
{
|
{
|
||||||
Minute = minute;
|
Minute = minute;
|
||||||
Second = second;
|
Second = second;
|
||||||
LyricText = lyricText;
|
LyricText = lyricText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CompareTo(LyricsItem other)
|
public int CompareTo(LyricsItem? other)
|
||||||
{
|
{
|
||||||
if (SortScore > other.SortScore)
|
if (SortScore > other?.SortScore)
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SortScore < other.SortScore)
|
if (SortScore < other?.SortScore)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -87,12 +87,12 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
return left.SortScore < right.SortScore;
|
return left.SortScore < right.SortScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator ==(LyricsItem left, LyricsItem right)
|
public static bool operator ==(LyricsItem? left, LyricsItem? right)
|
||||||
{
|
{
|
||||||
return (int?)left?.SortScore == (int?)right?.SortScore;
|
return (int?)left?.SortScore == (int?)right?.SortScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator !=(LyricsItem item1, LyricsItem item2)
|
public static bool operator !=(LyricsItem? item1, LyricsItem? item2)
|
||||||
{
|
{
|
||||||
return !(item1 == item2);
|
return !(item1 == item2);
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
return LyricText == other.LyricText && Minute == other.Minute && Second.Equals(other.Second);
|
return LyricText == other.LyricText && Minute == other.Minute && Second.Equals(other.Second);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(null, obj)) return false;
|
if (ReferenceEquals(null, obj)) return false;
|
||||||
if (ReferenceEquals(this, obj)) return true;
|
if (ReferenceEquals(this, obj)) return true;
|
||||||
|
@@ -14,7 +14,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPruneMusic => Count == 0;
|
public bool IsPruneMusic => Count == 0;
|
||||||
|
|
||||||
public GlobalLyricsConfigOptions? Options { get; private set; }
|
public GlobalLyricsConfigOptions? Options { get; }
|
||||||
|
|
||||||
public LyricsItemCollection(GlobalLyricsConfigOptions? options)
|
public LyricsItemCollection(GlobalLyricsConfigOptions? options)
|
||||||
{
|
{
|
||||||
@@ -29,6 +29,11 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
}
|
}
|
||||||
|
|
||||||
var option = left.Options;
|
var option = left.Options;
|
||||||
|
if (option == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("LyricsItemCollection.Options");
|
||||||
|
}
|
||||||
|
|
||||||
var newCollection = new LyricsItemCollection(option);
|
var newCollection = new LyricsItemCollection(option);
|
||||||
var indexDiff = left.Count - right.Count;
|
var indexDiff = left.Count - right.Count;
|
||||||
if (!option.IsOneLine)
|
if (!option.IsOneLine)
|
||||||
@@ -100,6 +105,11 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
if (Options == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("LyricsItemCollection.Options");
|
||||||
|
}
|
||||||
|
|
||||||
var lyricBuilder = new StringBuilder();
|
var lyricBuilder = new StringBuilder();
|
||||||
ForEach(lyric => lyricBuilder.Append(lyric).Append(Options.LineBreak));
|
ForEach(lyric => lyricBuilder.Append(lyric).Append(Options.LineBreak));
|
||||||
return lyricBuilder.ToString().TrimEnd(Options.LineBreak);
|
return lyricBuilder.ToString().TrimEnd(Options.LineBreak);
|
||||||
|
@@ -30,7 +30,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
return lyric;
|
return lyric;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LyricsItemCollection Build(string sourceLyric, string? translationLyric)
|
public LyricsItemCollection Build(string? sourceLyric, string? translationLyric)
|
||||||
{
|
{
|
||||||
var lyric = new LyricsItemCollection(_options.Provider.Lyric.Config);
|
var lyric = new LyricsItemCollection(_options.Provider.Lyric.Config);
|
||||||
if (string.IsNullOrEmpty(sourceLyric))
|
if (string.IsNullOrEmpty(sourceLyric))
|
||||||
|
@@ -10,9 +10,9 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("client")] public string UnknownParameters3 { get; }
|
[JsonProperty("client")] public string UnknownParameters3 { get; }
|
||||||
|
|
||||||
[JsonProperty("hash")] public string FileHash { get; }
|
[JsonProperty("hash")] public string? FileHash { get; }
|
||||||
|
|
||||||
public GetLyricAccessKeyRequest(string fileHash)
|
public GetLyricAccessKeyRequest(string? fileHash)
|
||||||
{
|
{
|
||||||
UnknownParameters1 = 1;
|
UnknownParameters1 = 1;
|
||||||
UnknownParameters2 = "yes";
|
UnknownParameters2 = "yes";
|
||||||
|
@@ -8,13 +8,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("errcode")] public int ErrorCode { get; set; }
|
[JsonProperty("errcode")] public int ErrorCode { get; set; }
|
||||||
|
|
||||||
[JsonProperty("candidates")] public List<GetLyricAccessKeyDataObject> AccessKeyDataObjects { get; set; }
|
[JsonProperty("candidates")] public List<GetLyricAccessKeyDataObject>? AccessKeyDataObjects { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetLyricAccessKeyDataObject
|
public class GetLyricAccessKeyDataObject
|
||||||
{
|
{
|
||||||
[JsonProperty("accesskey")] public string AccessKey { get; set; }
|
[JsonProperty("accesskey")] public string? AccessKey { get; set; }
|
||||||
|
|
||||||
[JsonProperty("id")] public string Id { get; set; }
|
[JsonProperty("id")] public string? Id { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -12,11 +12,11 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("charset")] public string UnknownParameters4 { get; }
|
[JsonProperty("charset")] public string UnknownParameters4 { get; }
|
||||||
|
|
||||||
[JsonProperty("id")] public string Id { get; }
|
[JsonProperty("id")] public string? Id { get; }
|
||||||
|
|
||||||
[JsonProperty("accesskey")] public string AccessKey { get; }
|
[JsonProperty("accesskey")] public string? AccessKey { get; }
|
||||||
|
|
||||||
public GetLyricRequest(string id, string accessKey)
|
public GetLyricRequest(string? id, string? accessKey)
|
||||||
{
|
{
|
||||||
UnknownParameters1 = 1;
|
UnknownParameters1 = 1;
|
||||||
UnknownParameters2 = "iphone";
|
UnknownParameters2 = "iphone";
|
||||||
|
@@ -6,20 +6,20 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel
|
|||||||
{
|
{
|
||||||
[JsonProperty("status")] public int Status { get; set; }
|
[JsonProperty("status")] public int Status { get; set; }
|
||||||
|
|
||||||
[JsonProperty("data")] public SongSearchResponseInnerData Data { get; set; }
|
[JsonProperty("data")] public SongSearchResponseInnerData? Data { get; set; }
|
||||||
|
|
||||||
[JsonProperty("error_code")] public int ErrorCode { get; set; }
|
[JsonProperty("error_code")] public int ErrorCode { get; set; }
|
||||||
|
|
||||||
[JsonProperty("error_msg")] public string ErrorMessage { get; set; }
|
[JsonProperty("error_msg")] public string? ErrorMessage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SongSearchResponseInnerData
|
public class SongSearchResponseInnerData
|
||||||
{
|
{
|
||||||
[JsonProperty("lists")] public List<SongSearchResponseSongDetail> List { get; set; }
|
[JsonProperty("lists")] public List<SongSearchResponseSongDetail>? List { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SongSearchResponseSongDetail
|
public class SongSearchResponseSongDetail
|
||||||
{
|
{
|
||||||
public string FileHash { get; set; }
|
public string? FileHash { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -38,9 +38,9 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
|
|||||||
|
|
||||||
// 获得特殊的 AccessToken 与 Id,真正请求歌词数据。
|
// 获得特殊的 AccessToken 与 Id,真正请求歌词数据。
|
||||||
var accessKeyResponse = await _warpHttpClient.GetAsync<GetLyricAccessKeyResponse>(KuGouGetLyricAccessKeyUrl,
|
var accessKeyResponse = await _warpHttpClient.GetAsync<GetLyricAccessKeyResponse>(KuGouGetLyricAccessKeyUrl,
|
||||||
new GetLyricAccessKeyRequest(searchResult.Data.List[0].FileHash));
|
new GetLyricAccessKeyRequest(searchResult.Data?.List?[0].FileHash));
|
||||||
|
|
||||||
if (accessKeyResponse.AccessKeyDataObjects.Count == 0)
|
if (accessKeyResponse.AccessKeyDataObjects == null || accessKeyResponse.AccessKeyDataObjects.Count == 0)
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
||||||
}
|
}
|
||||||
@@ -54,18 +54,18 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
|
|||||||
{
|
{
|
||||||
await ValueTask.CompletedTask;
|
await ValueTask.CompletedTask;
|
||||||
var lyricJsonObj = JObject.Parse((data as string)!);
|
var lyricJsonObj = JObject.Parse((data as string)!);
|
||||||
if (lyricJsonObj.SelectToken("$.status").Value<int>() != 200)
|
if (lyricJsonObj.SelectToken("$.status")?.Value<int>() != 200)
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
||||||
}
|
}
|
||||||
|
|
||||||
var lyricText = Encoding.UTF8.GetString(Convert.FromBase64String(lyricJsonObj.SelectToken("$.content").Value<string>()));
|
var lyricText = Encoding.UTF8.GetString(Convert.FromBase64String(lyricJsonObj.SelectToken("$.content")?.Value<string>() ?? string.Empty));
|
||||||
return _lyricsItemCollectionFactory.Build(lyricText);
|
return _lyricsItemCollectionFactory.Build(lyricText);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
||||||
{
|
{
|
||||||
if ((response.ErrorCode != 0 && response.Status != 1) || response.Data.List.Count == 0)
|
if ((response.ErrorCode != 0 && response.Status != 1) || response.Data?.List?.Count == 0)
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ public class GetLyricsResponse
|
|||||||
{
|
{
|
||||||
[JsonProperty("status")] public int Status { get; set; }
|
[JsonProperty("status")] public int Status { get; set; }
|
||||||
|
|
||||||
[JsonProperty("data")] public GetLyricsResponseInnerData Data { get; set; }
|
[JsonProperty("data")] public GetLyricsResponseInnerData? Data { get; set; }
|
||||||
|
|
||||||
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
|
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ public class GetLyricsResponseInnerData
|
|||||||
|
|
||||||
public class GetLyricsItem
|
public class GetLyricsItem
|
||||||
{
|
{
|
||||||
[JsonProperty("lineLyric")] public string Text { get; set; }
|
[JsonProperty("lineLyric")] public string? Text { get; set; }
|
||||||
|
|
||||||
[JsonProperty("time")] public string Position { get; set; }
|
[JsonProperty("time")] public string Position { get; set; } = null!;
|
||||||
}
|
}
|
@@ -6,7 +6,7 @@ public class SongSearchResponse
|
|||||||
{
|
{
|
||||||
[JsonProperty("code")] public int Code { get; set; }
|
[JsonProperty("code")] public int Code { get; set; }
|
||||||
|
|
||||||
[JsonProperty("data")] public SongSearchResponseInnerData InnerData { get; set; }
|
[JsonProperty("data")] public SongSearchResponseInnerData InnerData { get; set; } = null!;
|
||||||
|
|
||||||
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
|
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
|
||||||
|
|
||||||
@@ -29,9 +29,9 @@ public class SongSearchResponse
|
|||||||
|
|
||||||
public class SongSearchResponseInnerData
|
public class SongSearchResponseInnerData
|
||||||
{
|
{
|
||||||
[JsonProperty("total")] public string Total { get; set; }
|
[JsonProperty("total")] public string? Total { get; set; }
|
||||||
|
|
||||||
[JsonProperty("list")] public ICollection<SongSearchResponseDetail> SongItems { get; set; }
|
[JsonProperty("list")] public ICollection<SongSearchResponseDetail> SongItems { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SongSearchResponseDetail
|
public class SongSearchResponseDetail
|
||||||
|
@@ -60,7 +60,7 @@ public class KuWoLyricsProvider : LyricsProvider
|
|||||||
await ValueTask.CompletedTask;
|
await ValueTask.CompletedTask;
|
||||||
|
|
||||||
var lyricsResponse = (GetLyricsResponse)lyricsObject;
|
var lyricsResponse = (GetLyricsResponse)lyricsObject;
|
||||||
if (lyricsResponse.Data.Lyrics == null)
|
if (lyricsResponse.Data?.Lyrics == null)
|
||||||
{
|
{
|
||||||
return new LyricsItemCollection(null);
|
return new LyricsItemCollection(null);
|
||||||
}
|
}
|
||||||
|
@@ -8,13 +8,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 原始的歌词。
|
/// 原始的歌词。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("lrc")]
|
[JsonProperty("lrc")]
|
||||||
public InnerLyric OriginalLyric { get; set; }
|
public InnerLyric? OriginalLyric { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 卡拉 OK 歌词。
|
/// 卡拉 OK 歌词。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("klyric")]
|
[JsonProperty("klyric")]
|
||||||
public InnerLyric KaraokeLyric { get; set; }
|
public InnerLyric? KaraokeLyric { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 如果存在翻译歌词,则本项内容为翻译歌词。
|
/// 如果存在翻译歌词,则本项内容为翻译歌词。
|
||||||
@@ -26,13 +26,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 如果存在罗马音歌词,则本项内容为罗马音歌词。
|
/// 如果存在罗马音歌词,则本项内容为罗马音歌词。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("romalrc")]
|
[JsonProperty("romalrc")]
|
||||||
public InnerLyric RomaLyric { get; set; }
|
public InnerLyric? RomaLyric { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 状态码。
|
/// 状态码。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("code")]
|
[JsonProperty("code")]
|
||||||
public string StatusCode { get; set; }
|
public string? StatusCode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -40,12 +40,12 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class InnerLyric
|
public class InnerLyric
|
||||||
{
|
{
|
||||||
[JsonProperty("version")] public string Version { get; set; }
|
[JsonProperty("version")] public string? Version { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 具体的歌词数据。
|
/// 具体的歌词数据。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("lyric")]
|
[JsonProperty("lyric")]
|
||||||
public string Text { get; set; }
|
public string? Text { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -45,17 +45,14 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("crypto")] public string Crypto { get; set; } = "weapi";
|
[JsonProperty("crypto")] public string Crypto { get; set; } = "weapi";
|
||||||
|
|
||||||
public SongSearchRequest()
|
public SongSearchRequest(string musicName, string artistName, int limit = 10)
|
||||||
{
|
{
|
||||||
CsrfToken = string.Empty;
|
CsrfToken = string.Empty;
|
||||||
Type = 1;
|
Type = 1;
|
||||||
Offset = 0;
|
Offset = 0;
|
||||||
IsTotal = true;
|
IsTotal = true;
|
||||||
Limit = 10;
|
Limit = 10;
|
||||||
}
|
|
||||||
|
|
||||||
public SongSearchRequest(string musicName, string artistName, int limit = 10) : this()
|
|
||||||
{
|
|
||||||
// Remove all the brackets and the content inside them.
|
// Remove all the brackets and the content inside them.
|
||||||
var regex = new Regex(@"\([^)]*\)");
|
var regex = new Regex(@"\([^)]*\)");
|
||||||
musicName = regex.Replace(musicName, string.Empty);
|
musicName = regex.Replace(musicName, string.Empty);
|
||||||
|
@@ -4,7 +4,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
{
|
{
|
||||||
public class SongSearchResponse
|
public class SongSearchResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("result")] public InnerListItemModel? Items { get; set; }
|
[JsonProperty("result")] public InnerListItemModel Items { get; set; } = null!;
|
||||||
|
|
||||||
[JsonProperty("code")] public int StatusCode { get; set; }
|
[JsonProperty("code")] public int StatusCode { get; set; }
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
|
|
||||||
public class InnerListItemModel
|
public class InnerListItemModel
|
||||||
{
|
{
|
||||||
[JsonProperty("songs")] public IList<SongModel>? SongItems { get; set; }
|
[JsonProperty("songs")] public IList<SongModel> SongItems { get; set; } = null!;
|
||||||
|
|
||||||
[JsonProperty("songCount")] public int SongCount { get; set; }
|
[JsonProperty("songCount")] public int SongCount { get; set; }
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 歌曲的名称。
|
/// 歌曲的名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的 Sid (Song Id)。
|
/// 歌曲的 Sid (Song Id)。
|
||||||
@@ -50,13 +50,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 歌曲的演唱者。
|
/// 歌曲的演唱者。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("artists")]
|
[JsonProperty("artists")]
|
||||||
public IList<SongArtistModel> Artists { get; set; }
|
public IList<SongArtistModel>? Artists { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的专辑信息。
|
/// 歌曲的专辑信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("album")]
|
[JsonProperty("album")]
|
||||||
public SongAlbumModel Album { get; set; }
|
public SongAlbumModel? Album { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的实际长度。
|
/// 歌曲的实际长度。
|
||||||
@@ -71,7 +71,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 歌手/艺术家的名称。
|
/// 歌手/艺术家的名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string? Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SongAlbumModel
|
public class SongAlbumModel
|
||||||
@@ -80,12 +80,12 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 专辑的名称。
|
/// 专辑的名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 专辑图像的 Url 地址。
|
/// 专辑图像的 Url 地址。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("img1v1Url")]
|
[JsonProperty("img1v1Url")]
|
||||||
public string PictureUrl { get; set; }
|
public string? PictureUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -16,10 +16,6 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("g_tk")] public int Gtk { get; set; }
|
[JsonProperty("g_tk")] public int Gtk { get; set; }
|
||||||
|
|
||||||
protected GetLyricRequest()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetLyricRequest(string? songId)
|
public GetLyricRequest(string? songId)
|
||||||
{
|
{
|
||||||
IsNoBase64Encoding = 1;
|
IsNoBase64Encoding = 1;
|
||||||
|
@@ -19,8 +19,8 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic.JsonModel
|
|||||||
public string Platform { get; protected set; }
|
public string Platform { get; protected set; }
|
||||||
|
|
||||||
[JsonProperty("key")]
|
[JsonProperty("key")]
|
||||||
public string Keyword { get; protected set; }
|
public string Keyword { get; protected set; } = null!;
|
||||||
|
|
||||||
protected SongSearchRequest()
|
protected SongSearchRequest()
|
||||||
{
|
{
|
||||||
Format = "json";
|
Format = "json";
|
||||||
|
@@ -6,21 +6,21 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic.JsonModel
|
|||||||
{
|
{
|
||||||
[JsonProperty("code")] public int StatusCode { get; set; }
|
[JsonProperty("code")] public int StatusCode { get; set; }
|
||||||
|
|
||||||
[JsonProperty("data")] public QQMusicInnerDataModel Data { get; set; }
|
[JsonProperty("data")] public QQMusicInnerDataModel? Data { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QQMusicInnerDataModel
|
public class QQMusicInnerDataModel
|
||||||
{
|
{
|
||||||
[JsonProperty("song")] public QQMusicInnerSongModel Song { get; set; }
|
[JsonProperty("song")] public QQMusicInnerSongModel? Song { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QQMusicInnerSongModel
|
public class QQMusicInnerSongModel
|
||||||
{
|
{
|
||||||
[JsonProperty("itemlist")] public List<QQMusicInnerSongItem> SongItems { get; set; }
|
[JsonProperty("itemlist")] public List<QQMusicInnerSongItem>? SongItems { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QQMusicInnerSongItem
|
public class QQMusicInnerSongItem
|
||||||
{
|
{
|
||||||
[JsonProperty("mid")] public string SongId { get; set; }
|
[JsonProperty("mid")] public string? SongId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -35,7 +35,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic
|
|||||||
ValidateSongSearchResponse(searchResult, args);
|
ValidateSongSearchResponse(searchResult, args);
|
||||||
|
|
||||||
return await _warpHttpClient.GetAsync(QQGetLyricUrl,
|
return await _warpHttpClient.GetAsync(QQGetLyricUrl,
|
||||||
new GetLyricRequest(searchResult.Data.Song.SongItems.FirstOrDefault()?.SongId),
|
new GetLyricRequest(searchResult.Data?.Song?.SongItems?.FirstOrDefault()?.SongId),
|
||||||
op => op.Headers.Referrer = new Uri(QQMusicRequestReferer));
|
op => op.Headers.Referrer = new Uri(QQMusicRequestReferer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,15 +57,15 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lyricJsonObj = JObject.Parse(lyricJsonString);
|
var lyricJsonObj = JObject.Parse(lyricJsonString);
|
||||||
var sourceLyric = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(lyricJsonObj.SelectToken("$.lyric").Value<string>()));
|
var sourceLyric = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(lyricJsonObj.SelectToken("$.lyric")!.Value<string>()));
|
||||||
var translateLyric = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(lyricJsonObj.SelectToken("$.trans").Value<string>()));
|
var translateLyric = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(lyricJsonObj.SelectToken("$.trans")!.Value<string>()));
|
||||||
|
|
||||||
return _lyricsItemCollectionFactory.Build(sourceLyric, translateLyric);
|
return _lyricsItemCollectionFactory.Build(sourceLyric, translateLyric);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
||||||
{
|
{
|
||||||
if (response is not { StatusCode: 0 } || response.Data.Song.SongItems == null)
|
if (response is not { StatusCode: 0 } || response.Data?.Song?.SongItems == null)
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, attachObj: args);
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ZonyLrcTools.Common
|
namespace ZonyLrcTools.Common
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲信息的承载类,携带歌曲的相关数据。
|
/// 歌曲信息的承载类,携带歌曲的相关数据。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MusicInfo
|
public partial class MusicInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲对应的物理文件路径。
|
/// 歌曲对应的物理文件路径。
|
||||||
@@ -30,6 +32,11 @@ namespace ZonyLrcTools.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsSuccessful { get; set; } = true;
|
public bool IsSuccessful { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否时纯音乐?
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPruneMusic { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建一个新的 <see cref="MusicInfo"/> 对象。
|
/// 构建一个新的 <see cref="MusicInfo"/> 对象。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -38,9 +45,17 @@ namespace ZonyLrcTools.Common
|
|||||||
/// <param name="artist">歌曲的作者。</param>
|
/// <param name="artist">歌曲的作者。</param>
|
||||||
public MusicInfo(string filePath, string name, string artist)
|
public MusicInfo(string filePath, string name, string artist)
|
||||||
{
|
{
|
||||||
FilePath = filePath;
|
FilePath = Path.Combine(Path.GetDirectoryName(filePath)!, HandleInvalidFilePath(Path.GetFileName(filePath)));
|
||||||
Name = name;
|
Name = name;
|
||||||
Artist = artist;
|
Artist = artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string HandleInvalidFilePath(string srcText)
|
||||||
|
{
|
||||||
|
return InvalidFilePathRegex().Replace(srcText, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"[<>:""/\\|?*]")]
|
||||||
|
private static partial Regex InvalidFilePathRegex();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -38,7 +38,7 @@ public sealed class PlayListSongModel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的艺术家信息,可能会有多位艺术家/歌手。
|
/// 歌曲的艺术家信息,可能会有多位艺术家/歌手。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("al")]
|
[JsonProperty("ar")]
|
||||||
[JsonConverter(typeof(PlayListSongArtistModelJsonConverter))]
|
[JsonConverter(typeof(PlayListSongArtistModelJsonConverter))]
|
||||||
public ICollection<PlayListSongArtistModel>? Artists { get; set; }
|
public ICollection<PlayListSongArtistModel>? Artists { get; set; }
|
||||||
}
|
}
|
||||||
@@ -62,15 +62,12 @@ public class PlayListSongArtistModelJsonConverter : JsonConverter
|
|||||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
var token = JToken.Load(reader);
|
var token = JToken.Load(reader);
|
||||||
switch (token.Type)
|
return token.Type switch
|
||||||
{
|
{
|
||||||
case JTokenType.Array:
|
JTokenType.Array => token.ToObject(objectType),
|
||||||
return token.ToObject(objectType);
|
JTokenType.Object => new List<PlayListSongArtistModel> { token.ToObject<PlayListSongArtistModel>()! },
|
||||||
case JTokenType.Object:
|
_ => null
|
||||||
return new List<PlayListSongArtistModel> { token.ToObject<PlayListSongArtistModel>() };
|
};
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
|
@@ -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>
|
||||||
|
@@ -22,11 +22,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
_wordsDictionary = new Lazy<Dictionary<string, string>>(() =>
|
_wordsDictionary = new Lazy<Dictionary<string, string>>(() =>
|
||||||
{
|
{
|
||||||
var jsonData = File.ReadAllText(_options.Provider.Tag.BlockWord.FilePath);
|
var jsonData = File.ReadAllText(_options.Provider.Tag.BlockWord.FilePath);
|
||||||
return JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonData);
|
return JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonData) ?? throw new InvalidOperationException("屏蔽词字典文件格式错误。");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetValue(string key)
|
public string? GetValue(string key)
|
||||||
{
|
{
|
||||||
if (_wordsDictionary.Value.TryGetValue(key, out var value))
|
if (_wordsDictionary.Value.TryGetValue(key, out var value))
|
||||||
{
|
{
|
||||||
|
@@ -25,7 +25,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<MusicInfo> LoadAsync(string filePath)
|
public async ValueTask<MusicInfo?> LoadAsync(string filePath)
|
||||||
{
|
{
|
||||||
await ValueTask.CompletedTask;
|
await ValueTask.CompletedTask;
|
||||||
|
|
||||||
|
@@ -13,6 +13,6 @@
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="key">原始单词。</param>
|
/// <param name="key">原始单词。</param>
|
||||||
/// <returns>原始单词对应的屏蔽词。</returns>
|
/// <returns>原始单词对应的屏蔽词。</returns>
|
||||||
string GetValue(string key);
|
string? GetValue(string key);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -18,6 +18,6 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath">歌曲文件的路径。</param>
|
/// <param name="filePath">歌曲文件的路径。</param>
|
||||||
/// <returns>加载完成的歌曲信息。</returns>
|
/// <returns>加载完成的歌曲信息。</returns>
|
||||||
ValueTask<MusicInfo> LoadAsync(string filePath);
|
ValueTask<MusicInfo?> LoadAsync(string filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -14,7 +14,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
public string Name => ConstantName;
|
public string Name => ConstantName;
|
||||||
public const string ConstantName = "Taglib";
|
public const string ConstantName = "Taglib";
|
||||||
|
|
||||||
public async ValueTask<MusicInfo> LoadAsync(string filePath)
|
public async ValueTask<MusicInfo?> LoadAsync(string filePath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -30,12 +30,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
|
|
||||||
await ValueTask.CompletedTask;
|
await ValueTask.CompletedTask;
|
||||||
|
|
||||||
if (songName == null && songArtist == null)
|
return songName == null ? null : new MusicInfo(filePath, songName, songArtist);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MusicInfo(filePath, songName, songArtist);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@@ -23,6 +23,11 @@ public class DefaultUpdater : IUpdater, ISingletonDependency
|
|||||||
|
|
||||||
public async Task CheckUpdateAsync()
|
public async Task CheckUpdateAsync()
|
||||||
{
|
{
|
||||||
|
if (!IsCheckUpdate())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var response = await _warpHttpClient.GetAsync<NewVersionResponse?>(UpdateUrl);
|
var response = await _warpHttpClient.GetAsync<NewVersionResponse?>(UpdateUrl);
|
||||||
if (response == null)
|
if (response == null)
|
||||||
{
|
{
|
||||||
@@ -56,4 +61,27 @@ public class DefaultUpdater : IUpdater, ISingletonDependency
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsCheckUpdate()
|
||||||
|
{
|
||||||
|
var lockFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update.lock");
|
||||||
|
|
||||||
|
if (!File.Exists(lockFile))
|
||||||
|
{
|
||||||
|
File.WriteAllText(lockFile, DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (long.TryParse(File.ReadAllText(lockFile), out var time))
|
||||||
|
{
|
||||||
|
var now = DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||||
|
if (now - time <= 86400 /* 1 Day */)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(lockFile, DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -4,7 +4,7 @@
|
|||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>4.0.0.51</Version>
|
<Version>4.0.0.55</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
||||||
<PackageReference Include="MusicDecrypto.Library" Version="2.2.0" />
|
<PackageReference Include="MusicDecrypto.Library" Version="2.2.0" />
|
||||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.0.0" />
|
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Polly" Version="7.2.3" />
|
<PackageReference Include="Polly" Version="7.2.3" />
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||||
|
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.4" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="7.0.4" />
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="7.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="SuperSocket.WebSocket" Version="2.0.0-beta.11" />
|
<PackageReference Include="SuperSocket.WebSocket" Version="2.0.0-beta.11" />
|
||||||
|
@@ -12,7 +12,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Exceptions
|
|||||||
ErrorCodeHelper.LoadErrorMessage();
|
ErrorCodeHelper.LoadErrorMessage();
|
||||||
|
|
||||||
ErrorCodeHelper.ErrorMessages.ShouldNotBeNull();
|
ErrorCodeHelper.ErrorMessages.ShouldNotBeNull();
|
||||||
ErrorCodeHelper.ErrorMessages.Count.ShouldBe(16);
|
ErrorCodeHelper.ErrorMessages.Count.ShouldBe(17);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@@ -32,12 +32,11 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
|
|||||||
{
|
{
|
||||||
await Should.ThrowAsync<ErrorCodeException>(_lyricsProvider.DownloadAsync("天ノ弱", "漆柚").AsTask);
|
await Should.ThrowAsync<ErrorCodeException>(_lyricsProvider.DownloadAsync("天ノ弱", "漆柚").AsTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DownloadAsync_Index_Exception_Test()
|
public async Task DownloadAsync_Index_Exception_Test()
|
||||||
{
|
{
|
||||||
var lyric = await _lyricsProvider.DownloadAsync("40'z", "ZOOLY");
|
await Should.ThrowAsync<ErrorCodeException>(async () => await _lyricsProvider.DownloadAsync("40'z", "ZOOLY"));
|
||||||
lyric.ToString().ShouldNotBeNullOrEmpty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -104,14 +104,13 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
|
|||||||
public async Task DownloadAsync_Issue123_Test()
|
public async Task DownloadAsync_Issue123_Test()
|
||||||
{
|
{
|
||||||
var lyric = await _lyricsProvider.DownloadAsync("橄榄树", "苏曼");
|
var lyric = await _lyricsProvider.DownloadAsync("橄榄树", "苏曼");
|
||||||
lyric.ToString().ShouldNotBeNullOrEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DownloadAsync_Issue133_Test()
|
public async Task DownloadAsync_Issue133_Test()
|
||||||
{
|
{
|
||||||
var lyric = await _lyricsProvider.DownloadAsync("Everything", "Yinyues");
|
var lyric = await _lyricsProvider.DownloadAsync("Everything", "Yinyues");
|
||||||
lyric.ToString().ShouldNotBeNullOrEmpty();
|
lyric.IsPruneMusic.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -129,5 +128,27 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
|
|||||||
|
|
||||||
lyric.IsPruneMusic.ShouldBeTrue();
|
lyric.IsPruneMusic.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DownloadAsync_Issue_20230429_01_Test()
|
||||||
|
{
|
||||||
|
var lyric = await _lyricsProvider.DownloadAsync("Don't Go Away", "By2 Twins+《Twins》");
|
||||||
|
|
||||||
|
lyric.ShouldContain(x => x.LyricText.Contains("吴庆隆"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DownloadAsync_Issue_20230429_02_Test()
|
||||||
|
{
|
||||||
|
var lyric = await _lyricsProvider.DownloadAsync("Hanging On (2009)", "Britt Nicole");
|
||||||
|
lyric.ShouldContain(x => x.LyricText.Contains("You see my anxious heart"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DownloadAsync_Issue_20230429_03_Test()
|
||||||
|
{
|
||||||
|
var lyric = await _lyricsProvider.DownloadAsync("Your Heart Is A Muscle (2012)", "Carly Rae Jepsen");
|
||||||
|
lyric.IsPruneMusic.ShouldBeFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,6 +3,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using ZonyLrcTools.Common.Infrastructure.Exceptions;
|
||||||
using ZonyLrcTools.Common.Lyrics;
|
using ZonyLrcTools.Common.Lyrics;
|
||||||
|
|
||||||
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
|
namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
|
||||||
@@ -37,12 +38,11 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
|
|||||||
lyric.IsPruneMusic.ShouldBeFalse();
|
lyric.IsPruneMusic.ShouldBeFalse();
|
||||||
lyric.ToString().ShouldContain("你好像快要不能呼吸");
|
lyric.ToString().ShouldContain("你好像快要不能呼吸");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DownloadAsync_Issue133_Test()
|
public async Task DownloadAsync_Issue133_Test()
|
||||||
{
|
{
|
||||||
var lyric = await _lyricsProvider.DownloadAsync("天ノ弱", "漆柚");
|
await Should.ThrowAsync<ErrorCodeException>(async () => await _lyricsProvider.DownloadAsync("天ノ弱", "漆柚"));
|
||||||
lyric.ToString().ShouldNotBeNullOrEmpty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
15
tests/ZonyLrcTools.Tests/MusicInfoTests.cs
Normal file
15
tests/ZonyLrcTools.Tests/MusicInfoTests.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZonyLrcTools.Common;
|
||||||
|
|
||||||
|
namespace ZonyLrcTools.Tests;
|
||||||
|
|
||||||
|
public class MusicInfoTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void InvalidFilePathTest()
|
||||||
|
{
|
||||||
|
var musicInfo = new MusicInfo("C:\\Users\\Zony\\Music\\[ZonyLrcTools]:? - 01. 你好.mp3", "你好", "Zony");
|
||||||
|
musicInfo.FilePath.ShouldBe(@"C:\Users\Zony\Music\[ZonyLrcTools] - 01. 你好.mp3");
|
||||||
|
}
|
||||||
|
}
|
@@ -7,13 +7,13 @@ namespace ZonyLrcTools.Tests.MusicScanner;
|
|||||||
|
|
||||||
public class NetEaseMusicSongListMusicScannerTests : TestBase
|
public class NetEaseMusicSongListMusicScannerTests : TestBase
|
||||||
{
|
{
|
||||||
[Fact]
|
// [Fact]
|
||||||
public async Task GetMusicInfoFromNetEaseMusicSongListAsync_Test()
|
// public async Task GetMusicInfoFromNetEaseMusicSongListAsync_Test()
|
||||||
{
|
// {
|
||||||
var musicScanner = GetService<NetEaseMusicSongListMusicScanner>();
|
// var musicScanner = GetService<NetEaseMusicSongListMusicScanner>();
|
||||||
var musicInfo = await musicScanner.GetMusicInfoFromNetEaseMusicSongListAsync("7224428149", "DownloadedLrc", "{Artist} - {Name}.lrc");
|
// var musicInfo = await musicScanner.GetMusicInfoFromNetEaseMusicSongListAsync("7224428149", "DownloadedLrc", "{Artist} - {Name}.lrc");
|
||||||
|
//
|
||||||
musicInfo.ShouldNotBeNull();
|
// musicInfo.ShouldNotBeNull();
|
||||||
musicInfo.Count.ShouldBeGreaterThan(10);
|
// musicInfo.Count.ShouldBeGreaterThan(10);
|
||||||
}
|
// }
|
||||||
}
|
}
|
@@ -7,14 +7,14 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.1.0" />
|
<PackageReference Include="Shouldly" Version="4.2.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="3.2.0">
|
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
New Features: None
|
New Features: None
|
||||||
|
|
||||||
Breaking Changes: None
|
Breaking Changes: None
|
||||||
Enhancement: :
|
Enhancement: :
|
||||||
|
|
||||||
- 替换了网易云歌词下载器的 API,减少 50004 报错。
|
- 网易云歌单下载歌词时,支持使用 `;` 传递多个歌单 ID。(`15445646;468435123;4131357`) - [`41cba02`](https://github.com/real-zony/ZonyLrcToolsX/commit/41cba028333d0cf65f7dc3ee466660cc20e558fc)
|
||||||
|
- 优化了结果提示信息,更加明确。- [`1e5c418`](https://github.com/real-zony/ZonyLrcToolsX/commit/1e5c41852f70df4b824d2795f175e57c82a0d104)
|
||||||
|
- 优化了程序的启动时间,不会每次启动都去检测更新了。- [`b240564`](https://github.com/real-zony/ZonyLrcToolsX/commit/b240564cf7ac3432715dd54c280d73f9f985c4fc)
|
||||||
|
|
||||||
Fixed Bugs:
|
Fixed Bugs:
|
||||||
|
|
||||||
- 修复了问题 [#133](https://github.com/real-zony/ZonyLrcToolsX/issues/133) 。
|
- 更新了依赖的 NuGet 程序包版本。- [`1f312c7`](https://github.com/real-zony/ZonyLrcToolsX/commit/1f312c749d8e7784e7670ec3821db0b4b430ce2e)
|
||||||
- 修复了问题 [#118](https://github.com/real-zony/ZonyLrcToolsX/issues/118) 。
|
- 过滤了包含特殊符号的文件路径,避免报错。 - [`62d08df`](https://github.com/real-zony/ZonyLrcToolsX/commit/62d08df7353343711741b23a6ecb9c3908754075)
|
||||||
- 修复了部分未处理的异常。
|
- 修复了从网易云歌单下载歌曲时,将专辑名称当作歌手名称进行取值。 - [`98c935e`](https://github.com/real-zony/ZonyLrcToolsX/commit/98c935ed93f9278e8606c056868824ea8335ee5d)
|
Reference in New Issue
Block a user