Browse Source

Make query parameters nullable or set default value

crobibero 4 years ago
parent
commit
5d34b07d1f
31 changed files with 442 additions and 408 deletions
  1. 1 1
      Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
  2. 2 2
      Jellyfin.Api/Controllers/AlbumsController.cs
  3. 51 51
      Jellyfin.Api/Controllers/ArtistsController.cs
  4. 11 11
      Jellyfin.Api/Controllers/ChannelsController.cs
  5. 3 3
      Jellyfin.Api/Controllers/CollectionController.cs
  6. 6 6
      Jellyfin.Api/Controllers/FilterController.cs
  7. 27 27
      Jellyfin.Api/Controllers/GenresController.cs
  8. 29 15
      Jellyfin.Api/Controllers/InstantMixController.cs
  9. 6 4
      Jellyfin.Api/Controllers/ItemsController.cs
  10. 31 25
      Jellyfin.Api/Controllers/LibraryController.cs
  11. 10 10
      Jellyfin.Api/Controllers/LibraryStructureController.cs
  12. 83 73
      Jellyfin.Api/Controllers/LiveTvController.cs
  13. 21 19
      Jellyfin.Api/Controllers/MediaInfoController.cs
  14. 8 18
      Jellyfin.Api/Controllers/MoviesController.cs
  15. 27 27
      Jellyfin.Api/Controllers/MusicGenresController.cs
  16. 27 27
      Jellyfin.Api/Controllers/PersonsController.cs
  17. 2 2
      Jellyfin.Api/Controllers/PlaylistsController.cs
  18. 16 16
      Jellyfin.Api/Controllers/PlaystateController.cs
  19. 1 1
      Jellyfin.Api/Controllers/RemoteImageController.cs
  20. 2 2
      Jellyfin.Api/Controllers/SearchController.cs
  21. 6 6
      Jellyfin.Api/Controllers/SessionController.cs
  22. 27 27
      Jellyfin.Api/Controllers/StudiosController.cs
  23. 2 2
      Jellyfin.Api/Controllers/SubtitleController.cs
  24. 3 3
      Jellyfin.Api/Controllers/SuggestionsController.cs
  25. 1 1
      Jellyfin.Api/Controllers/TrailersController.cs
  26. 17 9
      Jellyfin.Api/Controllers/TvShowsController.cs
  27. 3 3
      Jellyfin.Api/Controllers/UserLibraryController.cs
  28. 3 3
      Jellyfin.Api/Controllers/UserViewsController.cs
  29. 4 2
      Jellyfin.Api/Controllers/VideosController.cs
  30. 6 6
      Jellyfin.Api/Controllers/YearsController.cs
  31. 6 6
      Jellyfin.Api/Helpers/SimilarItemsHelper.cs

+ 1 - 1
Jellyfin.Api/Auth/BaseAuthorizationHandler.cs

@@ -52,7 +52,7 @@ namespace Jellyfin.Api.Auth
         {
             // Ensure claim has userId.
             var userId = ClaimHelpers.GetUserId(claimsPrincipal);
-            if (userId == null)
+            if (!userId.HasValue)
             {
                 return false;
             }

+ 2 - 2
Jellyfin.Api/Controllers/AlbumsController.cs

@@ -52,7 +52,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
             [FromRoute] string albumId,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] int? limit)
         {
@@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
             [FromRoute] string artistId,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] int? limit)
         {

+ 51 - 51
Jellyfin.Api/Controllers/ArtistsController.cs

@@ -83,31 +83,31 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] double? minCommunityRating,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] string searchTerm,
-            [FromQuery] string parentId,
-            [FromQuery] string fields,
-            [FromQuery] string excludeItemTypes,
-            [FromQuery] string includeItemTypes,
-            [FromQuery] string filters,
+            [FromQuery] string? searchTerm,
+            [FromQuery] string? parentId,
+            [FromQuery] string? fields,
+            [FromQuery] string? excludeItemTypes,
+            [FromQuery] string? includeItemTypes,
+            [FromQuery] string? filters,
             [FromQuery] bool? isFavorite,
-            [FromQuery] string mediaTypes,
-            [FromQuery] string genres,
-            [FromQuery] string genreIds,
-            [FromQuery] string officialRatings,
-            [FromQuery] string tags,
-            [FromQuery] string years,
+            [FromQuery] string? mediaTypes,
+            [FromQuery] string? genres,
+            [FromQuery] string? genreIds,
+            [FromQuery] string? officialRatings,
+            [FromQuery] string? tags,
+            [FromQuery] string? years,
             [FromQuery] bool? enableUserData,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string person,
-            [FromQuery] string personIds,
-            [FromQuery] string personTypes,
-            [FromQuery] string studios,
-            [FromQuery] string studioIds,
-            [FromQuery] Guid userId,
-            [FromQuery] string nameStartsWithOrGreater,
-            [FromQuery] string nameStartsWith,
-            [FromQuery] string nameLessThan,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? person,
+            [FromQuery] string? personIds,
+            [FromQuery] string? personTypes,
+            [FromQuery] string? studios,
+            [FromQuery] string? studioIds,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? nameStartsWithOrGreater,
+            [FromQuery] string? nameStartsWith,
+            [FromQuery] string? nameLessThan,
             [FromQuery] bool? enableImages = true,
             [FromQuery] bool enableTotalRecordCount = true)
         {
@@ -119,9 +119,9 @@ namespace Jellyfin.Api.Controllers
             User? user = null;
             BaseItem parentItem;
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                user = _userManager.GetUserById(userId);
+                user = _userManager.GetUserById(userId.Value);
                 parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
             }
             else
@@ -292,31 +292,31 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] double? minCommunityRating,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] string searchTerm,
-            [FromQuery] string parentId,
-            [FromQuery] string fields,
-            [FromQuery] string excludeItemTypes,
-            [FromQuery] string includeItemTypes,
-            [FromQuery] string filters,
+            [FromQuery] string? searchTerm,
+            [FromQuery] string? parentId,
+            [FromQuery] string? fields,
+            [FromQuery] string? excludeItemTypes,
+            [FromQuery] string? includeItemTypes,
+            [FromQuery] string? filters,
             [FromQuery] bool? isFavorite,
-            [FromQuery] string mediaTypes,
-            [FromQuery] string genres,
-            [FromQuery] string genreIds,
-            [FromQuery] string officialRatings,
-            [FromQuery] string tags,
-            [FromQuery] string years,
+            [FromQuery] string? mediaTypes,
+            [FromQuery] string? genres,
+            [FromQuery] string? genreIds,
+            [FromQuery] string? officialRatings,
+            [FromQuery] string? tags,
+            [FromQuery] string? years,
             [FromQuery] bool? enableUserData,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string person,
-            [FromQuery] string personIds,
-            [FromQuery] string personTypes,
-            [FromQuery] string studios,
-            [FromQuery] string studioIds,
-            [FromQuery] Guid userId,
-            [FromQuery] string nameStartsWithOrGreater,
-            [FromQuery] string nameStartsWith,
-            [FromQuery] string nameLessThan,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? person,
+            [FromQuery] string? personIds,
+            [FromQuery] string? personTypes,
+            [FromQuery] string? studios,
+            [FromQuery] string? studioIds,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? nameStartsWithOrGreater,
+            [FromQuery] string? nameStartsWith,
+            [FromQuery] string? nameLessThan,
             [FromQuery] bool? enableImages = true,
             [FromQuery] bool enableTotalRecordCount = true)
         {
@@ -328,9 +328,9 @@ namespace Jellyfin.Api.Controllers
             User? user = null;
             BaseItem parentItem;
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                user = _userManager.GetUserById(userId);
+                user = _userManager.GetUserById(userId.Value);
                 parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
             }
             else
@@ -469,15 +469,15 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the artist.</returns>
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetArtistByName([FromRoute] string name, [FromQuery] Guid userId)
+        public ActionResult<BaseItemDto> GetArtistByName([FromRoute] string name, [FromQuery] Guid? userId)
         {
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
             var item = _libraryManager.GetArtist(name, dtoOptions);
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                var user = _userManager.GetUserById(userId);
+                var user = _userManager.GetUserById(userId.Value);
 
                 return _dtoService.GetBaseItemDto(item, dtoOptions, user);
             }

+ 11 - 11
Jellyfin.Api/Controllers/ChannelsController.cs

