using Microsoft.AspNetCore.Mvc; using System.Net.Http; using System.Threading.Tasks; using System.Xml.Linq; using System.Text.Json; using Microsoft.Extensions.Options; using octo_fiesta.Models; namespace octo_fiesta.Controllers; [ApiController] [Route("")] public class SubsonicController : ControllerBase { private readonly HttpClient _httpClient; private readonly SubsonicSettings _subsonicSettings; public SubsonicController(IHttpClientFactory httpClientFactory, IOptions subsonicSettings) { _httpClient = httpClientFactory.CreateClient(); _subsonicSettings = subsonicSettings.Value; if (string.IsNullOrWhiteSpace(_subsonicSettings.Url)) { throw new Exception("Error: Environment variable SUBSONIC_URL is not set."); } } // Extract all parameters (query + body) private async Task> ExtractAllParameters() { var parameters = new Dictionary(); // Get query parameters foreach (var query in Request.Query) { parameters[query.Key] = query.Value.ToString(); } // Get body parameters (JSON) if (Request.ContentLength > 0 && Request.ContentType?.Contains("application/json") == true) { using var reader = new StreamReader(Request.Body); var body = await reader.ReadToEndAsync(); if (!string.IsNullOrEmpty(body)) { try { var bodyParams = JsonSerializer.Deserialize>(body); foreach (var param in bodyParams) { parameters[param.Key] = param.Value?.ToString() ?? ""; } } catch (JsonException) { } } } return parameters; } private async Task<(object Body, string? ContentType)> RelayToSubsonic(string endpoint, Dictionary parameters) { var query = string.Join("&", parameters.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}")); var url = $"{_subsonicSettings.Url}/{endpoint}?{query}"; HttpResponseMessage response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsByteArrayAsync(); var contentType = response.Content.Headers.ContentType?.ToString(); return (body, contentType); } [HttpGet, HttpPost] [Route("ping")] public async Task Ping() { var parameters = await ExtractAllParameters(); var xml = await RelayToSubsonic("ping", parameters); var doc = XDocument.Parse((string)xml.Body); var status = doc.Root?.Attribute("status")?.Value; return Ok(new { status }); } // Generic endpoint to handle all subsonic API calls [HttpGet, HttpPost] [Route("{**endpoint}")] public async Task GenericEndpoint(string endpoint) { var parameters = await ExtractAllParameters(); try { var result = await RelayToSubsonic(endpoint, parameters); var contentType = result.ContentType ?? $"application/{parameters.GetValueOrDefault("f", "xml")}"; return File((byte[])result.Body, contentType); } catch (HttpRequestException ex) { return BadRequest(new { error = $"Error while calling Subsonic: {ex.Message}" }); } } }