| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 | 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{    /// <summary>    /// Add models not directly used by the API, but used for discovery and websockets.    /// </summary>    public class AdditionalModelFilter : IDocumentFilter    {        // Array of options that should not be visible in the api spec.        private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions) };        private readonly IServerConfigurationManager _serverConfigurationManager;        /// <summary>        /// Initializes a new instance of the <see cref="AdditionalModelFilter"/> class.        /// </summary>        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>        public AdditionalModelFilter(IServerConfigurationManager serverConfigurationManager)        {            _serverConfigurationManager = serverConfigurationManager;        }        /// <inheritdoc />        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<OpenApiSchema>();            var inboundWebSocketDiscriminators = new Dictionary<string, string>();            foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t)))            {                var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.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<OpenApiSchema>();            var outboundWebSocketDiscriminators = new Dictionary<string, string>();            foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t)))            {                var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;                if (messageType is null)                {                    continue;                }                var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);                outboundWebSocketSchemas.Add(schema);                outboundWebSocketDiscriminators.Add(messageType.ToString()!, schema.Reference.ReferenceV3);            }            // Add custom "SyncPlayGroupUpdateMessage" schema because Swashbuckle cannot generate it for us            var syncPlayGroupUpdateMessageSchema = new OpenApiSchema            {                Type = "object",                Description = "Untyped sync play command.",                Properties = new Dictionary<string, OpenApiSchema>                {                    {                        "Data", new OpenApiSchema                        {                            AllOf =                            [                                new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(GroupUpdate<object>) } }                            ],                            Description = "Group update data",                            Nullable = false,                        }                    },                    { "MessageId", new OpenApiSchema { Type = "string", Format = "uuid", Description = "Gets or sets the message id." } },                    {                        "MessageType", new OpenApiSchema                        {                            Enum = Enum.GetValues<SessionMessageType>().Select(type => new OpenApiString(type.ToString())).ToList<IOpenApiAny>(),                            AllOf =                            [                                new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(SessionMessageType) } }                            ],                            Description = "The different kinds of messages that are used in the WebSocket api.",                            Default = new OpenApiString(nameof(SessionMessageType.SyncPlayGroupUpdate)),                            ReadOnly = true                        }                    },                },                AdditionalPropertiesAllowed = false,                Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "SyncPlayGroupUpdateMessage" }            };            context.SchemaRepository.AddDefinition("SyncPlayGroupUpdateMessage", syncPlayGroupUpdateMessageSchema);            outboundWebSocketSchemas.Add(syncPlayGroupUpdateMessageSchema);            outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayGroupUpdateMessageSchema.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.            var groupUpdateTypes = typeof(GroupUpdate<>).Assembly.GetTypes()                .Where(t => t.BaseType is not null                            && t.BaseType.IsGenericType                            && t.BaseType.GetGenericTypeDefinition() == typeof(GroupUpdate<>))                .ToList();            var groupUpdateSchemas = new List<OpenApiSchema>();            var groupUpdateDiscriminators = new Dictionary<string, string>();            foreach (var type in groupUpdateTypes)            {                var groupUpdateType = (GroupUpdateType?)type.GetProperty(nameof(GroupUpdate<object>.Type))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;                if (groupUpdateType is null)                {                    continue;                }                var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);                groupUpdateSchemas.Add(schema);                groupUpdateDiscriminators[groupUpdateType.ToString()!] = schema.Reference.ReferenceV3;            }            var groupUpdateSchema = new OpenApiSchema            {                Type = "object",                Description = "Represents the list of possible group update types",                Reference = new OpenApiReference                {                    Id = nameof(GroupUpdate<object>),                    Type = ReferenceType.Schema                },                OneOf = groupUpdateSchemas,                Discriminator = new OpenApiDiscriminator                {                    PropertyName = nameof(GroupUpdate<object>.Type),                    Mapping = groupUpdateDiscriminators                }            };            context.SchemaRepository.Schemas[nameof(GroupUpdate<object>)] = groupUpdateSchema;            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<TranscodeReason>()                    .Select(e => new OpenApiString(e))                    .Cast<IOpenApiAny>()                    .ToArray()            });        }    }}
 |