diff --git a/octo-fiesta/Controllers/SubSonicController.cs b/octo-fiesta/Controllers/SubSonicController.cs new file mode 100644 index 0000000..13bf693 --- /dev/null +++ b/octo-fiesta/Controllers/SubSonicController.cs @@ -0,0 +1,226 @@ +using Microsoft.AspNetCore.Mvc; +using System.Net.Http; +using System.Threading.Tasks; +using System.Xml.Linq; +using System.Text.Json; + +namespace octo_fiesta.Controllers; + +[ApiController] +[Route("")] +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) + { + _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; + + 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."); + } + + // Extract all parameters (query + body) + private async Task> ExtractAllParameters() + { + var parameters = new Dictionary(); + + // Default parameters + parameters["u"] = _defaultUser; + parameters["p"] = _defaultPassword; + + // 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 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); + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsStringAsync(); + } + + [HttpGet, HttpPost] + [Route("ping")] + public async Task Ping() + { + var parameters = await ExtractAllParameters(); + var xml = await RelayToSubsonic("ping", parameters); + var doc = XDocument.Parse(xml); + 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}")] + public async Task GenericEndpoint(string endpoint) + { + var parameters = await ExtractAllParameters(); + try + { + var xml = await RelayToSubsonic(endpoint, parameters); + return Content(xml, "application/xml"); + } + catch (HttpRequestException ex) + { + return BadRequest(new { error = $"Error while calling Subsonic: {ex.Message}" }); + } + } +} \ No newline at end of file diff --git a/octo-fiesta/Controllers/WeatherForecastController.cs b/octo-fiesta/Controllers/WeatherForecastController.cs deleted file mode 100644 index fdbabd4..0000000 --- a/octo-fiesta/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace octo_fiesta.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} \ No newline at end of file diff --git a/octo-fiesta/Program.cs b/octo-fiesta/Program.cs index db45ed1..8d90c86 100644 --- a/octo-fiesta/Program.cs +++ b/octo-fiesta/Program.cs @@ -3,6 +3,7 @@ var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); +builder.Services.AddHttpClient(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); diff --git a/octo-fiesta/WeatherForecast.cs b/octo-fiesta/WeatherForecast.cs deleted file mode 100644 index 4f4442f..0000000 --- a/octo-fiesta/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace octo_fiesta; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} \ No newline at end of file