@@ -53,7 +53,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetChannels(
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] bool? supportsLatestItems,
@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
             {
                 Limit = limit,
                 StartIndex = startIndex,
-                UserId = userId,
+                UserId = userId ?? Guid.Empty,
                 SupportsLatestItems = supportsLatestItems,
                 SupportsMediaDeletion = supportsMediaDeletion,
                 IsFavorite = isFavorite
@@ -124,9 +124,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? sortBy,
             [FromQuery] string? fields)
         {
-            var user = userId == null
-                ? null
-                : _userManager.GetUserById(userId.Value);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var query = new InternalItemsQuery(user)
             {
@@ -195,13 +195,13 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] string filters,
-            [FromQuery] string fields,
-            [FromQuery] string channelIds)
+            [FromQuery] string? filters,
+            [FromQuery] string? fields,
+            [FromQuery] string? channelIds)
         {
-            var user = userId == null || userId == Guid.Empty
-                ? null
-                : _userManager.GetUserById(userId.Value);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var query = new InternalItemsQuery(user)
             {

+ 3 - 3
Jellyfin.Api/Controllers/CollectionController.cs

@@ -44,8 +44,8 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="name">The name of the collection.</param>
         /// <param name="ids">Item Ids to add to the collection.</param>
-        /// <param name="isLocked">Whether or not to lock the new collection.</param>
         /// <param name="parentId">Optional. Create the collection within a specific folder.</param>
+        /// <param name="isLocked">Whether or not to lock the new collection.</param>
         /// <response code="200">Collection created.</response>
         /// <returns>A <see cref="CollectionCreationOptions"/> with information about the new collection.</returns>
         [HttpPost]
@@ -53,8 +53,8 @@ namespace Jellyfin.Api.Controllers
         public ActionResult<CollectionCreationResult> CreateCollection(
             [FromQuery] string? name,
             [FromQuery] string? ids,
-            [FromQuery] bool isLocked,
-            [FromQuery] Guid? parentId)
+            [FromQuery] Guid? parentId,
+            [FromQuery] bool isLocked = false)
         {
             var userId = _authContext.GetAuthorizationInfo(Request).UserId;
 

+ 6 - 6
Jellyfin.Api/Controllers/FilterController.cs

@@ -57,9 +57,9 @@ namespace Jellyfin.Api.Controllers
                 ? null
                 : _libraryManager.GetItemById(parentId);
 
-            var user = userId == null || userId == Guid.Empty
-                ? null
-                : _userManager.GetUserById(userId.Value);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             if (string.Equals(includeItemTypes, nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
                 || string.Equals(includeItemTypes, nameof(Playlist), StringComparison.OrdinalIgnoreCase)
@@ -152,9 +152,9 @@ namespace Jellyfin.Api.Controllers
                 ? null
                 : _libraryManager.GetItemById(parentId);
 
-            var user = userId == null || userId == Guid.Empty
-                ? null
-                : _userManager.GetUserById(userId.Value);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             if (string.Equals(includeItemTypes, nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
                 || string.Equals(includeItemTypes, nameof(Playlist), StringComparison.OrdinalIgnoreCase)

+ 27 - 27
Jellyfin.Api/Controllers/GenresController.cs

@@ -84,31 +84,31 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] double? minCommunityRating,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] string searchTerm,
-            [FromQuery] string parentId,
-            [FromQuery] string fields,
-            [FromQuery] string excludeItemTypes,
-            [FromQuery] string includeItemTypes,
-            [FromQuery] string filters,
+            [FromQuery] string? searchTerm,
+            [FromQuery] string? parentId,
+            [FromQuery] string? fields,
+            [FromQuery] string? excludeItemTypes,
+            [FromQuery] string? includeItemTypes,
+            [FromQuery] string? filters,
             [FromQuery] bool? isFavorite,
-            [FromQuery] string mediaTypes,
-            [FromQuery] string genres,
-            [FromQuery] string genreIds,
-            [FromQuery] string officialRatings,
-            [FromQuery] string tags,
-            [FromQuery] string years,
+            [FromQuery] string? mediaTypes,
+            [FromQuery] string? genres,
+            [FromQuery] string? genreIds,
+            [FromQuery] string? officialRatings,
+            [FromQuery] string? tags,
+            [FromQuery] string? years,
             [FromQuery] bool? enableUserData,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string person,
-            [FromQuery] string personIds,
-            [FromQuery] string personTypes,
-            [FromQuery] string studios,
-            [FromQuery] string studioIds,
-            [FromQuery] Guid userId,
-            [FromQuery] string nameStartsWithOrGreater,
-            [FromQuery] string nameStartsWith,
-            [FromQuery] string nameLessThan,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? person,
+            [FromQuery] string? personIds,
+            [FromQuery] string? personTypes,
+            [FromQuery] string? studios,
+            [FromQuery] string? studioIds,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? nameStartsWithOrGreater,
+            [FromQuery] string? nameStartsWith,
+            [FromQuery] string? nameLessThan,
             [FromQuery] bool? enableImages = true,
             [FromQuery] bool enableTotalRecordCount = true)
         {
@@ -120,9 +120,9 @@ namespace Jellyfin.Api.Controllers
             User? user = null;
             BaseItem parentItem;
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                user = _userManager.GetUserById(userId);
+                user = _userManager.GetUserById(userId.Value);
                 parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
             }
             else
@@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the genre.</returns>
         [HttpGet("{genreName}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetGenre([FromRoute] string genreName, [FromQuery] Guid userId)
+        public ActionResult<BaseItemDto> GetGenre([FromRoute] string genreName, [FromQuery] Guid? userId)
         {
             var dtoOptions = new DtoOptions()
                 .AddClientFields(Request);
@@ -280,9 +280,9 @@ namespace Jellyfin.Api.Controllers
                 item = _libraryManager.GetGenre(genreName);
             }
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                var user = _userManager.GetUserById(userId);
+                var user = _userManager.GetUserById(userId.Value);
 
                 return _dtoService.GetBaseItemDto(item, dtoOptions, user);
             }

+ 29 - 15
Jellyfin.Api/Controllers/InstantMixController.cs

@@ -63,7 +63,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
             [FromRoute] Guid id,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] bool? enableImages,
@@ -72,7 +72,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? enableImageTypes)
         {
             var item = _libraryManager.GetItemById(id);
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request)
@@ -98,7 +100,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
             [FromRoute] Guid id,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] bool? enableImages,
@@ -107,7 +109,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? enableImageTypes)
         {
             var album = _libraryManager.GetItemById(id);
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request)
@@ -133,7 +137,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
             [FromRoute] Guid id,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] bool? enableImages,
@@ -142,7 +146,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? enableImageTypes)
         {
             var playlist = (Playlist)_libraryManager.GetItemById(id);
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request)
@@ -168,7 +174,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
             [FromRoute] string? name,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] bool? enableImages,
@@ -176,7 +182,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? imageTypeLimit,
             [FromQuery] string? enableImageTypes)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request)
@@ -202,7 +210,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
             [FromRoute] Guid id,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] bool? enableImages,
@@ -211,7 +219,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? enableImageTypes)
         {
             var item = _libraryManager.GetItemById(id);
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request)
@@ -237,7 +247,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
             [FromRoute] Guid id,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] bool? enableImages,
@@ -246,7 +256,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? enableImageTypes)
         {
             var item = _libraryManager.GetItemById(id);
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request)
@@ -272,7 +284,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
             [FromRoute] Guid id,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] bool? enableImages,
@@ -281,7 +293,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? enableImageTypes)
         {
             var item = _libraryManager.GetItemById(id);
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request)
@@ -290,7 +304,7 @@ namespace Jellyfin.Api.Controllers
             return GetResult(items, user, limit, dtoOptions);
         }
 
-        private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User user, int? limit, DtoOptions dtoOptions)
+        private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
         {
             var list = items;
 

+ 6 - 4
Jellyfin.Api/Controllers/ItemsController.cs

@@ -143,8 +143,8 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("/Users/{uId}/Items")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetItems(
-            [FromRoute] Guid uId,
-            [FromQuery] Guid userId,
+            [FromRoute] Guid? uId,
+            [FromQuery] Guid? userId,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] bool? hasThemeSong,
             [FromQuery] bool? hasThemeVideo,
@@ -226,9 +226,11 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] bool? enableImages = true)
         {
             // use user id route parameter over query parameter
-            userId = (uId != null) ? uId : userId;
+            userId = uId ?? userId;
 
-            var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request)

