Fix all route for base url support

pull/3819/head
crobibero 4 years ago
parent 2b7cefdf15
commit 858aecd409

@ -17,6 +17,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The albums controller.
/// </summary>
[Route("")]
public class AlbumsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@ -48,7 +49,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <response code="200">Similar albums returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar albums.</returns>
[HttpGet("/Albums/{albumId}/Similar")]
[HttpGet("Albums/{albumId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
[FromRoute] string albumId,
@ -80,7 +81,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <response code="200">Similar artists returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar artists.</returns>
[HttpGet("/Artists/{artistId}/Similar")]
[HttpGet("Artists/{artistId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
[FromRoute] string artistId,

@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The dashboard controller.
/// </summary>
[Route("")]
public class DashboardController : BaseJellyfinApiController
{
private readonly ILogger<DashboardController> _logger;
@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">ConfigurationPages returned.</response>
/// <response code="404">Server still loading.</response>
/// <returns>An <see cref="IEnumerable{ConfigurationPageInfo}"/> with infos about the plugins.</returns>
[HttpGet("/web/ConfigurationPages")]
[HttpGet("web/ConfigurationPages")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ConfigurationPageInfo?>> GetConfigurationPages(
@ -118,7 +119,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">ConfigurationPage returned.</response>
/// <response code="404">Plugin configuration page not found.</response>
/// <returns>The configuration page.</returns>
[HttpGet("/web/ConfigurationPage")]
[HttpGet("web/ConfigurationPage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetDashboardConfigurationPage([FromQuery] string? name)
@ -172,7 +173,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="200">Robots.txt returned.</response>
/// <returns>The robots.txt.</returns>
[HttpGet("/robots.txt")]
[HttpGet("robots.txt")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ApiExplorerSettings(IgnoreApi = true)]
public ActionResult GetRobotsTxt()
@ -187,7 +188,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Web client returned.</response>
/// <response code="404">Server does not host a web client.</response>
/// <returns>The resource.</returns>
[HttpGet("/web/{*resourceName}")]
[HttpGet("web/{*resourceName}")]
[ApiExplorerSettings(IgnoreApi = true)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -218,7 +219,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="200">Favicon.ico returned.</response>
/// <returns>The favicon.</returns>
[HttpGet("/favicon.ico")]
[HttpGet("favicon.ico")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ApiExplorerSettings(IgnoreApi = true)]
public ActionResult GetFavIcon()

@ -37,6 +37,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Dynamic hls controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class DynamicHlsController : BaseJellyfinApiController
{
@ -164,8 +165,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
[HttpGet("/Videos/{itemId}/master.m3u8")]
[HttpHead("/Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
[HttpGet("Videos/{itemId}/master.m3u8")]
[HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetMasterHlsVideoPlaylist(
[FromRoute] Guid itemId,
@ -334,8 +335,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
/// <response code="200">Audio stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
[HttpGet("/Audio/{itemId}/master.m3u8")]
[HttpHead("/Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
[HttpGet("Audio/{itemId}/master.m3u8")]
[HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetMasterHlsAudioPlaylist(
[FromRoute] Guid itemId,
@ -503,7 +504,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("/Videos/{itemId}/main.m3u8")]
[HttpGet("Videos/{itemId}/main.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetVariantHlsVideoPlaylist(
[FromRoute] Guid itemId,
@ -668,7 +669,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Audio stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("/Audio/{itemId}/main.m3u8")]
[HttpGet("Audio/{itemId}/main.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetVariantHlsAudioPlaylist(
[FromRoute] Guid itemId,
@ -835,7 +836,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("/Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
[HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetHlsVideoSegment(
@ -1004,7 +1005,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("/Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
[HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetHlsAudioSegment(

@ -18,6 +18,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Filters controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class FilterController : BaseJellyfinApiController
{
@ -44,7 +45,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
/// <response code="200">Legacy filters retrieved.</response>
/// <returns>Legacy query filters.</returns>
[HttpGet("/Items/Filters")]
[HttpGet("Items/Filters")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
[FromQuery] Guid? userId,
@ -133,7 +134,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="recursive">Optional. Search recursive.</param>
/// <response code="200">Filters retrieved.</response>
/// <returns>Query filters.</returns>
[HttpGet("/Items/Filters2")]
[HttpGet("Items/Filters2")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryFilters> GetQueryFilters(
[FromQuery] Guid? userId,

@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The hls segment controller.
/// </summary>
[Route("")]
public class HlsSegmentController : BaseJellyfinApiController
{
private readonly IFileSystem _fileSystem;
@ -50,8 +51,8 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="FileStreamResult"/> containing the audio stream.</returns>
// Can't require authentication just yet due to seeing some requests come from Chrome without full query string
// [Authenticated]
[HttpGet("/Audio/{itemId}/hls/{segmentId}/stream.mp3", Name = "GetHlsAudioSegmentLegacyMp3")]
[HttpGet("/Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
[HttpGet("Audio/{itemId}/hls/{segmentId}/stream.mp3", Name = "GetHlsAudioSegmentLegacyMp3")]
[HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
public ActionResult GetHlsAudioSegmentLegacy([FromRoute] string itemId, [FromRoute] string segmentId)
@ -70,7 +71,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playlistId">The playlist id.</param>
/// <response code="200">Hls video playlist returned.</response>
/// <returns>A <see cref="FileStreamResult"/> containing the playlist.</returns>
[HttpGet("/Videos/{itemId}/hls/{playlistId}/stream.m3u8")]
[HttpGet("Videos/{itemId}/hls/{playlistId}/stream.m3u8")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
@ -89,7 +90,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playSessionId">The play session id.</param>
/// <response code="204">Encoding stopped successfully.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpDelete("/Videos/ActiveEncodings")]
[HttpDelete("Videos/ActiveEncodings")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult StopEncodingProcess([FromQuery] string deviceId, [FromQuery] string playSessionId)
@ -109,7 +110,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="FileStreamResult"/> containing the video segment.</returns>
// Can't require authentication just yet due to seeing some requests come from Chrome without full query string
// [Authenticated]
[HttpGet("/Videos/{itemId}/hls/{playlistId}/{segmentId}.{segmentContainer}")]
[HttpGet("Videos/{itemId}/hls/{playlistId}/{segmentId}.{segmentContainer}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
public ActionResult GetHlsVideoSegmentLegacy(

@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Image controller.
/// </summary>
[Route("")]
public class ImageController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@ -81,8 +82,8 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Image updated.</response>
/// <response code="403">User does not have permission to delete the image.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Users/{userId}/Images/{imageType}")]
[HttpPost("/Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")]
[HttpPost("Users/{userId}/Images/{imageType}")]
[HttpPost("Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@ -127,8 +128,8 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Image deleted.</response>
/// <response code="403">User does not have permission to delete the image.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("/Users/{userId}/Images/{itemType}")]
[HttpDelete("/Users/{userId}/Images/{itemType}/{index?}", Name = "DeleteUserImage_2")]
[HttpDelete("Users/{userId}/Images/{itemType}")]
[HttpDelete("Users/{userId}/Images/{itemType}/{index?}", Name = "DeleteUserImage_2")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@ -166,8 +167,8 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Image deleted.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpDelete("/Items/{itemId}/Images/{imageType}")]
[HttpDelete("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "DeleteItemImage_2")]
[HttpDelete("Items/{itemId}/Images/{imageType}")]
[HttpDelete("Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "DeleteItemImage_2")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -195,8 +196,8 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Image saved.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpPost("/Items/{itemId}/Images/{imageType}")]
[HttpPost("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "SetItemImage_2")]
[HttpPost("Items/{itemId}/Images/{imageType}")]
[HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "SetItemImage_2")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -230,7 +231,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Image index updated.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpPost("/Items/{itemId}/Images/{imageType}/{imageIndex}/Index")]
[HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex}/Index")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -257,7 +258,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Item images returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>The list of image infos on success, or <see cref="NotFoundResult"/> if item not found.</returns>
[HttpGet("/Items/{itemId}/Images")]
[HttpGet("Items/{itemId}/Images")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ImageInfo>> GetItemImageInfos([FromRoute] Guid itemId)
@ -341,10 +342,10 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Items/{itemId}/Images/{imageType}")]
[HttpHead("/Items/{itemId}/Images/{imageType}", Name = "HeadItemImage")]
[HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "GetItemImage_2")]
[HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "HeadItemImage_2")]
[HttpGet("Items/{itemId}/Images/{imageType}")]
[HttpHead("Items/{itemId}/Images/{imageType}", Name = "HeadItemImage")]
[HttpGet("Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "GetItemImage_2")]
[HttpHead("Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "HeadItemImage_2")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetItemImage(
@ -421,8 +422,8 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")]
[HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}", Name = "HeadItemImage2")]
[HttpGet("Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")]
[HttpHead("Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}", Name = "HeadItemImage2")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetItemImage2(
@ -499,8 +500,8 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Artists/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Artists/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadArtistImage")]
[HttpGet("Artists/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("Artists/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadArtistImage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetArtistImage(
@ -577,8 +578,8 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Genres/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Genres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadGenreImage")]
[HttpGet("Genres/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("Genres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadGenreImage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetGenreImage(
@ -655,8 +656,8 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadMusicGenreImage")]
[HttpGet("MusicGenres/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("MusicGenres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadMusicGenreImage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetMusicGenreImage(
@ -733,8 +734,8 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Persons/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Persons/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadPersonImage")]
[HttpGet("Persons/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("Persons/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadPersonImage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetPersonImage(
@ -811,8 +812,8 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Studios/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Studios/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadStudioImage")]
[HttpGet("Studios/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("Studios/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadStudioImage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetStudioImage(
@ -889,8 +890,8 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Users/{userId}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Users/{userId}/Images/{imageType}/{imageIndex?}", Name = "HeadUserImage")]
[HttpGet("Users/{userId}/Images/{imageType}/{imageIndex?}")]
[HttpHead("Users/{userId}/Images/{imageType}/{imageIndex?}", Name = "HeadUserImage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetUserImage(

@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The instant mix controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class InstantMixController : BaseJellyfinApiController
{
@ -59,7 +60,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("/Songs/{id}/InstantMix")]
[HttpGet("Songs/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
[FromRoute] Guid id,
@ -96,7 +97,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("/Albums/{id}/InstantMix")]
[HttpGet("Albums/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
[FromRoute] Guid id,
@ -133,7 +134,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("/Playlists/{id}/InstantMix")]
[HttpGet("Playlists/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
[FromRoute] Guid id,
@ -170,7 +171,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("/MusicGenres/{name}/InstantMix")]
[HttpGet("MusicGenres/{name}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
[FromRoute] string? name,
@ -206,7 +207,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("/Artists/InstantMix")]
[HttpGet("Artists/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
[FromRoute] Guid id,
@ -243,7 +244,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("/MusicGenres/InstantMix")]
[HttpGet("MusicGenres/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
[FromRoute] Guid id,
@ -280,7 +281,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <response code="200">Instant playlist returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("/Items/{id}/InstantMix")]
[HttpGet("Items/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
[FromRoute] Guid id,

@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Item lookup controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class ItemLookupController : BaseJellyfinApiController
{
@ -68,7 +69,7 @@ namespace Jellyfin.Api.Controllers
/// <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")]
[HttpGet("Items/{itemId}/ExternalIdInfos")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -92,7 +93,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/Movie")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMovieRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MovieInfo> query)
{
var results = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(query, CancellationToken.None)
@ -109,7 +110,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/Trailer")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetTrailerRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<TrailerInfo> query)
{
var results = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(query, CancellationToken.None)
@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/MusicVideo")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicVideoRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MusicVideoInfo> query)
{
var results = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(query, CancellationToken.None)
@ -143,7 +144,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/Series")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetSeriesRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<SeriesInfo> query)
{
var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, CancellationToken.None)
@ -160,7 +161,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/BoxSet")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBoxSetRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BoxSetInfo> query)
{
var results = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(query, CancellationToken.None)
@ -177,7 +178,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/MusicArtist")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicArtistRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<ArtistInfo> query)
{
var results = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(query, CancellationToken.None)
@ -194,7 +195,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/MusicAlbum")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicAlbumRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<AlbumInfo> query)
{
var results = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(query, CancellationToken.None)
@ -211,7 +212,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/Person")]
[Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetPersonRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<PersonLookupInfo> query)
{
@ -229,7 +230,7 @@ namespace Jellyfin.Api.Controllers
/// 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")]
[HttpPost("Items/RemoteSearch/Book")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBookRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BookInfo> query)
{
var results = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(query, CancellationToken.None)
@ -247,7 +248,7 @@ namespace Jellyfin.Api.Controllers
/// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
/// The task result contains an <see cref="FileStreamResult"/> containing the images file stream.
/// </returns>
[HttpGet("/Items/RemoteSearch/Image")]
[HttpGet("Items/RemoteSearch/Image")]
public async Task<ActionResult> GetRemoteSearchImage(
[FromQuery, Required] string imageUrl,
[FromQuery, Required] string providerName)
@ -291,7 +292,7 @@ namespace Jellyfin.Api.Controllers
/// 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/{id}")]
[HttpPost("Items/RemoteSearch/Apply/{id}")]
[Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult> ApplySearchCriteria(
[FromRoute] Guid itemId,

@ -24,6 +24,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Item update controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.RequiresElevation)]
public class ItemUpdateController : BaseJellyfinApiController
{
@ -63,7 +64,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Item updated.</response>
/// <response code="404">Item not found.</response>
/// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
[HttpPost("/Items/{itemId}")]
[HttpPost("Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, BindRequired] BaseItemDto request)
@ -136,7 +137,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Item metadata editor returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>An <see cref="OkResult"/> on success containing the metadata editor, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
[HttpGet("/Items/{itemId}/MetadataEditor")]
[HttpGet("Items/{itemId}/MetadataEditor")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute] Guid itemId)
@ -190,7 +191,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Item content type updated.</response>
/// <response code="404">Item not found.</response>
/// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
[HttpPost("/Items/{itemId}/ContentType")]
[HttpPost("Items/{itemId}/ContentType")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string? contentType)

@ -23,6 +23,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The items controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class ItemsController : BaseJellyfinApiController
{
@ -139,8 +140,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
[HttpGet("/Items")]
[HttpGet("/Users/{uId}/Items", Name = "GetItems_2")]
[HttpGet("Items")]
[HttpGet("Users/{uId}/Items", Name = "GetItems_2")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetItems(
[FromRoute] Guid? uId,
@ -523,7 +524,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImages">Optional. Include image information in output.</param>
/// <response code="200">Items returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns>
[HttpGet("/Users/{userId}/Items/Resume")]
[HttpGet("Users/{userId}/Items/Resume")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
[FromRoute] Guid userId,

@ -43,6 +43,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Library Controller.
/// </summary>
[Route("")]
public class LibraryController : BaseJellyfinApiController
{
private readonly IProviderManager _providerManager;
@ -100,7 +101,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">File stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="FileStreamResult"/> with the original file.</returns>
[HttpGet("/Items/{itemId}/File")]
[HttpGet("Items/{itemId}/File")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -121,7 +122,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="200">Critic reviews returned.</response>
/// <returns>The list of critic reviews.</returns>
[HttpGet("/Items/{itemId}/CriticReviews")]
[HttpGet("Items/{itemId}/CriticReviews")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[Obsolete("This endpoint is obsolete.")]
[ProducesResponseType(StatusCodes.Status200OK)]
@ -139,7 +140,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Theme songs returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>The item theme songs.</returns>
[HttpGet("/Items/{itemId}/ThemeSongs")]
[HttpGet("Items/{itemId}/ThemeSongs")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -205,7 +206,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Theme videos returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>The item theme videos.</returns>
[HttpGet("/Items/{itemId}/ThemeVideos")]
[HttpGet("Items/{itemId}/ThemeVideos")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -271,7 +272,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Theme songs and videos returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>The item theme videos.</returns>
[HttpGet("/Items/{itemId}/ThemeMedia")]
[HttpGet("Items/{itemId}/ThemeMedia")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<AllThemeMediaResult> GetThemeMedia(
@ -302,7 +303,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="204">Library scan started.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpGet("/Library/Refresh")]
[HttpGet("Library/Refresh")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RefreshLibrary()
@ -326,7 +327,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Item deleted.</response>
/// <response code="401">Unauthorized access.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("/Items/{itemId}")]
[HttpDelete("Items/{itemId}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
@ -356,7 +357,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Items deleted.</response>
/// <response code="401">Unauthorized access.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("/Items")]
[HttpDelete("Items")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
@ -400,7 +401,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="isFavorite">Optional. Get counts of favorite items.</param>
/// <response code="200">Item counts returned.</response>
/// <returns>Item counts.</returns>
[HttpGet("/Items/Counts")]
[HttpGet("Items/Counts")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<ItemCounts> GetItemCounts(
@ -434,7 +435,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Item parents returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>Item parents.</returns>
[HttpGet("/Items/{itemId}/Ancestors")]
[HttpGet("Items/{itemId}/Ancestors")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -476,7 +477,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="200">Physical paths returned.</response>
/// <returns>List of physical paths.</returns>
[HttpGet("/Library/PhysicalPaths")]
[HttpGet("Library/PhysicalPaths")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<string>> GetPhysicalPaths()
@ -491,7 +492,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="isHidden">Optional. Filter by folders that are marked hidden, or not.</param>
/// <response code="200">Media folders returned.</response>
/// <returns>List of user media folders.</returns>
[HttpGet("/Library/MediaFolders")]
[HttpGet("Library/MediaFolders")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden)
@ -521,8 +522,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="tvdbId">The tvdbId.</param>
/// <response code="204">Report success.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Library/Series/Added", Name = "PostAddedSeries")]
[HttpPost("/Library/Series/Updated")]
[HttpPost("Library/Series/Added", Name = "PostAddedSeries")]
[HttpPost("Library/Series/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedSeries([FromQuery] string? tvdbId)
@ -551,8 +552,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="imdbId">The imdbId.</param>
/// <response code="204">Report success.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Library/Movies/Added", Name = "PostAddedMovies")]
[HttpPost("/Library/Movies/Updated")]
[HttpPost("Library/Movies/Added", Name = "PostAddedMovies")]
[HttpPost("Library/Movies/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedMovies([FromRoute] string? tmdbId, [FromRoute] string? imdbId)
@ -593,7 +594,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="updates">A list of updated media paths.</param>
/// <response code="204">Report success.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Library/Media/Updated")]
[HttpPost("Library/Media/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedMedia([FromBody, BindRequired] MediaUpdateInfoDto[] updates)
@ -614,7 +615,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="FileResult"/> containing the media stream.</returns>
/// <exception cref="ArgumentException">User can't download or item can't be downloaded.</exception>
[HttpGet("/Items/{itemId}/Download")]
[HttpGet("Items/{itemId}/Download")]
[Authorize(Policy = Policies.Download)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -679,12 +680,12 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">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, TrailerUrls.</param>
/// <response code="200">Similar items returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
[HttpGet("/Artists/{itemId}/Similar", Name = "GetSimilarArtists2")]
[HttpGet("/Items/{itemId}/Similar")]
[HttpGet("/Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")]
[HttpGet("/Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
[HttpGet("/Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
[HttpGet("/Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
[HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists2")]
[HttpGet("Items/{itemId}/Similar")]
[HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")]
[HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
[HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
[HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
[FromRoute] Guid itemId,
@ -735,7 +736,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="isNewLibrary">Whether this is a new library.</param>
/// <response code="200">Library options info returned.</response>
/// <returns>Library options info.</returns>
[HttpGet("/Libraries/AvailableOptions")]
[HttpGet("Libraries/AvailableOptions")]
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(

@ -34,6 +34,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The media info controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class MediaInfoController : BaseJellyfinApiController
{
@ -88,7 +89,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">The user id.</param>
/// <response code="200">Playback info returned.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
[HttpGet("/Items/{itemId}/PlaybackInfo")]
[HttpGet("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid? userId)
{
@ -116,7 +117,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
/// <response code="200">Playback info returned.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
[HttpPost("/Items/{itemId}/PlaybackInfo")]
[HttpPost("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
[FromRoute] Guid itemId,
@ -237,7 +238,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
/// <response code="200">Media source opened.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
[HttpPost("/LiveStreams/Open")]
[HttpPost("LiveStreams/Open")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
[FromQuery] string? openToken,
@ -278,7 +279,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="liveStreamId">The livestream id.</param>
/// <response code="204">Livestream closed.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("/LiveStreams/Close")]
[HttpPost("LiveStreams/Close")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CloseLiveStream([FromQuery] string? liveStreamId)
{
@ -293,7 +294,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Test buffer returned.</response>
/// <response code="400">Size has to be a numer between 0 and 10,000,000.</response>
/// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
[HttpGet("/Playback/BitrateTest")]
[HttpGet("Playback/BitrateTest")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Produces(MediaTypeNames.Application.Octet)]

@ -16,7 +16,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Package Controller.
/// </summary>
[Route("Packages")]
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class PackageController : BaseJellyfinApiController
{
@ -41,7 +41,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="assemblyGuid">The GUID of the associated assembly.</param>
/// <response code="200">Package retrieved.</response>
/// <returns>A <see cref="PackageInfo"/> containing package information.</returns>
[HttpGet("/{name}")]
[HttpGet("Packages/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PackageInfo>> GetPackageInfo(
[FromRoute] [Required] string? name,
@ -61,7 +61,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="200">Available packages returned.</response>
/// <returns>An <see cref="PackageInfo"/> containing available packages information.</returns>
[HttpGet]
[HttpGet("Packages")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IEnumerable<PackageInfo>> GetPackages()
{
@ -79,7 +79,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Package found.</response>
/// <response code="404">Package not found.</response>
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the package could not be found.</returns>
[HttpPost("/Installed/{name}")]
[HttpPost("Packages/Installed/{name}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.RequiresElevation)]
@ -111,7 +111,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="packageId">Installation Id.</param>
/// <response code="204">Installation cancelled.</response>
/// <returns>A <see cref="NoContentResult"/> on successfully cancelling a package installation.</returns>
[HttpDelete("/Installing/{packageId}")]
[HttpDelete("Packages/Installing/{packageId}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CancelPackageInstallation(
@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="200">Package repositories returned.</response>
/// <returns>An <see cref="OkResult"/> containing the list of package repositories.</returns>
[HttpGet("/Repositories")]
[HttpGet("Repositories")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<RepositoryInfo>> GetRepositories()
@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="repositoryInfos">The list of package repositories.</param>
/// <response code="204">Package repositories saved.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpOptions("/Repositories")]
[HttpOptions("Repositories")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos)

@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Playstate controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class PlaystateController : BaseJellyfinApiController
{
@ -67,7 +68,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="datePlayed">Optional. The date the item was played.</param>
/// <response code="200">Item marked as played.</response>
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpPost("/Users/{userId}/PlayedItems/{itemId}")]
[HttpPost("Users/{userId}/PlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkPlayedItem(
[FromRoute] Guid userId,
@ -93,7 +94,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <response code="200">Item marked as unplayed.</response>
/// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("/Users/{userId}/PlayedItem/{itemId}")]
[HttpDelete("Users/{userId}/PlayedItem/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
@ -115,7 +116,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playbackStartInfo">The playback start info.</param>
/// <response code="204">Playback start recorded.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/Playing")]
[HttpPost("Sessions/Playing")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> ReportPlaybackStart([FromBody] PlaybackStartInfo playbackStartInfo)
{
@ -131,7 +132,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playbackProgressInfo">The playback progress info.</param>
/// <response code="204">Playback progress recorded.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/Playing/Progress")]
[HttpPost("Sessions/Playing/Progress")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> ReportPlaybackProgress([FromBody] PlaybackProgressInfo playbackProgressInfo)
{
@ -147,7 +148,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playSessionId">Playback session id.</param>
/// <response code="204">Playback session pinged.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/Playing/Ping")]
[HttpPost("Sessions/Playing/Ping")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PingPlaybackSession([FromQuery] string playSessionId)
{
@ -161,7 +162,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playbackStopInfo">The playback stop info.</param>
/// <response code="204">Playback stop recorded.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/Playing/Stopped")]
[HttpPost("Sessions/Playing/Stopped")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> ReportPlaybackStopped([FromBody] PlaybackStopInfo playbackStopInfo)
{
@ -190,7 +191,7 @@ namespace Jellyfin.Api.Controllers
/// <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}")]
[HttpPost("Users/{userId}/PlayingItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackStart(
@ -240,7 +241,7 @@ namespace Jellyfin.Api.Controllers
/// <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")]
[HttpPost("Users/{userId}/PlayingItems/{itemId}/Progress")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackProgress(
@ -292,7 +293,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playSessionId">The play session id.</param>
/// <response code="204">Playback stop recorded.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("/Users/{userId}/PlayingItems/{itemId}")]
[HttpDelete("Users/{userId}/PlayingItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackStopped(

@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Not Implemented.</returns>
/// <exception cref="NotImplementedException">This endpoint is not implemented.</exception>
[Obsolete("Paid plugins are not supported")]
[HttpGet("/Registrations/{name}")]
[HttpGet("Registrations/{name}")]
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
public ActionResult GetRegistration([FromRoute] string? name)
{

@ -23,6 +23,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The session controller.
/// </summary>
[Route("")]
public class SessionController : BaseJellyfinApiController
{
private readonly ISessionManager _sessionManager;
@ -57,7 +58,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="activeWithinSeconds">Optional. Filter by sessions that were active in the last n seconds.</param>
/// <response code="200">List of sessions returned.</response>
/// <returns>An <see cref="IEnumerable{SessionInfo}"/> with the available sessions.</returns>
[HttpGet("/Sessions")]
[HttpGet("Sessions")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<SessionInfo>> GetSessions(
@ -120,7 +121,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemName">The name of the item.</param>
/// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/{sessionId}/Viewing")]
[HttpPost("Sessions/{sessionId}/Viewing")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DisplayContent(
[FromRoute] string? sessionId,
@ -154,7 +155,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playRequest">The <see cref="PlayRequest"/>.</param>
/// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/{sessionId}/Playing")]
[HttpPost("Sessions/{sessionId}/Playing")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult Play(
[FromRoute] string? sessionId,
@ -188,7 +189,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="playstateRequest">The <see cref="PlaystateRequest"/>.</param>
/// <response code="204">Playstate command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/{sessionId}/Playing/{command}")]
[HttpPost("Sessions/{sessionId}/Playing/{command}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendPlaystateCommand(
[FromRoute] string? sessionId,
@ -210,7 +211,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="command">The command to send.</param>
/// <response code="204">System command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/{sessionId}/System/{command}")]
[HttpPost("Sessions/{sessionId}/System/{command}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendSystemCommand(
[FromRoute] string? sessionId,
@ -241,7 +242,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="command">The command to send.</param>
/// <response code="204">General command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/{sessionId}/Command/{command}")]
[HttpPost("Sessions/{sessionId}/Command/{command}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendGeneralCommand(
[FromRoute] string? sessionId,
@ -267,7 +268,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="command">The <see cref="GeneralCommand"/>.</param>
/// <response code="204">Full general command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/{sessionId}/Command")]
[HttpPost("Sessions/{sessionId}/Command")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendFullGeneralCommand(
[FromRoute] string? sessionId,
@ -300,7 +301,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="timeoutMs">The message timeout. If omitted the user will have to confirm viewing the message.</param>
/// <response code="204">Message sent.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/{sessionId}/Message")]
[HttpPost("Sessions/{sessionId}/Message")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendMessageCommand(
[FromRoute] string? sessionId,
@ -327,7 +328,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">The user id.</param>
/// <response code="204">User added to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/{sessionId}/User/{userId}")]
[HttpPost("Sessions/{sessionId}/User/{userId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddUserToSession(
[FromRoute] string? sessionId,
@ -344,7 +345,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">The user id.</param>
/// <response code="204">User removed from session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("/Sessions/{sessionId}/User/{userId}")]
[HttpDelete("Sessions/{sessionId}/User/{userId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RemoveUserFromSession(
[FromRoute] string? sessionId,
@ -365,7 +366,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param>
/// <response code="204">Capabilities posted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/Capabilities")]
[HttpPost("Sessions/Capabilities")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostCapabilities(
[FromQuery] string? id,
@ -398,7 +399,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="capabilities">The <see cref="ClientCapabilities"/>.</param>
/// <response code="204">Capabilities updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/Capabilities/Full")]
[HttpPost("Sessions/Capabilities/Full")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostFullCapabilities(
[FromQuery] string? id,
@ -421,7 +422,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">The item id.</param>
/// <response code="204">Session reported to server.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/Viewing")]
[HttpPost("Sessions/Viewing")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult ReportViewing(
[FromQuery] string? sessionId,
@ -438,7 +439,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="204">Session end reported to server.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Sessions/Logout")]
[HttpPost("Sessions/Logout")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult ReportSessionEnded()
{
@ -453,7 +454,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="200">Auth providers retrieved.</response>
/// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the auth providers.</returns>
[HttpGet("/Auth/Providers")]
[HttpGet("Auth/Providers")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<NameIdPair>> GetAuthProviders()
{
@ -465,7 +466,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="200">Password reset providers retrieved.</response>
/// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the password reset providers.</returns>
[HttpGet("/Auto/PasswordResetProviders")]
[HttpGet("Auto/PasswordResetProviders")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<NameIdPair>> GetPasswordResetProviders()
{

@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Subtitle controller.
/// </summary>
[Route("")]
public class SubtitleController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@ -80,7 +81,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Subtitle deleted.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("/Videos/{itemId}/Subtitles/{index}")]
[HttpDelete("Videos/{itemId}/Subtitles/{index}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@ -107,7 +108,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="isPerfectMatch">Optional. Only show subtitles which are a perfect match.</param>
/// <response code="200">Subtitles retrieved.</response>
/// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns>
[HttpGet("/Items/{itemId}/RemoteSearch/Subtitles/{language}")]
[HttpGet("Items/{itemId}/RemoteSearch/Subtitles/{language}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
@ -127,7 +128,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="subtitleId">The subtitle id.</param>
/// <response code="204">Subtitle downloaded.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("/Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")]
[HttpPost("Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> DownloadRemoteSubtitles(
@ -157,7 +158,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="id">The item id.</param>
/// <response code="200">File returned.</response>
/// <returns>A <see cref="FileStreamResult"/> with the subtitle file.</returns>
[HttpGet("/Providers/Subtitles/Subtitles/{id}")]
[HttpGet("Providers/Subtitles/Subtitles/{id}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[Produces(MediaTypeNames.Application.Octet)]
@ -181,8 +182,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="startPositionTicks">Optional. The start position of the subtitle in ticks.</param>
/// <response code="200">File returned.</response>
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
[HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
[HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks?}/Stream.{format}", Name = "GetSubtitle_2")]
[HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
[HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks?}/Stream.{format}", Name = "GetSubtitle_2")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetSubtitle(
[FromRoute, Required] Guid itemId,
@ -247,7 +248,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="segmentLength">The subtitle segment length.</param>
/// <response code="200">Subtitle playlist retrieved.</response>
/// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns>
[HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")]
[HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]

@ -16,6 +16,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The suggestions controller.
/// </summary>
[Route("")]
public class SuggestionsController : BaseJellyfinApiController
{
private readonly IDtoService _dtoService;
@ -49,7 +50,7 @@ namespace Jellyfin.Api.Controllers
/// <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")]
[HttpGet("Users/{userId}/Suggestions")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
[FromRoute] Guid userId,

@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the trailers.</returns>
[HttpGet("/Trailers")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
[FromQuery] Guid? userId,

@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The universal audio controller.
/// </summary>
[Route("")]
public class UniversalAudioController : BaseJellyfinApiController
{
private readonly IAuthorizationContext _authorizationContext;
@ -68,10 +69,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Audio stream returned.</response>
/// <response code="302">Redirected to remote audio stream.</response>
/// <returns>A <see cref="Task"/> containing the audio file.</returns>
[HttpGet("/Audio/{itemId}/universal")]
[HttpGet("/Audio/{itemId}/{universal=universal}.{container?}", Name = "GetUniversalAudioStream_2")]
[HttpHead("/Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
[HttpHead("/Audio/{itemId}/{universal=universal}.{container?}", Name = "HeadUniversalAudioStream_2")]
[HttpGet("Audio/{itemId}/universal")]
[HttpGet("Audio/{itemId}/{universal=universal}.{container?}", Name = "GetUniversalAudioStream_2")]
[HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
[HttpHead("Audio/{itemId}/{universal=universal}.{container?}", Name = "HeadUniversalAudioStream_2")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status302Found)]

@ -450,7 +450,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="request">The create user by name request body.</param>
/// <response code="200">User created.</response>
/// <returns>An <see cref="UserDto"/> of the new user.</returns>
[HttpPost("/Users/New")]
[HttpPost("New")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<UserDto>> CreateUserByName([FromBody] CreateUserByName request)

@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// User library controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class UserLibraryController : BaseJellyfinApiController
{
@ -67,7 +68,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <response code="200">Item returned.</response>
/// <returns>An <see cref="OkResult"/> containing the d item.</returns>
[HttpGet("/Users/{userId}/Items/{itemId}")]
[HttpGet("Users/{userId}/Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
@ -90,7 +91,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">User id.</param>
/// <response code="200">Root folder returned.</response>
/// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns>
[HttpGet("/Users/{userId}/Items/Root")]
[HttpGet("Users/{userId}/Items/Root")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetRootFolder([FromRoute] Guid userId)
{
@ -107,7 +108,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <response code="200">Intros returned.</response>
/// <returns>An <see cref="OkResult"/> containing the intros to play.</returns>
[HttpGet("/Users/{userId}/Items/{itemId}/Intros")]
[HttpGet("Users/{userId}/Items/{itemId}/Intros")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
@ -135,7 +136,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <response code="200">Item marked as favorite.</response>
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpPost("/Users/{userId}/FavoriteItems/{itemId}")]
[HttpPost("Users/{userId}/FavoriteItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
@ -149,7 +150,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <response code="200">Item unmarked as favorite.</response>
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("/Users/{userId}/FavoriteItems/{itemId}")]
[HttpDelete("Users/{userId}/FavoriteItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
@ -163,7 +164,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <response code="200">Personal rating removed.</response>
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("/Users/{userId}/Items/{itemId}/Rating")]
[HttpDelete("Users/{userId}/Items/{itemId}/Rating")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
@ -178,7 +179,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="likes">Whether this <see cref="UpdateUserItemRating" /> is likes.</param>
/// <response code="200">Item rating updated.</response>
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpPost("/Users/{userId}/Items/{itemId}/Rating")]
[HttpPost("Users/{userId}/Items/{itemId}/Rating")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool? likes)
{
@ -192,7 +193,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <response code="200">An <see cref="OkResult"/> containing the item's local trailers.</response>
/// <returns>The items local trailers.</returns>
[HttpGet("/Users/{userId}/Items/{itemId}/LocalTrailers")]
[HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
@ -227,7 +228,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <response code="200">Special features returned.</response>
/// <returns>An <see cref="OkResult"/> containing the special features.</returns>
[HttpGet("/Users/{userId}/Items/{itemId}/SpecialFeatures")]
[HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
@ -260,7 +261,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="groupItems">Whether or not to group items into a parent container.</param>
/// <response code="200">Latest media returned.</response>
/// <returns>An <see cref="OkResult"/> containing the latest media.</returns>
[HttpGet("/Users/{userId}/Items/Latest")]
[HttpGet("Users/{userId}/Items/Latest")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
[FromRoute] Guid userId,

@ -21,6 +21,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// User views controller.
/// </summary>
[Route("")]
public class UserViewsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@ -60,7 +61,7 @@ namespace Jellyfin.Api.Controllers
/// <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")]
[HttpGet("Users/{userId}/Views")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
[FromRoute] Guid userId,
@ -122,7 +123,7 @@ namespace Jellyfin.Api.Controllers
/// An <see cref="OkResult"/> containing the user view grouping options
/// or a <see cref="NotFoundResult"/> if user not found.
/// </returns>
[HttpGet("/Users/{userId}/GroupingOptions")]
[HttpGet("Users/{userId}/GroupingOptions")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute] Guid userId)

@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// The video hls controller.
/// </summary>
[Route("")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class VideoHlsController : BaseJellyfinApiController
{
@ -158,7 +159,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
/// <response code="200">Hls live stream retrieved.</response>
/// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
[HttpGet("/Videos/{itemId}/live.m3u8")]
[HttpGet("Videos/{itemId}/live.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetLiveHlsStream(
[FromRoute] Guid itemId,
@ -271,7 +272,7 @@ namespace Jellyfin.Api.Controllers
};
var cancellationTokenSource = new CancellationTokenSource();
var state = await StreamingHelpers.GetStreamingState(
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
Request,
_authContext,

Loading…
Cancel
Save