diff --git a/Jellyfin.Plugin.Danmu.Test/DanmuProviderIdTest.cs b/Jellyfin.Plugin.Danmu.Test/DanmuProviderIdTest.cs new file mode 100644 index 0000000..7cd3906 --- /dev/null +++ b/Jellyfin.Plugin.Danmu.Test/DanmuProviderIdTest.cs @@ -0,0 +1,105 @@ +using System; +using Jellyfin.Plugin.Danmu.Core; +using Jellyfin.Plugin.Danmu.Scrapers; +using Jellyfin.Plugin.Danmu.Scrapers.Tencent; +using MediaBrowser.Controller.Entities.Movies; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Plugin.Danmu.Test; + +[TestClass] +public class DanmuProviderIdTest +{ + private static readonly ILoggerFactory TestLoggerFactory = LoggerFactory.Create(builder => { }); + + [DataTestMethod] + [DataRow("BilibiliID", "bilibili")] + [DataRow("DandanID", "dandan")] + [DataRow("DanmuApiID", "danmuapi")] + [DataRow("IqiyiID", "iqiyi")] + [DataRow("MgtvID", "mgtv")] + [DataRow("TencentID", "tencent")] + [DataRow("YoukuID", "youku")] + public void Encode_UsesMappedLowercasePrefix(string scraperProviderId, string expectedPrefix) + { + var encodedValue = DanmuProviderId.Encode(scraperProviderId, "provider-value"); + + Assert.AreEqual($"{expectedPrefix}:provider-value", encodedValue); + } + + [DataTestMethod] + [DataRow("TencentID:g41000zu2yv", "TencentID", "g41000zu2yv")] + [DataRow("tencent:g41000zu2yv", "TencentID", "g41000zu2yv")] + public void TryDecode_SupportsLegacyAndMappedPrefix(string rawValue, string expectedScraperProviderId, string expectedProviderValue) + { + var success = DanmuProviderId.TryDecode(rawValue, out var scraperProviderId, out var providerValue); + + Assert.IsTrue(success); + Assert.AreEqual(expectedScraperProviderId, scraperProviderId); + Assert.AreEqual(expectedProviderValue, providerValue); + } + + [TestMethod] + public void Set_WritesMappedUnifiedProviderId() + { + var item = new Movie(); + + DanmuProviderId.Set(item, Array.Empty(), Tencent.ScraperProviderId, "g41000zu2yv"); + + Assert.AreEqual("tencent:g41000zu2yv", item.ProviderIds[DanmuProviderId.UnifiedProviderId]); + } + + [TestMethod] + public void TryGet_ReadsMappedUnifiedProviderIdWithLegacyScraperProviderId() + { + var item = new Movie(); + item.ProviderIds[DanmuProviderId.UnifiedProviderId] = "tencent:g41000zu2yv"; + + var success = DanmuProviderId.TryGet(item, Tencent.ScraperProviderId, out var providerValue); + + Assert.IsTrue(success); + Assert.AreEqual("g41000zu2yv", providerValue); + } + + [TestMethod] + public void TryMigrateToUnified_ConvertsLegacyScraperProviderIdToUnifiedProviderId() + { + var item = new Movie(); + item.ProviderIds[Tencent.ScraperProviderId] = "g41000zu2yv"; + + var migrated = DanmuProviderId.TryMigrateToUnified(item, CreateScrapers(), Tencent.ScraperProviderId, "g41000zu2yv"); + + Assert.IsTrue(migrated); + Assert.IsFalse(item.ProviderIds.ContainsKey(Tencent.ScraperProviderId)); + Assert.AreEqual("tencent:g41000zu2yv", item.ProviderIds[DanmuProviderId.UnifiedProviderId]); + } + + [TestMethod] + public void TryMigrateToUnified_RewritesLegacyUnifiedValueToCanonicalValue() + { + var item = new Movie(); + item.ProviderIds[DanmuProviderId.UnifiedProviderId] = "TencentID:g41000zu2yv"; + + var migrated = DanmuProviderId.TryMigrateToUnified(item, CreateScrapers(), Tencent.ScraperProviderId, "g41000zu2yv"); + + Assert.IsTrue(migrated); + Assert.AreEqual("tencent:g41000zu2yv", item.ProviderIds[DanmuProviderId.UnifiedProviderId]); + } + + [TestMethod] + public void TryMigrateToUnified_SkipsCanonicalUnifiedValue() + { + var item = new Movie(); + item.ProviderIds[DanmuProviderId.UnifiedProviderId] = "tencent:g41000zu2yv"; + + var migrated = DanmuProviderId.TryMigrateToUnified(item, CreateScrapers(), Tencent.ScraperProviderId, "g41000zu2yv"); + + Assert.IsFalse(migrated); + Assert.AreEqual("tencent:g41000zu2yv", item.ProviderIds[DanmuProviderId.UnifiedProviderId]); + } + + private static AbstractScraper[] CreateScrapers() + { + return new AbstractScraper[] { new Tencent(TestLoggerFactory) }; + } +} diff --git a/Jellyfin.Plugin.Danmu.Test/LibraryManagerEventsHelperTest.cs b/Jellyfin.Plugin.Danmu.Test/LibraryManagerEventsHelperTest.cs index ba9382e..84374a4 100644 --- a/Jellyfin.Plugin.Danmu.Test/LibraryManagerEventsHelperTest.cs +++ b/Jellyfin.Plugin.Danmu.Test/LibraryManagerEventsHelperTest.cs @@ -2,12 +2,15 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Reflection; using System.Text; using System.Threading.Tasks; +using Jellyfin.Plugin.Danmu.Core; using Jellyfin.Plugin.Danmu.Model; using Jellyfin.Plugin.Danmu.Scrapers; using Jellyfin.Plugin.Danmu.Scrapers.Bilibili; using Jellyfin.Plugin.Danmu.Scrapers.Dandan; +using Jellyfin.Plugin.Danmu.Scrapers.Tencent; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; @@ -289,6 +292,41 @@ namespace Jellyfin.Plugin.Danmu.Test } + [TestMethod] + public void ProcessQueuedUpdateMeta_ShouldKeepUnifiedProviderId_WhenQueueItemAndItemAreSameInstance() + { + var scraperManager = new ScraperManager(loggerFactory); + scraperManager.Register(new Tencent(loggerFactory)); + + var fileSystemStub = new Mock(); + var itemRepositoryStub = new Mock(); + var libraryManagerStub = new Mock(); + var currentItem = new Movie + { + Name = "test", + ProviderIds = new Dictionary + { + [DanmuProviderId.UnifiedProviderId] = "tencent:p4101tg0b9n", + }, + }; + + libraryManagerStub + .Setup(x => x.GetItemById(It.IsAny())) + .Returns(currentItem); + libraryManagerStub + .Setup(x => x.RunMetadataSavers(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + var libraryManagerEventsHelper = new LibraryManagerEventsHelper(itemRepositoryStub.Object, libraryManagerStub.Object, loggerFactory, fileSystemStub.Object, scraperManager); + var queue = new List { currentItem }; + var processQueuedUpdateMeta = typeof(LibraryManagerEventsHelper).GetMethod("ProcessQueuedUpdateMeta", BindingFlags.NonPublic | BindingFlags.Instance); + + var task = (Task)processQueuedUpdateMeta!.Invoke(libraryManagerEventsHelper, new object[] { queue })!; + task.GetAwaiter().GetResult(); + + Assert.AreEqual("tencent:p4101tg0b9n", currentItem.ProviderIds[DanmuProviderId.UnifiedProviderId]); + } + [TestMethod] public void TestDownloadDanmu() { diff --git a/Jellyfin.Plugin.Danmu/Core/DanmuProviderId.cs b/Jellyfin.Plugin.Danmu/Core/DanmuProviderId.cs new file mode 100644 index 0000000..4b6431c --- /dev/null +++ b/Jellyfin.Plugin.Danmu/Core/DanmuProviderId.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Plugin.Danmu.Scrapers; +using Jellyfin.Plugin.Danmu.Scrapers.Bilibili; +using Jellyfin.Plugin.Danmu.Scrapers.Dandan; +using Jellyfin.Plugin.Danmu.Scrapers.DanmuApi; +using Jellyfin.Plugin.Danmu.Scrapers.Iqiyi; +using Jellyfin.Plugin.Danmu.Scrapers.Mgtv; +using Jellyfin.Plugin.Danmu.Scrapers.Tencent; +using Jellyfin.Plugin.Danmu.Scrapers.Youku; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; + +namespace Jellyfin.Plugin.Danmu.Core; + +public static class DanmuProviderId +{ + public const string UnifiedProviderId = "DanmuID"; + public const string UnifiedProviderName = "Danmu"; + + private const char Separator = ':'; + + private static readonly IReadOnlyDictionary ScraperProviderIdToPrefixMap = new Dictionary(StringComparer.Ordinal) + { + [Bilibili.ScraperProviderId] = "bilibili", + [Dandan.ScraperProviderId] = "dandan", + [DanmuApi.ScraperProviderId] = "danmuapi", + [Iqiyi.ScraperProviderId] = "iqiyi", + [Mgtv.ScraperProviderId] = "mgtv", + [Tencent.ScraperProviderId] = "tencent", + [Youku.ScraperProviderId] = "youku", + }; + + private static readonly IReadOnlyDictionary PrefixToScraperProviderIdMap = ScraperProviderIdToPrefixMap + .ToDictionary(pair => pair.Value, pair => pair.Key, StringComparer.OrdinalIgnoreCase); + + public static string Encode(string scraperProviderId, string providerValue) + { + return $"{ToPrefix(scraperProviderId)}{Separator}{providerValue}"; + } + + public static bool TryDecode(string? value, out string scraperProviderId, out string providerValue) + { + if (string.IsNullOrWhiteSpace(value)) + { + scraperProviderId = string.Empty; + providerValue = string.Empty; + return false; + } + + var separatorIndex = value.IndexOf(Separator); + if (separatorIndex <= 0 || separatorIndex >= value.Length - 1) + { + scraperProviderId = string.Empty; + providerValue = string.Empty; + return false; + } + + scraperProviderId = ToScraperProviderId(value[..separatorIndex]); + providerValue = value[(separatorIndex + 1)..]; + return true; + } + + public static string Get(IHasProviderIds item, string scraperProviderId) + { + return TryGet(item, scraperProviderId, out var providerValue) ? providerValue : string.Empty; + } + + public static bool TryGet(IHasProviderIds item, string scraperProviderId, out string providerValue) + { + if (item.ProviderIds.ContainsKey(UnifiedProviderId)) + { + if (TryGetUnified(item, out var matchedProviderId, out var unifiedProviderValue) + && string.Equals(matchedProviderId, scraperProviderId, StringComparison.Ordinal)) + { + providerValue = unifiedProviderValue; + return true; + } + + providerValue = string.Empty; + return false; + } + + if (item.ProviderIds.TryGetValue(scraperProviderId, out var legacyProviderValue) + && !string.IsNullOrEmpty(legacyProviderValue)) + { + providerValue = legacyProviderValue; + return true; + } + + providerValue = string.Empty; + return false; + } + + public static bool TryGetFirst(IHasProviderIds item, IEnumerable scrapers, out AbstractScraper? scraper, out string providerValue) + { + if (item.ProviderIds.ContainsKey(UnifiedProviderId)) + { + if (TryGetUnified(item, out var unifiedScraperProviderId, out var unifiedProviderValue)) + { + scraper = scrapers.FirstOrDefault(current => string.Equals(current.ProviderId, unifiedScraperProviderId, StringComparison.Ordinal)); + if (scraper != null) + { + providerValue = unifiedProviderValue; + return true; + } + } + + scraper = null; + providerValue = string.Empty; + return false; + } + + foreach (var currentScraper in scrapers) + { + if (item.ProviderIds.TryGetValue(currentScraper.ProviderId, out var legacyProviderValue) + && !string.IsNullOrEmpty(legacyProviderValue)) + { + scraper = currentScraper; + providerValue = legacyProviderValue; + return true; + } + } + + scraper = null; + providerValue = string.Empty; + return false; + } + + public static bool HasAny(IHasProviderIds item, IEnumerable scrapers) + { + return TryGetFirst(item, scrapers, out _, out _); + } + + public static bool TryGetUnified(IHasProviderIds item, out string scraperProviderId, out string providerValue) + { + if (item.ProviderIds.TryGetValue(UnifiedProviderId, out var rawProviderValue) + && TryDecode(rawProviderValue, out scraperProviderId, out providerValue)) + { + return true; + } + + scraperProviderId = string.Empty; + providerValue = string.Empty; + return false; + } + + public static void Clear(BaseItem item, IEnumerable scrapers) + { + item.ProviderIds.Remove(UnifiedProviderId); + foreach (var scraper in scrapers) + { + item.ProviderIds.Remove(scraper.ProviderId); + } + } + + public static void Set(BaseItem item, IEnumerable scrapers, string scraperProviderId, string providerValue) + { + Clear(item, scrapers); + item.SetProviderId(UnifiedProviderId, Encode(scraperProviderId, providerValue)); + } + + public static bool TryMigrateToUnified(BaseItem item, IEnumerable scrapers, string scraperProviderId, string providerValue) + { + if (string.IsNullOrEmpty(providerValue)) + { + return false; + } + + var scraperList = scrapers as IReadOnlyCollection ?? scrapers.ToArray(); + var encodedProviderValue = Encode(scraperProviderId, providerValue); + var hasCanonicalUnifiedProviderId = item.ProviderIds.TryGetValue(UnifiedProviderId, out var rawProviderValue) + && string.Equals(rawProviderValue, encodedProviderValue, StringComparison.Ordinal); + var hasLegacyScraperProviderId = scraperList.Any(scraper => item.ProviderIds.ContainsKey(scraper.ProviderId)); + + if (hasCanonicalUnifiedProviderId && !hasLegacyScraperProviderId) + { + return false; + } + + Set(item, scraperList, scraperProviderId, providerValue); + return true; + } + + private static string ToPrefix(string scraperProviderId) + { + return ScraperProviderIdToPrefixMap.TryGetValue(scraperProviderId, out var prefix) + ? prefix + : scraperProviderId; + } + + private static string ToScraperProviderId(string prefix) + { + return PrefixToScraperProviderIdMap.TryGetValue(prefix, out var scraperProviderId) + ? scraperProviderId + : prefix; + } +} \ No newline at end of file diff --git a/Jellyfin.Plugin.Danmu/ExternalId/SharedExternalIds.cs b/Jellyfin.Plugin.Danmu/ExternalId/SharedExternalIds.cs new file mode 100644 index 0000000..4432612 --- /dev/null +++ b/Jellyfin.Plugin.Danmu/ExternalId/SharedExternalIds.cs @@ -0,0 +1,42 @@ +using Jellyfin.Plugin.Danmu.Core; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace Jellyfin.Plugin.Danmu.ExternalId; + +public abstract class BaseExternalId : IExternalId +{ + public string ProviderName => DanmuProviderId.UnifiedProviderName; + + public string Key => DanmuProviderId.UnifiedProviderId; + + public string UrlFormatString => string.Empty; + + public abstract ExternalIdMediaType? Type { get; } + + public abstract bool Supports(IHasProviderIds item); +} + +public class MovieExternalId : BaseExternalId +{ + public override ExternalIdMediaType? Type => ExternalIdMediaType.Movie; + + public override bool Supports(IHasProviderIds item) => item is Movie; +} + +public class EpisodeExternalId : BaseExternalId +{ + public override ExternalIdMediaType? Type => ExternalIdMediaType.Episode; + + public override bool Supports(IHasProviderIds item) => item is Episode; +} + +public class SeasonExternalId : BaseExternalId +{ + public override ExternalIdMediaType? Type => ExternalIdMediaType.Season; + + public override bool Supports(IHasProviderIds item) => item is Season; +} \ No newline at end of file diff --git a/Jellyfin.Plugin.Danmu/LibraryManagerEventsHelper.cs b/Jellyfin.Plugin.Danmu/LibraryManagerEventsHelper.cs index 4893d89..bfdc5a3 100644 --- a/Jellyfin.Plugin.Danmu/LibraryManagerEventsHelper.cs +++ b/Jellyfin.Plugin.Danmu/LibraryManagerEventsHelper.cs @@ -305,7 +305,7 @@ public class LibraryManagerEventsHelper : IDisposable _logger.LogInformation("[{0}]匹配成功:name={1} ProviderId: {2}", scraper.Name, item.Name, providerVal); // 更新epid元数据 - item.SetProviderId(scraper.ProviderId, providerVal); + DanmuProviderId.Set(item, _scraperManager.All(), scraper.ProviderId, providerVal); queueUpdateMeta.Add(item); // 下载弹幕 @@ -331,15 +331,21 @@ public class LibraryManagerEventsHelper : IDisposable // 更新 if (eventType == EventType.Update) { + var queueUpdateMeta = new List(); foreach (var item in movies) { foreach (var scraper in _scraperManager.All()) { try { - var providerVal = item.GetProviderId(scraper.ProviderId); + var providerVal = DanmuProviderId.Get(item, scraper.ProviderId); if (!string.IsNullOrEmpty(providerVal)) { + if (DanmuProviderId.TryMigrateToUnified(item, _scraperManager.All(), scraper.ProviderId, providerVal)) + { + queueUpdateMeta.Add(item); + } + var episode = await scraper.GetMediaEpisode(item, providerVal); if (episode != null) { @@ -361,6 +367,8 @@ public class LibraryManagerEventsHelper : IDisposable } } } + + await ProcessQueuedUpdateMeta(queueUpdateMeta).ConfigureAwait(false); } // 强制刷新指定来源弹幕 @@ -369,15 +377,7 @@ public class LibraryManagerEventsHelper : IDisposable foreach (var queueItem in movies) { // 找到选择的scraper - var scraper = _scraperManager.All().FirstOrDefault(x => queueItem.ProviderIds.ContainsKey(x.ProviderId)); - if (scraper == null) - { - continue; - } - - // 获取选择的弹幕Id - var mediaId = queueItem.GetProviderId(scraper.ProviderId); - if (string.IsNullOrEmpty(mediaId)) + if (!DanmuProviderId.TryGetFirst(queueItem, _scraperManager.All(), out var scraper, out var mediaId) || scraper == null) { continue; } @@ -525,7 +525,7 @@ public class LibraryManagerEventsHelper : IDisposable // 更新seasonId元数据 - season.SetProviderId(scraper.ProviderId, mediaId); + DanmuProviderId.Set(season, _scraperManager.All(), scraper.ProviderId, mediaId); queueUpdateMeta.Add(season); _logger.LogInformation("[{0}]匹配成功:{1}-{2} season_number:{3} ProviderId: {4}", scraper.Name, series.Name, season.Name, season.IndexNumber, mediaId); @@ -569,12 +569,17 @@ public class LibraryManagerEventsHelper : IDisposable { try { - var providerVal = season.GetProviderId(scraper.ProviderId); + var providerVal = DanmuProviderId.Get(season, scraper.ProviderId); if (string.IsNullOrEmpty(providerVal)) { continue; } + if (DanmuProviderId.TryMigrateToUnified(season, _scraperManager.All(), scraper.ProviderId, providerVal)) + { + queueUpdateMeta.Add(season); + } + var media = await scraper.GetMedia(season, providerVal); if (media == null) { @@ -609,10 +614,10 @@ public class LibraryManagerEventsHelper : IDisposable _logger.LogInformation("[{0}]成功匹配. {1}.{2} -> epId: {3} cid: {4}", scraper.Name, indexNumber, episode.Name, epId, commentId); // 更新eposide元数据 - var episodeProviderVal = episode.GetProviderId(scraper.ProviderId); + var episodeProviderVal = DanmuProviderId.Get(episode, scraper.ProviderId); if (!string.IsNullOrEmpty(epId) && episodeProviderVal != epId) { - episode.SetProviderId(scraper.ProviderId, epId); + DanmuProviderId.Set(episode, _scraperManager.All(), scraper.ProviderId, epId); queueUpdateMeta.Add(episode); } @@ -670,20 +675,24 @@ public class LibraryManagerEventsHelper : IDisposable // 如果 Episode 没有弹幕元数据,表示该集是刮削完成后再新增的,需要重新匹配获取 var scrapers = this._scraperManager.All(); var season = item.Season; - var allDanmuProviderIds = scrapers.Select(x => x.ProviderId).ToList(); - var episodeFirstProviderId = allDanmuProviderIds.FirstOrDefault(x => !string.IsNullOrEmpty(item.GetProviderId(x))); - var seasonFirstProviderId = allDanmuProviderIds.FirstOrDefault(x => !string.IsNullOrEmpty(season.GetProviderId(x))); - if (string.IsNullOrEmpty(episodeFirstProviderId) && !string.IsNullOrEmpty(seasonFirstProviderId) && item.IndexNumber.HasValue) + var hasEpisodeProvider = DanmuProviderId.TryGetFirst(item, scrapers, out _, out _); + var hasSeasonProvider = DanmuProviderId.TryGetFirst(season, scrapers, out var seasonScraper, out var seasonProviderVal); + if (!hasEpisodeProvider && hasSeasonProvider && seasonScraper != null && item.IndexNumber.HasValue) { - var scraper = scrapers.First(x => x.ProviderId == seasonFirstProviderId); - var providerVal = season.GetProviderId(seasonFirstProviderId); + var scraper = seasonScraper; + var providerVal = seasonProviderVal; if (scraper == null) { - _logger.LogInformation("找不到对应的弹幕来源. ProviderId: {0}", seasonFirstProviderId); + _logger.LogInformation("找不到对应的弹幕来源. ProviderId: {0}", string.Empty); continue; } + if (DanmuProviderId.TryMigrateToUnified(season, _scraperManager.All(), scraper.ProviderId, providerVal)) + { + queueUpdateMeta.Add(season); + } + var media = await scraper.GetMedia(season, providerVal); if (media != null) { @@ -714,10 +723,10 @@ public class LibraryManagerEventsHelper : IDisposable this._logger.LogInformation("[{0}]成功匹配. {1}.{2} -> epId: {3} cid: {4}", scraper.Name, item.IndexNumber, item.Name, epId, commentId); // 更新 eposide 元数据 - var episodeProviderVal = item.GetProviderId(scraper.ProviderId); + var episodeProviderVal = DanmuProviderId.Get(item, scraper.ProviderId); if (!string.IsNullOrEmpty(epId) && episodeProviderVal != epId) { - item.SetProviderId(scraper.ProviderId, epId); + DanmuProviderId.Set(item, _scraperManager.All(), scraper.ProviderId, epId); queueUpdateMeta.Add(item); } @@ -733,12 +742,17 @@ public class LibraryManagerEventsHelper : IDisposable { try { - var providerVal = item.GetProviderId(scraper.ProviderId); + var providerVal = DanmuProviderId.Get(item, scraper.ProviderId); if (string.IsNullOrEmpty(providerVal)) { continue; } + if (DanmuProviderId.TryMigrateToUnified(item, _scraperManager.All(), scraper.ProviderId, providerVal)) + { + queueUpdateMeta.Add(item); + } + var episode = await scraper.GetMediaEpisode(item, providerVal); if (episode != null) { @@ -769,15 +783,7 @@ public class LibraryManagerEventsHelper : IDisposable foreach (var queueItem in items) { // 找到选择的scraper - var scraper = _scraperManager.All().FirstOrDefault(x => queueItem.ProviderIds.ContainsKey(x.ProviderId)); - if (scraper == null) - { - continue; - } - - // 获取选择的弹幕Id - var mediaId = queueItem.GetProviderId(scraper.ProviderId); - if (string.IsNullOrEmpty(mediaId)) + if (!DanmuProviderId.TryGetFirst(queueItem, _scraperManager.All(), out var scraper, out var mediaId) || scraper == null) { continue; } @@ -869,14 +875,18 @@ public class LibraryManagerEventsHelper : IDisposable var item = _libraryManager.GetItemById(queueItem.Id); if (item != null) { - // 合并新添加的provider id - foreach (var pair in queueItem.ProviderIds) - { - if (string.IsNullOrEmpty(pair.Value)) - { - continue; - } + var providerIdSnapshot = queueItem.ProviderIds + .Where(pair => !string.IsNullOrEmpty(pair.Value)) + .ToDictionary(pair => pair.Key, pair => pair.Value); + if (providerIdSnapshot.ContainsKey(DanmuProviderId.UnifiedProviderId)) + { + DanmuProviderId.Clear(item, _scraperManager.All()); + } + + // 合并新添加的provider id + foreach (var pair in providerIdSnapshot) + { item.ProviderIds[pair.Key] = pair.Value; } @@ -993,13 +1003,7 @@ public class LibraryManagerEventsHelper : IDisposable private async Task ForceSaveProviderId(BaseItem item, string providerId, string providerVal) { - // 先清空旧弹幕的所有元数据 - foreach (var s in _scraperManager.All()) - { - item.ProviderIds.Remove(s.ProviderId); - } - // 保存指定弹幕元数据 - item.ProviderIds[providerId] = providerVal; + DanmuProviderId.Set(item, _scraperManager.All(), providerId, providerVal); await this.UpdateItemsAsync(item, CancellationToken.None).ConfigureAwait(false); } diff --git a/Jellyfin.Plugin.Danmu/ScheduledTasks/ScanLibraryTask.cs b/Jellyfin.Plugin.Danmu/ScheduledTasks/ScanLibraryTask.cs index 93911d9..9a438b0 100644 --- a/Jellyfin.Plugin.Danmu/ScheduledTasks/ScanLibraryTask.cs +++ b/Jellyfin.Plugin.Danmu/ScheduledTasks/ScanLibraryTask.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; +using Jellyfin.Plugin.Danmu.Core; using Jellyfin.Plugin.Danmu.Core.Extensions; using Jellyfin.Plugin.Danmu.Model; using Jellyfin.Plugin.Danmu.Scrapers; @@ -128,24 +129,19 @@ namespace Jellyfin.Plugin.Danmu.ScheduledTasks private bool HasAnyScraperProviderId(ReadOnlyCollection scrapers, BaseItem item) { - foreach (var scraper in scrapers) - { - var providerVal = item.GetProviderId(scraper.ProviderId); - if (!string.IsNullOrEmpty(providerVal)) - { - return true; - } - } - - return false; + return DanmuProviderId.HasAny(item, scrapers); } private Dictionary GetScraperFilter(ReadOnlyCollection scrapers) { - var filter = new Dictionary(); + var filter = new Dictionary + { + { DanmuProviderId.UnifiedProviderId, string.Empty } + }; + foreach (var scraper in scrapers) { - filter.Add(scraper.ProviderId, string.Empty); + filter[scraper.ProviderId] = string.Empty; } return filter; diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/EpisodeExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/EpisodeExternalId.cs deleted file mode 100644 index ea862b7..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/EpisodeExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Bilibili.ExternalId -{ - /// - public class EpisodeExternalId : IExternalId - { - /// - public string ProviderName => Bilibili.ScraperProviderName; - - /// - public string Key => Bilibili.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/ExternalUrlProvider.cs b/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/ExternalUrlProvider.cs index 3cb71a0..dcc975f 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/ExternalUrlProvider.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/ExternalUrlProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Plugin.Danmu.Core; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -22,7 +23,7 @@ public class ExternalUrlProvider : IExternalUrlProvider switch (item) { case Season season: - if (item.TryGetProviderId(Bilibili.ScraperProviderId, out var externalId)) + if (DanmuProviderId.TryGet(item, Bilibili.ScraperProviderId, out var externalId)) { if (externalId.StartsWith("bv", StringComparison.OrdinalIgnoreCase) || externalId.StartsWith("av", StringComparison.OrdinalIgnoreCase)) { @@ -36,14 +37,14 @@ public class ExternalUrlProvider : IExternalUrlProvider break; case Episode episode: - if (item.TryGetProviderId(Bilibili.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Bilibili.ScraperProviderId, out externalId)) { yield return $"https://www.bilibili.com/bangumi/play/ep{externalId}"; } break; case Movie: - if (item.TryGetProviderId(Bilibili.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Bilibili.ScraperProviderId, out externalId)) { yield return $"https://www.bilibili.com/bangumi/play/ep{externalId}"; } diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/MovieExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/MovieExternalId.cs deleted file mode 100644 index 8395e49..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/MovieExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Bilibili.ExternalId -{ - /// - public class MovieExternalId : IExternalId - { - /// - public string ProviderName => Bilibili.ScraperProviderName; - - /// - public string Key => Bilibili.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Movie; - - /// - public bool Supports(IHasProviderIds item) => item is Movie; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/SeasonExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/SeasonExternalId.cs deleted file mode 100644 index 0433184..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Bilibili/ExternalId/SeasonExternalId.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Bilibili.ExternalId -{ - - /// - public class SeasonExternalId : IExternalId - { - /// - public string ProviderName => Bilibili.ScraperProviderName; - - /// - public string Key => Bilibili.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Season; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } - -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/EpisodeExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/EpisodeExternalId.cs deleted file mode 100644 index 53ef41c..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/EpisodeExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Dandan.ExternalId -{ - /// - public class EpisodeExternalId : IExternalId - { - /// - public string ProviderName => Dandan.ScraperProviderName; - - /// - public string Key => Dandan.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/ExternalUrlProvider.cs b/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/ExternalUrlProvider.cs index 43d7e49..1398594 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/ExternalUrlProvider.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/ExternalUrlProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Plugin.Danmu.Core; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -22,21 +23,21 @@ public class ExternalUrlProvider : IExternalUrlProvider switch (item) { case Season season: - if (item.TryGetProviderId(Dandan.ScraperProviderId, out var externalId)) + if (DanmuProviderId.TryGet(item, Dandan.ScraperProviderId, out var externalId)) { yield return $"https://api.dandanplay.net/api/v2/bangumi/{externalId}"; } break; case Episode episode: - if (item.TryGetProviderId(Dandan.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Dandan.ScraperProviderId, out externalId)) { yield return "#"; } break; case Movie: - if (item.TryGetProviderId(Dandan.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Dandan.ScraperProviderId, out externalId)) { yield return $"https://api.dandanplay.net/api/v2/bangumi/{externalId}"; } diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/MovieExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/MovieExternalId.cs deleted file mode 100644 index 47eeb6e..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/MovieExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Dandan.ExternalId -{ - /// - public class MovieExternalId : IExternalId - { - /// - public string ProviderName => Dandan.ScraperProviderName; - - /// - public string Key => Dandan.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Movie; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/SeasonExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/SeasonExternalId.cs deleted file mode 100644 index 483515f..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Dandan/ExternalId/SeasonExternalId.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Dandan.ExternalId -{ - - /// - public class SeasonExternalId : IExternalId - { - /// - public string ProviderName => Dandan.ScraperProviderName; - - /// - public string Key => Dandan.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } - -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/EpisodeExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/EpisodeExternalId.cs deleted file mode 100644 index 8595528..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/EpisodeExternalId.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.DanmuApi.ExternalId -{ - /// - public class EpisodeExternalId : IExternalId - { - /// - public string ProviderName => DanmuApi.ScraperProviderName; - - /// - public string Key => DanmuApi.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/ExternalUrlProvider.cs b/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/ExternalUrlProvider.cs index 1864586..53c76a5 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/ExternalUrlProvider.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/ExternalUrlProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Plugin.Danmu.Core; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -22,7 +23,7 @@ public class ExternalUrlProvider : IExternalUrlProvider switch (item) { case Season season: - if (item.TryGetProviderId(DanmuApi.ScraperProviderId, out var externalId)) + if (DanmuProviderId.TryGet(item, DanmuApi.ScraperProviderId, out var externalId)) { var serverUrl = GetServerUrl(); if (!string.IsNullOrEmpty(serverUrl)) @@ -37,7 +38,7 @@ public class ExternalUrlProvider : IExternalUrlProvider break; case Episode episode: - if (item.TryGetProviderId(DanmuApi.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, DanmuApi.ScraperProviderId, out externalId)) { var serverUrl = GetServerUrl(); if (!string.IsNullOrEmpty(serverUrl)) @@ -52,7 +53,7 @@ public class ExternalUrlProvider : IExternalUrlProvider break; case Movie: - if (item.TryGetProviderId(DanmuApi.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, DanmuApi.ScraperProviderId, out externalId)) { var serverUrl = GetServerUrl(); if (!string.IsNullOrEmpty(serverUrl)) diff --git a/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/MovieExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/MovieExternalId.cs deleted file mode 100644 index 2b8fe56..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/MovieExternalId.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.DanmuApi.ExternalId -{ - /// - public class MovieExternalId : IExternalId - { - /// - public string ProviderName => DanmuApi.ScraperProviderName; - - /// - public string Key => DanmuApi.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Movie; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/SeasonExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/SeasonExternalId.cs deleted file mode 100644 index 80f6e64..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/DanmuApi/ExternalId/SeasonExternalId.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.DanmuApi.ExternalId -{ - /// - public class SeasonExternalId : IExternalId - { - /// - public string ProviderName => DanmuApi.ScraperProviderName; - - /// - public string Key => DanmuApi.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/EpisodeExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/EpisodeExternalId.cs deleted file mode 100644 index 5840d40..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/EpisodeExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Iqiyi.ExternalId -{ - /// - public class EpisodeExternalId : IExternalId - { - /// - public string ProviderName => Iqiyi.ScraperProviderName; - - /// - public string Key => Iqiyi.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/ExternalUrlProvider.cs b/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/ExternalUrlProvider.cs index f03bcab..9027c36 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/ExternalUrlProvider.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/ExternalUrlProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Plugin.Danmu.Core; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -22,21 +23,21 @@ public class ExternalUrlProvider : IExternalUrlProvider switch (item) { case Season season: - if (item.TryGetProviderId(Iqiyi.ScraperProviderId, out var externalId)) + if (DanmuProviderId.TryGet(item, Iqiyi.ScraperProviderId, out var externalId)) { yield return $"https://www.iqiyi.com/v_{externalId}.html"; } break; case Episode episode: - if (item.TryGetProviderId(Iqiyi.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Iqiyi.ScraperProviderId, out externalId)) { yield return $"https://www.iqiyi.com/v_{externalId}.html"; } break; case Movie: - if (item.TryGetProviderId(Iqiyi.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Iqiyi.ScraperProviderId, out externalId)) { yield return $"https://www.iqiyi.com/v_{externalId}.html"; } diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/MovieExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/MovieExternalId.cs deleted file mode 100644 index 97fe8d8..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/MovieExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Iqiyi.ExternalId -{ - /// - public class MovieExternalId : IExternalId - { - /// - public string ProviderName => Iqiyi.ScraperProviderName; - - /// - public string Key => Iqiyi.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Movie; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/SeasonExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/SeasonExternalId.cs deleted file mode 100644 index 177f187..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Iqiyi/ExternalId/SeasonExternalId.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Iqiyi.ExternalId -{ - - /// - public class SeasonExternalId : IExternalId - { - /// - public string ProviderName => Iqiyi.ScraperProviderName; - - /// - public string Key => Iqiyi.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } - -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/EpisodeExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/EpisodeExternalId.cs deleted file mode 100644 index 5a69d2a..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/EpisodeExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId -{ - /// - public class EpisodeExternalId : IExternalId - { - /// - public string ProviderName => Mgtv.ScraperProviderName; - - /// - public string Key => Mgtv.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/ExternalUrlProvider.cs b/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/ExternalUrlProvider.cs index bb41831..1722625 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/ExternalUrlProvider.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/ExternalUrlProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Plugin.Danmu.Core; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -22,16 +23,16 @@ public class ExternalUrlProvider : IExternalUrlProvider switch (item) { case Season season: - if (item.TryGetProviderId(Mgtv.ScraperProviderId, out var externalId)) + if (DanmuProviderId.TryGet(item, Mgtv.ScraperProviderId, out var externalId)) { yield return $"https://www.mgtv.com/h/{externalId}.html"; } break; case Episode episode: - if (item.TryGetProviderId(Mgtv.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Mgtv.ScraperProviderId, out externalId)) { - if (episode.Season?.TryGetProviderId(Mgtv.ScraperProviderId, out var seasonExternalId) == true) + if (episode.Season != null && DanmuProviderId.TryGet(episode.Season, Mgtv.ScraperProviderId, out var seasonExternalId)) { yield return $"https://www.mgtv.com/b/{seasonExternalId}/{externalId}.html"; } @@ -43,7 +44,7 @@ public class ExternalUrlProvider : IExternalUrlProvider break; case Movie: - if (item.TryGetProviderId(Mgtv.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Mgtv.ScraperProviderId, out externalId)) { yield return $"https://www.mgtv.com/h/{externalId}.html"; } diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/MovieExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/MovieExternalId.cs deleted file mode 100644 index 27cb2ce..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/MovieExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId -{ - /// - public class MovieExternalId : IExternalId - { - /// - public string ProviderName => Mgtv.ScraperProviderName; - - /// - public string Key => Mgtv.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Movie; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/SeasonExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/SeasonExternalId.cs deleted file mode 100644 index bbf0408..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/ExternalId/SeasonExternalId.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Mgtv.ExternalId -{ - - /// - public class SeasonExternalId : IExternalId - { - /// - public string ProviderName => Mgtv.ScraperProviderName; - - /// - public string Key => Mgtv.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } - -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Mgtv.cs b/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Mgtv.cs index 465b9aa..84042d8 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Mgtv.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Mgtv/Mgtv.cs @@ -1,6 +1,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Plugin.Danmu.Core; using MediaBrowser.Controller.Entities; using Microsoft.Extensions.Logging; using Jellyfin.Plugin.Danmu.Scrapers.Entity; @@ -176,7 +177,7 @@ public class Mgtv : AbstractScraper // 从季信息元数据中,获取cid值 // 不能通过GetParent获取Season,因为没有SXX季文件夹时,GetParent是Series var season = ((MediaBrowser.Controller.Entities.TV.Episode)item).Season; - season.ProviderIds.TryGetValue(ScraperProviderId, out var cid); + DanmuProviderId.TryGet(season, ScraperProviderId, out var cid); return new ScraperEpisode() { Id = id, CommentId = $"{cid},{id}" }; } diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/EpisodeExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/EpisodeExternalId.cs deleted file mode 100644 index 79db917..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/EpisodeExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Tencent.ExternalId -{ - /// - public class EpisodeExternalId : IExternalId - { - /// - public string ProviderName => Tencent.ScraperProviderName; - - /// - public string Key => Tencent.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/ExternalUrlProvider.cs b/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/ExternalUrlProvider.cs index 7544898..cdf3b42 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/ExternalUrlProvider.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/ExternalUrlProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Plugin.Danmu.Core; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -22,16 +23,16 @@ public class ExternalUrlProvider : IExternalUrlProvider switch (item) { case Season season: - if (item.TryGetProviderId(Tencent.ScraperProviderId, out var externalId)) + if (DanmuProviderId.TryGet(item, Tencent.ScraperProviderId, out var externalId)) { yield return $"https://v.qq.com/x/cover/{externalId}.html"; } break; case Episode episode: - if (item.TryGetProviderId(Tencent.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Tencent.ScraperProviderId, out externalId)) { - if (episode.Season?.TryGetProviderId(Tencent.ScraperProviderId, out var seasonExternalId) == true) + if (episode.Season != null && DanmuProviderId.TryGet(episode.Season, Tencent.ScraperProviderId, out var seasonExternalId)) { yield return $"https://v.qq.com/x/cover/{seasonExternalId}/{externalId}.html"; } @@ -43,7 +44,7 @@ public class ExternalUrlProvider : IExternalUrlProvider break; case Movie: - if (item.TryGetProviderId(Tencent.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Tencent.ScraperProviderId, out externalId)) { yield return $"https://v.qq.com/x/cover/{externalId}.html"; } diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/MovieExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/MovieExternalId.cs deleted file mode 100644 index cbc8682..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/MovieExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Tencent.ExternalId -{ - /// - public class MovieExternalId : IExternalId - { - /// - public string ProviderName => Tencent.ScraperProviderName; - - /// - public string Key => Tencent.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Movie; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/SeasonExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/SeasonExternalId.cs deleted file mode 100644 index 7dbacc6..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Tencent/ExternalId/SeasonExternalId.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Tencent.ExternalId -{ - - /// - public class SeasonExternalId : IExternalId - { - /// - public string ProviderName => Tencent.ScraperProviderName; - - /// - public string Key => Tencent.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } - -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/EpisodeExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/EpisodeExternalId.cs deleted file mode 100644 index c9ddc6c..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/EpisodeExternalId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Youku.ExternalId -{ - /// - public class EpisodeExternalId : IExternalId - { - /// - public string ProviderName => Youku.ScraperProviderName; - - /// - public string Key => Youku.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/ExternalUrlProvider.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/ExternalUrlProvider.cs index 33b2d37..34769d5 100644 --- a/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/ExternalUrlProvider.cs +++ b/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/ExternalUrlProvider.cs @@ -1,6 +1,7 @@ using System; using System.Web; using System.Collections.Generic; +using Jellyfin.Plugin.Danmu.Core; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -23,14 +24,14 @@ public class ExternalUrlProvider : IExternalUrlProvider switch (item) { case Season season: - if (item.TryGetProviderId(Youku.ScraperProviderId, out var externalId)) + if (DanmuProviderId.TryGet(item, Youku.ScraperProviderId, out var externalId)) { yield return $"https://v.youku.com/v_nextstage/id_{externalId}.html"; } break; case Episode episode: - if (item.TryGetProviderId(Youku.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Youku.ScraperProviderId, out externalId)) { var decodeExternalId = HttpUtility.UrlDecode(externalId).Replace("||", "=="); yield return $"https://v.youku.com/v_show/id_{decodeExternalId}.html"; @@ -38,7 +39,7 @@ public class ExternalUrlProvider : IExternalUrlProvider break; case Movie: - if (item.TryGetProviderId(Youku.ScraperProviderId, out externalId)) + if (DanmuProviderId.TryGet(item, Youku.ScraperProviderId, out externalId)) { yield return $"https://v.youku.com/v_nextstage/id_{externalId}.html"; } diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/MovieExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/MovieExternalId.cs deleted file mode 100644 index 61efe96..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/MovieExternalId.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Youku.ExternalId -{ - /// - public class MovieExternalId : IExternalId - { - /// - public string ProviderName => Youku.ScraperProviderName; - - /// - public string Key => Youku.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => "https://v.youku.com/v_show/id_{0}.html"; - - /// - public bool Supports(IHasProviderIds item) => item is Movie; - } -} diff --git a/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/SeasonExternalId.cs b/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/SeasonExternalId.cs deleted file mode 100644 index 6582901..0000000 --- a/Jellyfin.Plugin.Danmu/Scrapers/Youku/ExternalId/SeasonExternalId.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace Jellyfin.Plugin.Danmu.Scrapers.Youku.ExternalId -{ - - /// - public class SeasonExternalId : IExternalId - { - /// - public string ProviderName => Youku.ScraperProviderName; - - /// - public string Key => Youku.ScraperProviderId; - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => "https://v.youku.com/v_nextstage/id_{0}.html"; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } - -}