Files
Jellyfin-Comments/Jellyfin.Plugin.RedditComments/Api/RedditCommentsController.cs
Gabrieal Jimmy f321976d70 init
2026-07-16 13:14:58 -05:00

171 lines
6.0 KiB
C#

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;
/// <summary>
/// API endpoints for the Reddit Comments plugin.
/// </summary>
[ApiController]
[Route("RedditComments")]
public class RedditCommentsController : ControllerBase
{
private readonly ILogger<RedditCommentsController> _logger;
private readonly ILibraryManager _libraryManager;
private readonly RedditCommentsService _commentsService;
private readonly RedditClient _redditClient;
/// <summary>
/// Initializes a new instance of the <see cref="RedditCommentsController"/> class.
/// </summary>
/// <param name="logger">Logger.</param>
/// <param name="libraryManager">Library manager.</param>
/// <param name="commentsService">Comments service.</param>
/// <param name="redditClient">Reddit client.</param>
public RedditCommentsController(
ILogger<RedditCommentsController> logger,
ILibraryManager libraryManager,
RedditCommentsService commentsService,
RedditClient redditClient)
{
_logger = logger;
_libraryManager = libraryManager;
_commentsService = commentsService;
_redditClient = redditClient;
}
/// <summary>
/// Gets the Reddit discussion comments for a Jellyfin item. Served from the local cache
/// unless this is the first lookup or the cache expired.
/// </summary>
/// <param name="itemId">The Jellyfin item id.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The comments response.</returns>
[HttpGet("Item/{itemId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CommentsResponse>> 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
};
}
}
/// <summary>
/// Forces a fresh lookup for an item, bypassing and replacing the cache.
/// </summary>
/// <param name="itemId">The Jellyfin item id.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The comments response.</returns>
[HttpPost("Item/{itemId}/Refresh")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CommentsResponse>> 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
};
}
}
/// <summary>
/// Tests the configured Reddit API credentials. Admin only.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Whether the credentials work.</returns>
[HttpGet("Test")]
[Authorize(Policy = "RequiresElevation")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<object>> 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 };
}
}
/// <summary>
/// Serves the web client script that adds the comments button/sidebar to the player.
/// </summary>
/// <returns>The JavaScript file.</returns>
[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");
}
}