+ 31 - 25
Jellyfin.Api/Controllers/LibraryController.cs

@@ -146,11 +146,11 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<ThemeMediaResult> GetThemeSongs(
             [FromRoute] Guid itemId,
-            [FromQuery] Guid userId,
-            [FromQuery] bool inheritFromParent)
+            [FromQuery] Guid? userId,
+            [FromQuery] bool inheritFromParent = false)
         {
-            var user = !userId.Equals(Guid.Empty)
-                ? _userManager.GetUserById(userId)
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
                 : null;
 
             var item = itemId.Equals(Guid.Empty)
@@ -212,11 +212,11 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<ThemeMediaResult> GetThemeVideos(
             [FromRoute] Guid itemId,
-            [FromQuery] Guid userId,
-            [FromQuery] bool inheritFromParent)
+            [FromQuery] Guid? userId,
+            [FromQuery] bool inheritFromParent = false)
         {
-            var user = !userId.Equals(Guid.Empty)
-                ? _userManager.GetUserById(userId)
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
                 : null;
 
             var item = itemId.Equals(Guid.Empty)
@@ -277,8 +277,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<AllThemeMediaResult> GetThemeMedia(
             [FromRoute] Guid itemId,
-            [FromQuery] Guid userId,
-            [FromQuery] bool inheritFromParent)
+            [FromQuery] Guid? userId,
+            [FromQuery] bool inheritFromParent = false)
         {
             var themeSongs = GetThemeSongs(
                 itemId,
@@ -361,12 +361,14 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status401Unauthorized)]
-        public ActionResult DeleteItems([FromQuery] string ids)
+        public ActionResult DeleteItems([FromQuery] string? ids)
         {
-            var itemIds = string.IsNullOrWhiteSpace(ids)
-                ? Array.Empty<string>()
-                : RequestHelpers.Split(ids, ',', true);
+            if (string.IsNullOrEmpty(ids))
+            {
+                return NoContent();
+            }
 
+            var itemIds = RequestHelpers.Split(ids, ',', true);
             foreach (var i in itemIds)
             {
                 var item = _libraryManager.GetItemById(i);
@@ -403,12 +405,12 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<ItemCounts> GetItemCounts(
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] bool? isFavorite)
         {
-            var user = userId.Equals(Guid.Empty)
-                ? null
-                : _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var counts = new ItemCounts
             {
@@ -437,7 +439,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid userId)
+        public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid? userId)
         {
             var item = _libraryManager.GetItemById(itemId);
 
@@ -448,8 +450,8 @@ namespace Jellyfin.Api.Controllers
 
             var baseItemDtos = new List<BaseItemDto>();
 
-            var user = !userId.Equals(Guid.Empty)
-                ? _userManager.GetUserById(userId)
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
                 : null;
 
             var dtoOptions = new DtoOptions().AddClientFields(Request);
@@ -688,7 +690,7 @@ namespace Jellyfin.Api.Controllers
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
             [FromRoute] Guid itemId,
             [FromQuery] string? excludeArtistIds,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] string? fields)
         {
@@ -737,7 +739,9 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("/Libraries/AvailableOptions")]
         [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo([FromQuery] string? libraryContentType, [FromQuery] bool isNewLibrary)
+        public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(
+            [FromQuery] string? libraryContentType,
+            [FromQuery] bool isNewLibrary = false)
         {
             var result = new LibraryOptionsResultDto();
 
@@ -878,13 +882,15 @@ namespace Jellyfin.Api.Controllers
         private QueryResult<BaseItemDto> GetSimilarItemsResult(
             BaseItem item,
             string? excludeArtistIds,
-            Guid userId,
+            Guid? userId,
             int? limit,
             string? fields,
             string[] includeItemTypes,
             bool isMovie)
         {
-            var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null;
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request);

+ 10 - 10
Jellyfin.Api/Controllers/LibraryStructureController.cs

@@ -64,9 +64,9 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="name">The name of the virtual folder.</param>
         /// <param name="collectionType">The type of the collection.</param>
-        /// <param name="refreshLibrary">Whether to refresh the library.</param>
         /// <param name="paths">The paths of the virtual folder.</param>
         /// <param name="libraryOptions">The library options.</param>
+        /// <param name="refreshLibrary">Whether to refresh the library.</param>
         /// <response code="204">Folder added.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpPost]
@@ -74,9 +74,9 @@ namespace Jellyfin.Api.Controllers
         public async Task<ActionResult> AddVirtualFolder(
             [FromQuery] string? name,
             [FromQuery] string? collectionType,
-            [FromQuery] bool refreshLibrary,
             [FromQuery] string[] paths,
-            [FromQuery] LibraryOptions libraryOptions)
+            [FromQuery] LibraryOptions? libraryOptions,
+            [FromQuery] bool refreshLibrary = false)
         {
             libraryOptions ??= new LibraryOptions();
 
@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> RemoveVirtualFolder(
             [FromQuery] string? name,
-            [FromQuery] bool refreshLibrary)
+            [FromQuery] bool refreshLibrary = false)
         {
             await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false);
             return NoContent();
@@ -125,7 +125,7 @@ namespace Jellyfin.Api.Controllers
         public ActionResult RenameVirtualFolder(
             [FromQuery] string? name,
             [FromQuery] string? newName,
-            [FromQuery] bool refreshLibrary)
+            [FromQuery] bool refreshLibrary = false)
         {
             if (string.IsNullOrWhiteSpace(name))
             {
@@ -207,8 +207,8 @@ namespace Jellyfin.Api.Controllers
         public ActionResult AddMediaPath(
             [FromQuery] string? name,
             [FromQuery] string? path,
-            [FromQuery] MediaPathInfo pathInfo,
-            [FromQuery] bool refreshLibrary)
+            [FromQuery] MediaPathInfo? pathInfo,
+            [FromQuery] bool refreshLibrary = false)
         {
             if (string.IsNullOrWhiteSpace(name))
             {
@@ -257,7 +257,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult UpdateMediaPath(
             [FromQuery] string? name,
-            [FromQuery] MediaPathInfo pathInfo)
+            [FromQuery] MediaPathInfo? pathInfo)
         {
             if (string.IsNullOrWhiteSpace(name))
             {
@@ -282,7 +282,7 @@ namespace Jellyfin.Api.Controllers
         public ActionResult RemoveMediaPath(
             [FromQuery] string? name,
             [FromQuery] string? path,
-            [FromQuery] bool refreshLibrary)
+            [FromQuery] bool refreshLibrary = false)
         {
             if (string.IsNullOrWhiteSpace(name))
             {
@@ -328,7 +328,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult UpdateLibraryOptions(
             [FromQuery] string? id,
-            [FromQuery] LibraryOptions libraryOptions)
+            [FromQuery] LibraryOptions? libraryOptions)
         {
             var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(id);
 

+ 83 - 73
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -113,7 +113,6 @@ namespace Jellyfin.Api.Controllers
         /// <param name="isFavorite">Optional. Filter by channels that are favorites, or not.</param>
         /// <param name="isLiked">Optional. Filter by channels that are liked, or not.</param>
         /// <param name="isDisliked">Optional. Filter by channels that are disliked, or not.</param>
-        /// <param name="enableFavoriteSorting">Optional. Incorporate favorite and like status into channel sorting.</param>
         /// <param name="enableImages">Optional. Include image information in output.</param>
         /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
         /// <param name="enableImageTypes">"Optional. The image types to include in the output.</param>
@@ -121,6 +120,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableUserData">Optional. Include user data.</param>
         /// <param name="sortBy">Optional. Key to sort by.</param>
         /// <param name="sortOrder">Optional. Sort order.</param>
+        /// <param name="enableFavoriteSorting">Optional. Incorporate favorite and like status into channel sorting.</param>
         /// <param name="addCurrentProgram">Optional. Adds current program info to each channel.</param>
         /// <response code="200">Available live tv channels returned.</response>
         /// <returns>
@@ -131,7 +131,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         public ActionResult<QueryResult<BaseItemDto>> GetChannels(
             [FromQuery] ChannelType? type,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
             [FromQuery] bool? isMovie,
             [FromQuery] bool? isSeries,
@@ -142,14 +142,14 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] bool? isFavorite,
             [FromQuery] bool? isLiked,
             [FromQuery] bool? isDisliked,
-            [FromQuery] bool enableFavoriteSorting,
             [FromQuery] bool? enableImages,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string fields,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? fields,
             [FromQuery] bool? enableUserData,
-            [FromQuery] string sortBy,
+            [FromQuery] string? sortBy,
             [FromQuery] SortOrder? sortOrder,
+            [FromQuery] bool enableFavoriteSorting = false,
             [FromQuery] bool addCurrentProgram = true)
         {
             var dtoOptions = new DtoOptions()
@@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
                 new LiveTvChannelQuery
                 {
                     ChannelType = type,
-                    UserId = userId,
+                    UserId = userId ?? Guid.Empty,
                     StartIndex = startIndex,
                     Limit = limit,
                     IsFavorite = isFavorite,
@@ -180,9 +180,9 @@ namespace Jellyfin.Api.Controllers
                 dtoOptions,
                 CancellationToken.None);
 
-            var user = userId.Equals(Guid.Empty)
-                ? null
-                : _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var fieldsList = dtoOptions.Fields.ToList();
             fieldsList.Remove(ItemFields.CanDelete);
@@ -210,9 +210,11 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Channels/{channelId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public ActionResult<BaseItemDto> GetChannel([FromRoute] Guid channelId, [FromQuery] Guid userId)
+        public ActionResult<BaseItemDto> GetChannel([FromRoute] Guid channelId, [FromQuery] Guid? userId)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var item = channelId.Equals(Guid.Empty)
                 ? _libraryManager.GetUserRootFolder()
                 : _libraryManager.GetItemById(channelId);
@@ -250,17 +252,17 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         public ActionResult<QueryResult<BaseItemDto>> GetRecordings(
-            [FromQuery] string channelId,
-            [FromQuery] Guid userId,
+            [FromQuery] string? channelId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] RecordingStatus? status,
             [FromQuery] bool? isInProgress,
-            [FromQuery] string seriesTimerId,
+            [FromQuery] string? seriesTimerId,
             [FromQuery] bool? enableImages,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string fields,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? fields,
             [FromQuery] bool? enableUserData,
             [FromQuery] bool? isMovie,
             [FromQuery] bool? isSeries,
@@ -279,7 +281,7 @@ namespace Jellyfin.Api.Controllers
                 new RecordingQuery
             {
                 ChannelId = channelId,
-                UserId = userId,
+                UserId = userId ?? Guid.Empty,
                 StartIndex = startIndex,
                 Limit = limit,
                 Status = status,
@@ -336,18 +338,18 @@ namespace Jellyfin.Api.Controllers
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableUserData", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableTotalRecordCount", Justification = "Imported from ServiceStack")]
         public ActionResult<QueryResult<BaseItemDto>> GetRecordingsSeries(
-            [FromQuery] string channelId,
-            [FromQuery] Guid userId,
-            [FromQuery] string groupId,
+            [FromQuery] string? channelId,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? groupId,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] RecordingStatus? status,
             [FromQuery] bool? isInProgress,
-            [FromQuery] string seriesTimerId,
+            [FromQuery] string? seriesTimerId,
             [FromQuery] bool? enableImages,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string fields,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? fields,
             [FromQuery] bool? enableUserData,
             [FromQuery] bool enableTotalRecordCount = true)
         {
@@ -365,7 +367,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Obsolete("This endpoint is obsolete.")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
-        public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid userId)
+        public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId)
         {
             return new QueryResult<BaseItemDto>();
         }
@@ -379,9 +381,11 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Recordings/Folders")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid userId)
+        public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid? userId)
         {
-            var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var folders = _liveTvManager.GetRecordingFolders(user);
 
             var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user);
@@ -403,9 +407,11 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Recordings/{recordingId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public ActionResult<BaseItemDto> GetRecording([FromRoute] Guid recordingId, [FromQuery] Guid userId)
+        public ActionResult<BaseItemDto> GetRecording([FromRoute] Guid recordingId, [FromQuery] Guid? userId)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var item = recordingId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
 
             var dtoOptions = new DtoOptions()
@@ -457,7 +463,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Timers/Defaults")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string programId)
+        public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId)
         {
             return string.IsNullOrEmpty(programId)
                 ? await _liveTvManager.GetNewTimerDefaults(CancellationToken.None).ConfigureAwait(false)
@@ -478,8 +484,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers(
-            [FromQuery] string channelId,
-            [FromQuery] string seriesTimerId,
+            [FromQuery] string? channelId,
+            [FromQuery] string? seriesTimerId,
             [FromQuery] bool? isActive,
             [FromQuery] bool? isScheduled)
         {
@@ -532,8 +538,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms(
-            [FromQuery] string channelIds,
-            [FromQuery] Guid userId,
+            [FromQuery] string? channelIds,
+            [FromQuery] Guid? userId,
             [FromQuery] DateTime? minStartDate,
             [FromQuery] bool? hasAired,
             [FromQuery] bool? isAiring,
@@ -547,20 +553,22 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] bool? isSports,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] string sortBy,
-            [FromQuery] string sortOrder,
-            [FromQuery] string genres,
-            [FromQuery] string genreIds,
+            [FromQuery] string? sortBy,
+            [FromQuery] string? sortOrder,
+            [FromQuery] string? genres,
+            [FromQuery] string? genreIds,
             [FromQuery] bool? enableImages,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
+            [FromQuery] string? enableImageTypes,
             [FromQuery] bool? enableUserData,
-            [FromQuery] string seriesTimerId,
-            [FromQuery] Guid librarySeriesId,
-            [FromQuery] string fields,
+            [FromQuery] string? seriesTimerId,
+            [FromQuery] Guid? librarySeriesId,
+            [FromQuery] string? fields,
             [FromQuery] bool enableTotalRecordCount = true)
         {
-            var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var query = new InternalItemsQuery(user)
             {
@@ -590,7 +598,7 @@ namespace Jellyfin.Api.Controllers
             {
                 query.IsSeries = true;
 
-                if (_libraryManager.GetItemById(librarySeriesId) is Series series)
+                if (_libraryManager.GetItemById(librarySeriesId ?? Guid.Empty) is Series series)
                 {
                     query.Name = series.Name;
                 }
@@ -684,7 +692,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetRecommendedPrograms(
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] bool? isAiring,
             [FromQuery] bool? hasAired,
@@ -695,13 +703,15 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] bool? isSports,
             [FromQuery] bool? enableImages,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string genreIds,
-            [FromQuery] string fields,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? genreIds,
+            [FromQuery] string? fields,
             [FromQuery] bool? enableUserData,
             [FromQuery] bool enableTotalRecordCount = true)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var query = new InternalItemsQuery(user)
             {
@@ -736,11 +746,11 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<BaseItemDto>> GetProgram(
             [FromRoute] string programId,
-            [FromQuery] Guid userId)
+            [FromQuery] Guid? userId)
         {
-            var user = userId.Equals(Guid.Empty)
-                ? null
-                : _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             return await _liveTvManager.GetProgram(programId, CancellationToken.None, user).ConfigureAwait(false);
         }
@@ -856,12 +866,12 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("SeriesTimers")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string sortBy, [FromQuery] SortOrder sortOrder)
+        public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder)
         {
             return await _liveTvManager.GetSeriesTimers(
                 new SeriesTimerQuery
                 {
-                    SortOrder = sortOrder,
+                    SortOrder = sortOrder ?? SortOrder.Ascending,
                     SortBy = sortBy
                 }, CancellationToken.None).ConfigureAwait(false);
         }
@@ -925,7 +935,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [Obsolete("This endpoint is obsolete.")]
-        public ActionResult<BaseItemDto> GetRecordingGroup([FromQuery] Guid groupId)
+        public ActionResult<BaseItemDto> GetRecordingGroup([FromQuery] Guid? groupId)
         {
             return NotFound();
         }
@@ -966,7 +976,7 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("TunerHosts")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult DeleteTunerHost([FromQuery] string id)
+        public ActionResult DeleteTunerHost([FromQuery] string? id)
         {
             var config = _configurationManager.GetConfiguration<LiveTvOptions>("livetv");
             config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
@@ -990,10 +1000,10 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Adds a listings provider.
         /// </summary>
-        /// <param name="validateLogin">Validate login.</param>
-        /// <param name="validateListings">Validate listings.</param>
         /// <param name="pw">Password.</param>
         /// <param name="listingsProviderInfo">New listings info.</param>
+        /// <param name="validateListings">Validate listings.</param>
+        /// <param name="validateLogin">Validate login.</param>
         /// <response code="200">Created listings provider returned.</response>
         /// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns>
         [HttpGet("ListingProviders")]
@@ -1001,10 +1011,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider(
-            [FromQuery] bool validateLogin,
-            [FromQuery] bool validateListings,
-            [FromQuery] string pw,
-            [FromBody] ListingsProviderInfo listingsProviderInfo)
+            [FromQuery] string? pw,
+            [FromBody] ListingsProviderInfo listingsProviderInfo,
+            [FromQuery] bool validateListings = false,
+            [FromQuery] bool validateLogin = false)
         {
             using var sha = SHA1.Create();
             if (!string.IsNullOrEmpty(pw))
@@ -1024,7 +1034,7 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("ListingProviders")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult DeleteListingProvider([FromQuery] string id)
+        public ActionResult DeleteListingProvider([FromQuery] string? id)
         {
             _liveTvManager.DeleteListingsProvider(id);
             return NoContent();
@@ -1043,10 +1053,10 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups(
-            [FromQuery] string id,
-            [FromQuery] string type,
-            [FromQuery] string location,
-            [FromQuery] string country)
+            [FromQuery] string? id,
+            [FromQuery] string? type,
+            [FromQuery] string? location,
+            [FromQuery] string? country)
         {
             return await _liveTvManager.GetLineups(type, id, country, location).ConfigureAwait(false);
         }
@@ -1079,7 +1089,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("ChannelMappingOptions")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string providerId)
+        public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string? providerId)
         {
             var config = _configurationManager.GetConfiguration<LiveTvOptions>("livetv");
 
@@ -1120,9 +1130,9 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping(
-            [FromQuery] string providerId,
-            [FromQuery] string tunerChannelId,
-            [FromQuery] string providerChannelId)
+            [FromQuery] string? providerId,
+            [FromQuery] string? tunerChannelId,
+            [FromQuery] string? providerChannelId)
         {
             return await _liveTvManager.SetChannelMapping(providerId, tunerChannelId, providerChannelId).ConfigureAwait(false);
         }
