fix(web): 'Unknown Artist' download from artist page + rename itunes*→deezer* identifiers
Fix (the reported bug):
- Deezer's /artist/{id}/albums omits the artist object, so discography album cards had an
empty artist name → the download modal fell back to the literal "Unknown Artist", which
poisoned the yt-dlp search ("Unknown Artist <album>") and its artist/channel filter, failing
with "Nicht eindeutig gefunden".
- deezerLookupArtistAlbums now backfills the known artist name (we're on that artist's page).
- openDlModal now defaults album cards to an ALBUM download (was always Track), so a one-click
download no longer grabs an album as a single track named after the album.
Rename (requested cleanup, dependency-checked so nothing breaks):
- All frontend itunes* identifiers → deezer* (functions, the ItunesItem-shape field keys
itunesData/itunesArtistId/itunesFanCount/itunesId, the itunes-album-detail route + DOM ids,
the badge-itunes CSS class). The backend reads none of these fields, so the boundary is safe.
- Verified: build passes; route/DOM-id/CSS producer↔consumer all consistent; only the factual
"Apple's iTunes /search 403" comments keep the historical name. SW ns-v62.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc87a04da5
commit
1e206e8df3
13 files changed
+118
-108
No files matched your search
+17
-17
@@ -1,6 +1,6 @@
|
||||
import { S } from '../state.js';
|
||||
import { esc, toast, fmtCount } from '../utils.js';
|
||||
import { api, itunesSearch, itunesLastError, itunesLookupAlbum, fetchRomajiIndex, dispArtistById } from '../api.js';
|
||||
import { api, deezerSearch, deezerLastError, deezerLookupAlbum, fetchRomajiIndex, dispArtistById } from '../api.js';
|
||||
import { makeCard, makeAlbumCard, makeGlobalTrackItem, makeSongItem, mkSection } from '../ui/components.js';
|
||||
import { playPreview } from '../player/player.js';
|
||||
import { openDlModal } from '../ui/ui.js';
|
||||
@@ -106,7 +106,7 @@ export function onHeaderSearch(val) {
|
||||
|
||||
export function onMobileSearch(val) { onHeaderSearch(val); }
|
||||
|
||||
// ─── GLOBAL (ITUNES) SEARCH ───
|
||||
// ─── GLOBAL (DEEZER) SEARCH ───
|
||||
export function onGlobalSearch(val) {
|
||||
clearTimeout(S.globalSearchTimer);
|
||||
const el = document.getElementById('global-search-results');
|
||||
@@ -119,9 +119,9 @@ export async function doGlobalSearch(q) {
|
||||
const el = document.getElementById('global-search-results');
|
||||
try {
|
||||
const [tracks, albums, artists] = await Promise.all([
|
||||
itunesSearch(q,'musicTrack',25),
|
||||
itunesSearch(q,'album',10),
|
||||
itunesSearch(q,'musicArtist',6),
|
||||
deezerSearch(q,'musicTrack',25),
|
||||
deezerSearch(q,'album',10),
|
||||
deezerSearch(q,'musicArtist',6),
|
||||
]);
|
||||
el.innerHTML = '';
|
||||
const trackResults = tracks.filter(t => t.wrapperType === 'track');
|
||||
@@ -132,10 +132,10 @@ export async function doGlobalSearch(q) {
|
||||
artists.forEach(a => {
|
||||
const sub = a.fanCount ? `${fmtCount(a.fanCount)} Fans` : 'Deezer Künstler';
|
||||
const c = makeCard(a.artistName, sub, a.artworkUrl100 || null, '🎤');
|
||||
c.onclick = () => loadItunesArtistResults(a.artistId, a.artistName, a.fanCount);
|
||||
c.onclick = () => loadDeezerArtistResults(a.artistId, a.artistName, a.fanCount);
|
||||
row.appendChild(c);
|
||||
if (a.artworkUrl100) return; // Deezer artists already carry a photo — no extra lookup
|
||||
itunesSearch(a.artistName,'album',1).then(res => {
|
||||
deezerSearch(a.artistName,'album',1).then(res => {
|
||||
if(!res[0]) return;
|
||||
const imgUrl = res[0].artworkUrl100?.replace('100x100','300x300');
|
||||
if(!imgUrl) return;
|
||||
@@ -157,15 +157,15 @@ export async function doGlobalSearch(q) {
|
||||
albums.forEach(a => {
|
||||
const art = a.artworkUrl100?.replace('100x100','300x300');
|
||||
const c = makeCard(a.collectionName, a.artistName, art, '💿');
|
||||
c.querySelector('.play-btn').onclick = (e) => { e.stopPropagation(); loadItunesAlbum(a); };
|
||||
c.querySelector('.play-btn').onclick = (e) => { e.stopPropagation(); loadDeezerAlbum(a); };
|
||||
c.querySelector('.dl-btn-overlay')?.remove();
|
||||
const dlOverlay = document.createElement('button');
|
||||
dlOverlay.className = 'dl-btn-overlay';
|
||||
dlOverlay.title = 'Auf Server laden';
|
||||
dlOverlay.innerHTML = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
|
||||
dlOverlay.onclick = (e) => { e.stopPropagation(); openDlModal({artistName:a.artistName,trackName:a.collectionName,artworkUrl100:a.artworkUrl100,collectionName:a.collectionName,isAlbum:true,itunesData:a}); };
|
||||
dlOverlay.onclick = (e) => { e.stopPropagation(); openDlModal({artistName:a.artistName,trackName:a.collectionName,artworkUrl100:a.artworkUrl100,collectionName:a.collectionName,isAlbum:true,deezerData:a}); };
|
||||
c.querySelector('.play-overlay').appendChild(dlOverlay);
|
||||
c.onclick = () => loadItunesAlbum(a);
|
||||
c.onclick = () => loadDeezerAlbum(a);
|
||||
row.appendChild(c);
|
||||
});
|
||||
s.appendChild(row); el.appendChild(s);
|
||||
@@ -177,7 +177,7 @@ export async function doGlobalSearch(q) {
|
||||
s.appendChild(list); el.appendChild(s);
|
||||
}
|
||||
if(!artists.length && !albums.length && !trackResults.length) {
|
||||
const err = itunesLastError();
|
||||
const err = deezerLastError();
|
||||
if (err) {
|
||||
// Distinguish "genuinely no results" from "search backend is down" so the user knows where
|
||||
// the problem is.
|
||||
@@ -192,9 +192,9 @@ export async function doGlobalSearch(q) {
|
||||
} catch(e) { el.innerHTML = `<p style="color:var(--danger)">Suche fehlgeschlagen: ${e.message}</p>`; }
|
||||
}
|
||||
|
||||
export async function loadItunesAlbum(album) {
|
||||
export async function loadDeezerAlbum(album) {
|
||||
try {
|
||||
const tracks = await itunesLookupAlbum(album.collectionId);
|
||||
const tracks = await deezerLookupAlbum(album.collectionId);
|
||||
if(!tracks.length) { toast('Keine Tracks gefunden'); return; }
|
||||
const el = document.getElementById('global-search-results');
|
||||
const s = document.createElement('div'); s.className = 'search-section';
|
||||
@@ -204,7 +204,7 @@ export async function loadItunesAlbum(album) {
|
||||
<div style="font-size:15px;font-weight:700">${esc(album.collectionName)}</div>
|
||||
<div style="font-size:12px;color:var(--text2)">${esc(album.artistName)}</div>
|
||||
</div>
|
||||
<button class="btn-outline blue" style="font-size:12px;padding:7px 12px" onclick="openDlModal({artistName:'${esc(album.artistName)}',trackName:'${esc(album.collectionName)}',artworkUrl100:'${album.artworkUrl100||''}',collectionName:'${esc(album.collectionName)}',isAlbum:true,itunesData:${JSON.stringify(album).replace(/"/g,'"')}})">⬇ Album laden</button>
|
||||
<button class="btn-outline blue" style="font-size:12px;padding:7px 12px" onclick="openDlModal({artistName:'${esc(album.artistName)}',trackName:'${esc(album.collectionName)}',artworkUrl100:'${album.artworkUrl100||''}',collectionName:'${esc(album.collectionName)}',isAlbum:true,deezerData:${JSON.stringify(album).replace(/"/g,'"')}})">⬇ Album laden</button>
|
||||
</div>
|
||||
<div class="search-section-title">Tracks</div>`;
|
||||
const list = document.createElement('div'); list.className = 'song-list';
|
||||
@@ -214,8 +214,8 @@ export async function loadItunesAlbum(album) {
|
||||
} catch(e) { toast('Fehler: ' + e.message); }
|
||||
}
|
||||
|
||||
export function loadItunesArtistResults(artistId, artistName, fanCount) {
|
||||
navigate('artist-detail', { id: null, name: artistName, itunesArtistId: artistId, itunesFanCount: fanCount });
|
||||
export function loadDeezerArtistResults(artistId, artistName, fanCount) {
|
||||
navigate('artist-detail', { id: null, name: artistName, deezerArtistId: artistId, deezerFanCount: fanCount });
|
||||
}
|
||||
|
||||
// ─── SEARCH HELPERS ───
|
||||
@@ -227,7 +227,7 @@ export function searchSimilarArtist(artistName) {
|
||||
|
||||
export async function searchAndPreview(trackName, artistName) {
|
||||
try {
|
||||
const res = await itunesSearch(trackName+' '+artistName, 'musicTrack', 1);
|
||||
const res = await deezerSearch(trackName+' '+artistName, 'musicTrack', 1);
|
||||
if(res[0]?.previewUrl) playPreview(res[0]); else toast('Kein Preview verfügbar');
|
||||
} catch { toast('Suche fehlgeschlagen'); }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user