| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 | using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.Threading;using System.Threading.Tasks;using Jellyfin.Api.Constants;using Jellyfin.Api.Extensions;using Jellyfin.Api.Helpers;using MediaBrowser.Common.Api;using MediaBrowser.Controller.Entities;using MediaBrowser.Controller.Entities.Audio;using MediaBrowser.Controller.Entities.Movies;using MediaBrowser.Controller.Entities.TV;using MediaBrowser.Controller.Library;using MediaBrowser.Controller.Providers;using MediaBrowser.Model.IO;using MediaBrowser.Model.Providers;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Mvc;using Microsoft.Extensions.Logging;namespace Jellyfin.Api.Controllers;/// <summary>/// Item lookup controller./// </summary>[Route("")][Authorize]public class ItemLookupController : BaseJellyfinApiController{    private readonly IProviderManager _providerManager;    private readonly IFileSystem _fileSystem;    private readonly ILibraryManager _libraryManager;    private readonly ILogger<ItemLookupController> _logger;    /// <summary>    /// Initializes a new instance of the <see cref="ItemLookupController"/> class.    /// </summary>    /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>    /// <param name="logger">Instance of the <see cref="ILogger{ItemLookupController}"/> interface.</param>    public ItemLookupController(        IProviderManager providerManager,        IFileSystem fileSystem,        ILibraryManager libraryManager,        ILogger<ItemLookupController> logger)    {        _providerManager = providerManager;        _fileSystem = fileSystem;        _libraryManager = libraryManager;        _logger = logger;    }    /// <summary>    /// Get the item's external id info.    /// </summary>    /// <param name="itemId">Item id.</param>    /// <response code="200">External id info retrieved.</response>    /// <response code="404">Item not found.</response>    /// <returns>List of external id info.</returns>    [HttpGet("Items/{itemId}/ExternalIdInfos")]    [Authorize(Policy = Policies.RequiresElevation)]    [ProducesResponseType(StatusCodes.Status200OK)]    [ProducesResponseType(StatusCodes.Status404NotFound)]    public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute, Required] Guid itemId)    {        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());        if (item is null)        {            return NotFound();        }        return Ok(_providerManager.GetExternalIdInfos(item));    }    /// <summary>    /// Get movie remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Movie remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/Movie")]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMovieRemoteSearchResults([FromBody, Required] RemoteSearchQuery<MovieInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Get trailer remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Trailer remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/Trailer")]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetTrailerRemoteSearchResults([FromBody, Required] RemoteSearchQuery<TrailerInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Get music video remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Music video remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/MusicVideo")]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicVideoRemoteSearchResults([FromBody, Required] RemoteSearchQuery<MusicVideoInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Get series remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Series remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/Series")]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetSeriesRemoteSearchResults([FromBody, Required] RemoteSearchQuery<SeriesInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Get box set remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Box set remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/BoxSet")]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBoxSetRemoteSearchResults([FromBody, Required] RemoteSearchQuery<BoxSetInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Get music artist remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Music artist remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/MusicArtist")]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicArtistRemoteSearchResults([FromBody, Required] RemoteSearchQuery<ArtistInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Get music album remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Music album remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/MusicAlbum")]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicAlbumRemoteSearchResults([FromBody, Required] RemoteSearchQuery<AlbumInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Get person remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Person remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/Person")]    [Authorize(Policy = Policies.RequiresElevation)]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetPersonRemoteSearchResults([FromBody, Required] RemoteSearchQuery<PersonLookupInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Get book remote search.    /// </summary>    /// <param name="query">Remote search query.</param>    /// <response code="200">Book remote search executed.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.    /// </returns>    [HttpPost("Items/RemoteSearch/Book")]    public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBookRemoteSearchResults([FromBody, Required] RemoteSearchQuery<BookInfo> query)    {        var results = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(query, CancellationToken.None)            .ConfigureAwait(false);        return Ok(results);    }    /// <summary>    /// Applies search criteria to an item and refreshes metadata.    /// </summary>    /// <param name="itemId">Item id.</param>    /// <param name="searchResult">The remote search result.</param>    /// <param name="replaceAllImages">Optional. Whether or not to replace all images. Default: True.</param>    /// <response code="204">Item metadata refreshed.</response>    /// <response code="404">Item not found.</response>    /// <returns>    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.    /// The task result contains an <see cref="NoContentResult"/>.    /// </returns>    [HttpPost("Items/RemoteSearch/Apply/{itemId}")]    [Authorize(Policy = Policies.RequiresElevation)]    [ProducesResponseType(StatusCodes.Status204NoContent)]    [ProducesResponseType(StatusCodes.Status404NotFound)]    public async Task<ActionResult> ApplySearchCriteria(        [FromRoute, Required] Guid itemId,        [FromBody, Required] RemoteSearchResult searchResult,        [FromQuery] bool replaceAllImages = true)    {        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());        if (item is null)        {            return NotFound();        }        _logger.LogInformation(            "Setting provider id's to item {ItemId}-{ItemName}: {@ProviderIds}",            item.Id,            item.Name,            searchResult.ProviderIds);        // Since the refresh process won't erase provider Ids, we need to set this explicitly now.        item.ProviderIds = searchResult.ProviderIds;        await _providerManager.RefreshFullItem(            item,            new MetadataRefreshOptions(new DirectoryService(_fileSystem))            {                MetadataRefreshMode = MetadataRefreshMode.FullRefresh,                ImageRefreshMode = MetadataRefreshMode.FullRefresh,                ReplaceAllMetadata = true,                ReplaceAllImages = replaceAllImages,                SearchResult = searchResult,                RemoveOldMetadata = true            },            CancellationToken.None).ConfigureAwait(false);        return NoContent();    }}
 |