@@ -1149,7 +1159,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Tuners/Discvover")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly)
+        public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
         {
             return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false);
         }

+ 21 - 19
Jellyfin.Api/Controllers/MediaInfoController.cs

@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
         [HttpGet("/Items/{itemId}/PlaybackInfo")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid userId)
+        public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid? userId)
         {
             return await GetPlaybackInfoInternal(itemId, userId, null, null).ConfigureAwait(false);
         }
@@ -118,16 +118,16 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
             [FromRoute] Guid itemId,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] long? maxStreamingBitrate,
             [FromQuery] long? startTimeTicks,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? subtitleStreamIndex,
             [FromQuery] int? maxAudioChannels,
-            [FromQuery] string mediaSourceId,
-            [FromQuery] string liveStreamId,
-            [FromQuery] DeviceProfile deviceProfile,
-            [FromQuery] bool autoOpenLiveStream,
+            [FromQuery] string? mediaSourceId,
+            [FromQuery] string? liveStreamId,
+            [FromQuery] DeviceProfile? deviceProfile,
+            [FromQuery] bool autoOpenLiveStream = false,
             [FromQuery] bool enableDirectPlay = true,
             [FromQuery] bool enableDirectStream = true,
             [FromQuery] bool enableTranscoding = true,
@@ -165,12 +165,12 @@ namespace Jellyfin.Api.Controllers
                         authInfo,
                         maxStreamingBitrate ?? profile.MaxStreamingBitrate,
                         startTimeTicks ?? 0,
