using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.Loader; using Jellyfin.Plugin.Seasonals.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Jellyfin.Plugin.Seasonals; /// /// The main plugin. /// public class SeasonalsPlugin : BasePlugin, IHasWebPages { private readonly ScriptInjector _scriptInjector; private readonly ILoggerFactory _loggerFactory; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public SeasonalsPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, ILoggerFactory loggerFactory) : base(applicationPaths, xmlSerializer) { Instance = this; _loggerFactory = loggerFactory; _scriptInjector = new ScriptInjector(applicationPaths, loggerFactory.CreateLogger()); if (Configuration.IsEnabled) { if (!_scriptInjector.Inject()) { TryRegisterFallback(loggerFactory.CreateLogger("FileTransformationFallback")); } } else { if (!_scriptInjector.Remove()) { TryRemoveFallback(loggerFactory.CreateLogger("FileTransformationFallback")); } } } /// public override void UpdateConfiguration(BasePluginConfiguration configuration) { var oldConfig = Configuration; base.UpdateConfiguration(configuration); if (Configuration.IsEnabled != oldConfig.IsEnabled) { if (Configuration.IsEnabled) { if (!_scriptInjector.Inject()) { TryRegisterFallback(_loggerFactory.CreateLogger("FileTransformationFallback")); } } else { if (!_scriptInjector.Remove()) { TryRemoveFallback(_loggerFactory.CreateLogger("FileTransformationFallback")); } } } } /// public override string Name => "Seasonals"; /// public override Guid Id => Guid.Parse("ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4"); /// /// Gets the current plugin instance. /// public static SeasonalsPlugin? Instance { get; private set; } /// /// Callback method for FileTransformation plugin. /// /// The payload containing the file contents. /// The modified file contents. public static string TransformIndexHtml(JObject payload) { // CRITICAL: Always return original content if something fails or is null string? originalContents = payload?["contents"]?.ToString(); if (string.IsNullOrEmpty(originalContents)) { return originalContents ?? string.Empty; } try { var html = originalContents; const string scriptTag = ""; // MARK: Remember me to remove legacy script tag support in future versions... const string legacyScriptTag = ""; if (Instance?.Configuration.IsEnabled != true) { // Remove script if present if (html.Contains("seasonals.js", StringComparison.Ordinal)) { return html.Replace(scriptTag, "", StringComparison.OrdinalIgnoreCase) .Replace(legacyScriptTag, "", StringComparison.OrdinalIgnoreCase); } return html; } if (!html.Contains("seasonals.js", StringComparison.Ordinal)) { var inject = $"{scriptTag}\n x.Assemblies) .FirstOrDefault(x => x.FullName?.Contains(".FileTransformation") ?? false); if (assembly == null) { logger.LogWarning("FileTransformation plugin not found. Fallback injection skipped."); return; } var type = assembly.GetType("Jellyfin.Plugin.FileTransformation.PluginInterface"); if (type == null) { logger.LogWarning("Jellyfin.Plugin.FileTransformation.PluginInterface not found."); return; } var method = type.GetMethod("RegisterTransformation"); if (method == null) { logger.LogWarning("RegisterTransformation method not found."); return; } // Create JObject payload directly using Newtonsoft.Json var payload = new JObject { { "id", Id.ToString() }, { "fileNamePattern", "index.html" }, { "callbackAssembly", this.GetType().Assembly.FullName }, { "callbackClass", this.GetType().FullName }, { "callbackMethod", nameof(TransformIndexHtml) } }; // Invoke RegisterTransformation with the JObject payload method.Invoke(null, new object[] { payload }); logger.LogInformation("Successfully registered fallback transformation via FileTransformation plugin."); } catch (Exception ex) { logger.LogError(ex, "Error attempting to register fallback transformation."); } } private void TryRemoveFallback(ILogger logger) { try { var assembly = AssemblyLoadContext.All .SelectMany(x => x.Assemblies) .FirstOrDefault(x => x.FullName?.Contains(".FileTransformation") ?? false); if (assembly == null) { logger.LogWarning("FileTransformation plugin not found. Fallback removal skipped."); return; } var type = assembly.GetType("Jellyfin.Plugin.FileTransformation.PluginInterface"); if (type == null) { logger.LogWarning("Jellyfin.Plugin.FileTransformation.PluginInterface not found."); return; } var method = type.GetMethod("RemoveTransformation"); if (method == null) { logger.LogWarning("RemoveTransformation method not found."); return; } Guid pluginId = Id is Guid g ? g : Guid.Parse(Id.ToString()); method.Invoke(null, new object[] { pluginId }); logger.LogInformation("Successfully unregistered fallback transformation via FileTransformation plugin."); } catch (Exception ex) { logger.LogError(ex, "Error attempting to unregister fallback transformation."); } } /// public IEnumerable GetPages() { return new[] { new PluginPageInfo { Name = Name, EnableInMainMenu = true, EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.configPage.html", GetType().Namespace) } }; } }