9 Commits

Author SHA1 Message Date
CodeDevMLH
ca813bacb7 add possible solution for later 2025-12-17 01:13:10 +01:00
CodeDevMLH
9bc6aedf87 fix path 2025-12-17 01:12:58 +01:00
MLH
ddfbf40bf7 manifest-v1.json hinzugefügt 2025-12-16 23:40:40 +01:00
CodeDevMLH
9ce88e19ad fix: Update sourceUrl for version 1.1.1.0 in manifest 2025-12-16 01:59:05 +01:00
CodeDevMLH
da6d868067 feat: Add version 1.1.1.0 with bug fixes and advanced configuration UI 2025-12-16 01:58:44 +01:00
CodeDevMLH
d97f017e32 fix: Update checksum and timestamp for version 1.1.0.0 in manifest 2025-12-16 01:40:56 +01:00
CodeDevMLH
29c6255904 fix: Update imageUrl in manifest to correct logo path 2025-12-16 01:40:23 +01:00
CodeDevMLH
baa1ddde66 fix: Update README for clarity and correct repository links 2025-12-16 01:37:40 +01:00
CodeDevMLH
93f09f42cf fix: Update source URLs in manifest for version downloads 2025-12-16 01:28:10 +01:00
9 changed files with 281 additions and 15 deletions

View File