-                        mediaSourceId,
+                        mediaSourceId ?? string.Empty,
                         audioStreamIndex,
                         subtitleStreamIndex,
                         maxAudioChannels,
                         info!.PlaySessionId!,
-                        userId,
+                        userId ?? Guid.Empty,
                         enableDirectPlay,
                         enableDirectStream,
                         enableTranscoding,
@@ -199,7 +199,7 @@ namespace Jellyfin.Api.Controllers
                         PlaySessionId = info.PlaySessionId,
                         StartTimeTicks = startTimeTicks,
                         SubtitleStreamIndex = subtitleStreamIndex,
-                        UserId = userId,
+                        UserId = userId ?? Guid.Empty,
                         OpenToken = mediaSource.OpenToken
                     }).ConfigureAwait(false);
 
@@ -239,16 +239,16 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("/LiveStreams/Open")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
-            [FromQuery] string openToken,
-            [FromQuery] Guid userId,
-            [FromQuery] string playSessionId,
+            [FromQuery] string? openToken,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? playSessionId,
             [FromQuery] long? maxStreamingBitrate,
             [FromQuery] long? startTimeTicks,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? subtitleStreamIndex,
             [FromQuery] int? maxAudioChannels,
-            [FromQuery] Guid itemId,
-            [FromQuery] DeviceProfile deviceProfile,
+            [FromQuery] Guid? itemId,
+            [FromQuery] DeviceProfile? deviceProfile,
             [FromQuery] MediaProtocol[] directPlayProtocols,
             [FromQuery] bool enableDirectPlay = true,
             [FromQuery] bool enableDirectStream = true)
@@ -256,14 +256,14 @@ namespace Jellyfin.Api.Controllers
             var request = new LiveStreamRequest
             {
                 OpenToken = openToken,
-                UserId = userId,
+                UserId = userId ?? Guid.Empty,
                 PlaySessionId = playSessionId,
                 MaxStreamingBitrate = maxStreamingBitrate,
                 StartTimeTicks = startTimeTicks,
                 AudioStreamIndex = audioStreamIndex,
                 SubtitleStreamIndex = subtitleStreamIndex,
                 MaxAudioChannels = maxAudioChannels,
-                ItemId = itemId,
+                ItemId = itemId ?? Guid.Empty,
                 DeviceProfile = deviceProfile,
                 EnableDirectPlay = enableDirectPlay,
                 EnableDirectStream = enableDirectStream,
@@ -280,7 +280,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("/LiveStreams/Close")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult CloseLiveStream([FromQuery] string liveStreamId)
+        public ActionResult CloseLiveStream([FromQuery] string? liveStreamId)
         {
             _mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult();
             return NoContent();
@@ -325,11 +325,13 @@ namespace Jellyfin.Api.Controllers
 
         private async Task<PlaybackInfoResponse> GetPlaybackInfoInternal(
             Guid id,
-            Guid userId,
+            Guid? userId,
             string? mediaSourceId = null,
             string? liveStreamId = null)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var item = _libraryManager.GetItemById(id);
             var result = new PlaybackInfoResponse();
 

+ 8 - 18
Jellyfin.Api/Controllers/MoviesController.cs

@@ -55,32 +55,22 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
         /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
-        /// <param name="enableImages">(Unused) Optional. include image information in output.</param>
-        /// <param name="enableUserData">(Unused) Optional. include user data.</param>
-        /// <param name="imageTypeLimit">(Unused) Optional. the max number of images to return, per image type.</param>
-        /// <param name="enableImageTypes">(Unused) Optional. The image types to include in the output.</param>
         /// <param name="fields">Optional. The fields to return.</param>
         /// <param name="categoryLimit">The max number of categories to return.</param>
         /// <param name="itemLimit">The max number of items to return per category.</param>
         /// <response code="200">Movie recommendations returned.</response>
         /// <returns>The list of movie recommendations.</returns>
         [HttpGet("Recommendations")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImages", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableUserData", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageTypeLimit", Justification = "Imported from ServiceStack")]
-        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImageTypes", Justification = "Imported from ServiceStack")]
         public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
-            [FromQuery] Guid userId,
-            [FromQuery] string parentId,
-            [FromQuery] bool? enableImages,
-            [FromQuery] bool? enableUserData,
-            [FromQuery] int? imageTypeLimit,
-            [FromQuery] string? enableImageTypes,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? parentId,
             [FromQuery] string? fields,
             [FromQuery] int categoryLimit = 5,
             [FromQuery] int itemLimit = 8)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
             var dtoOptions = new DtoOptions()
                 .AddItemFields(fields)
                 .AddClientFields(Request);
@@ -185,7 +175,7 @@ namespace Jellyfin.Api.Controllers
         }
 
         private IEnumerable<RecommendationDto> GetWithDirector(
-            User user,
+            User? user,
             IEnumerable<string> names,
             int itemLimit,
             DtoOptions dtoOptions,
@@ -230,7 +220,7 @@ namespace Jellyfin.Api.Controllers
             }
         }
 
