fix(spotify): parse flat title/subtitle embed format for playlist tracks
Spotify's playlist embed uses {title, subtitle, audioPreview} per track
instead of {name, artists, album}. Fixes:
- _spTrackItem: reads title as trackName, subtitle as artistName fallback,
audioPreview.url as previewUrl, isExplicit as boolean
- Route: reads entity.authors[0].name for playlist owner, entity.subtitle
for track artist fallback, entity.title fallback for entity.name
- Playlist tracks get playlist cover art as default artwork (no per-track art)
- Removes temporary debug logging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
363fed61a5
commit
a305ec98e2
1 file changed
+36
-20
+36
-20
@@ -447,24 +447,38 @@ function _spArtistName(artistsRaw) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _spTrackItem(t, defaultAlbum, defaultArt, defaultAlbumArtist) {
|
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;
|
let raw = t?.track || t;
|
||||||
// Handle GraphQL-style {data: {__typename, name, artists, ...}}
|
// Handle GraphQL-style {data: {__typename, ...}}
|
||||||
const d = raw?.data || raw;
|
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 albumObj = d?.albumOfTrack || d?.album || {};
|
||||||
const album = albumObj?.name || defaultAlbum || '';
|
const album = albumObj?.name || defaultAlbum || '';
|
||||||
const date = albumObj?.date?.isoString || albumObj?.release_date || '';
|
const date = albumObj?.date?.isoString || albumObj?.release_date || '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
trackName: d?.name || '',
|
trackName,
|
||||||
artistName: artist,
|
artistName: artist,
|
||||||
collectionName: album,
|
collectionName: album,
|
||||||
artworkUrl100: art,
|
artworkUrl100: art,
|
||||||
releaseDate: date,
|
releaseDate: date,
|
||||||
trackNumber: d?.trackNumber || d?.track_number,
|
trackNumber: d?.trackNumber || d?.track_number,
|
||||||
trackExplicitness: d?.contentRating?.label === 'EXPLICIT' || d?.explicit ? 'explicit' : 'notExplicit',
|
trackExplicitness: d?.contentRating?.label === 'EXPLICIT' || d?.isExplicit || d?.explicit ? 'explicit' : 'notExplicit',
|
||||||
previewUrl: d?.previewUrl || d?.preview_url || null,
|
previewUrl: d?.previewUrl || d?.preview_url || d?.audioPreview?.url || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,13 +494,14 @@ app.post('/api/spotify/resolve', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { type, id } = parseSpotifyUrl(url);
|
const { type, id } = parseSpotifyUrl(url);
|
||||||
const entity = await fetchSpotifyEmbed(type, id);
|
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);
|
const artUrl = _spArtwork(entity);
|
||||||
|
|
||||||
if (type === 'track') {
|
if (type === 'track') {
|
||||||
return res.json({
|
return res.json({
|
||||||
type: 'track',
|
type: 'track',
|
||||||
name: entity.name,
|
name: entity.name || entity.title || '',
|
||||||
artist,
|
artist,
|
||||||
artworkUrl: artUrl,
|
artworkUrl: artUrl,
|
||||||
tracks: [_spTrackItem(entity, entity.album?.name, artUrl, artist)],
|
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 || [];
|
const trackList = entity.trackList || entity.tracks?.items || [];
|
||||||
return res.json({
|
return res.json({
|
||||||
type: 'album',
|
type: 'album',
|
||||||
name: entity.name,
|
name: entity.name || entity.title || '',
|
||||||
artist,
|
artist,
|
||||||
artworkUrl: artUrl,
|
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') {
|
if (type === 'playlist') {
|
||||||
const trackList = entity.trackList || entity.content?.items || entity.tracks?.items || [];
|
const trackList = entity.trackList || entity.content?.items || entity.tracks?.items || [];
|
||||||
const owner = entity.ownerV2?.data?.name || entity.owner?.displayName || entity.owner?.display_name || '';
|
const owner = entity.authors?.[0]?.name
|
||||||
const firstRaw = trackList[0];
|
|| entity.ownerV2?.data?.name
|
||||||
console.log('[spotify/resolve] entity keys:', Object.keys(entity));
|
|| entity.owner?.displayName
|
||||||
console.log('[spotify/resolve] trackList[0]:', JSON.stringify(firstRaw, null, 2).slice(0, 800));
|
|| entity.owner?.display_name
|
||||||
|
|| (typeof entity.subtitle === 'string' ? entity.subtitle : '');
|
||||||
return res.json({
|
return res.json({
|
||||||
type: 'playlist',
|
type: 'playlist',
|
||||||
name: entity.name,
|
name: entity.name || entity.title || '',
|
||||||
artist: owner,
|
artist: owner,
|
||||||
artworkUrl: artUrl,
|
artworkUrl: artUrl,
|
||||||
description: entity.description || '',
|
description: entity.description || '',
|
||||||
tracks: trackList.map(t => _spTrackItem(t, null, null, null)),
|
// pass artUrl as defaultArt so tracks show the playlist cover when no per-track art exists
|
||||||
_debug: { entityKeys: Object.keys(entity), firstTrack: firstRaw },
|
tracks: trackList.map(t => _spTrackItem(t, null, artUrl, null)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user