Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45c9a199c2 | ||
|
|
1df6fb37b1 | ||
|
|
82a1e8a178 | ||
|
|
22bf887d10 | ||
|
|
07600766cf |
207
Injector_new.cs
Normal file
207
Injector_new.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the injection of the Seasonals script into the Jellyfin web interface.
|
||||||
|
/// </summary>
|
||||||
|
public class ScriptInjector
|
||||||
|
{
|
||||||
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
private readonly ILogger<ScriptInjector> _logger;
|
||||||
|
public const string ScriptTag = "<script src=\"../Seasonals/Resources/seasonals.js\" defer></script>";
|
||||||
|
public const string Marker = "</body>";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ScriptInjector"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The application paths.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
public ScriptInjector(IApplicationPaths appPaths, ILogger<ScriptInjector> logger)
|
||||||
|
{
|
||||||
|
_appPaths = appPaths;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Injects the script tag into index.html if it's not already present.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the script tag from index.html.
|
||||||
|
/// </summary>
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the path to the Jellyfin web interface directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The path to the web directory, or null if not found.</returns>
|
||||||
|
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<JObject> payloads = new List<JObject>();
|
||||||
|
|
||||||
|
{
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
Resurrection = new ResurrectionOptions();
|
Resurrection = new ResurrectionOptions();
|
||||||
Spring = new SpringOptions();
|
Spring = new SpringOptions();
|
||||||
Summer = new SummerOptions();
|
Summer = new SummerOptions();
|
||||||
|
CherryBlossom = new CherryBlossomOptions();
|
||||||
Carnival = new CarnivalOptions();
|
Carnival = new CarnivalOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
public ResurrectionOptions Resurrection { get; set; }
|
public ResurrectionOptions Resurrection { get; set; }
|
||||||
public SpringOptions Spring { get; set; }
|
public SpringOptions Spring { get; set; }
|
||||||
public SummerOptions Summer { get; set; }
|
public SummerOptions Summer { get; set; }
|
||||||
|
public CherryBlossomOptions CherryBlossom { get; set; }
|
||||||
public CarnivalOptions Carnival { get; set; }
|
public CarnivalOptions Carnival { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +193,6 @@ public class ResurrectionOptions
|
|||||||
|
|
||||||
public class SpringOptions
|
public class SpringOptions
|
||||||
{
|
{
|
||||||
public int PetalCount { get; set; } = 25;
|
|
||||||
public int PollenCount { get; set; } = 15;
|
public int PollenCount { get; set; } = 15;
|
||||||
public int LadybugCount { get; set; } = 5;
|
public int LadybugCount { get; set; } = 5;
|
||||||
public int SunbeamCount { get; set; } = 5;
|
public int SunbeamCount { get; set; } = 5;
|
||||||
@@ -219,3 +220,12 @@ public class CarnivalOptions
|
|||||||
public bool EnableRandomCarnivalMobile { get; set; } = false;
|
public bool EnableRandomCarnivalMobile { get; set; } = false;
|
||||||
public bool EnableDifferentDuration { get; set; } = true;
|
public bool EnableDifferentDuration { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CherryBlossomOptions
|
||||||
|
{
|
||||||
|
public int PetalCount { get; set; } = 25;
|
||||||
|
public bool EnableCherryBlossom { get; set; } = true;
|
||||||
|
public bool EnableRandomCherryBlossom { get; set; } = true;
|
||||||
|
public bool EnableRandomCherryBlossomMobile { get; set; } = false;
|
||||||
|
public bool EnableDifferentDuration { get; set; } = true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
|
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
|
||||||
<Title>Jellyfin Seasonals Plugin</Title>
|
<Title>Jellyfin Seasonals Plugin</Title>
|
||||||
<Authors>CodeDevMLH</Authors>
|
<Authors>CodeDevMLH</Authors>
|
||||||
<Version>1.7.1.0</Version>
|
<Version>1.7.1.1</Version>
|
||||||
<RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl>
|
<RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,18 @@ public class ScriptInjector
|
|||||||
}
|
}
|
||||||
|
|
||||||
var content = File.ReadAllText(indexPath);
|
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))
|
if (!content.Contains(ScriptTag))
|
||||||
{
|
{
|
||||||
var index = content.IndexOf(Marker, StringComparison.OrdinalIgnoreCase);
|
var index = content.IndexOf(Marker, StringComparison.OrdinalIgnoreCase);
|
||||||
@@ -113,6 +125,17 @@ public class ScriptInjector
|
|||||||
} else {
|
} else {
|
||||||
_logger.LogInformation("Seasonals script tag not found in index.html. No removal necessary.");
|
_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, ref modified);
|
||||||
|
if (modified)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Removed legacy tags from index.html.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
@@ -204,4 +227,21 @@ public class ScriptInjector
|
|||||||
_logger.LogWarning(ex, "Error attempting to unregister file transformation. It might not have been registered.");
|
_logger.LogWarning(ex, "Error attempting to unregister file transformation. It might not have been registered.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Legacy Tags, remove in future versions
|
||||||
|
/// <summary>
|
||||||
|
/// Removes legacy script tags from the content.
|
||||||
|
/// </summary>
|
||||||
|
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 = "<script src=\"/Seasonals/Resources/seasonals.js\" defer></script>";
|
||||||
|
|
||||||
|
if (content.Contains(LegacyScriptTag))
|
||||||
|
{
|
||||||
|
content = content.Replace(LegacyScriptTag + Environment.NewLine, "").Replace(LegacyScriptTag, "");
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,12 +9,12 @@
|
|||||||
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
|
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
"version": "1.7.1.0",
|
"version": "1.7.1.1",
|
||||||
"changelog": "- feat: add summer, spring and carnival themes",
|
"changelog": "- feat: add summer, spring and carnival themes",
|
||||||
"targetAbi": "10.11.0.0",
|
"targetAbi": "10.11.0.0",
|
||||||
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.7.1.0/Jellyfin.Plugin.Seasonals.zip",
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.7.1.1/Jellyfin.Plugin.Seasonals.zip",
|
||||||
"checksum": "5f288972124771d1223484f75138f566",
|
"checksum": "58a418e0070aab6f47b136fc305e2fc1",
|
||||||
"timestamp": "2026-02-19T02:20:23Z"
|
"timestamp": "2026-02-19T18:00:57Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "1.7.0.15",
|
"version": "1.7.0.15",
|
||||||
|
|||||||
Reference in New Issue
Block a user