-        private IEnumerable<RecommendationDto> GetWithActor(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+        private IEnumerable<RecommendationDto> GetWithActor(User? user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
         {
             var itemTypes = new List<string> { nameof(Movie) };
             if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
@@ -270,7 +260,7 @@ namespace Jellyfin.Api.Controllers
             }
         }
 
-        private IEnumerable<RecommendationDto> GetSimilarTo(User user, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+        private IEnumerable<RecommendationDto> GetSimilarTo(User? user, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
         {
             var itemTypes = new List<string> { nameof(Movie) };
             if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)

+ 27 - 27
Jellyfin.Api/Controllers/MusicGenresController.cs

@@ -83,31 +83,31 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] double? minCommunityRating,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] string searchTerm,
-            [FromQuery] string parentId,
-            [FromQuery] string fields,
-            [FromQuery] string excludeItemTypes,
-            [FromQuery] string includeItemTypes,
-            [FromQuery] string filters,
+            [FromQuery] string? searchTerm,
+            [FromQuery] string? parentId,
+            [FromQuery] string? fields,
+            [FromQuery] string? excludeItemTypes,
+            [FromQuery] string? includeItemTypes,
+            [FromQuery] string? filters,
             [FromQuery] bool? isFavorite,
-            [FromQuery] string mediaTypes,
-            [FromQuery] string genres,
-            [FromQuery] string genreIds,
-            [FromQuery] string officialRatings,
-            [FromQuery] string tags,
-            [FromQuery] string years,
+            [FromQuery] string? mediaTypes,
+            [FromQuery] string? genres,
+            [FromQuery] string? genreIds,
+            [FromQuery] string? officialRatings,
+            [FromQuery] string? tags,
+            [FromQuery] string? years,
             [FromQuery] bool? enableUserData,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string person,
-            [FromQuery] string personIds,
-            [FromQuery] string personTypes,
-            [FromQuery] string studios,
-            [FromQuery] string studioIds,
-            [FromQuery] Guid userId,
-            [FromQuery] string nameStartsWithOrGreater,
-            [FromQuery] string nameStartsWith,
-            [FromQuery] string nameLessThan,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? person,
+            [FromQuery] string? personIds,
+            [FromQuery] string? personTypes,
+            [FromQuery] string? studios,
+            [FromQuery] string? studioIds,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? nameStartsWithOrGreater,
+            [FromQuery] string? nameStartsWith,
+            [FromQuery] string? nameLessThan,
             [FromQuery] bool? enableImages = true,
             [FromQuery] bool enableTotalRecordCount = true)
         {
@@ -119,9 +119,9 @@ namespace Jellyfin.Api.Controllers
             User? user = null;
             BaseItem parentItem;
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                user = _userManager.GetUserById(userId);
+                user = _userManager.GetUserById(userId.Value);
                 parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
             }
             else
@@ -258,7 +258,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing a <see cref="BaseItemDto"/> with the music genre.</returns>
         [HttpGet("{genreName}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetMusicGenre([FromRoute] string genreName, [FromQuery] Guid userId)
+        public ActionResult<BaseItemDto> GetMusicGenre([FromRoute] string genreName, [FromQuery] Guid? userId)
         {
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
@@ -273,9 +273,9 @@ namespace Jellyfin.Api.Controllers
                 item = _libraryManager.GetMusicGenre(genreName);
             }
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                var user = _userManager.GetUserById(userId);
+                var user = _userManager.GetUserById(userId.Value);
 
                 return _dtoService.GetBaseItemDto(item, dtoOptions, user);
             }

+ 27 - 27
Jellyfin.Api/Controllers/PersonsController.cs

@@ -80,31 +80,31 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] double? minCommunityRating,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] string searchTerm,
-            [FromQuery] string parentId,
-            [FromQuery] string fields,
-            [FromQuery] string excludeItemTypes,
-            [FromQuery] string includeItemTypes,
-            [FromQuery] string filters,
+            [FromQuery] string? searchTerm,
+            [FromQuery] string? parentId,
+            [FromQuery] string? fields,
+            [FromQuery] string? excludeItemTypes,
+            [FromQuery] string? includeItemTypes,
+            [FromQuery] string? filters,
             [FromQuery] bool? isFavorite,
-            [FromQuery] string mediaTypes,
-            [FromQuery] string genres,
-            [FromQuery] string genreIds,
-            [FromQuery] string officialRatings,
-            [FromQuery] string tags,
-            [FromQuery] string years,
+            [FromQuery] string? mediaTypes,
+            [FromQuery] string? genres,
+            [FromQuery] string? genreIds,
+            [FromQuery] string? officialRatings,
+            [FromQuery] string? tags,
+            [FromQuery] string? years,
             [FromQuery] bool? enableUserData,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string person,
-            [FromQuery] string personIds,
-            [FromQuery] string personTypes,
-            [FromQuery] string studios,
-            [FromQuery] string studioIds,
-            [FromQuery] Guid userId,
-            [FromQuery] string nameStartsWithOrGreater,
-            [FromQuery] string nameStartsWith,
-            [FromQuery] string nameLessThan,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? person,
+            [FromQuery] string? personIds,
+            [FromQuery] string? personTypes,
+            [FromQuery] string? studios,
+            [FromQuery] string? studioIds,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? nameStartsWithOrGreater,
+            [FromQuery] string? nameStartsWith,
+            [FromQuery] string? nameLessThan,
             [FromQuery] bool? enableImages = true,
             [FromQuery] bool enableTotalRecordCount = true)
         {
@@ -116,9 +116,9 @@ namespace Jellyfin.Api.Controllers
             User? user = null;
             BaseItem parentItem;
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                user = _userManager.GetUserById(userId);
+                user = _userManager.GetUserById(userId.Value);
                 parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
             }
             else
@@ -259,7 +259,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<BaseItemDto> GetPerson([FromRoute] string name, [FromQuery] Guid userId)
+        public ActionResult<BaseItemDto> GetPerson([FromRoute] string name, [FromQuery] Guid? userId)
         {
             var dtoOptions = new DtoOptions()
                 .AddClientFields(Request);
@@ -270,9 +270,9 @@ namespace Jellyfin.Api.Controllers
                 return NotFound();
             }
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                var user = _userManager.GetUserById(userId);
+                var user = _userManager.GetUserById(userId.Value);
                 return _dtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 

+ 2 - 2
Jellyfin.Api/Controllers/PlaylistsController.cs

@@ -86,9 +86,9 @@ namespace Jellyfin.Api.Controllers
         public ActionResult AddToPlaylist(
             [FromRoute] string? playlistId,
             [FromQuery] string? ids,
-            [FromQuery] Guid userId)
+            [FromQuery] Guid? userId)
         {
-            _playlistManager.AddToPlaylist(playlistId, RequestHelpers.GetGuids(ids), userId);
+            _playlistManager.AddToPlaylist(playlistId, RequestHelpers.GetGuids(ids), userId ?? Guid.Empty);
             return NoContent();
         }
 

+ 16 - 16
Jellyfin.Api/Controllers/PlaystateController.cs

@@ -188,12 +188,12 @@ namespace Jellyfin.Api.Controllers
         /// <param name="userId">User id.</param>
         /// <param name="itemId">Item id.</param>
         /// <param name="mediaSourceId">The id of the MediaSource.</param>
-        /// <param name="canSeek">Indicates if the client can seek.</param>
         /// <param name="audioStreamIndex">The audio stream index.</param>
         /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
         /// <param name="playMethod">The play method.</param>
         /// <param name="liveStreamId">The live stream id.</param>
         /// <param name="playSessionId">The play session id.</param>
+        /// <param name="canSeek">Indicates if the client can seek.</param>
         /// <response code="204">Play start recorded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpPost("/Users/{userId}/PlayingItems/{itemId}")]