@@ -13,7 +13,7 @@ public class ScriptInjector
{
private readonly IApplicationPaths _appPaths;
private readonly ILogger<ScriptInjector> _logger;
private const string ScriptTag = "<script src=\"Seasonals/Resources/seasonals.js\"></script>";
private const string ScriptTag = "<script src=\"/Seasonals/Resources/seasonals.js\"></script>";
private const string Marker = "</body>";
/// <summary>
@@ -66,6 +66,10 @@ public class ScriptInjector
File.WriteAllText(indexPath, newContent);
_logger.LogInformation("Successfully injected Seasonals script into index.html.");
}
catch (UnauthorizedAccessException)
{
_logger.LogWarning("Permission 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);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error injecting Seasonals script.");
@@ -104,6 +108,10 @@ public class ScriptInjector
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.");

View File

@@ -2,7 +2,9 @@
Jellyfin Seasonals is a plugin that adds seasonal themes to your Jellyfin web interface. Depending on the configuration, it automatically selects a theme based on the current date or allows you to manually set a default theme.
This plugin is based on my manual mod (see the `manual` branch), which builds up on the awesome work of [BobHasNoSoul-jellyfin-mods](https://github.com/BobHasNoSoul/jellyfin-mods).
This plugin is based on my manual mod (see the [legacy branch](https://github.com/CodeDevMLH/Jellyfin-Seasonals/tree/legacy)), which builds up on the awesome work of [BobHasNoSoul-jellyfin-mods](https://github.com/BobHasNoSoul/jellyfin-mods).
![logo](https://raw.githubusercontent.com/CodeDevMLH/Jellyfin-Seasonals/refs/heads/main/logo.png)
---
@@ -13,7 +15,7 @@ This plugin is based on my manual mod (see the `manual` branch), which builds up
- [Overview](#overview)
- [Installation](#installation)
- [Usage](#usage)
- [Automatic Selection Dates](#automatic-selection-dates)
- [Automatic Theme Selection](#automatic-theme-selection)
- [Build Process](#build-process)
- [Contributing](#contributing)
- [Legacy Manual Installation](#legacy-manual-installation)
@@ -63,18 +65,20 @@ This plugin is based on my manual mod (see the `manual` branch), which builds up
## Installation
This plugin is based on Jellyfin Version `10.11.x`
To install this plugin, you will first need to add the repository in Jellyfin.
1. Open your Jellyfin Dashboard.
2. Navigate to **Plugins** > **Repositories**.
3. Click the **+** sign to add a new repository.
2. Navigate to **Plugins** > **Manage Repositories**.
3. Click the **+ New Repository** button to add a new repository.
4. Enter a name (e.g., "Seasonals") and paste the following URL into the 'Repository URL' field:
```bash
https://raw.githubusercontent.com/CodeDevMLH/jellyfin-plugin-seasonals/main/manifest.json
https://raw.githubusercontent.com/CodeDevMLH/Jellyfin-Seasonals/refs/heads/main/manifest.json
```
5. Click **Save**.
6. Go to the **Catalog** tab at the top.
7. Under **General**, find the **Seasonals** plugin.
5. Click **Add**.
6. Go to the **Available** tab at the top.
7. Find the **Seasonals** plugin (Under **General**)
8. Click on it and select **Install**.
9. **Restart your Jellyfin server.**
10. **You may need to refresh your browser page** (F5 or Ctrl+R) to see the changes.
@@ -144,7 +148,7 @@ Feel free to contribute to this project by creating pull requests or reporting i
<script src="seasonals/seasonals.js"></script>
```
2. **Deploy Files**
Place the seasonals folder (including seasonals.js, CSS, and additional JavaScript files for each theme [this one](https://github.com/CodeDevMLH/Jellyfin-Seasonals/tree/main/seasonals)) inside the Jellyfin web server directory (labeld with "web").
Place the seasonals folder (including seasonals.js, CSS, and additional JavaScript files for each theme [this one](https://github.com/CodeDevMLH/Jellyfin-Seasonals/tree/legacy/seasonals)) inside the Jellyfin web server directory (labeld with "web").
3. **Configure Themes**
Customize the theme-configs.js file to modify or add new themes. The default configuration is shown below:

View File

@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<JellyfinVersion>10.11.0</JellyfinVersion>
<TargetFramework Condition="$(JellyfinVersion.StartsWith('10.11'))">net9.0</TargetFramework>
<TargetFramework Condition="!$(JellyfinVersion.StartsWith('10.11'))">net8.0</TargetFramework>
<RootNamespace>Jellyfin.Plugin.Seasonals</RootNamespace>
<Nullable>enable</Nullable>
<!-- <AnalysisMode>AllEnabledByDefault</AnalysisMode> -->
<!-- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> -->
<!-- <GenerateDocumentationFile>true</GenerateDocumentationFile> -->
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
<Title>Jellyfin Seasonals Plugin</Title>
<Authors>CodeDevMLH</Authors>
<Version>1.1.0.0</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/jellyfin-plugin-seasonals</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Jellyfin.Controller" Version="$(JellyfinVersion)" />
<PackageReference Include="Jellyfin.Model" Version="$(JellyfinVersion)" />
</ItemGroup>
<ItemGroup>
<None Remove="Configuration\configPage.html" />
<EmbeddedResource Include="Configuration\configPage.html" />
<None Remove="Web\**" />
<EmbeddedResource Include="Web\**" />
<None Include="..\README.md" />
<None Include="..\logo.png" CopyToOutputDirectory="Always" />
</ItemGroup>
</Project>

50
feat/Plugin.cs Normal file
View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using Jellyfin.Plugin.Seasonals.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
namespace Jellyfin.Plugin.Seasonals;
/// <summary>
/// The main plugin.
/// </summary>
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
/// <summary>
/// Initializes a new instance of the <see cref="Plugin"/> class.
/// </summary>
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
}
/// <inheritdoc />
public override string Name => "Seasonals";
/// <inheritdoc />
public override Guid Id => Guid.Parse("ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4");
/// <summary>
/// Gets the current plugin instance.
/// </summary>
public static Plugin? Instance { get; private set; }
/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
{
return
[
new PluginPageInfo
{
Name = "seasonals",
EmbeddedResourcePath = GetType().Namespace + ".Configuration.configPage.html"
}
];
}
}

View File

@@ -0,0 +1,18 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Jellyfin.Plugin.Seasonals;
/// <summary>
/// Registers plugin services.
/// </summary>
public class PluginServiceRegistrator : IPluginServiceRegistrator
{
/// <inheritdoc />
public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost)
{
serviceCollection.AddTransient<IStartupFilter, ScriptInjectionStartupFilter>();
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Seasonals;
/// <summary>
/// Middleware to inject the Seasonals script into the Jellyfin web interface.
/// </summary>
public class ScriptInjectionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ScriptInjectionMiddleware> _logger;
private const string ScriptTag = "<script src=\"Seasonals/Resources/seasonals.js\"></script>";
private const string Marker = "</body>";
/// <summary>
/// Initializes a new instance of the <see cref="ScriptInjectionMiddleware"/> class.
/// </summary>
/// <param name="next">The next delegate in the pipeline.</param>
/// <param name="logger">The logger.</param>
public ScriptInjectionMiddleware(RequestDelegate next, ILogger<ScriptInjectionMiddleware> logger)
{
_next = next;
_logger = logger;
}
/// <summary>
/// Invokes the middleware.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
if (IsIndexRequest(context.Request.Path))
{
var originalBodyStream = context.Response.Body;
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
try
{
await _next(context);
context.Response.Body = originalBodyStream;
responseBody.Seek(0, SeekOrigin.Begin);
if (context.Response.StatusCode == 200 &&
context.Response.ContentType != null &&
context.Response.ContentType.Contains("text/html", StringComparison.OrdinalIgnoreCase))
{
using var reader = new StreamReader(responseBody);
var content = await reader.ReadToEndAsync();
if (!content.Contains(ScriptTag, StringComparison.Ordinal) && content.Contains(Marker, StringComparison.Ordinal))
{
var newContent = content.Replace(Marker, $"{ScriptTag}\n{Marker}", StringComparison.Ordinal);
var bytes = Encoding.UTF8.GetBytes(newContent);
context.Response.ContentLength = bytes.Length;
await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
return;
}
}
responseBody.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(originalBodyStream);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error injecting Seasonals script via middleware.");
// Ensure we try to write back the original response if something failed
if (responseBody.Length > 0 && context.Response.Body == originalBodyStream)
{
responseBody.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(originalBodyStream);
}
}
}
else
{
await _next(context);
}
}
private static bool IsIndexRequest(PathString path)
{
if (!path.HasValue)
{
return false;
}
var p = path.Value;
// Check for root, index.html, or web/index.html
return p.Equals("/", StringComparison.OrdinalIgnoreCase) ||
p.Equals("/index.html", StringComparison.OrdinalIgnoreCase) ||
p.EndsWith("/web/index.html", StringComparison.OrdinalIgnoreCase);
}
}

View File

@@ -0,0 +1,21 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
namespace Jellyfin.Plugin.Seasonals;
/// <summary>
/// Startup filter to register the ScriptInjectionMiddleware.
/// </summary>
public class ScriptInjectionStartupFilter : IStartupFilter
{
/// <inheritdoc />
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
builder.UseMiddleware<ScriptInjectionMiddleware>();
next(builder);
};
}
}

21
manifest-v1.json Normal file
View File

@@ -0,0 +1,21 @@
[
{
"guid": "ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4",
"name": "Seasonals",
"description": "Adds seasonal effects like snow, leaves, etc. to the Jellyfin web interface.",
"overview": "Seasonal effects for Jellyfin",
"owner": "CodeDevMLH",
"category": "General",
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
"versions": [
{
"version": "1.0.0.0",
"changelog": "Initial release",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.0.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "be6d06a959b3e18e5058a6d8fb6d800c",
"timestamp": "2025-12-15T15:33:15Z"
}
]
}
]

View File

@@ -6,21 +6,29 @@
"overview": "Seasonal effects for Jellyfin",
"owner": "CodeDevMLH",
"category": "General",
"imageUrl": "https://raw.githubusercontent.com/CodeDevMLH/jellyfin-plugin-seasonals/main/icon.png",
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
"versions": [
{
"version": "1.1.1.0",
"changelog": "Bug fixing: Added Advanced Configuration UI for customizing individual seasonal effects.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.1.1.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "f1d7e2b24ad474904fa0eb1749e855b0",
"timestamp": "2025-12-16T00:56:08Z"
},
{
"version": "1.1.0.0",
"changelog": "Added Advanced Configuration UI for customizing individual seasonal effects.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://github.com/CodeDevMLH/jellyfin-plugin-seasonals",
"checksum": "CHECKSUM_HIER_EINFÜGEN",
"timestamp": "2025-12-16T00:00:00Z"
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.1.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "efc26cf0d09b313c52c089fc43df12ab",
"timestamp": "2025-12-16T00:22:10Z"
},
{
"version": "1.0.0.0",
"changelog": "Initial release",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/bin/Publish/Jellyfin.Plugin.Seasonals.zip",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.0.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "be6d06a959b3e18e5058a6d8fb6d800c",
"timestamp": "2025-12-15T15:33:15Z"
}