feat: implement real search results merging from Subsonic and Deezer

- Parse Subsonic search3 response (JSON and XML formats)
- Merge local results with external Deezer results
- Local results appear first, then external results
- Add isExternal flag to distinguish sources
This commit is contained in:
V1ck3s
2025-12-08 15:13:25 +01:00
committed by Vickes
parent 8aa9c2d437
commit 5a317c8de7

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Xml.Linq; using System.Xml.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using octo_fiesta.Models; using octo_fiesta.Models;
@@ -351,9 +352,91 @@ public class SubsonicController : ControllerBase
SearchResult externalResult, SearchResult externalResult,
string format) string format)
{ {
// Créer laponse fusionnée au format Subsonic // Parser lessultats Subsonic si disponibles
var localSongs = new List<object>();
var localAlbums = new List<object>();
var localArtists = new List<object>();
if (subsonicResult.Success && subsonicResult.Body != null)
{
try
{
var subsonicContent = Encoding.UTF8.GetString(subsonicResult.Body);
if (format == "json" || subsonicResult.ContentType?.Contains("json") == true)
{
// Parser JSON Subsonic
var jsonDoc = JsonDocument.Parse(subsonicContent);
if (jsonDoc.RootElement.TryGetProperty("subsonic-response", out var response) &&
response.TryGetProperty("searchResult3", out var searchResult))
{
if (searchResult.TryGetProperty("song", out var songs))
{
foreach (var song in songs.EnumerateArray())
{
localSongs.Add(ConvertSubsonicJsonElement(song, true));
}
}
if (searchResult.TryGetProperty("album", out var albums))
{
foreach (var album in albums.EnumerateArray())
{
localAlbums.Add(ConvertSubsonicJsonElement(album, true));
}
}
if (searchResult.TryGetProperty("artist", out var artists))
{
foreach (var artist in artists.EnumerateArray())
{
localArtists.Add(ConvertSubsonicJsonElement(artist, true));
}
}
}
}
else
{
// Parser XML Subsonic
var xmlDoc = XDocument.Parse(subsonicContent);
var ns = xmlDoc.Root?.GetDefaultNamespace() ?? XNamespace.None;
var searchResult = xmlDoc.Descendants(ns + "searchResult3").FirstOrDefault();
if (searchResult != null)
{
foreach (var song in searchResult.Elements(ns + "song"))
{
localSongs.Add(ConvertSubsonicXmlElement(song, "song"));
}
foreach (var album in searchResult.Elements(ns + "album"))
{
localAlbums.Add(ConvertSubsonicXmlElement(album, "album"));
}
foreach (var artist in searchResult.Elements(ns + "artist"))
{
localArtists.Add(ConvertSubsonicXmlElement(artist, "artist"));
}
}
}
}
catch (Exception ex)
{
// Log l'erreur mais continue avec les résultats externes
Console.WriteLine($"Error parsing Subsonic response: {ex.Message}");
}
}
// Fusionner: résultats locaux en premier, puis externes
if (format == "json") if (format == "json")
{ {
var mergedSongs = localSongs
.Concat(externalResult.Songs.Select(s => ConvertSongToSubsonicJson(s)))
.ToList();
var mergedAlbums = localAlbums
.Concat(externalResult.Albums.Select(a => ConvertAlbumToSubsonicJson(a)))
.ToList();
var mergedArtists = localArtists
.Concat(externalResult.Artists.Select(a => ConvertArtistToSubsonicJson(a)))
.ToList();
var response = new var response = new
{ {
subsonicResponse = new subsonicResponse = new
@@ -362,30 +445,60 @@ public class SubsonicController : ControllerBase
version = "1.16.1", version = "1.16.1",
searchResult3 = new searchResult3 = new
{ {
song = externalResult.Songs.Select(s => ConvertSongToSubsonicJson(s)).ToList(), song = mergedSongs,
album = externalResult.Albums.Select(a => ConvertAlbumToSubsonicJson(a)).ToList(), album = mergedAlbums,
artist = externalResult.Artists.Select(a => ConvertArtistToSubsonicJson(a)).ToList() artist = mergedArtists
} }
} }
}; };
// TODO: Fusionner avec les résultats Subsonic si disponibles
return Ok(response); return Ok(response);
} }
else else
{ {
// Format XML // Format XML
var ns = XNamespace.Get("http://subsonic.org/restapi"); var ns = XNamespace.Get("http://subsonic.org/restapi");
var searchResult3 = new XElement(ns + "searchResult3");
// Ajouter les artistes locaux puis externes
foreach (var artist in localArtists.Cast<XElement>())
{
artist.Name = ns + "artist";
searchResult3.Add(artist);
}
foreach (var artist in externalResult.Artists)
{
searchResult3.Add(ConvertArtistToSubsonicXml(artist, ns));
}
// Ajouter les albums locaux puis externes
foreach (var album in localAlbums.Cast<XElement>())
{
album.Name = ns + "album";
searchResult3.Add(album);
}
foreach (var album in externalResult.Albums)
{
searchResult3.Add(ConvertAlbumToSubsonicXml(album, ns));
}
// Ajouter les chansons locales puis externes
foreach (var song in localSongs.Cast<XElement>())
{
song.Name = ns + "song";
searchResult3.Add(song);
}
foreach (var song in externalResult.Songs)
{
searchResult3.Add(ConvertSongToSubsonicXml(song, ns));
}
var doc = new XDocument( var doc = new XDocument(
new XElement(ns + "subsonic-response", new XElement(ns + "subsonic-response",
new XAttribute("status", "ok"), new XAttribute("status", "ok"),
new XAttribute("version", "1.16.1"), new XAttribute("version", "1.16.1"),
new XElement(ns + "searchResult3", searchResult3
externalResult.Artists.Select(a => ConvertArtistToSubsonicXml(a, ns)),
externalResult.Albums.Select(a => ConvertAlbumToSubsonicXml(a, ns)),
externalResult.Songs.Select(s => ConvertSongToSubsonicXml(s, ns))
)
) )
); );
@@ -393,6 +506,31 @@ public class SubsonicController : ControllerBase
} }
} }
private object ConvertSubsonicJsonElement(JsonElement element, bool isLocal)
{
var dict = new Dictionary<string, object>();
foreach (var prop in element.EnumerateObject())
{
dict[prop.Name] = prop.Value.ValueKind switch
{
JsonValueKind.String => prop.Value.GetString() ?? "",
JsonValueKind.Number => prop.Value.TryGetInt32(out var i) ? i : prop.Value.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => prop.Value.ToString()
};
}
dict["isExternal"] = !isLocal;
return dict;
}
private XElement ConvertSubsonicXmlElement(XElement element, string type)
{
var newElement = new XElement(element);
newElement.SetAttributeValue("isExternal", "false");
return newElement;
}
private object ConvertSongToSubsonicJson(Song song) private object ConvertSongToSubsonicJson(Song song)
{ {
return new return new