using System;
using System.Linq;
using System.Text.Json.Nodes;
using System.IO;
using MediaBrowser.Common.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
namespace Jellyfin.Plugin.Enigma2;
/// Apply presentation preferences after the native API has enforced access rights.
public sealed class NativeChannelFilter : IStartupFilter
{
public Action Configure(Action next) => app =>
{
app.Use(async (context, continuePipeline) =>
{
if (!HttpMethods.IsGet(context.Request.Method) ||
!context.Request.Path.Value.TrimEnd('/').Equals("/LiveTv/Channels",StringComparison.OrdinalIgnoreCase))
{ await continuePipeline(); return; }
var originalQuery=context.Request.QueryString;
var originalEncoding=context.Request.Headers.AcceptEncoding;
// Buffer JSON before applying preferences; compression must not run inside this buffer.
context.Request.Headers.Remove("Accept-Encoding");
var parsed=QueryHelpers.ParseQuery(originalQuery.Value ?? "");
int Number(string name,int fallback) => parsed.TryGetValue(name,out var value) && int.TryParse(value,out var number) && number>=0 ? number : fallback;
var start=Number("StartIndex",0);var limit=Number("Limit",int.MaxValue);
// Fetch the permission-filtered full result, then paginate after preferences.
context.Request.QueryString=QueryString.Create(parsed.Where(p=>!p.Key.Equals("StartIndex",StringComparison.OrdinalIgnoreCase)&&!p.Key.Equals("Limit",StringComparison.OrdinalIgnoreCase)));
var originalBody=context.Response.Body;
await using var buffer=new MemoryStream();context.Response.Body=buffer;
try
{
await continuePipeline();
context.Response.Body=originalBody;
if (context.Response.StatusCode==200 && context.Response.ContentType?.StartsWith("application/json",StringComparison.OrdinalIgnoreCase)==true)
{
buffer.Position=0;
var document=await JsonNode.ParseAsync(buffer,cancellationToken:context.RequestAborted);
if(document?["Items"] is JsonArray items)
{
Guid.TryParse(context.User.FindFirst("Jellyfin-UserId")?.Value,out var userId);
if(userId==Guid.Empty && parsed.TryGetValue("UserId",out var requested)&&Guid.TryParse(requested,out var requestedId)) userId=requestedId;
var preferences=userId==Guid.Empty?new ChannelPreferences():await context.RequestServices.GetRequiredService().Load(userId,context.RequestAborted);
var hidden=preferences.HiddenIds.ToHashSet(StringComparer.OrdinalIgnoreCase);
var includeHidden=parsed.TryGetValue("DecoderIncludeHidden",out var includeValue)&&bool.TryParse(includeValue,out var include)&&include;
var order=preferences.OrderedIds.Select((id,index)=>(id,index)).ToDictionary(x=>x.id,x=>x.index,StringComparer.OrdinalIgnoreCase);
string Id(JsonNode node) => Guid.TryParse((string)node?["Id"],out var id)?id.ToString("N"):"";
var filtered=items.Where(item=>includeHidden||!hidden.Contains(Id(item))).OrderBy(item=>order.TryGetValue(Id(item),out var position)?position:int.MaxValue).ToArray();
document["TotalRecordCount"]=filtered.Length;
document["StartIndex"]=start;
document["Items"]=new JsonArray(filtered.Skip(start).Take(limit).Select(item=>item.DeepClone()).ToArray());
context.Response.ContentLength=null;
await context.Response.WriteAsync(document.ToJsonString(),context.RequestAborted);
return;
}
}
buffer.Position=0;await buffer.CopyToAsync(originalBody,context.RequestAborted);
}
finally { context.Response.Body=originalBody;context.Request.QueryString=originalQuery;context.Request.Headers.AcceptEncoding=originalEncoding; }
});
next(app);
};
}