using System; using System.IO; using System.Reflection; using Microsoft.Extensions.Logging; using MediaBrowser.Common.Configuration; 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; private const string ScriptTag = ""; private 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. /// /// True if injection was successful or already present, false otherwise. public bool Inject() { try { var webPath = GetWebPath(); if (string.IsNullOrEmpty(webPath)) { _logger.LogWarning("Could not find Jellyfin web path. Script injection skipped."); return false; } var indexPath = Path.Combine(webPath, "index.html"); if (!File.Exists(indexPath)) { _logger.LogWarning("index.html not found at {Path}. Script injection skipped.", indexPath); return false; } var content = File.ReadAllText(indexPath); if (content.Contains(ScriptTag, StringComparison.Ordinal)) { _logger.LogInformation("Seasonals script already injected."); return true; } // Insert before the closing body tag var newContent = content.Replace(Marker, $"{ScriptTag}\n{Marker}", StringComparison.Ordinal); if (string.Equals(newContent, content, StringComparison.Ordinal)) { _logger.LogWarning("Could not find closing body tag in index.html. Script injection skipped."); return false; } File.WriteAllText(indexPath, newContent); _logger.LogInformation("Successfully injected Seasonals script into index.html."); return true; } catch (UnauthorizedAccessException) { _logger.LogWarning("Access was denied when attempting to inject script into index.html. Automatic injection failed. Please ensure the Jellyfin web directory is writable by the process, or manually add the script tag: {ScriptTag}", ScriptTag); return false; } catch (Exception ex) { _logger.LogError(ex, "Error injecting Seasonals script."); return false; } } /// /// Removes the script tag from index.html. /// public void Remove() { 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, StringComparison.Ordinal)) { return; } // Try to remove with newline first, then just the tag to ensure clean removal var newContent = content.Replace($"{ScriptTag}\n", "", StringComparison.Ordinal) .Replace(ScriptTag, "", StringComparison.Ordinal); File.WriteAllText(indexPath, newContent); _logger.LogInformation("Successfully removed Seasonals script from index.html."); } catch (UnauthorizedAccessException) { _logger.LogWarning("Permission denied 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; } }