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 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-04-26 17:58:32 +02:00
1 parent fc951d103b
commit 3c5048f017
2 files changed
+16 -5

No files matched your search

+8 -1
View File
@@ -43,7 +43,14 @@ export async function itunesSearch(query, entity='musicTrack', limit=25) {
} }
export async function itunesLookupAlbum(collectionId) { 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(); const d = await r.json();
return (d.results || []).filter(x => x.wrapperType === 'track'); return (d.results || []).filter(x => x.wrapperType === 'track');
} }
+8 -4
View File
@@ -1,7 +1,7 @@
import { S } from './state.js'; import { S } from './state.js';
import { esc, formatDur, toast } from './utils.js'; import { esc, formatDur, toast } from './utils.js';
import { api, coverUrl } from './api.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) // Circular imports — safe (only used inside function bodies)
import { playQueue, dlSongLocal, toggleStar, playPreview } from './player.js'; import { playQueue, dlSongLocal, toggleStar, playPreview } from './player.js';
import { showCtxMenu, openEditPlModal, openPlModal, openDlModal, closeSidebar, switchNavMode, closeNowPlaying, closeNpMenu } from './ui.js'; import { showCtxMenu, openEditPlModal, openPlModal, openDlModal, closeSidebar, switchNavMode, closeNowPlaying, closeNpMenu } from './ui.js';
@@ -230,14 +230,18 @@ export async function loadArtistDetail(artist) {
g.innerHTML = '<div class="spinner"></div>'; g.innerHTML = '<div class="spinner"></div>';
document.getElementById('artist-itunes-extra')?.remove(); 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), getArtistImage(artist.id),
api('getArtist', `&id=${artist.id}`), api('getArtist', `&id=${artist.id}`),
api('getArtistInfo2', `&id=${artist.id}&count=8`), api('getArtistInfo2', `&id=${artist.id}&count=8`),
api('getTopSongs', `&artist=${encodeURIComponent(artist.name)}&count=10`), api('getTopSongs', `&artist=${encodeURIComponent(artist.name)}&count=10`),
itunesSearch(artist.name, 'musicTrack', 10), itunesSearch(artist.name, 'musicArtist', 1),
itunesSearch(artist.name, 'album', 20), 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; const img = imgRes.status==='fulfilled' ? imgRes.value : null;
if(img) { if(img) {
@@ -625,7 +629,7 @@ export async function loadItunesArtistResults(artistId, artistName) {
try { try {
const [albums, topTracks] = await Promise.all([ const [albums, topTracks] = await Promise.all([
itunesSearch(artistName, 'album', 20), itunesSearch(artistName, 'album', 20),
itunesSearch(artistName, 'musicTrack', 10), itunesLookupArtistSongs(artistId, 15),
]); ]);
const artistAlbums = albums.filter(a => a.artistId===artistId || a.artistName===artistName); 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)); const artistTracks = topTracks.filter(t => t.wrapperType==='track' && (t.artistId===artistId || t.artistName===artistName));