mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
feat: add subsonic all in one endpoint, subsonic settings configuration and CORS policy
This commit is contained in:
@@ -3,6 +3,8 @@ using System.Net.Http;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using octo_fiesta.Models;
|
||||||
|
|
||||||
namespace octo_fiesta.Controllers;
|
namespace octo_fiesta.Controllers;
|
||||||
|
|
||||||
@@ -11,31 +13,16 @@ namespace octo_fiesta.Controllers;
|
|||||||
public class SubsonicController : ControllerBase
|
public class SubsonicController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly string _subsonicUrl;
|
private readonly SubsonicSettings _subsonicSettings;
|
||||||
private readonly string _defaultUser;
|
public SubsonicController(IHttpClientFactory httpClientFactory, IOptions<SubsonicSettings> subsonicSettings)
|
||||||
private readonly string _defaultPassword;
|
|
||||||
private readonly string _apiVersion;
|
|
||||||
private readonly string _client;
|
|
||||||
|
|
||||||
public SubsonicController(IHttpClientFactory httpClientFactory)
|
|
||||||
{
|
{
|
||||||
_httpClient = httpClientFactory.CreateClient();
|
_httpClient = httpClientFactory.CreateClient();
|
||||||
_subsonicUrl = Environment.GetEnvironmentVariable("SUBSONIC_URL") ?? string.Empty;
|
_subsonicSettings = subsonicSettings.Value;
|
||||||
_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))
|
if (string.IsNullOrWhiteSpace(_subsonicSettings.Url))
|
||||||
Console.WriteLine("Warning: Environment variable SUBSONIC_URL is not set.");
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_defaultUser))
|
throw new Exception("Error: Environment variable SUBSONIC_URL is not set.");
|
||||||
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)
|
// Extract all parameters (query + body)
|
||||||
@@ -43,10 +30,6 @@ public class SubsonicController : ControllerBase
|
|||||||
{
|
{
|
||||||
var parameters = new Dictionary<string, string>();
|
var parameters = new Dictionary<string, string>();
|
||||||
|
|
||||||
// Default parameters
|
|
||||||
parameters["u"] = _defaultUser;
|
|
||||||
parameters["p"] = _defaultPassword;
|
|
||||||
|
|
||||||
// Get query parameters
|
// Get query parameters
|
||||||
foreach (var query in Request.Query)
|
foreach (var query in Request.Query)
|
||||||
{
|
{
|
||||||
@@ -79,13 +62,15 @@ public class SubsonicController : ControllerBase
|
|||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> RelayToSubsonic(string endpoint, Dictionary<string, string> parameters)
|
private async Task<(string Body, string? ContentType)> RelayToSubsonic(string endpoint, Dictionary<string, string> parameters)
|
||||||
{
|
{
|
||||||
var query = string.Join("&", parameters.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}"));
|
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 url = $"{_subsonicSettings.Url}/{endpoint}?{query}";
|
||||||
var response = await _httpClient.GetAsync(url);
|
HttpResponseMessage response = await _httpClient.GetAsync(url);
|
||||||
response.EnsureSuccessStatusCode();
|
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]
|
[HttpGet, HttpPost]
|
||||||
@@ -94,129 +79,22 @@ public class SubsonicController : ControllerBase
|
|||||||
{
|
{
|
||||||
var parameters = await ExtractAllParameters();
|
var parameters = await ExtractAllParameters();
|
||||||
var xml = await RelayToSubsonic("ping", parameters);
|
var xml = await RelayToSubsonic("ping", parameters);
|
||||||
var doc = XDocument.Parse(xml);
|
var doc = XDocument.Parse(xml.Body);
|
||||||
var status = doc.Root?.Attribute("status")?.Value;
|
var status = doc.Root?.Attribute("status")?.Value;
|
||||||
return Ok(new { status });
|
return Ok(new { status });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getLicense")]
|
|
||||||
public async Task<IActionResult> GetLicense()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getLicense", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getMusicFolders")]
|
|
||||||
public async Task<IActionResult> GetMusicFolders()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getMusicFolders", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getIndexes")]
|
|
||||||
public async Task<IActionResult> GetIndexes()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getIndexes", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getMusicDirectory")]
|
|
||||||
public async Task<IActionResult> GetMusicDirectory()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getMusicDirectory", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getGenres")]
|
|
||||||
public async Task<IActionResult> GetGenres()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getGenres", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getArtists")]
|
|
||||||
public async Task<IActionResult> GetArtists()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getArtists", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getArtist")]
|
|
||||||
public async Task<IActionResult> GetArtist()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getArtist", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getAlbum")]
|
|
||||||
public async Task<IActionResult> GetAlbum()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getAlbum", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getSong")]
|
|
||||||
public async Task<IActionResult> GetSong()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getSong", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("search3")]
|
|
||||||
public async Task<IActionResult> Search3()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("search3", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getPlaylists")]
|
|
||||||
public async Task<IActionResult> GetPlaylists()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getPlaylists", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet, HttpPost]
|
|
||||||
[Route("getPlaylist")]
|
|
||||||
public async Task<IActionResult> GetPlaylist()
|
|
||||||
{
|
|
||||||
var parameters = await ExtractAllParameters();
|
|
||||||
var xml = await RelayToSubsonic("getPlaylist", parameters);
|
|
||||||
return Content(xml, "application/xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic endpoint to handle all subsonic API calls
|
// Generic endpoint to handle all subsonic API calls
|
||||||
[HttpGet, HttpPost]
|
[HttpGet, HttpPost]
|
||||||
[Route("{endpoint}")]
|
[Route("{**endpoint}")]
|
||||||
public async Task<IActionResult> GenericEndpoint(string endpoint)
|
public async Task<IActionResult> GenericEndpoint(string endpoint)
|
||||||
{
|
{
|
||||||
var parameters = await ExtractAllParameters();
|
var parameters = await ExtractAllParameters();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var xml = await RelayToSubsonic(endpoint, parameters);
|
var result = await RelayToSubsonic(endpoint, parameters);
|
||||||
return Content(xml, "application/xml");
|
var contentType = result.ContentType ?? $"application/{parameters.GetValueOrDefault("f", "xml")}";
|
||||||
|
return Content(result.Body, contentType);
|
||||||
}
|
}
|
||||||
catch (HttpRequestException ex)
|
catch (HttpRequestException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
6
octo-fiesta/Models/SubsonicSettings.cs
Normal file
6
octo-fiesta/Models/SubsonicSettings.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace octo_fiesta.Models;
|
||||||
|
|
||||||
|
public class SubsonicSettings
|
||||||
|
{
|
||||||
|
public string? Url { get; set; }
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using octo_fiesta.Models;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
@@ -7,6 +9,20 @@ builder.Services.AddHttpClient();
|
|||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.Configure<SubsonicSettings>(
|
||||||
|
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();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
@@ -20,6 +36,8 @@ app.UseHttpsRedirection();
|
|||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseCors();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
Reference in New Issue
Block a user