@@ -202,13 +202,13 @@ namespace Jellyfin.Api.Controllers
         public async Task<ActionResult> OnPlaybackStart(
             [FromRoute] Guid userId,
             [FromRoute] Guid itemId,
-            [FromQuery] string mediaSourceId,
-            [FromQuery] bool canSeek,
+            [FromQuery] string? mediaSourceId,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? subtitleStreamIndex,
             [FromQuery] PlayMethod playMethod,
-            [FromQuery] string liveStreamId,
-            [FromQuery] string playSessionId)
+            [FromQuery] string? liveStreamId,
+            [FromQuery] string playSessionId,
+            [FromQuery] bool canSeek = false)
         {
             var playbackStartInfo = new PlaybackStartInfo
             {
@@ -235,8 +235,6 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="mediaSourceId">The id of the MediaSource.</param>
         /// <param name="positionTicks">Optional. The current position, in ticks. 1 tick = 10000 ms.</param>
-        /// <param name="isPaused">Indicates if the player is paused.</param>
-        /// <param name="isMuted">Indicates if the player is muted.</param>
         /// <param name="audioStreamIndex">The audio stream index.</param>
         /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
         /// <param name="volumeLevel">Scale of 0-100.</param>
@@ -244,6 +242,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="liveStreamId">The live stream id.</param>
         /// <param name="playSessionId">The play session id.</param>
         /// <param name="repeatMode">The repeat mode.</param>
+        /// <param name="isPaused">Indicates if the player is paused.</param>
+        /// <param name="isMuted">Indicates if the player is muted.</param>
         /// <response code="204">Play progress recorded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpPost("/Users/{userId}/PlayingItems/{itemId}/Progress")]
@@ -252,17 +252,17 @@ namespace Jellyfin.Api.Controllers
         public async Task<ActionResult> OnPlaybackProgress(
             [FromRoute] Guid userId,
             [FromRoute] Guid itemId,
-            [FromQuery] string mediaSourceId,
+            [FromQuery] string? mediaSourceId,
             [FromQuery] long? positionTicks,
-            [FromQuery] bool isPaused,
-            [FromQuery] bool isMuted,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? subtitleStreamIndex,
             [FromQuery] int? volumeLevel,
             [FromQuery] PlayMethod playMethod,
-            [FromQuery] string liveStreamId,
+            [FromQuery] string? liveStreamId,
             [FromQuery] string playSessionId,
-            [FromQuery] RepeatMode repeatMode)
+            [FromQuery] RepeatMode repeatMode,
+            [FromQuery] bool isPaused = false,
+            [FromQuery] bool isMuted = false)
         {
             var playbackProgressInfo = new PlaybackProgressInfo
             {
@@ -304,11 +304,11 @@ namespace Jellyfin.Api.Controllers
         public async Task<ActionResult> OnPlaybackStopped(
             [FromRoute] Guid userId,
             [FromRoute] Guid itemId,
-            [FromQuery] string mediaSourceId,
-            [FromQuery] string nextMediaType,
+            [FromQuery] string? mediaSourceId,
+            [FromQuery] string? nextMediaType,
             [FromQuery] long? positionTicks,
-            [FromQuery] string liveStreamId,
-            [FromQuery] string playSessionId)
+            [FromQuery] string? liveStreamId,
+            [FromQuery] string? playSessionId)
         {
             var playbackStopInfo = new PlaybackStopInfo
             {

+ 1 - 1
Jellyfin.Api/Controllers/RemoteImageController.cs

@@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] string providerName,
-            [FromQuery] bool includeAllLanguages)
+            [FromQuery] bool includeAllLanguages = false)
         {
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)

+ 2 - 2
Jellyfin.Api/Controllers/SearchController.cs

@@ -80,7 +80,7 @@ namespace Jellyfin.Api.Controllers
         public ActionResult<SearchHintResult> Get(
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery, Required] string? searchTerm,
             [FromQuery] string? includeItemTypes,
             [FromQuery] string? excludeItemTypes,
@@ -107,7 +107,7 @@ namespace Jellyfin.Api.Controllers
                 IncludePeople = includePeople,
                 IncludeStudios = includeStudios,
                 StartIndex = startIndex,
-                UserId = userId,
+                UserId = userId ?? Guid.Empty,
                 IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
                 ExcludeItemTypes = RequestHelpers.Split(excludeItemTypes, ',', true),
                 MediaTypes = RequestHelpers.Split(mediaTypes, ',', true),

+ 6 - 6
Jellyfin.Api/Controllers/SessionController.cs

@@ -61,7 +61,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<SessionInfo>> GetSessions(
-            [FromQuery] Guid controllableByUserId,
+            [FromQuery] Guid? controllableByUserId,
             [FromQuery] string? deviceId,
             [FromQuery] int? activeWithinSeconds)
         {
@@ -72,15 +72,15 @@ namespace Jellyfin.Api.Controllers
                 result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
             }
 
-            if (!controllableByUserId.Equals(Guid.Empty))
+            if (controllableByUserId.HasValue && !controllableByUserId.Equals(Guid.Empty))
             {
                 result = result.Where(i => i.SupportsRemoteControl);
 
-                var user = _userManager.GetUserById(controllableByUserId);
+                var user = _userManager.GetUserById(controllableByUserId.Value);
 
                 if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
                 {
-                    result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(controllableByUserId));
+                    result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(controllableByUserId.Value));
                 }
 
                 if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl))
@@ -371,8 +371,8 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? id,
             [FromQuery] string? playableMediaTypes,
             [FromQuery] string? supportedCommands,
-            [FromQuery] bool supportsMediaControl,
-            [FromQuery] bool supportsSync,
+            [FromQuery] bool supportsMediaControl = false,
+            [FromQuery] bool supportsSync = false,
             [FromQuery] bool supportsPersistentIdentifier = true)
         {
             if (string.IsNullOrWhiteSpace(id))

+ 27 - 27
Jellyfin.Api/Controllers/StudiosController.cs

@@ -82,31 +82,31 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] double? minCommunityRating,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
-            [FromQuery] string searchTerm,
-            [FromQuery] string parentId,
-            [FromQuery] string fields,
-            [FromQuery] string excludeItemTypes,
-            [FromQuery] string includeItemTypes,
-            [FromQuery] string filters,
+            [FromQuery] string? searchTerm,
+            [FromQuery] string? parentId,
+            [FromQuery] string? fields,
+            [FromQuery] string? excludeItemTypes,
+            [FromQuery] string? includeItemTypes,
+            [FromQuery] string? filters,
             [FromQuery] bool? isFavorite,
-            [FromQuery] string mediaTypes,
-            [FromQuery] string genres,
-            [FromQuery] string genreIds,
-            [FromQuery] string officialRatings,
-            [FromQuery] string tags,
-            [FromQuery] string years,
+            [FromQuery] string? mediaTypes,
+            [FromQuery] string? genres,
+            [FromQuery] string? genreIds,
+            [FromQuery] string? officialRatings,
+            [FromQuery] string? tags,
+            [FromQuery] string? years,
             [FromQuery] bool? enableUserData,
             [FromQuery] int? imageTypeLimit,
-            [FromQuery] string enableImageTypes,
-            [FromQuery] string person,
-            [FromQuery] string personIds,
-            [FromQuery] string personTypes,
-            [FromQuery] string studios,
-            [FromQuery] string studioIds,
-            [FromQuery] Guid userId,
-            [FromQuery] string nameStartsWithOrGreater,
-            [FromQuery] string nameStartsWith,
-            [FromQuery] string nameLessThan,
+            [FromQuery] string? enableImageTypes,
+            [FromQuery] string? person,
+            [FromQuery] string? personIds,
+            [FromQuery] string? personTypes,
+            [FromQuery] string? studios,
+            [FromQuery] string? studioIds,
+            [FromQuery] Guid? userId,
+            [FromQuery] string? nameStartsWithOrGreater,
+            [FromQuery] string? nameStartsWith,
+            [FromQuery] string? nameLessThan,
             [FromQuery] bool? enableImages = true,
             [FromQuery] bool enableTotalRecordCount = true)
         {
@@ -118,9 +118,9 @@ namespace Jellyfin.Api.Controllers
             User? user = null;
             BaseItem parentItem;
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                user = _userManager.GetUserById(userId);
+                user = _userManager.GetUserById(userId.Value);
                 parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
             }
             else
@@ -259,14 +259,14 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the studio.</returns>
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetStudio([FromRoute] string name, [FromQuery] Guid userId)
+        public ActionResult<BaseItemDto> GetStudio([FromRoute] string name, [FromQuery] Guid? userId)
         {
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
             var item = _libraryManager.GetStudio(name);
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                var user = _userManager.GetUserById(userId);
+                var user = _userManager.GetUserById(userId.Value);
 
                 return _dtoService.GetBaseItemDto(item, dtoOptions, user);
             }

