using System.Net.Mime; using System.Text; using System.Web; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.ComponentModel.DataAnnotations; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Library; using YamlDotNet.Serialization; using Jellyfin.Plugin.SmartPlaylist.Lisp; namespace Jellyfin.Plugin.SmartPlaylist.Api { [Serializable] public class ProgramInputDto { public string InputLinks { get; set; } public string Program { get; set; } } [Serializable] public class ProgramOutputDto { public string Output { get; set; } public string FinalExpression { get; set; } public string Traceback { get; set; } } [ApiController] [Authorize(Policy = Policies.RequiresElevation)] [Route("LispPlayground")] [Produces(MediaTypeNames.Application.Json)] public class LispPlaygroundController : ControllerBase { private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; public LispPlaygroundController( ILogger logger, ILibraryManager libraryManager ) { _logger = logger; _libraryManager = libraryManager; } private Executor SetupExecutor(StringBuilder sb, IList inputs) { var env = new DefaultEnvironment(); var executor = new Executor(env); env["*items*"] = Lisp.Cons.FromList(inputs.Select(x => _libraryManager.GetItemById(x)).Select(x => Lisp.Object.FromBase(x)).ToList()); executor.builtins["logd"] = (x) => { _logger.LogDebug(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray()); return Lisp.Boolean.TRUE; }; executor.builtins["logi"] = (x) => { _logger.LogInformation(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray()); return Lisp.Boolean.TRUE; }; executor.builtins["logw"] = (x) => { _logger.LogWarning(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray()); return Lisp.Boolean.TRUE; }; executor.builtins["loge"] = (x) => { _logger.LogError(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray()); return Lisp.Boolean.TRUE; }; executor.builtins["print"] = (x) => { sb.Append(string.Join(" ", x.Select((i) => { if (i is Lisp.String i_s) { return i_s.Value(); } return i.ToString(); }))); return Lisp.Boolean.TRUE; }; executor.builtins["print"] = (x) => { sb.Append(string.Join(" ", x.Select((i) => { if (i is Lisp.String i_s) { return i_s.Value(); } return i.ToString(); }))); sb.Append("\n"); return Lisp.Boolean.TRUE; }; if (Plugin.Instance is not null) { executor.eval(Plugin.Instance.Configuration.InitialProgram); } else { throw new ApplicationException("Plugin Instance is not yet initialized"); } return executor; } private List extractItemIds(string s) { List r = new List(); foreach (string line in s.Split("\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)) { var uri = new Uri(line); uri = new Uri("http://some-domain.tld" + uri.Fragment.Substring(1)); var id = HttpUtility.ParseQueryString(uri.Query).Get("id"); if (id == null) { continue; } r.Add(id); } return r; } [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> SetPlaylist() { try { string input; using (StreamReader reader = new StreamReader(Request.Body)) { input = await reader.ReadToEndAsync(); } var dto = new DeserializerBuilder().Build().Deserialize(input); StringBuilder output = new StringBuilder(); var e = SetupExecutor(output, extractItemIds(dto.InputLinks)); var r = e.eval(dto.Program).ToString(); return Ok(new ProgramOutputDto() { FinalExpression = r, Output = output.ToString(), }); } catch (Exception ex) { return Ok(new ProgramOutputDto() { Traceback = ex.ToString(), }); } } } }