From 3c5048f017814077838cf62ecd0261daf90ca8b6 Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 26 Apr 2026 17:58:32 +0200 Subject: [PATCH] fix: use iTunes lookup endpoint for explicit artist tracks iTunes search endpoint always returns cleaned versions. Added itunesLookupArtistSongs() using /lookup?explicit=Yes which returns the actual explicit tracks. Used in artist profile (global search) and artist detail page (library). Album track lookup also gets explicit=Yes. Co-Authored-By: Claude Sonnet 4.6 --- src/api.js | 9 ++++++++- src/views.js | 12 ++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/api.js b/src/api.js index de253ac..32397bb 100644 --- a/src/api.js +++ b/src/api.js @@ -43,7 +43,14 @@ export async function itunesSearch(query, entity='musicTrack', limit=25) { } export async function itunesLookupAlbum(collectionId) { - const r = await fetch(`${ITUNES}/lookup?id=${collectionId}&entity=song`); + const r = await fetch(`${ITUNES}/lookup?id=${collectionId}&entity=song&explicit=Yes`); + const d = await r.json(); + return (d.results || []).filter(x => x.wrapperType === 'track'); +} + +// Returns actual explicit tracks for an artist via lookup (search endpoint only returns cleaned versions) +export async function itunesLookupArtistSongs(artistId, limit=15) { + const r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=song&limit=${limit}&country=us&explicit=Yes`); const d = await r.json(); return (d.results || []).filter(x => x.wrapperType === 'track'); } diff --git a/src/views.js b/src/views.js index 42dd916..be38182 100644 --- a/src/views.js +++ b/src/views.js @@ -1,7 +1,7 @@ import { S } from './state.js'; import { esc, formatDur, toast } from './utils.js'; import { api, coverUrl } from './api.js'; -import { itunesSearch, itunesLookupAlbum, itunesTopCharts, itunesTopSongs } from './api.js'; +import { itunesSearch, itunesLookupAlbum, itunesLookupArtistSongs, itunesTopCharts, itunesTopSongs } from './api.js'; // Circular imports — safe (only used inside function bodies) import { playQueue, dlSongLocal, toggleStar, playPreview } from './player.js'; import { showCtxMenu, openEditPlModal, openPlModal, openDlModal, closeSidebar, switchNavMode, closeNowPlaying, closeNpMenu } from './ui.js'; @@ -230,14 +230,18 @@ export async function loadArtistDetail(artist) { g.innerHTML = '
'; document.getElementById('artist-itunes-extra')?.remove(); - const [imgRes, albumRes, infoRes, topRes, itunesTracksRes, itunesAlbumsRes] = await Promise.allSettled([ + const [imgRes, albumRes, infoRes, topRes, itunesArtistRes, itunesAlbumsRes] = await Promise.allSettled([ getArtistImage(artist.id), api('getArtist', `&id=${artist.id}`), api('getArtistInfo2', `&id=${artist.id}&count=8`), api('getTopSongs', `&artist=${encodeURIComponent(artist.name)}&count=10`), - itunesSearch(artist.name, 'musicTrack', 10), + itunesSearch(artist.name, 'musicArtist', 1), itunesSearch(artist.name, 'album', 20), ]); + const itunesArtistId = itunesArtistRes.status==='fulfilled' ? (itunesArtistRes.value[0]?.artistId ?? null) : null; + const itunesTracksRes = itunesArtistId + ? await Promise.allSettled([itunesLookupArtistSongs(itunesArtistId, 10)])[0] + : { status: 'rejected' }; const img = imgRes.status==='fulfilled' ? imgRes.value : null; if(img) { @@ -625,7 +629,7 @@ export async function loadItunesArtistResults(artistId, artistName) { try { const [albums, topTracks] = await Promise.all([ itunesSearch(artistName, 'album', 20), - itunesSearch(artistName, 'musicTrack', 10), + itunesLookupArtistSongs(artistId, 15), ]); const artistAlbums = albums.filter(a => a.artistId===artistId || a.artistName===artistName); const artistTracks = topTracks.filter(t => t.wrapperType==='track' && (t.artistId===artistId || t.artistName===artistName));