diff --git a/Common.cs b/Common.cs index d63e73a..1da85ae 100644 --- a/Common.cs +++ b/Common.cs @@ -238,6 +238,8 @@ public static class ActionContextExtensions "ItemID", ]; + private static readonly string[] UserGuidKeys = ["userId", "UserId", "UserID"]; + private static readonly string[] IdsGuidKeys = ["ids", "Ids", "IDs"]; private static readonly HashSet SearchActionNames = new( @@ -277,13 +279,17 @@ public static class ActionContextExtensions ) { "GetItems", + "GetItemById", "GetItem", "GetItemLegacy", "GetItemsByUserIdLegacy", "GetPlaybackInfo", + "GetPlaybackMediaSources", "GetPostedPlaybackInfo", "GetVideoStream", + "GetVideoStreamByContainer", "GetDownload", + "GetSubtitle", "GetSubtitleWithTicks", }; @@ -347,8 +353,18 @@ public static bool IsInsertableAction(this HttpContext ctx) ); } - public static bool IsInsertableAction(this ActionExecutingContext ctx) => - ctx.HttpContext.IsInsertableAction(); + public static bool IsInsertableAction(this ActionExecutingContext ctx) + { + var actionName = ctx.GetActionName(); + if (actionName is null) + return ctx.HttpContext.IsInsertableAction(); + + return InsertableActionNames.Contains(actionName) + && ( + !InsertableListActionNames.Contains(actionName) + || InsertableListActionNames.Contains(actionName) && ctx.IsSingleItemList() + ); + } public static bool IsSingleItemList(this HttpContext ctx) { @@ -448,7 +464,30 @@ public static void ReplaceGuid(this ActionExecutingContext ctx, Guid value) public static bool TryGetUserId(this ActionExecutingContext ctx, out Guid userId) { - return ctx.HttpContext.TryGetUserId(out userId); + if (ctx.HttpContext.TryGetUserId(out userId)) + return true; + + foreach (var key in UserGuidKeys) + { + if ( + ctx.ActionArguments.TryGetValue(key, out var arg) + && TryParseGuidValue(arg, out userId) + ) + { + return true; + } + + if ( + ctx.RouteData.Values.TryGetValue(key, out var route) + && TryParseGuidValue(route, out userId) + ) + { + return true; + } + } + + userId = Guid.Empty; + return false; } public static bool TryGetUserId(this HttpContext ctx, out Guid userId) @@ -465,6 +504,22 @@ public static bool TryGetUserId(this HttpContext ctx, out Guid userId) return userId != Guid.Empty; } + private static bool TryParseGuidValue(object? raw, out Guid guid) + { + switch (raw) + { + case Guid g when g != Guid.Empty: + guid = g; + return true; + case string s when Guid.TryParse(s, out var parsed) && parsed != Guid.Empty: + guid = parsed; + return true; + default: + guid = Guid.Empty; + return false; + } + } + public static bool TryGetActionArgument( this ActionExecutingContext ctx, string key, diff --git a/Decorators/DtoServiceDecorator.cs b/Decorators/DtoServiceDecorator.cs index 36b76a1..2317e03 100644 --- a/Decorators/DtoServiceDecorator.cs +++ b/Decorators/DtoServiceDecorator.cs @@ -88,9 +88,7 @@ private void Patch(BaseItemDto dto, BaseItem? item, bool isList, User? user) { if (dto.Path is not null && dto.Path.IsUrl()) { - // dto.Path = "/stub"; - - + dto.Path = "/stub"; } dto.CanDownload = true; @@ -107,9 +105,13 @@ private void Patch(BaseItemDto dto, BaseItem? item, bool isList, User? user) { foreach (var source in dto.MediaSources) { - //source.Path = "/stub"; - //source.IsRemote = false; - // source.Protocol = MediaProtocol.File; + source.SupportsDirectPlay = false; + if (source.Path is not null && source.Path.IsUrl()) + { + source.Path = "/stub"; + source.IsRemote = false; + source.Protocol = MediaProtocol.File; + } } } return; diff --git a/Decorators/MediaSourceManagerDecorator.cs b/Decorators/MediaSourceManagerDecorator.cs index b155d9e..13eecac 100644 --- a/Decorators/MediaSourceManagerDecorator.cs +++ b/Decorators/MediaSourceManagerDecorator.cs @@ -388,12 +388,14 @@ await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, ct) } // Stub path after probing is done so the real URL is never sent to clients. - // Force File protocol so clients proxy through Jellyfin instead of direct-playing. - if (ctx.GetActionName() == "GetPostedPlaybackInfo") + // Force File protocol so clients proxy through Jellyfin instead of direct-playing + // internal addon URLs like http://caddy:8088/... + if (ctx.GetActionName() is "GetPlaybackInfo" or "GetPostedPlaybackInfo") { selected.Path = "/stub"; selected.IsRemote = false; selected.Protocol = MediaProtocol.File; + selected.SupportsDirectPlay = false; } return [selected]; @@ -525,7 +527,9 @@ private MediaSourceInfo GetVersionInfo( Size = item.Size, Type = type, SupportsDirectStream = true, - SupportsDirectPlay = true, + // Never direct-play Gelato HTTP URLs from clients. They may be internal Docker + // URLs (for example http://caddy:8088/...) and must be proxied by Jellyfin. + SupportsDirectPlay = false, // just always say yes HasSegments = true, //HasSegments = MediaSegmentManager.HasSegments(item.Id) diff --git a/Filters/SearchActionFilter.cs b/Filters/SearchActionFilter.cs index 0ac1ecb..c389bc7 100644 --- a/Filters/SearchActionFilter.cs +++ b/Filters/SearchActionFilter.cs @@ -173,6 +173,12 @@ private List ConvertMetasToDtos(List metas) if (baseItem is null) continue; + if (manager.FindExistingItem(baseItem) is { } existing) + { + dtos.Add(dtoService.GetBaseItemDto(existing, options)); + continue; + } + var dto = dtoService.GetBaseItemDto(baseItem, options); var stremioUri = StremioUri.FromBaseItem(baseItem); dto.Id = stremioUri.ToGuid();