using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using Jellyfin.Extensions; using Jellyfin.Server.Migrations; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net.WebSocketMessages; using MediaBrowser.Controller.Net.WebSocketMessages.Outbound; using MediaBrowser.Model.ApiClient; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; namespace Jellyfin.Server.Filters { /// /// Add models not directly used by the API, but used for discovery and websockets. /// public class AdditionalModelFilter : IDocumentFilter { // Array of options that should not be visible in the api spec. private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions) }; private readonly IServerConfigurationManager _serverConfigurationManager; /// /// Initializes a new instance of the class. /// /// Instance of the interface. public AdditionalModelFilter(IServerConfigurationManager serverConfigurationManager) { _serverConfigurationManager = serverConfigurationManager; } /// public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { context.SchemaGenerator.GenerateSchema(typeof(IPlugin), context.SchemaRepository); var webSocketTypes = typeof(WebSocketMessage).Assembly.GetTypes() .Where(t => t.IsSubclassOf(typeof(WebSocketMessage)) && !t.IsGenericType && t != typeof(WebSocketMessageInfo)) .ToList(); var inboundWebSocketSchemas = new List(); var inboundWebSocketDiscriminators = new Dictionary(); foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t))) { var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute()?.Value; if (messageType is null) { continue; } var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository); inboundWebSocketSchemas.Add(schema); inboundWebSocketDiscriminators[messageType.ToString()!] = schema.Reference.ReferenceV3; } var inboundWebSocketMessageSchema = new OpenApiSchema { Type = "object", Description = "Represents the list of possible inbound websocket types", Reference = new OpenApiReference { Id = nameof(InboundWebSocketMessage), Type = ReferenceType.Schema }, OneOf = inboundWebSocketSchemas, Discriminator = new OpenApiDiscriminator { PropertyName = nameof(WebSocketMessage.MessageType), Mapping = inboundWebSocketDiscriminators } }; context.SchemaRepository.AddDefinition(nameof(InboundWebSocketMessage), inboundWebSocketMessageSchema); var outboundWebSocketSchemas = new List(); var outboundWebSocketDiscriminators = new Dictionary(); foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t))) { var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute()?.Value; if (messageType is null) { continue; } // Additional discriminator needed for GroupUpdate models... if (messageType == SessionMessageType.SyncPlayGroupUpdate && type != typeof(SyncPlayGroupUpdateCommandMessage)) { continue; } var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository); outboundWebSocketSchemas.Add(schema); outboundWebSocketDiscriminators.Add(messageType.ToString()!, schema.Reference.ReferenceV3); } var outboundWebSocketMessageSchema = new OpenApiSchema { Type = "object", Description = "Represents the list of possible outbound websocket types", Reference = new OpenApiReference { Id = nameof(OutboundWebSocketMessage), Type = ReferenceType.Schema }, OneOf = outboundWebSocketSchemas, Discriminator = new OpenApiDiscriminator { PropertyName = nameof(WebSocketMessage.MessageType), Mapping = outboundWebSocketDiscriminators } }; context.SchemaRepository.AddDefinition(nameof(OutboundWebSocketMessage), outboundWebSocketMessageSchema); context.SchemaRepository.AddDefinition( nameof(WebSocketMessage), new OpenApiSchema { Type = "object", Description = "Represents the possible websocket types", Reference = new OpenApiReference { Id = nameof(WebSocketMessage), Type = ReferenceType.Schema }, OneOf = new[] { inboundWebSocketMessageSchema, outboundWebSocketMessageSchema } }); // Manually generate sync play GroupUpdate messages. if (!context.SchemaRepository.Schemas.TryGetValue(nameof(GroupUpdate), out var groupUpdateSchema)) { groupUpdateSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); } var groupUpdateOfGroupInfoSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); var groupUpdateOfGroupStateSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); var groupUpdateOfStringSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); var groupUpdateOfPlayQueueSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); groupUpdateSchema.OneOf = new List { groupUpdateOfGroupInfoSchema, groupUpdateOfGroupStateSchema, groupUpdateOfStringSchema, groupUpdateOfPlayQueueSchema }; groupUpdateSchema.Discriminator = new OpenApiDiscriminator { PropertyName = nameof(GroupUpdate.Type), Mapping = new Dictionary { { GroupUpdateType.UserJoined.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, { GroupUpdateType.UserLeft.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, { GroupUpdateType.GroupJoined.ToString(), groupUpdateOfGroupInfoSchema.Reference.ReferenceV3 }, { GroupUpdateType.GroupLeft.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, { GroupUpdateType.StateUpdate.ToString(), groupUpdateOfGroupStateSchema.Reference.ReferenceV3 }, { GroupUpdateType.PlayQueue.ToString(), groupUpdateOfPlayQueueSchema.Reference.ReferenceV3 }, { GroupUpdateType.NotInGroup.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, { GroupUpdateType.GroupDoesNotExist.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }, { GroupUpdateType.LibraryAccessDenied.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 } } }; context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository); foreach (var configuration in _serverConfigurationManager.GetConfigurationStores()) { if (_ignoredConfigurations.IndexOf(configuration.ConfigurationType) != -1) { continue; } context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository); } context.SchemaRepository.AddDefinition(nameof(TranscodeReason), new OpenApiSchema { Type = "string", Enum = Enum.GetNames() .Select(e => new OpenApiString(e)) .Cast() .ToArray() }); } } }