171 lines
6.0 KiB
C#
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");
|
|
}
|
|
}
|