using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Serialization; using IntroSkipper.Configuration; using IntroSkipper.Data; using IntroSkipper.Db; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Logging; namespace IntroSkipper.Helper; internal static class LegacyMigrations { public static void MigrateAll( Plugin plugin, IServerConfigurationManager serverConfiguration, ILogger logger, IApplicationPaths applicationPaths) { var pluginDirName = "introskipper"; var introPath = Path.Join(applicationPaths.DataPath, pluginDirName, "intros.xml"); var creditsPath = Path.Join(applicationPaths.DataPath, pluginDirName, "credits.xml"); // Migrate XML files from XMLSchema to DataContract XmlSerializationHelper.MigrateXML(introPath); XmlSerializationHelper.MigrateXML(creditsPath); MigrateConfig(plugin, applicationPaths.PluginConfigurationsPath, logger); MigrateRepoUrl(plugin, serverConfiguration, logger); InjectSkipButton(plugin, applicationPaths.WebPath, logger); RestoreTimestamps(plugin.DbPath, introPath, creditsPath); } private static void MigrateConfig(Plugin plugin, string pluginConfigurationsPath, ILogger logger) { var oldConfigFile = Path.Join(pluginConfigurationsPath, "ConfusedPolarBear.Plugin.IntroSkipper.xml"); if (File.Exists(oldConfigFile)) { try { XmlSerializer serializer = new XmlSerializer(typeof(PluginConfiguration)); using FileStream fileStream = new FileStream(oldConfigFile, FileMode.Open); var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, // Disable DTD processing XmlResolver = null // Disable the XmlResolver }; using var reader = XmlReader.Create(fileStream, settings); if (serializer.Deserialize(reader) is PluginConfiguration oldConfig) { plugin.UpdateConfiguration(oldConfig); fileStream.Close(); File.Delete(oldConfigFile); } } catch (Exception ex) { // Handle exceptions, such as file not found, deserialization errors, etc. logger.LogWarning("Failed to migrate from the ConfusedPolarBear Config {Exception}", ex); } } } private static void MigrateRepoUrl(Plugin plugin, IServerConfigurationManager serverConfiguration, ILogger logger) { try { List oldRepos = [ "https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/manifest.json", "https://raw.githubusercontent.com/jumoog/intro-skipper/master/manifest.json", "https://manifest.intro-skipper.workers.dev/manifest.json" ]; var config = serverConfiguration.Configuration; var pluginRepositories = config.PluginRepositories.ToList(); if (pluginRepositories.Exists(repo => repo.Url != null && oldRepos.Contains(repo.Url))) { pluginRepositories.RemoveAll(repo => repo.Url != null && oldRepos.Contains(repo.Url)); if (!pluginRepositories.Exists(repo => repo.Url == "https://manifest.intro-skipper.org/manifest.json") && plugin.Configuration.OverrideManifestUrl) { pluginRepositories.Add(new RepositoryInfo { Name = "intro skipper (automatically migrated by plugin)", Url = "https://manifest.intro-skipper.org/manifest.json", Enabled = true, }); } config.PluginRepositories = [.. pluginRepositories]; serverConfiguration.SaveConfiguration(); } } catch (Exception ex) { logger.LogError(ex, "Error occurred while migrating repo URL"); } } private static void InjectSkipButton(Plugin plugin, string webPath, ILogger logger) { string pattern = @""; if (contents.Contains(scriptTag, StringComparison.OrdinalIgnoreCase)) { logger.LogDebug("The skip button has already been injected."); return; } contents = Regex.Replace(contents, pattern, string.Empty, RegexOptions.IgnoreCase); Regex headEnd = new Regex(@"", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); contents = headEnd.Replace(contents, scriptTag + "", 1); File.WriteAllText(indexPath, contents); logger.LogInformation("Skip button added successfully."); } catch (UnauthorizedAccessException) { WarningManager.SetFlag(PluginWarning.UnableToAddSkipButton); logger.LogError("Failed to add skip button to web interface. See https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#skip-button-is-not-visible for the most common issues."); } catch (IOException) { WarningManager.SetFlag(PluginWarning.UnableToAddSkipButton); logger.LogError("Failed to add skip button to web interface. See https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#skip-button-is-not-visible for the most common issues."); } } private static void RestoreTimestamps(string dbPath, string introPath, string creditsPath) { using var db = new IntroSkipperDbContext(dbPath); // Import intros if (File.Exists(introPath)) { var introList = XmlSerializationHelper.DeserializeFromXml(introPath); foreach (var intro in introList) { db.DbSegment.Add(new DbSegment(intro, AnalysisMode.Introduction)); } } // Import credits if (File.Exists(creditsPath)) { var creditList = XmlSerializationHelper.DeserializeFromXml(creditsPath); foreach (var credit in creditList) { db.DbSegment.Add(new DbSegment(credit, AnalysisMode.Credits)); } } db.SaveChanges(); File.Delete(introPath); File.Delete(creditsPath); } }