diff --git a/Jellyfin.Plugin.Danmu.Test/YoukuApiTest.cs b/Jellyfin.Plugin.Danmu.Test/YoukuApiTest.cs index b5d25ea..963fd10 100644 --- a/Jellyfin.Plugin.Danmu.Test/YoukuApiTest.cs +++ b/Jellyfin.Plugin.Danmu.Test/YoukuApiTest.cs @@ -24,7 +24,7 @@ public class YoukuApiTest [TestMethod] public void TestSearch() { - var keyword = "夏洛特"; + var keyword = "一拳超人"; var api = new YoukuApi(loggerFactory); Task.Run(async () => diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuSearchResult.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuSearchResult.cs new file mode 100644 index 0000000..afca21b --- /dev/null +++ b/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuSearchResult.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Jellyfin.Plugin.Danmu.Scrapers.Youku.Entity +{ + public class YoukuSearchResult + { + [JsonPropertyName("pageComponentList")] + public List PageComponentList { get; set; } + } + + public class YoukuSearchComponent + { + [JsonPropertyName("commonData")] + public YoukuSearchComponentData CommonData { get; set; } + } + + public class YoukuSearchComponentData + { + [JsonPropertyName("showId")] + public string ShowId { get; set; } + + [JsonPropertyName("episodeTotal")] + public int EpisodeTotal { get; set; } + + [JsonPropertyName("feature")] + public string Feature { get; set; } + + [JsonPropertyName("isYouku")] + public int IsYouku { get; set; } + + [JsonPropertyName("hasYouku")] + public int HasYouku { get; set; } + + [JsonPropertyName("ugcSupply")] + public int UgcSupply { get; set; } + + [JsonPropertyName("titleDTO")] + public YoukuSearchTitle TitleDTO { get; set; } + } + + public class YoukuSearchTitle + { + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } + } +} \ No newline at end of file diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuTrackInfo.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuTrackInfo.cs deleted file mode 100644 index c249424..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuTrackInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Youku.Entity -{ - public class YoukuTrackInfo - { - [JsonPropertyName("group_id")] - public string GroupID { get; set; } - - [JsonPropertyName("object_type")] - public int ObjectType { get; set; } - - [JsonPropertyName("object_title")] - public string ObjectTitle { get; set; } - - [JsonPropertyName("object_url")] - public string ObjectUrl { get; set; } - - [JsonIgnore] - public int? Year { get; set; } - - [JsonIgnore] - public string Type { get; set; } - - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuVideo.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuVideo.cs index 74fa86b..cd0a489 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuVideo.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Youku/Entity/YoukuVideo.cs @@ -15,5 +15,17 @@ public class YoukuVideo [JsonPropertyName("videos")] public List Videos { get; set; } = new List(); + [JsonIgnore] + public string ID { get; set; } + + [JsonIgnore] + public string Title { get; set; } + + [JsonIgnore] + public int? Year { get; set; } + + [JsonIgnore] + public string Type { get; set; } + } } diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/Youku.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/Youku.cs index 08f0021..c4db22a 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Youku/Youku.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Youku/Youku.cs @@ -45,8 +45,8 @@ public Youku(ILoggerFactory logManager) var videos = await this._api.SearchAsync(searchName, CancellationToken.None).ConfigureAwait(false); foreach (var video in videos) { - var videoId = video.GroupID; - var title = video.ObjectTitle; + var videoId = video.ID; + var title = video.Title; var pubYear = video.Year; var isMovieItemType = item is MediaBrowser.Controller.Entities.Movies.Movie; diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/YoukuApi.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/YoukuApi.cs index 7329505..76d277a 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Youku/YoukuApi.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Youku/YoukuApi.cs @@ -48,60 +48,61 @@ public YoukuApi(ILoggerFactory loggerFactory) } - public async Task> SearchAsync(string keyword, CancellationToken cancellationToken) + public async Task> SearchAsync(string keyword, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(keyword)) { - return new List(); + return new List(); } var cacheKey = $"search_{keyword}"; var expiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) }; - if (_memoryCache.TryGetValue>(cacheKey, out var searchResult)) + if (_memoryCache.TryGetValue>(cacheKey, out var cacheValue)) { - return searchResult; + return cacheValue; } await this.LimitRequestFrequently(); keyword = HttpUtility.UrlEncode(keyword); - var url = $"https://search.youku.com/search_video?keyword={keyword}"; + var ua = HttpUtility.UrlEncode(HTTP_USER_AGENT); + var url = $"https://search.youku.com/api/search?keyword={keyword}&userAgent={ua}&site=1&categories=0&ftype=0&ob=0&pg=1"; var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); - var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - - var result = new List(); - var matchs = moviesReg.Matches(body); - foreach (Match match in matchs) + var result = new List(); + var searchResult = await response.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken).ConfigureAwait(false); + if (searchResult != null && searchResult.PageComponentList != null) { - var text = HttpUtility.HtmlDecode(match.Groups[1].Value); - var trackInfoJson = trackInfoReg.FirstMatchGroup(text); - try + foreach (YoukuSearchComponent component in searchResult.PageComponentList) { - if (string.IsNullOrEmpty(trackInfoJson)) + if (component.CommonData == null + || component.CommonData.TitleDTO == null + || component.CommonData.HasYouku != 1 + || component.CommonData.IsYouku != 1 + || component.CommonData.UgcSupply == 1) { continue; } - var trackInfo = JsonSerializer.Deserialize(trackInfoJson); - if (trackInfo != null && trackInfo.ObjectType == 101 && !trackInfo.ObjectTitle.Contains("中配版")) + if (component.CommonData.TitleDTO.DisplayName.Contains("中配版")) { - var featureInfo = featureReg.FirstMatchGroup(text); - var year = yearReg.FirstMatch(featureInfo).ToInt(); - trackInfo.Year = year > 0 ? year : null; - trackInfo.Type = featureInfo.Contains("电影") ? "movie" : "tv"; - trackInfo.ObjectTitle = unusedReg.Replace(trackInfo.ObjectTitle, ""); - result.Add(trackInfo); + continue; } - } - catch (Exception ex) - { + var year = yearReg.FirstMatch(component.CommonData.Feature).ToInt(); + result.Add(new YoukuVideo() + { + ID = component.CommonData.ShowId, + Type = component.CommonData.Feature.Contains("电影") ? "movie" : "tv", + Year = year > 0 ? year : null, + Title = unusedReg.Replace(component.CommonData.TitleDTO.DisplayName, ""), + Total = component.CommonData.EpisodeTotal + }); } } - _memoryCache.Set>(cacheKey, result, expiredOption); + _memoryCache.Set>(cacheKey, result, expiredOption); return result; } @@ -119,6 +120,8 @@ public async Task> SearchAsync(string keyword, Cancellation return video; } + // 获取影片信息:https://openapi.youku.com/v2/shows/show.json?client_id=53e6cc67237fc59a&package=com.huawei.hwvplayer.youku&show_id=0b39c5b6569311e5b2ad + // 获取影片剧集信息:https://openapi.youku.com/v2/shows/videos.json?client_id=53e6cc67237fc59a&package=com.huawei.hwvplayer.youku&ext=show&show_id=XMTM1MTc4MDU3Ng== var url = $"https://openapi.youku.com/v2/shows/videos.json?client_id=53e6cc67237fc59a&package=com.huawei.hwvplayer.youku&ext=show&show_id={id}"; var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode();