diff --git a/Jellyfin.Plugin.MetaShark.Test/DoubanApiTest.cs b/Jellyfin.Plugin.MetaShark.Test/DoubanApiTest.cs index 9dee4e5..0f616c1 100644 --- a/Jellyfin.Plugin.MetaShark.Test/DoubanApiTest.cs +++ b/Jellyfin.Plugin.MetaShark.Test/DoubanApiTest.cs @@ -60,7 +60,7 @@ namespace Jellyfin.Plugin.MetaShark.Test [TestMethod] - public void TestGetVideoByBvidAsync() + public void TestGetVideoBySidAsync() { var sid = "26654184"; @@ -80,6 +80,28 @@ namespace Jellyfin.Plugin.MetaShark.Test }).GetAwaiter().GetResult(); } + [TestMethod] + public void TestFixGetImage() + { + // 演员入驻了豆瓣, 下载的不是演员的头像#5 + var sid = "35460157"; + + var api = new DoubanApi(loggerFactory); + + Task.Run(async () => + { + try + { + var result = await api.GetMovieAsync(sid, CancellationToken.None); + Assert.AreEqual("https://img2.doubanio.com/view/celebrity/raw/public/p1598199472.61.jpg", result.Celebrities.First(x => x.Name == "刘陆").Img); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + }).GetAwaiter().GetResult(); + } + [TestMethod] public void TestGetCelebritiesBySidAsync() { @@ -100,5 +122,26 @@ namespace Jellyfin.Plugin.MetaShark.Test } }).GetAwaiter().GetResult(); } + + [TestMethod] + public void TestGetCelebritiesByCidAsync() + { + var sid = "1340364"; + + var api = new DoubanApi(loggerFactory); + + Task.Run(async () => + { + try + { + var result = await api.GetCelebrityAsync(sid, CancellationToken.None); + TestContext.WriteLine(result.ToJson()); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + }).GetAwaiter().GetResult(); + } } } diff --git a/Jellyfin.Plugin.MetaShark.Test/MovieProviderTest.cs b/Jellyfin.Plugin.MetaShark.Test/MovieProviderTest.cs index 00a2fa3..a54f73a 100644 --- a/Jellyfin.Plugin.MetaShark.Test/MovieProviderTest.cs +++ b/Jellyfin.Plugin.MetaShark.Test/MovieProviderTest.cs @@ -3,6 +3,7 @@ using Jellyfin.Plugin.MetaShark.Core; using Jellyfin.Plugin.MetaShark.Providers; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; using Moq; using System; @@ -69,5 +70,26 @@ namespace Jellyfin.Plugin.MetaShark.Test }).GetAwaiter().GetResult(); } + [TestMethod] + public void TestGetMetadataByTMDB() + { + var info = new MovieInfo() { Name = "人生大事", MetadataLanguage = "zh", ProviderIds = new Dictionary { { MetadataProvider.Tmdb.ToString(), "945664" } } }; + var doubanApi = new DoubanApi(loggerFactory); + var tmdbApi = new TmdbApi(loggerFactory); + var omdbApi = new OmdbApi(loggerFactory); + var httpClientFactory = new DefaultHttpClientFactory(); + var libraryManagerStub = new Mock(); + + Task.Run(async () => + { + var provider = new MovieProvider(httpClientFactory, loggerFactory, libraryManagerStub.Object, doubanApi, tmdbApi, omdbApi); + var result = await provider.GetMetadata(info, CancellationToken.None); + Assert.IsNotNull(result); + + var str = result.ToJson(); + Console.WriteLine(result.ToJson()); + }).GetAwaiter().GetResult(); + } + } } diff --git a/Jellyfin.Plugin.MetaShark/Api/DoubanApi.cs b/Jellyfin.Plugin.MetaShark/Api/DoubanApi.cs index 66ee4c1..fe58491 100644 --- a/Jellyfin.Plugin.MetaShark/Api/DoubanApi.cs +++ b/Jellyfin.Plugin.MetaShark/Api/DoubanApi.cs @@ -62,7 +62,7 @@ namespace Jellyfin.Plugin.MetaShark.Api Regex regSite = new Regex(@"官方网站: (.+?)\n", RegexOptions.Compiled); Regex regNameMath = new Regex(@"(.+第\w季|[\w\uff1a\uff01\uff0c\u00b7]+)\s*(.*)", RegexOptions.Compiled); Regex regRole = new Regex(@"\([饰|配] (.+?)\)", RegexOptions.Compiled); - Regex regBackgroundImage = new Regex(@"url\((.+?)\)", RegexOptions.Compiled); + Regex regBackgroundImage = new Regex(@"url\(([^)]+?)\)$", RegexOptions.Compiled); Regex regGender = new Regex(@"性别: \n(.+?)\n", RegexOptions.Compiled); Regex regConstellation = new Regex(@"星座: \n(.+?)\n", RegexOptions.Compiled); Regex regBirthdate = new Regex(@"出生日期: \n(.+?)\n", RegexOptions.Compiled); @@ -336,7 +336,7 @@ namespace Jellyfin.Plugin.MetaShark.Api var celebrityImg = celebrityImgStr.GetMatchGroup(this.regBackgroundImage); var celebrityNameStr = node.GetText("div.info a.name") ?? string.Empty; var arr = celebrityNameStr.Split(" "); - var celebrityName = arr.Length > 1 ? arr[0] : string.Empty; + var celebrityName = arr.Length > 1 ? arr[0] : celebrityNameStr; var celebrityRoleStr = node.GetText("div.info span.role") ?? string.Empty; var celebrityRole = celebrityRoleStr.GetMatchGroup(this.regRole); var arrRole = celebrityRoleStr.Split(" "); @@ -393,7 +393,10 @@ namespace Jellyfin.Plugin.MetaShark.Api if (contentNode != null) { var img = contentNode.GetAttr("#headline .nbg img", "src") ?? string.Empty; - var name = contentNode.GetText("h1") ?? string.Empty; + var nameStr = contentNode.GetText("h1") ?? string.Empty; + var arr = nameStr.Split(" "); + var name = arr.Length > 1 ? arr[0] : nameStr; + var intro = contentNode.GetText("#intro span.all") ?? string.Empty; if (string.IsNullOrEmpty(intro)) { @@ -434,6 +437,59 @@ namespace Jellyfin.Plugin.MetaShark.Api return null; } + + public async Task> SearchCelebrityAsync(string keyword, CancellationToken cancellationToken) + { + var list = new List(); + if (string.IsNullOrEmpty(keyword)) + { + return list; + } + + var cacheKey = $"search_celebrity_{keyword}"; + var expiredOption = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) }; + List searchResult; + if (_memoryCache.TryGetValue>(cacheKey, out searchResult)) + { + return searchResult; + } + + + keyword = HttpUtility.UrlEncode(keyword); + var url = $"https://movie.douban.com/celebrities/search?search_text={keyword}"; + var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); + if (!response.IsSuccessStatusCode) + { + return list; + } + + var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + var context = BrowsingContext.New(); + var doc = await context.OpenAsync(req => req.Content(body), cancellationToken).ConfigureAwait(false); + var elements = doc.QuerySelectorAll("div.article .result"); + + foreach (var el in elements) + { + + var celebrity = new DoubanCelebrity(); + var img = el.GetAttr("div.pic img", "src") ?? string.Empty; + var href = el.GetAttr("h3>a", "href") ?? string.Empty; + var cid = href.GetMatchGroup(this.regId); + var nameStr = el.GetText("h3>a") ?? string.Empty; + var arr = nameStr.Split(" "); + var name = arr.Length > 1 ? arr[0] : nameStr; + + celebrity.Name = name; + celebrity.Img = img; + celebrity.Id = cid; + list.Add(celebrity); + } + + + _memoryCache.Set>(cacheKey, list, expiredOption); + return list; + } + public async Task> GetWallpaperBySidAsync(string sid, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(sid)) diff --git a/Jellyfin.Plugin.MetaShark/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.MetaShark/Configuration/PluginConfiguration.cs index 1d04943..40e8b35 100644 --- a/Jellyfin.Plugin.MetaShark/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.MetaShark/Configuration/PluginConfiguration.cs @@ -26,9 +26,6 @@ public enum SomeOptions public class PluginConfiguration : BasePluginConfiguration { public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - - public string Pattern { get; set; } = @"(S\d{2}|E\d{2}|HDR|\d{3,4}p|WEBRip|WEB|YIFY|BrRip|BluRay|H265|H264|x264|AAC\.\d\.\d|AAC|HDTV|mkv|mp4)|(\[.*\])|(\-\w+|\{.*\}|【.*】|\(.*\)|\d+MB)|(\.|\-)"; - public bool EnableTmdb { get; set; } = true; public bool EnableTmdbSearch { get; set; } = false; diff --git a/Jellyfin.Plugin.MetaShark/Configuration/configPage.html b/Jellyfin.Plugin.MetaShark/Configuration/configPage.html index c7defac..14f266d 100644 --- a/Jellyfin.Plugin.MetaShark/Configuration/configPage.html +++ b/Jellyfin.Plugin.MetaShark/Configuration/configPage.html @@ -77,7 +77,8 @@ .addEventListener('pageshow', function () { Dashboard.showLoadingMsg(); ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) { - document.querySelector('#current_version').value = "v" + config.Version; + $('#current_version').text("v" + config.Version); + document.querySelector('#DoubanCookies').value = config.DoubanCookies; document.querySelector('#EnableTmdb').checked = config.EnableTmdb; document.querySelector('#EnableTmdbSearch').checked = config.EnableTmdbSearch; diff --git a/Jellyfin.Plugin.MetaShark/Controllers/MetaBotController.cs b/Jellyfin.Plugin.MetaShark/Controllers/MetaBotController.cs index d51cb53..0b5c4ce 100644 --- a/Jellyfin.Plugin.MetaShark/Controllers/MetaBotController.cs +++ b/Jellyfin.Plugin.MetaShark/Controllers/MetaBotController.cs @@ -40,9 +40,8 @@ namespace Jellyfin.Plugin.MetaShark.Controllers /// - /// 获取弹幕文件内容. + /// 代理访问图片. /// - /// xml弹幕文件内容 [Route("proxy/image")] [HttpGet] public async Task ProxyImage(string url) @@ -54,7 +53,22 @@ namespace Jellyfin.Plugin.MetaShark.Controllers } var httpClient = GetHttpClient(); - return await httpClient.GetStreamAsync(url).ConfigureAwait(false); + var response = await httpClient.GetAsync(url); + var stream = await response.Content.ReadAsStreamAsync(); + + Response.StatusCode = (int)response.StatusCode; + if (response.Content.Headers.ContentType != null) + { + Response.ContentType = response.Content.Headers.ContentType.ToString(); + } + Response.ContentLength = response.Content.Headers.ContentLength; + + foreach (var header in response.Headers) + { + Response.Headers.Add(header.Key, header.Value.First()); + } + + return stream; } private HttpClient GetHttpClient() diff --git a/Jellyfin.Plugin.MetaShark/Core/ElementExtension.cs b/Jellyfin.Plugin.MetaShark/Core/ElementExtension.cs index 38d6d0c..63566be 100644 --- a/Jellyfin.Plugin.MetaShark/Core/ElementExtension.cs +++ b/Jellyfin.Plugin.MetaShark/Core/ElementExtension.cs @@ -20,15 +20,39 @@ namespace Jellyfin.Plugin.MetaShark.Core return null; } + public static string GetTextOrDefault(this IElement el, string css, string defaultVal = "") + { + var node = el.QuerySelector(css); + if (node != null) + { + return node.Text().Trim(); + } + + return defaultVal; + } + public static string? GetAttr(this IElement el, string css, string attr) { var node = el.QuerySelector(css); if (node != null) { - return node.GetAttribute(attr).Trim(); + var attrVal = node.GetAttribute(attr); + return attrVal != null ? attrVal.Trim() : null; } return null; } + + public static string? GetAttrOrDefault(this IElement el, string css, string attr, string defaultVal = "") + { + var node = el.QuerySelector(css); + if (node != null) + { + var attrVal = node.GetAttribute(attr); + return attrVal != null ? attrVal.Trim() : defaultVal; + } + + return defaultVal; + } } } diff --git a/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs index e122209..fdc99aa 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/BaseProvider.cs @@ -50,14 +50,6 @@ namespace Jellyfin.Plugin.MetaShark.Providers protected Regex regMetaSourcePrefix = new Regex(@"^\[.+\]", RegexOptions.Compiled); - public string Pattern - { - get - { - return this.config.Pattern; - } - } - protected PluginConfiguration config { get diff --git a/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs index 25ba180..53de691 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/MovieProvider.cs @@ -125,6 +125,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers Genres = subject.Genres, // ProductionLocations = [x?.Country], PremiereDate = subject.ScreenTime, + Tagline = string.Empty, }; if (!string.IsNullOrEmpty(subject.Imdb)) { @@ -175,8 +176,9 @@ namespace Jellyfin.Plugin.MetaShark.Providers Tagline = movieResult.Tagline, ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray() }; - var metadataResult = new MetadataResult + result = new MetadataResult { + QueriedById = true, HasMetadata = true, ResultLanguage = info.MetadataLanguage, Item = movie @@ -207,7 +209,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers result.AddPerson(person); } - return metadataResult; + return result; } return result; diff --git a/Jellyfin.Plugin.MetaShark/Providers/PersonImageProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/PersonImageProvider.cs new file mode 100644 index 0000000..38f3ae7 --- /dev/null +++ b/Jellyfin.Plugin.MetaShark/Providers/PersonImageProvider.cs @@ -0,0 +1,111 @@ +using Jellyfin.Plugin.MetaShark.Api; +using Jellyfin.Plugin.MetaShark.Core; +using Jellyfin.Plugin.MetaShark.Model; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TMDbLib.Objects.Languages; + +namespace Jellyfin.Plugin.MetaShark.Providers +{ + public class PersonImageProvider : BaseProvider, IRemoteImageProvider + { + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of . + public PersonImageProvider(IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory, ILibraryManager libraryManager, DoubanApi doubanApi, TmdbApi tmdbApi, OmdbApi omdbApi) + : base(httpClientFactory, loggerFactory.CreateLogger(), libraryManager, doubanApi, tmdbApi, omdbApi) + { + } + + /// + public string Name => Plugin.PluginName; + + /// + public bool Supports(BaseItem item) => item is Person; + + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + yield return ImageType.Primary; + } + + /// + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var cid = item.GetProviderId(DoubanProviderId); + var metaSource = item.GetProviderId(Plugin.ProviderId); + this.Log($"GetImages for item: {item.Name} [metaSource]: {metaSource}"); + if (!string.IsNullOrEmpty(cid)) + { + var celebrity = await this._doubanApi.GetCelebrityAsync(cid, cancellationToken).ConfigureAwait(false); + if (celebrity != null) + { + return new List { + new RemoteImageInfo + { + ProviderName = celebrity.Name, + Url = celebrity.Img, + Type = ImageType.Primary + } + }; + } + } + + this.Log($"Got images failed because the sid of \"{item.Name}\" is empty!"); + return new List(); + } + + /// + public async Task GetImageResponse(string url, CancellationToken cancellationToken) + { + this.Log("GetImageResponse url: {0}", url); + return await this._httpClientFactory.CreateClient().GetAsync(new Uri(url), cancellationToken).ConfigureAwait(false); + } + + /// + /// Query for a background photo + /// + /// a subject/movie id + /// Instance of the interface. + private async Task> GetBackdrop(string sid, CancellationToken cancellationToken) + { + this.Log("GetBackdrop of sid: {0}", sid); + var photo = await this._doubanApi.GetWallpaperBySidAsync(sid, cancellationToken); + var list = new List(); + + if (photo == null) + { + return list; + } + + return photo.Where(x => x.Width > x.Height * 1.3).Select(x => + { + return new RemoteImageInfo + { + ProviderName = Name, + Url = x.Large, + Type = ImageType.Backdrop, + }; + }); + } + + } +} diff --git a/Jellyfin.Plugin.MetaShark/Providers/PersonProvider.cs b/Jellyfin.Plugin.MetaShark/Providers/PersonProvider.cs index f425e53..5aa806e 100644 --- a/Jellyfin.Plugin.MetaShark/Providers/PersonProvider.cs +++ b/Jellyfin.Plugin.MetaShark/Providers/PersonProvider.cs @@ -1,4 +1,6 @@ -using Jellyfin.Plugin.MetaShark.Api; +using System.Net.Mime; +using System.Xml.Schema; +using Jellyfin.Plugin.MetaShark.Api; using Jellyfin.Plugin.MetaShark.Core; using Jellyfin.Plugin.MetaShark.Model; using MediaBrowser.Controller.Entities; @@ -42,7 +44,42 @@ namespace Jellyfin.Plugin.MetaShark.Providers public async Task> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken) { this.Log($"GetPersonSearchResults of [name]: {searchInfo.Name}"); - return await Task.FromResult>(new List()); + + var result = new List(); + var cid = searchInfo.GetProviderId(DoubanProviderId); + if (!string.IsNullOrEmpty(cid)) + { + var celebrity = await this._doubanApi.GetCelebrityAsync(cid, cancellationToken).ConfigureAwait(false); + if (celebrity != null) + { + result.Add(new RemoteSearchResult + { + SearchProviderName = DoubanProviderName, + ProviderIds = new Dictionary { { DoubanProviderId, celebrity.Id } }, + ImageUrl = this.GetProxyImageUrl(celebrity.Img), + Name = celebrity.Name, + } + ); + + return result; + } + } + + + + var res = await this._doubanApi.SearchCelebrityAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false); + result.AddRange(res.Take(this.config.MaxSearchResult).Select(x => + { + return new RemoteSearchResult + { + SearchProviderName = DoubanProviderName, + ProviderIds = new Dictionary { { DoubanProviderId, x.Id } }, + ImageUrl = this.GetProxyImageUrl(x.Img), + Name = x.Name, + }; + })); + + return result; } /// @@ -83,6 +120,7 @@ namespace Jellyfin.Plugin.MetaShark.Providers } } + result.QueriedById = true; result.HasMetadata = true; result.Item = item; diff --git a/README.md b/README.md index c121820..8d5a06c 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,8 @@ rm -rf MediaBrowser*.dll Microsoft*.dll Newtonsoft*.dll System*.dll Emby*.dll Je 3. Move folder `metashark` to jellyfin `data/plugins` folder -## QA +## FAQ -1. Plugin run in error: `System.BadImageFormatException: Bad IL format.` +#### Plugin run in error: `System.BadImageFormatException: Bad IL format.` -> Remove all hidden file in `metashark` plugin folder \ No newline at end of file +Remove all hidden file and `meta.json` in `metashark` plugin folder \ No newline at end of file diff --git a/build.meta.json b/build.meta.json index 18d3898..83416f6 100644 --- a/build.meta.json +++ b/build.meta.json @@ -1,7 +1,7 @@ { "category": "Metadata", "changelog": "NA", - "description": "jellyfin电影元数据插件,影片信息只要从豆瓣获取,并由TMDB补充缺失的季数据和剧集数据。", + "description": "jellyfin电影元数据插件,影片信息只要从豆瓣获取,并由TMDB补充缺失的剧集数据。", "guid": "9a19103f-16f7-4668-be54-9a1e7a4f7556", "imageUrl": "https://github.com/cxfksword/jellyfin-plugin-metashark/raw/main/doc/logo.png", "name": "MetaShark",