mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-09-06 21:56:53 +00:00
feat: Reinitialize the Repository.
重新初始化仓库。
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
/// <summary>
|
||||
/// 歌词数据下载器,用于匹配并下载歌曲的歌词。
|
||||
/// </summary>
|
||||
public interface ILyricDownloader
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载歌词数据。
|
||||
/// </summary>
|
||||
/// <param name="songName">歌曲的名称。</param>
|
||||
/// <param name="artist">歌曲的作者。</param>
|
||||
/// <returns>歌曲的歌词数据对象。</returns>
|
||||
ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist);
|
||||
|
||||
/// <summary>
|
||||
/// 下载器的名称。
|
||||
/// </summary>
|
||||
string DownloaderName { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
/// <summary>
|
||||
/// 构建 <see cref="LyricItemCollection"/> 对象的工厂。
|
||||
/// </summary>
|
||||
public interface ILyricItemCollectionFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据指定的歌曲数据构建新的 <see cref="LyricItemCollection"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="sourceLyric">原始歌词数据。</param>
|
||||
/// <param name="translateLyric">翻译歌词数据。</param>
|
||||
/// <returns>构建完成的 <see cref="LyricItemCollection"/> 对象。</returns>
|
||||
LyricItemCollection Build(string sourceLyric, string translateLyric = null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
public interface ILyricTextResolver
|
||||
{
|
||||
LyricItemCollection Resolve(string lyricText);
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义了程序默认提供的歌词下载器。
|
||||
/// </summary>
|
||||
public static class InternalLyricDownloaderNames
|
||||
{
|
||||
/// <summary>
|
||||
/// 网易云音乐歌词下载器。
|
||||
/// </summary>
|
||||
public const string NetEase = nameof(NetEase);
|
||||
|
||||
/// <summary>
|
||||
/// QQ 音乐歌词下载器。
|
||||
/// </summary>
|
||||
public const string QQ = nameof(QQ);
|
||||
|
||||
/// <summary>
|
||||
/// 酷狗音乐歌词下载器。
|
||||
/// </summary>
|
||||
public const string KuGou = nameof(KuGou);
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using System.Threading.Tasks;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Network;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou
|
||||
{
|
||||
public class KuGourLyricDownloader : LyricDownloader
|
||||
{
|
||||
public override string DownloaderName => InternalLyricDownloaderNames.KuGou;
|
||||
|
||||
private readonly IWarpHttpClient _warpHttpClient;
|
||||
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
|
||||
|
||||
public KuGourLyricDownloader(IWarpHttpClient warpHttpClient,
|
||||
ILyricItemCollectionFactory lyricItemCollectionFactory)
|
||||
{
|
||||
_warpHttpClient = warpHttpClient;
|
||||
_lyricItemCollectionFactory = lyricItemCollectionFactory;
|
||||
}
|
||||
|
||||
protected override ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
protected override ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
23
src/ZonyLrcTools.Cli/Infrastructure/Lyric/LineBreakType.cs
Normal file
23
src/ZonyLrcTools.Cli/Infrastructure/Lyric/LineBreakType.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
/// <summary>
|
||||
/// 换行符格式定义。
|
||||
/// </summary>
|
||||
public static class LineBreakType
|
||||
{
|
||||
/// <summary>
|
||||
/// Windows 系统。
|
||||
/// </summary>
|
||||
public const string Windows = "\r\n";
|
||||
|
||||
/// <summary>
|
||||
/// macOS 系统。
|
||||
/// </summary>
|
||||
public const string MacOs = "\r";
|
||||
|
||||
/// <summary>
|
||||
/// UNIX 系统(Linux)。
|
||||
/// </summary>
|
||||
public const string Unix = "\n";
|
||||
}
|
||||
}
|
41
src/ZonyLrcTools.Cli/Infrastructure/Lyric/LyricDownloader.cs
Normal file
41
src/ZonyLrcTools.Cli/Infrastructure/Lyric/LyricDownloader.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Threading.Tasks;
|
||||
using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
/// <summary>
|
||||
/// 歌词下载器的基类,定义了歌词下载器的常规逻辑。
|
||||
/// </summary>
|
||||
public abstract class LyricDownloader : ILyricDownloader, ITransientDependency
|
||||
{
|
||||
public abstract string DownloaderName { get; }
|
||||
|
||||
public virtual async ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist)
|
||||
{
|
||||
var args = new LyricDownloaderArgs(songName, artist);
|
||||
await ValidateAsync(args);
|
||||
var downloadDataBytes = await DownloadDataAsync(args);
|
||||
return await GenerateLyricAsync(downloadDataBytes);
|
||||
}
|
||||
|
||||
protected virtual ValueTask ValidateAsync(LyricDownloaderArgs args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(args.SongName))
|
||||
{
|
||||
throw new ErrorCodeException(ErrorCodes.SongNameIsNull, attachObj: args);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(args.SongName) && string.IsNullOrEmpty(args.Artist))
|
||||
{
|
||||
throw new ErrorCodeException(ErrorCodes.SongNameAndArtistIsNull, attachObj: args);
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
protected abstract ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args);
|
||||
|
||||
protected abstract ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
public class LyricDownloaderArgs
|
||||
{
|
||||
public string SongName { get; set; }
|
||||
|
||||
public string Artist { get; set; }
|
||||
|
||||
public LyricDownloaderArgs(string songName, string artist)
|
||||
{
|
||||
SongName = songName;
|
||||
Artist = artist;
|
||||
}
|
||||
}
|
||||
}
|
126
src/ZonyLrcTools.Cli/Infrastructure/Lyric/LyricItem.cs
Normal file
126
src/ZonyLrcTools.Cli/Infrastructure/Lyric/LyricItem.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
/// <summary>
|
||||
/// 每一行歌词的对象。
|
||||
/// </summary>
|
||||
public class LyricItem : IComparable<LyricItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// 原始时间轴,格式类似于 [01:55.12]。
|
||||
/// </summary>
|
||||
public string OriginalTimeline => $"[{Minute:00}:{Second:00.00}]";
|
||||
|
||||
/// <summary>
|
||||
/// 歌词文本数据。
|
||||
/// </summary>
|
||||
public string LyricText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌词所在的时间(分)。
|
||||
/// </summary>
|
||||
public int Minute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌词所在的时间(秒)。
|
||||
/// </summary>
|
||||
public double Second { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序分数,用于一组歌词当中的排序权重。<br/>
|
||||
/// </summary>
|
||||
public double SortScore => Minute * 60 + Second;
|
||||
|
||||
/// <summary>
|
||||
/// 构建新的 <see cref="LyricItem"/> 对象。
|
||||
/// </summary>
|
||||
/// <param name="lyricText">原始的 Lyric 歌词。</param>
|
||||
public LyricItem(string lyricText)
|
||||
{
|
||||
var timeline = new Regex(@"\[\d+:\d+.\d+\]").Match(lyricText)
|
||||
.Value.Replace("]", string.Empty)
|
||||
.Replace("[", string.Empty)
|
||||
.Split(':');
|
||||
|
||||
if (int.TryParse(timeline[0], out var minute)) Minute = minute;
|
||||
if (double.TryParse(timeline[1], out var second)) Second = second;
|
||||
|
||||
LyricText = new Regex(@"(?<=\[\d+:\d+.\d+\]).+").Match(lyricText).Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造新的 <see cref="LyricItem"/> 对象。
|
||||
/// </summary>
|
||||
/// <param name="minute">歌词所在的时间(分)。</param>
|
||||
/// <param name="second">歌词所在的时间(秒)。</param>
|
||||
/// <param name="lyricText">歌词文本数据。</param>
|
||||
public LyricItem(int minute, double second, string lyricText)
|
||||
{
|
||||
Minute = minute;
|
||||
Second = second;
|
||||
LyricText = lyricText;
|
||||
}
|
||||
|
||||
public int CompareTo(LyricItem other)
|
||||
{
|
||||
if (SortScore > other.SortScore)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (SortScore < other.SortScore)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static bool operator >(LyricItem left, LyricItem right)
|
||||
{
|
||||
return left.SortScore > right.SortScore;
|
||||
}
|
||||
|
||||
public static bool operator <(LyricItem left, LyricItem right)
|
||||
{
|
||||
return left.SortScore < right.SortScore;
|
||||
}
|
||||
|
||||
public static bool operator ==(LyricItem left, LyricItem right)
|
||||
{
|
||||
return (int?) left?.SortScore == (int?) right?.SortScore;
|
||||
}
|
||||
|
||||
public static bool operator !=(LyricItem item1, LyricItem item2)
|
||||
{
|
||||
return !(item1 == item2);
|
||||
}
|
||||
|
||||
public static LyricItem operator +(LyricItem src, LyricItem dist)
|
||||
{
|
||||
return new LyricItem(src.Minute, src.Second, $"{src.LyricText} {dist.LyricText}");
|
||||
}
|
||||
|
||||
protected bool Equals(LyricItem other)
|
||||
{
|
||||
return LyricText == other.LyricText && Minute == other.Minute && Second.Equals(other.Second);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((LyricItem) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(LyricText, Minute, Second);
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{Minute:00}:{Second:00.00}]{LyricText}";
|
||||
}
|
||||
}
|
111
src/ZonyLrcTools.Cli/Infrastructure/Lyric/LyricItemCollection.cs
Normal file
111
src/ZonyLrcTools.Cli/Infrastructure/Lyric/LyricItemCollection.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Extensions;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
/// <summary>
|
||||
/// 歌词数据,包含多条歌词对象(<see cref="LyricItem"/>)。
|
||||
/// </summary>
|
||||
public class LyricItemCollection : List<LyricItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为纯音乐,当没有任何歌词数据的时候,属性值为 True。
|
||||
/// </summary>
|
||||
public bool IsPruneMusic => Count == 0;
|
||||
|
||||
public LyricItemCollectionOption Option { get; private set; }
|
||||
|
||||
public LyricItemCollection(LyricItemCollectionOption option)
|
||||
{
|
||||
Option = option;
|
||||
}
|
||||
|
||||
public static LyricItemCollection operator +(LyricItemCollection left, LyricItemCollection right)
|
||||
{
|
||||
if (right.IsPruneMusic)
|
||||
{
|
||||
return left;
|
||||
}
|
||||
|
||||
var option = left.Option;
|
||||
var newCollection = new LyricItemCollection(option);
|
||||
var indexDiff = left.Count - right.Count;
|
||||
|
||||
if (!option.IsOneLine)
|
||||
{
|
||||
left.ForEach(item => newCollection.Add(item));
|
||||
right.ForEach(item => newCollection.Add(item));
|
||||
|
||||
newCollection.Sort();
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
// 如果索引相等,直接根据索引快速匹配构建。
|
||||
if (indexDiff == 0)
|
||||
{
|
||||
newCollection.AddRange(left.Select((t, index) => t + right[index]));
|
||||
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
// 首先按照时间轴进行合并。
|
||||
var leftMarkDict = BuildMarkDictionary(left);
|
||||
var rightMarkDict = BuildMarkDictionary(right);
|
||||
|
||||
for (var leftIndex = 0; leftIndex < left.Count; leftIndex++)
|
||||
{
|
||||
var rightItem = right.Find(lyric => Math.Abs(lyric.SortScore - left[leftIndex].SortScore) < 0.001);
|
||||
if (rightItem != null)
|
||||
{
|
||||
newCollection.Add(left[leftIndex] + rightItem);
|
||||
var rightIndex = right.FindIndex(item => item == rightItem);
|
||||
rightMarkDict[rightIndex] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
newCollection.Add(left[leftIndex]);
|
||||
}
|
||||
|
||||
leftMarkDict[leftIndex] = true;
|
||||
}
|
||||
|
||||
// 遍历未处理的歌词项,将其添加到返回集合当中。
|
||||
var leftWaitProcessIndex = leftMarkDict
|
||||
.Where(item => item.Value)
|
||||
.Select(pair => pair.Key);
|
||||
var rightWaitProcessIndex = rightMarkDict
|
||||
.Where(item => item.Value)
|
||||
.Select(pair => pair.Key);
|
||||
|
||||
leftWaitProcessIndex.Foreach(index => newCollection.Add(left[index]));
|
||||
rightWaitProcessIndex.Foreach(index => newCollection.Add(right[index]));
|
||||
|
||||
newCollection.Sort();
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据歌词集合构建一个索引状态字典。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 这个索引字典用于标识每个索引的歌词是否被处理,为 True 则为已处理,为 False 为未处理。
|
||||
/// </remarks>
|
||||
/// <param name="items">等待构建的歌词集合实例。</param>
|
||||
private static Dictionary<int, bool> BuildMarkDictionary(LyricItemCollection items)
|
||||
{
|
||||
return items
|
||||
.Select((item, index) => new {index, item})
|
||||
.ToDictionary(item => item.index, item => false);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var lyricBuilder = new StringBuilder();
|
||||
ForEach(lyric => lyricBuilder.Append(lyric).Append("\r\n"));
|
||||
return lyricBuilder.ToString().TrimEnd("\r\n");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
public class LyricItemCollectionFactory : ILyricItemCollectionFactory, ITransientDependency
|
||||
{
|
||||
private readonly ToolOptions _options;
|
||||
|
||||
public LyricItemCollectionFactory(IOptions<ToolOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public LyricItemCollection Build(string sourceLyric, string translateLyric = null)
|
||||
{
|
||||
var items = new LyricItemCollection(_options.LyricOption);
|
||||
if (string.IsNullOrEmpty(sourceLyric))
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
var regex = new Regex(@"\[\d+:\d+.\d+\].+\n?");
|
||||
foreach (Match match in regex.Matches(sourceLyric))
|
||||
{
|
||||
items.Add(new LyricItem(match.Value));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
public class LyricItemCollectionOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 双语歌词是否合并为一行。
|
||||
/// </summary>
|
||||
public bool IsOneLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 换行符格式,取值来自 <see cref="LineBreakType"/> 常量类。
|
||||
/// </summary>
|
||||
public string LineBreak { get; set; } = LineBreakType.Windows;
|
||||
|
||||
public static readonly LyricItemCollectionOption NullInstance = new();
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
{
|
||||
public class GetLyricRequest
|
||||
{
|
||||
public GetLyricRequest(long songId)
|
||||
{
|
||||
OS = "osx";
|
||||
Id = songId;
|
||||
Lv = Kv = Tv = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求的操作系统。
|
||||
/// </summary>
|
||||
[JsonProperty("os")]
|
||||
public string OS { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌曲的 SID 值。
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public long Id { get; }
|
||||
|
||||
[JsonProperty("lv")] public int Lv { get; }
|
||||
|
||||
[JsonProperty("kv")] public int Kv { get; }
|
||||
|
||||
[JsonProperty("tv")] public int Tv { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
{
|
||||
public class GetLyricResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 原始的歌词。
|
||||
/// </summary>
|
||||
[JsonProperty("lrc")]
|
||||
public InnerLyric OriginalLyric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 卡拉 OK 歌词。
|
||||
/// </summary>
|
||||
[JsonProperty("klyric")]
|
||||
public InnerLyric KaraokeLyric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果存在翻译歌词,则本项内容为翻译歌词。
|
||||
/// </summary>
|
||||
[JsonProperty("tlyric")]
|
||||
public InnerLyric TranslationLyric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态码。
|
||||
/// </summary>
|
||||
[JsonProperty("code")]
|
||||
public string StatusCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 歌词 JSON 类型
|
||||
/// </summary>
|
||||
public class InnerLyric
|
||||
{
|
||||
[JsonProperty("version")] public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 具体的歌词数据。
|
||||
/// </summary>
|
||||
[JsonProperty("lyric")]
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
{
|
||||
public class GetSongDetailsRequest
|
||||
{
|
||||
public GetSongDetailsRequest(int songId)
|
||||
{
|
||||
SongId = songId;
|
||||
SongIds = $"%5B{songId}%5D";
|
||||
}
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int SongId { get; }
|
||||
|
||||
[JsonProperty("ids")]
|
||||
public string SongIds { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
{
|
||||
public class SongSearchRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// CSRF 标识,一般为空即可,接口不会进行校验。
|
||||
/// </summary>
|
||||
[JsonProperty("csrf_token")]
|
||||
public string CsrfToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 需要搜索的内容,一般是歌曲名 + 歌手的格式。
|
||||
/// </summary>
|
||||
[JsonProperty("s")]
|
||||
public string SearchKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 页偏移量。
|
||||
/// </summary>
|
||||
[JsonProperty("offset")]
|
||||
public int Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 搜索类型。
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否获取全部的搜索结果。
|
||||
/// </summary>
|
||||
[JsonProperty("total")]
|
||||
public bool IsTotal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每页的最大结果容量。
|
||||
/// </summary>
|
||||
[JsonProperty("limit")]
|
||||
public int Limit { get; set; }
|
||||
|
||||
public SongSearchRequest()
|
||||
{
|
||||
CsrfToken = string.Empty;
|
||||
Type = 1;
|
||||
Offset = 0;
|
||||
IsTotal = true;
|
||||
Limit = 5;
|
||||
}
|
||||
|
||||
public SongSearchRequest(string musicName, string artistName) : this()
|
||||
{
|
||||
SearchKey = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
{
|
||||
public class SongSearchResponse
|
||||
{
|
||||
[JsonProperty("result")] public InnerListItemModel Items { get; set; }
|
||||
|
||||
[JsonProperty("code")] public int StatusCode { get; set; }
|
||||
|
||||
public int GetFirstSongId()
|
||||
{
|
||||
return Items.SongItems[0].Id;
|
||||
}
|
||||
}
|
||||
|
||||
public class InnerListItemModel
|
||||
{
|
||||
[JsonProperty("songs")] public IList<SongModel> SongItems { get; set; }
|
||||
|
||||
[JsonProperty("songCount")] public int SongCount { get; set; }
|
||||
}
|
||||
|
||||
public class SongModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 歌曲的名称。
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌曲的 Sid (Song Id)。
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌曲的演唱者。
|
||||
/// </summary>
|
||||
[JsonProperty("artists")]
|
||||
public IList<SongArtistModel> Artists { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌曲的专辑信息。
|
||||
/// </summary>
|
||||
[JsonProperty("album")]
|
||||
public SongAlbumModel Album { get; set; }
|
||||
}
|
||||
|
||||
public class SongArtistModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 歌手/艺术家的名称。
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class SongAlbumModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 专辑的名称。
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 专辑图像的 Url 地址。
|
||||
/// </summary>
|
||||
[JsonProperty("img1v1Url")]
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
{
|
||||
public static class SongSearchResponseStatusCode
|
||||
{
|
||||
public const int Success = 200;
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Network;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
|
||||
{
|
||||
public class NetEaseLyricDownloader : LyricDownloader
|
||||
{
|
||||
public override string DownloaderName => InternalLyricDownloaderNames.NetEase;
|
||||
|
||||
private readonly IWarpHttpClient _warpHttpClient;
|
||||
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
|
||||
|
||||
private const string NetEaseSearchMusicUrl = @"https://music.163.com/api/search/get/web";
|
||||
private const string NetEaseGetLyricUrl = @"https://music.163.com/api/song/lyric";
|
||||
private const string NetEaseRequestReferer = @"https://music.163.com";
|
||||
private const string NetEaseRequestContentType = @"application/x-www-form-urlencoded";
|
||||
|
||||
public NetEaseLyricDownloader(IWarpHttpClient warpHttpClient,
|
||||
ILyricItemCollectionFactory lyricItemCollectionFactory)
|
||||
{
|
||||
_warpHttpClient = warpHttpClient;
|
||||
_lyricItemCollectionFactory = lyricItemCollectionFactory;
|
||||
}
|
||||
|
||||
protected override async ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args)
|
||||
{
|
||||
var searchResult = await _warpHttpClient.PostAsync<SongSearchResponse>(
|
||||
NetEaseSearchMusicUrl,
|
||||
new SongSearchRequest(args.SongName, args.Artist),
|
||||
true,
|
||||
msg =>
|
||||
{
|
||||
msg.Headers.Referrer = new Uri(NetEaseRequestReferer);
|
||||
if (msg.Content != null)
|
||||
{
|
||||
msg.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(NetEaseRequestContentType);
|
||||
}
|
||||
});
|
||||
|
||||
ValidateSongSearchResponse(searchResult, args);
|
||||
|
||||
var lyricResponse = await _warpHttpClient.GetAsync(
|
||||
NetEaseGetLyricUrl,
|
||||
new GetLyricRequest(searchResult.GetFirstSongId()),
|
||||
msg => msg.Headers.Referrer = new Uri(NetEaseRequestReferer));
|
||||
|
||||
return Encoding.UTF8.GetBytes(lyricResponse);
|
||||
}
|
||||
|
||||
protected override async ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data)
|
||||
{
|
||||
await ValueTask.CompletedTask;
|
||||
|
||||
var json = JsonConvert.DeserializeObject<GetLyricResponse>(Encoding.UTF8.GetString(data));
|
||||
if (json?.OriginalLyric == null)
|
||||
{
|
||||
return new LyricItemCollection(LyricItemCollectionOption.NullInstance);
|
||||
}
|
||||
|
||||
return _lyricItemCollectionFactory.Build(
|
||||
json.OriginalLyric.Text,
|
||||
json.TranslationLyric.Text);
|
||||
}
|
||||
|
||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricDownloaderArgs args)
|
||||
{
|
||||
if (response?.StatusCode != SongSearchResponseStatusCode.Success)
|
||||
{
|
||||
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, attachObj: args);
|
||||
}
|
||||
|
||||
if (response.Items?.SongCount <= 0)
|
||||
{
|
||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic.JsonModel
|
||||
{
|
||||
public class SongSearchRequest
|
||||
{
|
||||
[JsonProperty("ct")] public int UnknownParameter1 { get; set; }
|
||||
|
||||
[JsonProperty("qqmusic_ver")] public int ClientVersion { get; set; }
|
||||
|
||||
[JsonProperty("new_json")] public int UnknownParameter2 { get; set; }
|
||||
|
||||
[JsonProperty("remoteplace")] public string RemotePlace { get; set; }
|
||||
|
||||
[JsonProperty("t")] public int UnknownParameter3 { get; set; }
|
||||
|
||||
[JsonProperty("aggr")] public int UnknownParameter4 { get; set; }
|
||||
|
||||
[JsonProperty("cr")] public int UnknownParameter5 { get; set; }
|
||||
|
||||
[JsonProperty("catZhida")] public int UnknownParameter6 { get; set; }
|
||||
|
||||
[JsonProperty("lossless")] public int LossLess { get; set; }
|
||||
|
||||
[JsonProperty("flag_qc")] public int UnknownParameter7 { get; set; }
|
||||
|
||||
[JsonProperty("p")] public int Page { get; set; }
|
||||
|
||||
[JsonProperty("n")] public int Limit { get; set; }
|
||||
|
||||
[JsonProperty("w")] public string Keyword { get; set; }
|
||||
|
||||
[JsonProperty("g_tk")] public int UnknownParameter8 { get; set; }
|
||||
|
||||
[JsonProperty("hostUin")] public int UnknownParameter9 { get; set; }
|
||||
|
||||
[JsonProperty("format")] public string ResultFormat { get; set; }
|
||||
|
||||
[JsonProperty("inCharset")] public string InCharset { get; set; }
|
||||
|
||||
[JsonProperty("outCharset")] public string OutCharset { get; set; }
|
||||
|
||||
[JsonProperty("notice")] public int UnknownParameter10 { get; set; }
|
||||
|
||||
[JsonProperty("platform")] public string Platform { get; set; }
|
||||
|
||||
[JsonProperty("needNewCode")] public int UnknownParameter11 { get; set; }
|
||||
|
||||
public SongSearchRequest()
|
||||
{
|
||||
UnknownParameter1 = 24;
|
||||
ClientVersion = 1298;
|
||||
UnknownParameter2 = 1;
|
||||
RemotePlace = "txt.yqq.song";
|
||||
UnknownParameter3 = 0;
|
||||
UnknownParameter4 = 1;
|
||||
UnknownParameter5 = 1;
|
||||
UnknownParameter6 = 1;
|
||||
LossLess = 0;
|
||||
UnknownParameter7 = 0;
|
||||
Page = 1;
|
||||
Limit = 5;
|
||||
UnknownParameter8 = 5381;
|
||||
UnknownParameter9 = 0;
|
||||
ResultFormat = "json";
|
||||
InCharset = "utf8";
|
||||
OutCharset = "utf8";
|
||||
UnknownParameter10 = 0;
|
||||
Platform = "yqq";
|
||||
UnknownParameter11 = 0;
|
||||
}
|
||||
|
||||
public SongSearchRequest(string musicName, string artistName) : this()
|
||||
{
|
||||
Keyword = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic.JsonModel
|
||||
{
|
||||
public class SongSearchResponse
|
||||
{
|
||||
[JsonProperty("code")]
|
||||
public int StatusCode { get; set; }
|
||||
|
||||
[JsonProperty("data")]
|
||||
public QQMusicInnerDataModel Data { get; set; }
|
||||
}
|
||||
|
||||
public class QQMusicInnerDataModel
|
||||
{
|
||||
[JsonProperty("song")]
|
||||
public QQMusicInnerSongModel Song { get; set; }
|
||||
}
|
||||
|
||||
public class QQMusicInnerSongModel
|
||||
{
|
||||
[JsonProperty("list")]
|
||||
public List<QQMusicInnerSongItem> SongItems { get; set; }
|
||||
}
|
||||
|
||||
public class QQMusicInnerSongItem
|
||||
{
|
||||
[JsonProperty("mid")]
|
||||
public string SongId { get; set; }
|
||||
}
|
||||
|
||||
public class AlbumInfo
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public long Id { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using System.Threading.Tasks;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Network;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic
|
||||
{
|
||||
public class QQLyricDownloader : LyricDownloader
|
||||
{
|
||||
public override string DownloaderName => InternalLyricDownloaderNames.QQ;
|
||||
|
||||
private readonly IWarpHttpClient _warpHttpClient;
|
||||
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
|
||||
|
||||
public QQLyricDownloader(IWarpHttpClient warpHttpClient,
|
||||
ILyricItemCollectionFactory lyricItemCollectionFactory)
|
||||
{
|
||||
_warpHttpClient = warpHttpClient;
|
||||
_lyricItemCollectionFactory = lyricItemCollectionFactory;
|
||||
}
|
||||
|
||||
protected override ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
protected override ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user