diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index f7de43d..490ba37 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -447,24 +447,38 @@ function _spArtistName(artistsRaw) { } function _spTrackItem(t, defaultAlbum, defaultArt, defaultAlbumArtist) { - // Unwrap optional {track: ...} wrapper (playlist item format) + // Unwrap optional {track: ...} wrapper (old playlist format) let raw = t?.track || t; - // Handle GraphQL-style {data: {__typename, name, artists, ...}} + // Handle GraphQL-style {data: {__typename, ...}} const d = raw?.data || raw; - const artist = _spAllArtists(d?.artists) || defaultAlbumArtist || ''; - const art = _spArtwork(d) || defaultArt || ''; + + // Name: new embed uses "title", old/track embed uses "name" + const trackName = d?.name || d?.title || ''; + + // Artists: array format [{name}] or [{profile:{name}}], GraphQL {items:[...]}, + // or flat string in "subtitle" (new playlist embed: "Myrne, Aviella") + let artist = ''; + if (d?.artists) { + artist = _spAllArtists(d.artists); + } else if (typeof d?.subtitle === 'string' && d.subtitle) { + artist = d.subtitle; + } + artist = artist || defaultAlbumArtist || ''; + + const art = _spArtwork(d) || defaultArt || ''; const albumObj = d?.albumOfTrack || d?.album || {}; - const album = albumObj?.name || defaultAlbum || ''; - const date = albumObj?.date?.isoString || albumObj?.release_date || ''; + const album = albumObj?.name || defaultAlbum || ''; + const date = albumObj?.date?.isoString || albumObj?.release_date || ''; + return { - trackName: d?.name || '', + trackName, artistName: artist, collectionName: album, artworkUrl100: art, releaseDate: date, trackNumber: d?.trackNumber || d?.track_number, - trackExplicitness: d?.contentRating?.label === 'EXPLICIT' || d?.explicit ? 'explicit' : 'notExplicit', - previewUrl: d?.previewUrl || d?.preview_url || null, + trackExplicitness: d?.contentRating?.label === 'EXPLICIT' || d?.isExplicit || d?.explicit ? 'explicit' : 'notExplicit', + previewUrl: d?.previewUrl || d?.preview_url || d?.audioPreview?.url || null, }; } @@ -480,13 +494,14 @@ app.post('/api/spotify/resolve', async (req, res) => { try { const { type, id } = parseSpotifyUrl(url); const entity = await fetchSpotifyEmbed(type, id); - const artist = _spArtistName(entity.artists); + // "subtitle" is used by the flat embed format as artist/owner string + const artist = _spArtistName(entity.artists) || (typeof entity.subtitle === 'string' ? entity.subtitle : ''); const artUrl = _spArtwork(entity); if (type === 'track') { return res.json({ type: 'track', - name: entity.name, + name: entity.name || entity.title || '', artist, artworkUrl: artUrl, tracks: [_spTrackItem(entity, entity.album?.name, artUrl, artist)], @@ -497,27 +512,28 @@ app.post('/api/spotify/resolve', async (req, res) => { const trackList = entity.trackList || entity.tracks?.items || []; return res.json({ type: 'album', - name: entity.name, + name: entity.name || entity.title || '', artist, artworkUrl: artUrl, - tracks: trackList.map(t => _spTrackItem(t, entity.name, artUrl, artist)), + tracks: trackList.map(t => _spTrackItem(t, entity.name || entity.title, artUrl, artist)), }); } if (type === 'playlist') { const trackList = entity.trackList || entity.content?.items || entity.tracks?.items || []; - const owner = entity.ownerV2?.data?.name || entity.owner?.displayName || entity.owner?.display_name || ''; - const firstRaw = trackList[0]; - console.log('[spotify/resolve] entity keys:', Object.keys(entity)); - console.log('[spotify/resolve] trackList[0]:', JSON.stringify(firstRaw, null, 2).slice(0, 800)); + const owner = entity.authors?.[0]?.name + || entity.ownerV2?.data?.name + || entity.owner?.displayName + || entity.owner?.display_name + || (typeof entity.subtitle === 'string' ? entity.subtitle : ''); return res.json({ type: 'playlist', - name: entity.name, + name: entity.name || entity.title || '', artist: owner, artworkUrl: artUrl, description: entity.description || '', - tracks: trackList.map(t => _spTrackItem(t, null, null, null)), - _debug: { entityKeys: Object.keys(entity), firstTrack: firstRaw }, + // pass artUrl as defaultArt so tracks show the playlist cover when no per-track art exists + tracks: trackList.map(t => _spTrackItem(t, null, artUrl, null)), }); }