diff --git a/Injector_new.cs b/Injector_new.cs new file mode 100644 index 0000000..db0305f --- /dev/null +++ b/Injector_new.cs @@ -0,0 +1,207 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using MediaBrowser.Common.Configuration; +using Jellyfin.Plugin.Seasonals.Helpers; +using System.Collections.Generic; +using System.Linq; + +namespace Jellyfin.Plugin.Seasonals; + +/// +/// Handles the injection of the Seasonals script into the Jellyfin web interface. +/// +public class ScriptInjector +{ + private readonly IApplicationPaths _appPaths; + private readonly ILogger _logger; + public const string ScriptTag = ""; + public const string Marker = ""; + + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The logger. + public ScriptInjector(IApplicationPaths appPaths, ILogger logger) + { + _appPaths = appPaths; + _logger = logger; + } + + /// + /// Injects the script tag into index.html if it's not already present. + /// + public void Inject() + { + try + { + var webPath = GetWebPath(); + if (string.IsNullOrEmpty(webPath)) + { + _logger.LogWarning("Could not find Jellyfin web path. Script injection skipped. Attempting fallback."); + RegisterFileTransformation(); + return; + } + + var indexPath = Path.Combine(webPath, "index.html"); + if (!File.Exists(indexPath)) + { + _logger.LogWarning("index.html not found at {Path}. Script injection skipped. Attempting fallback.", indexPath); + RegisterFileTransformation(); + return; + } + + var content = File.ReadAllText(indexPath); + if (!content.Contains(ScriptTag)) + { + var index = content.IndexOf(Marker, StringComparison.OrdinalIgnoreCase); + if (index != -1) + { + content = content.Insert(index, ScriptTag + Environment.NewLine); + File.WriteAllText(indexPath, content); + _logger.LogInformation("Successfully injected Seasonals script into index.html."); + } + else + { + _logger.LogWarning("Script already present in index.html. Or could not be injected."); + } + } + } + catch (UnauthorizedAccessException) + { + _logger.LogWarning("Unauthorized access when attempting to inject script into index.html. Automatic injection failed. Attempting fallback now..."); + RegisterFileTransformation(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error injecting Seasonals script. Attempting fallback."); + RegisterFileTransformation(); + } + } + + /// + /// Removes the script tag from index.html. + /// + public void Remove() + { + UnregisterFileTransformation(); + + try + { + var webPath = GetWebPath(); + if (string.IsNullOrEmpty(webPath)) + { + return; + } + + var indexPath = Path.Combine(webPath, "index.html"); + if (!File.Exists(indexPath)) + { + return; + } + + var content = File.ReadAllText(indexPath); + if (content.Contains(ScriptTag)) + { + content = content.Replace(ScriptTag + Environment.NewLine, "").Replace(ScriptTag, ""); + File.WriteAllText(indexPath, content); + _logger.LogInformation("Successfully removed Seasonals script from index.html."); + } else { + _logger.LogInformation("Seasonals script tag not found in index.html. No removal necessary."); + } + } + catch (UnauthorizedAccessException) + { + _logger.LogWarning("Unauthorized access when attempting to remove script from index.html."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error removing Seasonals script."); + } + } + + /// + /// Retrieves the path to the Jellyfin web interface directory. + /// + /// The path to the web directory, or null if not found. + private string? GetWebPath() + { + // Use reflection to access WebPath property to ensure compatibility across different Jellyfin versions + var prop = _appPaths.GetType().GetProperty("WebPath", BindingFlags.Instance | BindingFlags.Public); + return prop?.GetValue(_appPaths) as string; + } + + private void RegisterFileTransformation() + { + _logger.LogInformation("Seasonals Fallback. Registering file transformations."); + + List payloads = new List(); + + { + JObject payload = new JObject(); + payload.Add("id", "ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4"); + payload.Add("fileNamePattern", "index.html"); + payload.Add("callbackAssembly", GetType().Assembly.FullName); + payload.Add("callbackClass", typeof(TransformationPatches).FullName); + payload.Add("callbackMethod", nameof(TransformationPatches.IndexHtml)); + + payloads.Add(payload); + } + + Assembly? fileTransformationAssembly = + AssemblyLoadContext.All.SelectMany(x => x.Assemblies).FirstOrDefault(x => + x.FullName?.Contains(".FileTransformation") ?? false); + + if (fileTransformationAssembly != null) + { + Type? pluginInterfaceType = fileTransformationAssembly.GetType("Jellyfin.Plugin.FileTransformation.PluginInterface"); + + if (pluginInterfaceType != null) + { + foreach (JObject payload in payloads) + { + pluginInterfaceType.GetMethod("RegisterTransformation")?.Invoke(null, new object?[] { payload }); + } + _logger.LogInformation("File transformations registered successfully."); + } + else + { + _logger.LogWarning("FileTransformation plugin found but PluginInterface type missing."); + } + } + else + { + _logger.LogWarning("FileTransformation plugin assembly not found. Fallback injection skipped."); + } + } + + private void UnregisterFileTransformation() + { + try + { + Assembly? fileTransformationAssembly = + AssemblyLoadContext.All.SelectMany(x => x.Assemblies).FirstOrDefault(x => + x.FullName?.Contains(".FileTransformation") ?? false); + + if (fileTransformationAssembly != null) + { + Type? pluginInterfaceType = fileTransformationAssembly.GetType("Jellyfin.Plugin.FileTransformation.PluginInterface"); + + if (pluginInterfaceType != null) + { + Guid id = Guid.Parse("ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4"); + pluginInterfaceType.GetMethod("RemoveTransformation")?.Invoke(null, new object?[] { id }); + _logger.LogInformation("File transformation unregistered successfully."); + } + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error attempting to unregister file transformation. It might not have been registered."); + } + } +} \ No newline at end of file diff --git a/Jellyfin.Plugin.Seasonals/ScriptInjector.cs b/Jellyfin.Plugin.Seasonals/ScriptInjector.cs index bcfdb9a..f9f73a3 100644 --- a/Jellyfin.Plugin.Seasonals/ScriptInjector.cs +++ b/Jellyfin.Plugin.Seasonals/ScriptInjector.cs @@ -56,6 +56,18 @@ public class ScriptInjector } var content = File.ReadAllText(indexPath); + + + // MARK: Legacy Tags, remove in future versions + bool modified = false; + // Cleanup legacy tags first to avoid duplicates or conflicts + content = RemoveLegacyTags(content, ref modified); + if (modified) + { + _logger.LogInformation("Removed legacy tags from index.html."); + } + + if (!content.Contains(ScriptTag)) { var index = content.IndexOf(Marker, StringComparison.OrdinalIgnoreCase); @@ -113,6 +125,17 @@ public class ScriptInjector } else { _logger.LogInformation("Seasonals script tag not found in index.html. No removal necessary."); } + + // MARK: Legacy Tags, remove in future versions + // Remove legacy tags + bool modified = false; + content = RemoveLegacyTags(content); + if (modified) + { + _logger.LogInformation("Removed legacy tags from index.html."); + } + + } catch (UnauthorizedAccessException) { @@ -204,4 +227,22 @@ public class ScriptInjector _logger.LogWarning(ex, "Error attempting to unregister file transformation. It might not have been registered."); } } + + // MARK: Legacy Tags, remove in future versions + /// + /// Removes legacy script and css tags from the content. + /// + private string RemoveLegacyTags(string content, ref bool modified) + { + // Legacy tags (used in versions prior to 1.6.3.0 where paths started with / instead of ../) + const string LegacyScriptTag = ""; + + if (content.Contains(LegacyScriptTag)) + { + content = content.Replace(LegacyScriptTag + Environment.NewLine, "").Replace(LegacyScriptTag, ""); + modified = true; + _logger.LogInformation("Legacy Seasonals script tag removed."); + } + return content; + } } \ No newline at end of file