refactor: Common components are moved to the Common library.

This commit is contained in:
real-zony
2022-10-06 13:02:20 +08:00
parent ecab0e0f5c
commit 740e8f4c63
64 changed files with 84 additions and 150 deletions

View File

@@ -0,0 +1,38 @@
using Newtonsoft.Json;
// ReSharper disable InconsistentNaming
namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
{
public class GetLyricRequest
{
public GetLyricRequest(long songId)
{
OS = "ios";
Id = songId;
Lv = Kv = Tv = Rv = -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; }
[JsonProperty("rv")] public int Rv { get; set; }
[JsonProperty("crypto")] public string Protocol { get; set; } = "api";
}
}

View File

@@ -0,0 +1,51 @@
using Newtonsoft.Json;
namespace ZonyLrcTools.Common.Lyrics.Providers.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("romalrc")]
public InnerLyric RomaLyric { 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; }
}
}

View File

@@ -0,0 +1,17 @@
using Newtonsoft.Json;
namespace ZonyLrcTools.Common.Lyrics.Providers.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; }
}
}

View File

@@ -0,0 +1,67 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using Newtonsoft.Json;
namespace ZonyLrcTools.Common.Lyrics.Providers.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; }
[JsonProperty("crypto")] public string Crypto { get; set; } = "weapi";
public SongSearchRequest()
{
CsrfToken = string.Empty;
Type = 1;
Offset = 0;
IsTotal = true;
Limit = 10;
}
public SongSearchRequest(string musicName, string artistName, int limit = 10) : this()
{
// Remove all the brackets and the content inside them.
var regex = new Regex(@"\([^)]*\)");
musicName = regex.Replace(musicName, string.Empty);
SearchKey = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8);
Limit = limit;
}
}
}

View File

@@ -0,0 +1,91 @@
using Newtonsoft.Json;
namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
{
public class SongSearchResponse
{
[JsonProperty("result")] public InnerListItemModel Items { get; set; }
[JsonProperty("code")] public int StatusCode { get; set; }
public int GetFirstMatchSongId(string songName, long? duration)
{
var perfectMatch = Items.SongItems.FirstOrDefault(x => x.Name == songName);
if (perfectMatch != null)
{
return perfectMatch.Id;
}
if (duration is null or 0)
{
return Items.SongItems.First().Id;
}
return Items.SongItems.OrderBy(t => Math.Abs(t.Duration - duration.Value)).First().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; }
/// <summary>
/// 歌曲的实际长度。
/// </summary>
[JsonProperty("duration")]
public long Duration { 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; }
}
}

View File

@@ -0,0 +1,7 @@
namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
{
public static class SongSearchResponseStatusCode
{
public const int Success = 200;
}
}

View File

@@ -0,0 +1,93 @@
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Infrastructure.Network;
using ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel;
namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase
{
public class NetEaseLyricDownloader : LyricDownloader
{
public override string DownloaderName => InternalLyricDownloaderNames.NetEase;
private readonly IWarpHttpClient _warpHttpClient;
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
private readonly GlobalOptions _options;
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,
IOptions<GlobalOptions> options)
{
_warpHttpClient = warpHttpClient;
_lyricItemCollectionFactory = lyricItemCollectionFactory;
_options = options.Value;
}
protected override async ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args)
{
var searchResult = await _warpHttpClient.PostAsync<SongSearchResponse>(
NetEaseSearchMusicUrl,
new SongSearchRequest(args.SongName, args.Artist, _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth),
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.GetFirstMatchSongId(args.SongName, args.Duration)),
msg => msg.Headers.Referrer = new Uri(NetEaseRequestReferer));
return Encoding.UTF8.GetBytes(lyricResponse);
}
protected override async ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data, LyricDownloaderArgs args)
{
await ValueTask.CompletedTask;
var json = JsonConvert.DeserializeObject<GetLyricResponse>(Encoding.UTF8.GetString(data));
if (json?.OriginalLyric == null || string.IsNullOrEmpty(json.OriginalLyric.Text))
{
return new LyricItemCollection(null);
}
if (json.OriginalLyric.Text.Contains("纯音乐,请欣赏"))
{
return new LyricItemCollection(null);
}
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);
}
}
}
}