using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers; /// /// The items controller. /// [Route("")] [Authorize] public class ItemsController : BaseJellyfinApiController { private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; private readonly IDtoService _dtoService; private readonly ILogger _logger; private readonly ISessionManager _sessionManager; private readonly IUserDataManager _userDataRepository; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public ItemsController( IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization, IDtoService dtoService, ILogger logger, ISessionManager sessionManager, IUserDataManager userDataRepository) { _userManager = userManager; _libraryManager = libraryManager; _localization = localization; _dtoService = dtoService; _logger = logger; _sessionManager = sessionManager; _userDataRepository = userDataRepository; } /// /// Gets items based on a query. /// /// The user id supplied as query parameter; this is required when not using an API key. /// Optional filter by maximum official rating (PG, PG-13, TV-MA, etc). /// Optional filter by items with theme songs. /// Optional filter by items with theme videos. /// Optional filter by items with subtitles. /// Optional filter by items with special features. /// Optional filter by items with trailers. /// Optional. Return items that are siblings of a supplied item. /// Optional filter by parent index number. /// Optional filter by items that have or do not have a parental rating. /// Optional filter by items that are HD or not. /// Optional filter by items that are 4K or not. /// Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited. /// Optional filter by items that are missing episodes or not. /// Optional filter by items that are unaired episodes or not. /// Optional filter by minimum community rating. /// Optional filter by minimum critic rating. /// Optional. The minimum premiere date. Format = ISO. /// Optional. The minimum last saved date. Format = ISO. /// Optional. The minimum last saved date for the current user. Format = ISO. /// Optional. The maximum premiere date. Format = ISO. /// Optional filter by items that have an overview or not. /// Optional filter by items that have an IMDb id or not. /// Optional filter by items that have a TMDb id or not. /// Optional filter by items that have a TVDb id or not. /// Optional filter for live tv movies. /// Optional filter for live tv series. /// Optional filter for live tv news. /// Optional filter for live tv kids. /// Optional filter for live tv sports. /// Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// When searching within folders, this determines whether or not the search will be recursive. true/false. /// Optional. Filter based on a search term. /// Sort Order - Ascending, Descending. /// Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. /// Optional filter by items that are marked as favorite, or not. /// Optional filter by MediaType. Allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited. /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime. /// Optional filter by items that are played, or not. /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited. /// Optional, include user data. /// Optional, the max number of images to return, per image type. /// Optional. The image types to include in the output. /// Optional. If specified, results will be filtered to include only those containing the specified person. /// Optional. If specified, results will be filtered to include only those containing the specified person id. /// Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited. /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered to include only those containing the specified artist id. /// Optional. If specified, results will be filtered to include only those containing the specified album artist id. /// Optional. If specified, results will be filtered to include only those containing the specified contributing artist id. /// Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited. /// Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited. /// Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited. /// Optional filter by minimum official rating (PG, PG-13, TV-MA, etc). /// Optional filter by items that are locked. /// Optional filter by items that are placeholders. /// Optional filter by items that have official ratings. /// Whether or not to hide items behind their boxsets. /// Optional. Filter by the minimum width of the item. /// Optional. Filter by the minimum height of the item. /// Optional. Filter by the maximum width of the item. /// Optional. Filter by the maximum height of the item. /// Optional filter by items that are 3D, or not. /// Optional filter by Series Status. Allows multiple, comma delimited. /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited. /// Optional. Enable the total record count. /// Optional, include image information in output. /// A with the items. [HttpGet("Items")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetItems( [FromQuery] Guid? userId, [FromQuery] string? maxOfficialRating, [FromQuery] bool? hasThemeSong, [FromQuery] bool? hasThemeVideo, [FromQuery] bool? hasSubtitles, [FromQuery] bool? hasSpecialFeature, [FromQuery] bool? hasTrailer, [FromQuery] Guid? adjacentTo, [FromQuery] int? parentIndexNumber, [FromQuery] bool? hasParentalRating, [FromQuery] bool? isHd, [FromQuery] bool? is4K, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, [FromQuery] double? minCriticRating, [FromQuery] DateTime? minPremiereDate, [FromQuery] DateTime? minDateLastSaved, [FromQuery] DateTime? minDateLastSavedForUser, [FromQuery] DateTime? maxPremiereDate, [FromQuery] bool? hasOverview, [FromQuery] bool? hasImdbId, [FromQuery] bool? hasTmdbId, [FromQuery] bool? hasTvdbId, [FromQuery] bool? isMovie, [FromQuery] bool? isSeries, [FromQuery] bool? isNews, [FromQuery] bool? isKids, [FromQuery] bool? isSports, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool? recursive, [FromQuery] string? searchTerm, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? isPlayed, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] artists, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] albums, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes, [FromQuery] string? minOfficialRating, [FromQuery] bool? isLocked, [FromQuery] bool? isPlaceHolder, [FromQuery] bool? hasOfficialRating, [FromQuery] bool? collapseBoxSetItems, [FromQuery] int? minWidth, [FromQuery] int? minHeight, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] bool? is3D, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { var isApiKey = User.GetIsApiKey(); // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method userId = RequestHelpers.GetUserId(User, userId); var user = !isApiKey && !userId.Value.Equals(default) ? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException() : null; // beyond this point, we're either using an api key or we have a valid user if (!isApiKey && user is null) { return BadRequest("userId is required"); } var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); if (includeItemTypes.Length == 1 && includeItemTypes[0] == BaseItemKind.BoxSet) { parentId = null; } var item = _libraryManager.GetParentItem(parentId, userId); QueryResult result; if (item is not Folder folder) { folder = _libraryManager.GetUserRootFolder(); } CollectionType? collectionType = null; if (folder is IHasCollectionType hasCollectionType) { collectionType = hasCollectionType.CollectionType; } if (collectionType == CollectionType.Playlists) { recursive = true; includeItemTypes = new[] { BaseItemKind.Playlist }; } if (item is not UserRootFolder // api keys can always access all folders && !isApiKey // check the item is visible for the user && !item.IsVisible(user)) { _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user!.Username, item.Name); return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}."); } if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder) { var query = new InternalItemsQuery(user) { IsPlayed = isPlayed, MediaTypes = mediaTypes, IncludeItemTypes = includeItemTypes, ExcludeItemTypes = excludeItemTypes, Recursive = recursive ?? false, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), IsFavorite = isFavorite, Limit = limit, StartIndex = startIndex, IsMissing = isMissing, IsUnaired = isUnaired, CollapseBoxSetItems = collapseBoxSetItems, NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, HasImdbId = hasImdbId, IsPlaceHolder = isPlaceHolder, IsLocked = isLocked, MinWidth = minWidth, MinHeight = minHeight, MaxWidth = maxWidth, MaxHeight = maxHeight, Is3D = is3D, HasTvdbId = hasTvdbId, HasTmdbId = hasTmdbId, IsMovie = isMovie, IsSeries = isSeries, IsNews = isNews, IsKids = isKids, IsSports = isSports, HasOverview = hasOverview, HasOfficialRating = hasOfficialRating, HasParentalRating = hasParentalRating, HasSpecialFeature = hasSpecialFeature, HasSubtitles = hasSubtitles, HasThemeSong = hasThemeSong, HasThemeVideo = hasThemeVideo, HasTrailer = hasTrailer, IsHD = isHd, Is4K = is4K, Tags = tags, OfficialRatings = officialRatings, Genres = genres, ArtistIds = artistIds, AlbumArtistIds = albumArtistIds, ContributingArtistIds = contributingArtistIds, GenreIds = genreIds, StudioIds = studioIds, Person = person, PersonIds = personIds, PersonTypes = personTypes, Years = years, ImageTypes = imageTypes, VideoTypes = videoTypes, AdjacentTo = adjacentTo, ItemIds = ids, MinCommunityRating = minCommunityRating, MinCriticRating = minCriticRating, ParentId = parentId ?? Guid.Empty, ParentIndexNumber = parentIndexNumber, EnableTotalRecordCount = enableTotalRecordCount, ExcludeItemIds = excludeItemIds, DtoOptions = dtoOptions, SearchTerm = searchTerm, MinDateLastSaved = minDateLastSaved?.ToUniversalTime(), MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(), MinPremiereDate = minPremiereDate?.ToUniversalTime(), MaxPremiereDate = maxPremiereDate?.ToUniversalTime(), }; if (ids.Length != 0 || !string.IsNullOrWhiteSpace(searchTerm)) { query.CollapseBoxSetItems = false; } foreach (var filter in filters) { switch (filter) { case ItemFilter.Dislikes: query.IsLiked = false; break; case ItemFilter.IsFavorite: query.IsFavorite = true; break; case ItemFilter.IsFavoriteOrLikes: query.IsFavoriteOrLiked = true; break; case ItemFilter.IsFolder: query.IsFolder = true; break; case ItemFilter.IsNotFolder: query.IsFolder = false; break; case ItemFilter.IsPlayed: query.IsPlayed = true; break; case ItemFilter.IsResumable: query.IsResumable = true; break; case ItemFilter.IsUnplayed: query.IsPlayed = false; break; case ItemFilter.Likes: query.IsLiked = true; break; } } // Filter by Series Status if (seriesStatus.Length != 0) { query.SeriesStatuses = seriesStatus; } // Exclude Blocked Unrated Items var blockedUnratedItems = user?.GetPreferenceValues(PreferenceKind.BlockUnratedItems); if (blockedUnratedItems is not null) { query.BlockUnratedItems = blockedUnratedItems; } // ExcludeLocationTypes if (excludeLocationTypes.Any(t => t == LocationType.Virtual)) { query.IsVirtualItem = false; } if (locationTypes.Length > 0 && locationTypes.Length < 4) { query.IsVirtualItem = locationTypes.Contains(LocationType.Virtual); } // Min official rating if (!string.IsNullOrWhiteSpace(minOfficialRating)) { query.MinParentalRating = _localization.GetRatingLevel(minOfficialRating); } // Max official rating if (!string.IsNullOrWhiteSpace(maxOfficialRating)) { query.MaxParentalRating = _localization.GetRatingLevel(maxOfficialRating); } // Artists if (artists.Length != 0) { query.ArtistIds = artists.Select(i => { try { return _libraryManager.GetArtist(i, new DtoOptions(false)); } catch { return null; } }).Where(i => i is not null).Select(i => i!.Id).ToArray(); } // ExcludeArtistIds if (excludeArtistIds.Length != 0) { query.ExcludeArtistIds = excludeArtistIds; } if (albumIds.Length != 0) { query.AlbumIds = albumIds; } // Albums if (albums.Length != 0) { query.AlbumIds = albums.SelectMany(i => { return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Name = i, Limit = 1 }); }).ToArray(); } // Studios if (studios.Length != 0) { query.StudioIds = studios.Select(i => { try { return _libraryManager.GetStudio(i); } catch { return null; } }).Where(i => i is not null).Select(i => i!.Id).ToArray(); } // Apply default sorting if none requested if (query.OrderBy.Count == 0) { // Albums by artist if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.MusicAlbum) { query.OrderBy = new[] { (ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending) }; } } query.Parent = null; result = folder.GetItems(query); } else { var itemsArray = folder.GetChildren(user, true); result = new QueryResult(itemsArray); } return new QueryResult( startIndex, result.TotalRecordCount, _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user)); } /// /// Gets items based on a query. /// /// The user id supplied as query parameter. /// Optional filter by maximum official rating (PG, PG-13, TV-MA, etc). /// Optional filter by items with theme songs. /// Optional filter by items with theme videos. /// Optional filter by items with subtitles. /// Optional filter by items with special features. /// Optional filter by items with trailers. /// Optional. Return items that are siblings of a supplied item. /// Optional filter by parent index number. /// Optional filter by items that have or do not have a parental rating. /// Optional filter by items that are HD or not. /// Optional filter by items that are 4K or not. /// Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited. /// Optional filter by items that are missing episodes or not. /// Optional filter by items that are unaired episodes or not. /// Optional filter by minimum community rating. /// Optional filter by minimum critic rating. /// Optional. The minimum premiere date. Format = ISO. /// Optional. The minimum last saved date. Format = ISO. /// Optional. The minimum last saved date for the current user. Format = ISO. /// Optional. The maximum premiere date. Format = ISO. /// Optional filter by items that have an overview or not. /// Optional filter by items that have an IMDb id or not. /// Optional filter by items that have a TMDb id or not. /// Optional filter by items that have a TVDb id or not. /// Optional filter for live tv movies. /// Optional filter for live tv series. /// Optional filter for live tv news. /// Optional filter for live tv kids. /// Optional filter for live tv sports. /// Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// When searching within folders, this determines whether or not the search will be recursive. true/false. /// Optional. Filter based on a search term. /// Sort Order - Ascending, Descending. /// Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. /// Optional filter by items that are marked as favorite, or not. /// Optional filter by MediaType. Allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited. /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime. /// Optional filter by items that are played, or not. /// Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited. /// Optional, include user data. /// Optional, the max number of images to return, per image type. /// Optional. The image types to include in the output. /// Optional. If specified, results will be filtered to include only those containing the specified person. /// Optional. If specified, results will be filtered to include only those containing the specified person id. /// Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited. /// Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered to include only those containing the specified artist id. /// Optional. If specified, results will be filtered to include only those containing the specified album artist id. /// Optional. If specified, results will be filtered to include only those containing the specified contributing artist id. /// Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited. /// Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited. /// Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited. /// Optional filter by minimum official rating (PG, PG-13, TV-MA, etc). /// Optional filter by items that are locked. /// Optional filter by items that are placeholders. /// Optional filter by items that have official ratings. /// Whether or not to hide items behind their boxsets. /// Optional. Filter by the minimum width of the item. /// Optional. Filter by the minimum height of the item. /// Optional. Filter by the maximum width of the item. /// Optional. Filter by the maximum height of the item. /// Optional filter by items that are 3D, or not. /// Optional filter by Series Status. Allows multiple, comma delimited. /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. /// Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited. /// Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited. /// Optional. Enable the total record count. /// Optional, include image information in output. /// A with the items. [HttpGet("Users/{userId}/Items")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetItemsByUserId( [FromRoute] Guid userId, [FromQuery] string? maxOfficialRating, [FromQuery] bool? hasThemeSong, [FromQuery] bool? hasThemeVideo, [FromQuery] bool? hasSubtitles, [FromQuery] bool? hasSpecialFeature, [FromQuery] bool? hasTrailer, [FromQuery] Guid? adjacentTo, [FromQuery] int? parentIndexNumber, [FromQuery] bool? hasParentalRating, [FromQuery] bool? isHd, [FromQuery] bool? is4K, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, [FromQuery] double? minCriticRating, [FromQuery] DateTime? minPremiereDate, [FromQuery] DateTime? minDateLastSaved, [FromQuery] DateTime? minDateLastSavedForUser, [FromQuery] DateTime? maxPremiereDate, [FromQuery] bool? hasOverview, [FromQuery] bool? hasImdbId, [FromQuery] bool? hasTmdbId, [FromQuery] bool? hasTvdbId, [FromQuery] bool? isMovie, [FromQuery] bool? isSeries, [FromQuery] bool? isNews, [FromQuery] bool? isKids, [FromQuery] bool? isSports, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool? recursive, [FromQuery] string? searchTerm, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? isPlayed, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] artists, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] albums, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes, [FromQuery] string? minOfficialRating, [FromQuery] bool? isLocked, [FromQuery] bool? isPlaceHolder, [FromQuery] bool? hasOfficialRating, [FromQuery] bool? collapseBoxSetItems, [FromQuery] int? minWidth, [FromQuery] int? minHeight, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] bool? is3D, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { return GetItems( userId, maxOfficialRating, hasThemeSong, hasThemeVideo, hasSubtitles, hasSpecialFeature, hasTrailer, adjacentTo, parentIndexNumber, hasParentalRating, isHd, is4K, locationTypes, excludeLocationTypes, isMissing, isUnaired, minCommunityRating, minCriticRating, minPremiereDate, minDateLastSaved, minDateLastSavedForUser, maxPremiereDate, hasOverview, hasImdbId, hasTmdbId, hasTvdbId, isMovie, isSeries, isNews, isKids, isSports, excludeItemIds, startIndex, limit, recursive, searchTerm, sortOrder, parentId, fields, excludeItemTypes, includeItemTypes, filters, isFavorite, mediaTypes, imageTypes, sortBy, isPlayed, genres, officialRatings, tags, years, enableUserData, imageTypeLimit, enableImageTypes, person, personIds, personTypes, studios, artists, excludeArtistIds, artistIds, albumArtistIds, contributingArtistIds, albums, albumIds, ids, videoTypes, minOfficialRating, isLocked, isPlaceHolder, hasOfficialRating, collapseBoxSetItems, minWidth, minHeight, maxWidth, maxHeight, is3D, seriesStatus, nameStartsWithOrGreater, nameStartsWith, nameLessThan, studioIds, genreIds, enableTotalRecordCount, enableImages); } /// /// Gets items based on a query. /// /// The user id. /// The start index. /// The item limit. /// The search term. /// Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. Filter by MediaType. Allows multiple, comma delimited. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited. /// Optional. Enable the total record count. /// Optional. Include image information in output. /// Optional. Whether to exclude the currently active sessions. /// Items returned. /// A with the items that are resumable. [HttpGet("Users/{userId}/Items/Resume")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetResumeItems( [FromRoute, Required] Guid userId, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true, [FromQuery] bool excludeActiveSessions = false) { var user = _userManager.GetUserById(userId); if (user is null) { return NotFound(); } var parentIdGuid = parentId ?? Guid.Empty; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var ancestorIds = Array.Empty(); var excludeFolderIds = user.GetPreferenceValues(PreferenceKind.LatestItemExcludes); if (parentIdGuid.Equals(default) && excludeFolderIds.Length > 0) { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) .Where(i => !excludeFolderIds.Contains(i.Id)) .Select(i => i.Id) .ToArray(); } var excludeItemIds = Array.Empty(); if (excludeActiveSessions) { excludeItemIds = _sessionManager.Sessions .Where(s => s.UserId.Equals(userId) && s.NowPlayingItem is not null) .Select(s => s.NowPlayingItem.Id) .ToArray(); } var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) { OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }, IsResumable = true, StartIndex = startIndex, Limit = limit, ParentId = parentIdGuid, Recursive = true, DtoOptions = dtoOptions, MediaTypes = mediaTypes, IsVirtualItem = false, CollapseBoxSetItems = false, EnableTotalRecordCount = enableTotalRecordCount, AncestorIds = ancestorIds, IncludeItemTypes = includeItemTypes, ExcludeItemTypes = excludeItemTypes, SearchTerm = searchTerm, ExcludeItemIds = excludeItemIds }); var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user); return new QueryResult( startIndex, itemsResult.TotalRecordCount, returnItems); } /// /// Get Item User Data. /// /// The user id. /// The item id. /// return item user data. /// Item is not found. /// Return . [HttpGet("Users/{userId}/Items/{itemId}/UserData")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetItemUserData( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) { if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to view this item user data."); } var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException(); var item = _libraryManager.GetItemById(itemId); return (item == null) ? NotFound() : _userDataRepository.GetUserDataDto(item, user); } /// /// Update Item User Data. /// /// The user id. /// The item id. /// New user data object. /// return updated user item data. /// Item is not found. /// Return . [HttpPost("Users/{userId}/Items/{itemId}/UserData")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateItemUserData( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromBody, Required] UserDataDto userDataDto) { if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update this item user data."); } var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException(); var item = _libraryManager.GetItemById(itemId); if (item == null) { return NotFound(); } _userDataRepository.SaveUserData(user, item, userDataDto, UserDataSaveReason.UpdateUserData); return _userDataRepository.GetUserDataDto(item, user); } }