using System; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.Seasonals; /// /// Middleware to inject the Seasonals script into the Jellyfin web interface. /// public class ScriptInjectionMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private const string ScriptTag = ""; private const string Marker = ""; /// /// Initializes a new instance of the class. /// /// The next delegate in the pipeline. /// The logger. public ScriptInjectionMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } /// /// Invokes the middleware. /// /// The HTTP context. /// A task representing the asynchronous operation. 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); } }