From 17769e2c285396a94354a40e9e2c827ba5a562c7 Mon Sep 17 00:00:00 2001 From: V1ck3s Date: Sun, 21 Sep 2025 16:22:18 +0200 Subject: [PATCH] feat: add subsonic all in one endpoint, subsonic settings configuration and CORS policy --- octo-fiesta/Controllers/SubSonicController.cs | 162 +++--------------- octo-fiesta/Models/SubsonicSettings.cs | 6 + octo-fiesta/Program.cs | 18 ++ 3 files changed, 44 insertions(+), 142 deletions(-) create mode 100644 octo-fiesta/Models/SubsonicSettings.cs diff --git a/octo-fiesta/Controllers/SubSonicController.cs b/octo-fiesta/Controllers/SubSonicController.cs index 13bf693..1ded556 100644 --- a/octo-fiesta/Controllers/SubSonicController.cs +++ b/octo-fiesta/Controllers/SubSonicController.cs @@ -3,6 +3,8 @@ 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; @@ -11,31 +13,16 @@ namespace octo_fiesta.Controllers; public class SubsonicController : ControllerBase { private readonly HttpClient _httpClient; - private readonly string _subsonicUrl; - private readonly string _defaultUser; - private readonly string _defaultPassword; - private readonly string _apiVersion; - private readonly string _client; - - public SubsonicController(IHttpClientFactory httpClientFactory) + private readonly SubsonicSettings _subsonicSettings; + public SubsonicController(IHttpClientFactory httpClientFactory, IOptions subsonicSettings) { _httpClient = httpClientFactory.CreateClient(); - _subsonicUrl = Environment.GetEnvironmentVariable("SUBSONIC_URL") ?? string.Empty; - _defaultUser = Environment.GetEnvironmentVariable("SUBSONIC_USER") ?? string.Empty; - _defaultPassword = Environment.GetEnvironmentVariable("SUBSONIC_PASSWORD") ?? string.Empty; - _apiVersion = Environment.GetEnvironmentVariable("SUBSONIC_API_VERSION") ?? string.Empty; - _client = Environment.GetEnvironmentVariable("SUBSONIC_CLIENT") ?? string.Empty; + _subsonicSettings = subsonicSettings.Value; - if (string.IsNullOrWhiteSpace(_subsonicUrl)) - Console.WriteLine("Warning: Environment variable SUBSONIC_URL is not set."); - if (string.IsNullOrWhiteSpace(_defaultUser)) - Console.WriteLine("Warning: Environment variable SUBSONIC_USER is not set."); - if (string.IsNullOrWhiteSpace(_defaultPassword)) - Console.WriteLine("Warning: Environment variable SUBSONIC_PASSWORD is not set."); - if (string.IsNullOrWhiteSpace(_apiVersion)) - Console.WriteLine("Warning: Environment variable SUBSONIC_API_VERSION is not set."); - if (string.IsNullOrWhiteSpace(_client)) - Console.WriteLine("Warning: Environment variable SUBSONIC_CLIENT is not set."); + if (string.IsNullOrWhiteSpace(_subsonicSettings.Url)) + { + throw new Exception("Error: Environment variable SUBSONIC_URL is not set."); + } } // Extract all parameters (query + body) @@ -43,10 +30,6 @@ public class SubsonicController : ControllerBase { var parameters = new Dictionary(); - // Default parameters - parameters["u"] = _defaultUser; - parameters["p"] = _defaultPassword; - // Get query parameters foreach (var query in Request.Query) { @@ -79,13 +62,15 @@ public class SubsonicController : ControllerBase return parameters; } - private async Task RelayToSubsonic(string endpoint, Dictionary parameters) + private async Task<(string 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 = $"{_subsonicUrl}/{endpoint}.view?{query}&v={_apiVersion}&c={_client}&f=xml"; - var response = await _httpClient.GetAsync(url); + var url = $"{_subsonicSettings.Url}/{endpoint}?{query}"; + HttpResponseMessage response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); - return await response.Content.ReadAsStringAsync(); + var body = await response.Content.ReadAsStringAsync(); + var contentType = response.Content.Headers.ContentType?.ToString(); + return (body, contentType); } [HttpGet, HttpPost] @@ -94,129 +79,22 @@ public class SubsonicController : ControllerBase { var parameters = await ExtractAllParameters(); var xml = await RelayToSubsonic("ping", parameters); - var doc = XDocument.Parse(xml); + var doc = XDocument.Parse(xml.Body); var status = doc.Root?.Attribute("status")?.Value; return Ok(new { status }); } - [HttpGet, HttpPost] - [Route("getLicense")] - public async Task GetLicense() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getLicense", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getMusicFolders")] - public async Task GetMusicFolders() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getMusicFolders", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getIndexes")] - public async Task GetIndexes() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getIndexes", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getMusicDirectory")] - public async Task GetMusicDirectory() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getMusicDirectory", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getGenres")] - public async Task GetGenres() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getGenres", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getArtists")] - public async Task GetArtists() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getArtists", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getArtist")] - public async Task GetArtist() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getArtist", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getAlbum")] - public async Task GetAlbum() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getAlbum", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getSong")] - public async Task GetSong() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getSong", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("search3")] - public async Task Search3() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("search3", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getPlaylists")] - public async Task GetPlaylists() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getPlaylists", parameters); - return Content(xml, "application/xml"); - } - - [HttpGet, HttpPost] - [Route("getPlaylist")] - public async Task GetPlaylist() - { - var parameters = await ExtractAllParameters(); - var xml = await RelayToSubsonic("getPlaylist", parameters); - return Content(xml, "application/xml"); - } - // Generic endpoint to handle all subsonic API calls [HttpGet, HttpPost] - [Route("{endpoint}")] + [Route("{**endpoint}")] public async Task GenericEndpoint(string endpoint) { var parameters = await ExtractAllParameters(); try { - var xml = await RelayToSubsonic(endpoint, parameters); - return Content(xml, "application/xml"); + var result = await RelayToSubsonic(endpoint, parameters); + var contentType = result.ContentType ?? $"application/{parameters.GetValueOrDefault("f", "xml")}"; + return Content(result.Body, contentType); } catch (HttpRequestException ex) { diff --git a/octo-fiesta/Models/SubsonicSettings.cs b/octo-fiesta/Models/SubsonicSettings.cs new file mode 100644 index 0000000..1309f4d --- /dev/null +++ b/octo-fiesta/Models/SubsonicSettings.cs @@ -0,0 +1,6 @@ +namespace octo_fiesta.Models; + +public class SubsonicSettings +{ + public string? Url { get; set; } +} \ No newline at end of file diff --git a/octo-fiesta/Program.cs b/octo-fiesta/Program.cs index 8d90c86..8932722 100644 --- a/octo-fiesta/Program.cs +++ b/octo-fiesta/Program.cs @@ -1,3 +1,5 @@ +using octo_fiesta.Models; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -7,6 +9,20 @@ builder.Services.AddHttpClient(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.Configure( + builder.Configuration.GetSection("Subsonic")); + +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + { + policy.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .WithExposedHeaders("X-Content-Duration", "X-Total-Count", "X-Nd-Authorization"); + }); +}); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -20,6 +36,8 @@ app.UseHttpsRedirection(); app.UseAuthorization(); +app.UseCors(); + app.MapControllers(); app.Run(); \ No newline at end of file