using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.RedditComments.Models;
using Jellyfin.Plugin.RedditComments.Services;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.RedditComments.Api;
///
/// API endpoints for the Reddit Comments plugin.
///
[ApiController]
[Route("RedditComments")]
public class RedditCommentsController : ControllerBase
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly RedditCommentsService _commentsService;
private readonly RedditClient _redditClient;
///
/// Initializes a new instance of the class.
///
/// Logger.
/// Library manager.
/// Comments service.
/// Reddit client.
public RedditCommentsController(
ILogger logger,
ILibraryManager libraryManager,
RedditCommentsService commentsService,
RedditClient redditClient)
{
_logger = logger;
_libraryManager = libraryManager;
_commentsService = commentsService;
_redditClient = redditClient;
}
///
/// Gets the Reddit discussion comments for a Jellyfin item. Served from the local cache
/// unless this is the first lookup or the cache expired.
///
/// The Jellyfin item id.
/// Cancellation token.
/// The comments response.
[HttpGet("Item/{itemId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> GetComments([FromRoute] Guid itemId, CancellationToken cancellationToken)
{
var item = _libraryManager.GetItemById(itemId);
if (item is null)
{
return NotFound();
}
try
{
return await _commentsService.GetForItemAsync(item, forceRefresh: false, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get Reddit comments for item {ItemId}", itemId);
return new CommentsResponse
{
Found = false,
ItemId = itemId.ToString("N", CultureInfo.InvariantCulture),
Message = "Failed to fetch Reddit comments: " + ex.Message
};
}
}
///
/// Forces a fresh lookup for an item, bypassing and replacing the cache.
///
/// The Jellyfin item id.
/// Cancellation token.
/// The comments response.
[HttpPost("Item/{itemId}/Refresh")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> RefreshComments([FromRoute] Guid itemId, CancellationToken cancellationToken)
{
var item = _libraryManager.GetItemById(itemId);
if (item is null)
{
return NotFound();
}
try
{
return await _commentsService.GetForItemAsync(item, forceRefresh: true, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to refresh Reddit comments for item {ItemId}", itemId);
return new CommentsResponse
{
Found = false,
ItemId = itemId.ToString("N", CultureInfo.InvariantCulture),
Message = "Failed to fetch Reddit comments: " + ex.Message
};
}
}
///
/// Tests the configured Reddit API credentials. Admin only.
///
/// Cancellation token.
/// Whether the credentials work.
[HttpGet("Test")]
[Authorize(Policy = "RequiresElevation")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> TestCredentials(CancellationToken cancellationToken)
{
if (!RedditClient.IsConfigured)
{
return new { ok = false, message = "Client id and secret are not configured." };
}
try
{
await _redditClient.TestCredentialsAsync(cancellationToken).ConfigureAwait(false);
return new { ok = true, message = "Successfully authenticated with Reddit." };
}
catch (Exception ex)
{
return new { ok = false, message = ex.Message };
}
}
///
/// Serves the web client script that adds the comments button/sidebar to the player.
///
/// The JavaScript file.
[HttpGet("Static/reddit-comments.js")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetScript()
{
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.RedditComments.Web.reddit-comments.js");
if (stream is null)
{
return NotFound();
}
// Cache for an hour so restarts/plugin updates pick up changes reasonably fast.
Response.Headers.CacheControl = "public, max-age=3600";
return File(stream, "text/javascript");
}
}