+ 2 - 2
Jellyfin.Api/Controllers/SubtitleController.cs

@@ -190,8 +190,8 @@ namespace Jellyfin.Api.Controllers
             [FromRoute, Required] int index,
             [FromRoute, Required] string? format,
             [FromQuery] long? endPositionTicks,
-            [FromQuery] bool copyTimestamps,
-            [FromQuery] bool addVttTimeMap,
+            [FromQuery] bool copyTimestamps = false,
+            [FromQuery] bool addVttTimeMap = false,
             [FromRoute] long startPositionTicks = 0)
         {
             if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))

+ 3 - 3
Jellyfin.Api/Controllers/SuggestionsController.cs

@@ -44,9 +44,9 @@ namespace Jellyfin.Api.Controllers
         /// <param name="userId">The user id.</param>
         /// <param name="mediaType">The media types.</param>
         /// <param name="type">The type.</param>
-        /// <param name="enableTotalRecordCount">Whether to enable the total record count.</param>
         /// <param name="startIndex">Optional. The start index.</param>
         /// <param name="limit">Optional. The limit.</param>
+        /// <param name="enableTotalRecordCount">Whether to enable the total record count.</param>
         /// <response code="200">Suggestions returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the suggestions.</returns>
         [HttpGet("/Users/{userId}/Suggestions")]
@@ -55,9 +55,9 @@ namespace Jellyfin.Api.Controllers
             [FromRoute] Guid userId,
             [FromQuery] string? mediaType,
             [FromQuery] string? type,
-            [FromQuery] bool enableTotalRecordCount,
             [FromQuery] int? startIndex,
-            [FromQuery] int? limit)
+            [FromQuery] int? limit,
+            [FromQuery] bool enableTotalRecordCount = false)
         {
             var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null;
 

+ 1 - 1
Jellyfin.Api/Controllers/TrailersController.cs

@@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("/Trailers")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] bool? hasThemeSong,
             [FromQuery] bool? hasThemeVideo,

+ 17 - 9
Jellyfin.Api/Controllers/TvShowsController.cs

@@ -69,7 +69,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("NextUp")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetNextUp(
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
@@ -93,12 +93,14 @@ namespace Jellyfin.Api.Controllers
                     ParentId = parentId,
                     SeriesId = seriesId,
                     StartIndex = startIndex,
-                    UserId = userId,
+                    UserId = userId ?? Guid.Empty,
                     EnableTotalRecordCount = enableTotalRecordCount
                 },
                 options);
 
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user);
 
@@ -125,7 +127,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Upcoming")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetUpcomingEpisodes(
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
@@ -135,7 +137,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? enableImageTypes,
             [FromQuery] bool? enableUserData)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1);
 
@@ -191,7 +195,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
             [FromRoute] string? seriesId,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] string? fields,
             [FromQuery] int? season,
             [FromQuery] string? seasonId,
@@ -206,7 +210,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] bool? enableUserData,
             [FromQuery] string? sortBy)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             List<BaseItem> episodes;
 
@@ -312,7 +318,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<QueryResult<BaseItemDto>> GetSeasons(
             [FromRoute] string? seriesId,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] string? fields,
             [FromQuery] bool? isSpecialSeason,
             [FromQuery] bool? isMissing,
@@ -322,7 +328,9 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] string? enableImageTypes,
             [FromQuery] bool? enableUserData)
         {
-            var user = _userManager.GetUserById(userId);
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             if (!(_libraryManager.GetItemById(seriesId) is Series series))
             {

+ 3 - 3
Jellyfin.Api/Controllers/UserLibraryController.cs

@@ -180,7 +180,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpPost("/Users/{userId}/Items/{itemId}/Rating")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool likes)
+        public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool? likes)
         {
             return UpdateUserItemRatingInternal(userId, itemId, likes);
         }
@@ -264,7 +264,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
             [FromRoute] Guid userId,
-            [FromQuery] Guid parentId,
+            [FromQuery] Guid? parentId,
             [FromQuery] string? fields,
             [FromQuery] string? includeItemTypes,
             [FromQuery] bool? isPlayed,
@@ -297,7 +297,7 @@ namespace Jellyfin.Api.Controllers
                     IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
                     IsPlayed = isPlayed,
                     Limit = limit,
-                    ParentId = parentId,
+                    ParentId = parentId ?? Guid.Empty,
                     UserId = userId,
                 }, dtoOptions);
 

+ 3 - 3
Jellyfin.Api/Controllers/UserViewsController.cs

@@ -56,8 +56,8 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="userId">User id.</param>
         /// <param name="includeExternalContent">Whether or not to include external views such as channels or live tv.</param>
-        /// <param name="includeHidden">Whether or not to include hidden content.</param>
         /// <param name="presetViews">Preset views.</param>
+        /// <param name="includeHidden">Whether or not to include hidden content.</param>
         /// <response code="200">User views returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the user views.</returns>
         [HttpGet("/Users/{userId}/Views")]
@@ -65,8 +65,8 @@ namespace Jellyfin.Api.Controllers
         public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
             [FromRoute] Guid userId,
             [FromQuery] bool? includeExternalContent,
-            [FromQuery] bool includeHidden,
-            [FromQuery] string? presetViews)
+            [FromQuery] string? presetViews,
+            [FromQuery] bool includeHidden = false)
         {
             var query = new UserViewQuery
             {

+ 4 - 2
Jellyfin.Api/Controllers/VideosController.cs

@@ -53,9 +53,11 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{itemId}/AdditionalParts")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute] Guid itemId, [FromQuery] Guid userId)
+        public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute] Guid itemId, [FromQuery] Guid? userId)
         {
-            var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null;
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
 
             var item = itemId.Equals(Guid.Empty)
                 ? (!userId.Equals(Guid.Empty)

+ 6 - 6
Jellyfin.Api/Controllers/YearsController.cs

@@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] bool? enableUserData,
             [FromQuery] int? imageTypeLimit,
             [FromQuery] string? enableImageTypes,
-            [FromQuery] Guid userId,
+            [FromQuery] Guid? userId,
             [FromQuery] bool recursive = true,
             [FromQuery] bool? enableImages = true)
         {
@@ -86,9 +86,9 @@ namespace Jellyfin.Api.Controllers
             User? user = null;
             BaseItem parentItem;
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                user = _userManager.GetUserById(userId);
+                user = _userManager.GetUserById(userId.Value);
                 parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
             }
             else
@@ -176,7 +176,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{year}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<BaseItemDto> GetYear([FromRoute] int year, [FromQuery] Guid userId)
+        public ActionResult<BaseItemDto> GetYear([FromRoute] int year, [FromQuery] Guid? userId)
         {
             var item = _libraryManager.GetYear(year);
             if (item == null)
@@ -187,9 +187,9 @@ namespace Jellyfin.Api.Controllers
             var dtoOptions = new DtoOptions()
                 .AddClientFields(Request);
 
-            if (!userId.Equals(Guid.Empty))
+            if (userId.HasValue && !userId.Equals(Guid.Empty))
             {
-                var user = _userManager.GetUserById(userId);
+                var user = _userManager.GetUserById(userId.Value);
                 return _dtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 

+ 6 - 6
Jellyfin.Api/Helpers/SimilarItemsHelper.cs

@@ -4,7 +4,6 @@ using System.Linq;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
@@ -21,14 +20,16 @@ namespace Jellyfin.Api.Helpers
             IUserManager userManager,
             ILibraryManager libraryManager,
             IDtoService dtoService,
-            Guid userId,
+            Guid? userId,
             string id,
             string? excludeArtistIds,
             int? limit,
             Type[] includeTypes,
             Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
         {
-            var user = !userId.Equals(Guid.Empty) ? userManager.GetUserById(userId) : null;
+            var user = userId.HasValue && !userId.Equals(Guid.Empty)
+                ? userManager.GetUserById(userId.Value)
+                : null;
 
             var item = string.IsNullOrEmpty(id) ?
                 (!userId.Equals(Guid.Empty) ? libraryManager.GetUserRootFolder() :
@@ -38,11 +39,10 @@ namespace Jellyfin.Api.Helpers
             {
                 IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(),
                 Recursive = true,
-                DtoOptions = dtoOptions
+                DtoOptions = dtoOptions,
+                ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds)
             };
 
-            query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
-
             var inputItems = libraryManager.GetItemList(query);
 
             var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)