using System.Net.Http.Headers; using API.MangaDownloadClients; using API.Schema; using API.Schema.Contexts; using API.Schema.Jobs; using Asp.Versioning; using FlareSolverrSharp; using log4net; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using static Microsoft.AspNetCore.Http.StatusCodes; namespace API.Controllers; [ApiVersion(2)] [ApiController] [Route("v{v:apiVersion}/[controller]")] public class SettingsController(PgsqlContext context, ILog Log) : Controller { /// /// Get all Settings /// /// [HttpGet] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetSettings() { return Ok(JObject.Parse(TrangaSettings.Serialize())); } /// /// Get the current UserAgent used by Tranga /// /// [HttpGet("UserAgent")] [ProducesResponseType(Status200OK, "text/plain")] public IActionResult GetUserAgent() { return Ok(TrangaSettings.userAgent); } /// /// Set a new UserAgent /// /// [HttpPatch("UserAgent")] [ProducesResponseType(Status200OK)] public IActionResult SetUserAgent([FromBody]string userAgent) { TrangaSettings.UpdateUserAgent(userAgent); return Ok(); } /// /// Reset the UserAgent to default /// /// [HttpDelete("UserAgent")] [ProducesResponseType(Status200OK)] public IActionResult ResetUserAgent() { TrangaSettings.UpdateUserAgent(TrangaSettings.DefaultUserAgent); return Ok(); } /// /// Get all Request-Limits /// /// [HttpGet("RequestLimits")] [ProducesResponseType>(Status200OK, "application/json")] public IActionResult GetRequestLimits() { return Ok(TrangaSettings.requestLimits); } /// /// Update all Request-Limits to new values /// ///

NOT IMPLEMENTED

[HttpPatch("RequestLimits")] [ProducesResponseType(Status501NotImplemented)] public IActionResult SetRequestLimits() { return StatusCode(501); } /// /// Updates a Request-Limit value /// /// Type of Request /// New limit in Requests/Minute /// /// Limit needs to be greater than 0 [HttpPatch("RequestLimits/{RequestType}")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status400BadRequest)] public IActionResult SetRequestLimit(RequestType RequestType, [FromBody]int requestLimit) { if (requestLimit <= 0) return BadRequest(); TrangaSettings.UpdateRequestLimit(RequestType, requestLimit); return Ok(); } /// /// Reset Request-Limit /// /// [HttpDelete("RequestLimits/{RequestType}")] [ProducesResponseType(Status200OK)] public IActionResult ResetRequestLimits(RequestType RequestType) { TrangaSettings.UpdateRequestLimit(RequestType, TrangaSettings.DefaultRequestLimits[RequestType]); return Ok(); } /// /// Reset Request-Limit /// /// [HttpDelete("RequestLimits")] [ProducesResponseType(Status200OK)] public IActionResult ResetRequestLimits() { TrangaSettings.ResetRequestLimits(); return Ok(); } /// /// Returns Level of Image-Compression for Images /// /// JPEG compression-level as Integer [HttpGet("ImageCompression")] [ProducesResponseType(Status200OK, "text/plain")] public IActionResult GetImageCompression() { return Ok(TrangaSettings.compression); } /// /// Set the Image-Compression-Level for Images /// /// 100 to disable, 0-99 for JPEG compression-Level /// /// Level outside permitted range [HttpPatch("ImageCompression")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status400BadRequest)] public IActionResult SetImageCompression([FromBody]int level) { if (level < 1 || level > 100) return BadRequest(); TrangaSettings.UpdateCompressImages(level); return Ok(); } /// /// Get state of Black/White-Image setting /// /// True if enabled [HttpGet("BWImages")] [ProducesResponseType(Status200OK, "text/plain")] public IActionResult GetBwImagesToggle() { return Ok(TrangaSettings.bwImages); } /// /// Enable/Disable conversion of Images to Black and White /// /// true to enable /// [HttpPatch("BWImages")] [ProducesResponseType(Status200OK)] public IActionResult SetBwImagesToggle([FromBody]bool enabled) { TrangaSettings.UpdateBwImages(enabled); return Ok(); } /// /// Get state of April Fools Mode /// /// April Fools Mode disables all downloads on April 1st /// True if enabled [HttpGet("AprilFoolsMode")] [ProducesResponseType(Status200OK, "text/plain")] public IActionResult GetAprilFoolsMode() { return Ok(TrangaSettings.aprilFoolsMode); } /// /// Enable/Disable April Fools Mode /// /// April Fools Mode disables all downloads on April 1st /// true to enable /// [HttpPatch("AprilFoolsMode")] [ProducesResponseType(Status200OK)] public IActionResult SetAprilFoolsMode([FromBody]bool enabled) { TrangaSettings.UpdateAprilFoolsMode(enabled); return Ok(); } /// /// Gets the Chapter Naming Scheme /// /// /// Placeholders: /// %M Manga Name /// %V Volume /// %C Chapter /// %T Title /// %A Author (first in list) /// %I Chapter Internal ID /// %i Manga Internal ID /// %Y Year (Manga) /// /// ?_(...) replace _ with a value from above: /// Everything inside the braces will only be added if the value of %_ is not null /// /// [HttpGet("ChapterNamingScheme")] [ProducesResponseType(Status200OK, "text/plain")] public IActionResult GetCustomNamingScheme() { return Ok(TrangaSettings.chapterNamingScheme); } /// /// Sets the Chapter Naming Scheme /// /// /// Placeholders: /// %M Manga Name /// %V Volume /// %C Chapter /// %T Title /// %A Author (first in list) /// %I Chapter Internal ID /// %i Manga Internal ID /// %Y Year (Manga) /// /// ?_(...) replace _ with a value from above: /// Everything inside the braces will only be added if the value of %_ is not null /// /// /// Error during Database Operation [HttpPatch("ChapterNamingScheme")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult SetCustomNamingScheme([FromBody]string namingScheme) { try { Dictionary oldPaths = context.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath); TrangaSettings.UpdateChapterNamingScheme(namingScheme); MoveFileOrFolderJob[] newJobs = oldPaths .Select(kv => new MoveFileOrFolderJob(kv.Value, kv.Key.FullArchiveFilePath)).ToArray(); context.Jobs.AddRange(newJobs); return Ok(); } catch (Exception e) { Log.Error(e); return StatusCode(500, e); } } /// /// Creates a UpdateCoverJob for all Manga /// /// Array of JobIds /// Error during Database Operation [HttpPost("CleanupCovers")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CleanupCovers() { try { Tranga.RemoveStaleFiles(context); List newJobs = context.Mangas.ToList().Select(m => new UpdateCoverJob(m, 0)).ToList(); context.Jobs.AddRange(newJobs); return Ok(newJobs.Select(j => j.JobId)); } catch (Exception e) { Log.Error(e); return StatusCode(500, e); } } /// /// Sets the FlareSolverr-URL /// /// URL of FlareSolverr-Instance /// [HttpPost("FlareSolverr/Url")] [ProducesResponseType(Status200OK)] public IActionResult SetFlareSolverrUrl([FromBody]string flareSolverrUrl) { TrangaSettings.UpdateFlareSolverrUrl(flareSolverrUrl); return Ok(); } /// /// Resets the FlareSolverr-URL (HttpClient does not use FlareSolverr anymore) /// /// [HttpDelete("FlareSolverr/Url")] [ProducesResponseType(Status200OK)] public IActionResult ClearFlareSolverrUrl() { TrangaSettings.UpdateFlareSolverrUrl(string.Empty); return Ok(); } /// /// /// /// FlareSolverr is working! /// FlareSolverr URL is malformed /// FlareSolverr is not working /// FlareSolverr could not be reached [HttpPost("FlareSolverr/Test")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status500InternalServerError)] [ProducesResponseType(Status503ServiceUnavailable)] public IActionResult TestFlareSolverrReachable() { const string knownProtectedUrl = "https://prowlarr.servarr.com/v1/ping"; HttpClient client = new(); if (!Uri.TryCreate(new(TrangaSettings.flareSolverrUrl), "v1", out Uri? uri)) return BadRequest(); HttpRequestMessage request = new(HttpMethod.Post, uri); JObject data = new() { ["cmd"] = "request.get", ["url"] = knownProtectedUrl }; request.Content = new StringContent(JsonConvert.SerializeObject(data)); request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); HttpResponseMessage response = client.Send(request); if (!response.IsSuccessStatusCode) return StatusCode(Status503ServiceUnavailable); client = new(new ClearanceHandler(TrangaSettings.flareSolverrUrl)); try { client.GetStringAsync(knownProtectedUrl).Wait(); return Ok(); } catch (Exception e) { return StatusCode(Status500InternalServerError); } } }